How to Develop State Machines

Build, control, and switch states using CACState and CStatesController.

This guide shows how to define states, group them under a controller, switch between them, and follow best practices for reliable state-machine behavior in APIC.

1. Create a state

A State is an implementation of a logical execution of a single state unit. Define it by deriving from CACState:

CSampleState: CACState({execution time interval in millisec})
{}

The constructor optionally accepts a frame time in milliseconds that sets the update interval of the state. If omitted, the state runs every frame.

2. Group states with a State Controller

A State Controller groups multiple states into a logical execution unit. Only one state runs at a time in each controller.

3. Initialize a controller in your model

Each model can declare and initialize its own state controllers:

// Add a controller with initial state
CStatesController::AddController<CSampleState>("StateControllerName");

// Set controller status
CStatesController::SetStateStatus(
"StateControllerName",
CACState::EStateStatus::ePlay
);

// Execute state
CStatesController::ExecuteState("StateControllerName");

4. Control the controller's status

Use SetStateStatus to change how a controller runs:

CStatesController::SetStateStatus("StateControllerName", CACState::EStateStatus::ePause);
ModeEffect
PlayThe state machine actively runs.
PauseExecution stops but the current state is preserved.
ResetThe controller resets the current state and transitions to the initial state.

5. Switch between states

From inside a state's Execute() method, return the new state object:

switchToState = TypeFactory::instance().getObject<COtherState>();
return switchToState;

From anywhere else, call the controller directly:

CStatesController::SwitchState(
"StateControllerName",
TypeFactory::instance().getObject<COtherState>()
);

State transitions occur by returning a new state object from Execute(). Returning nullptr keeps the current state active. All state objects must be generated via TypeFactory to enforce controlled allocation and deterministic memory behavior.

6. Implement the state lifecycle

OnStateChanged()

Called whenever the state controller changes the state status. Use it for cleanup or reset logic:

bool CSampleState::OnStateChanged(EStateStatus eStateStatus)
{
if (eStateStatus == EStateStatus::eReset) {
// Reset internal state
}
return true;
}

Execute()

This is where the core state functionality runs. The state may perform work, interact with LRUs, switch states, or maintain internal logic:

std::unique_ptr<CACState> CSampleState::Execute()
{
std::unique_ptr<CACState> switchToState;

// Access LRU
// lru_XXXX* pLRU = (lru_XXXX*)CLRUsProj::LRUXXXX->GetLRU();

// Change controller status if needed
// CStatesController::SetStateStatus("NormalFlow", CACState::EStateStatus::ePause);

// Switch to another state
// switchToState = TypeFactory::instance().getObject<COtherState>();

return switchToState;
}

7. Follow best practices