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);
| Mode | Effect |
|---|---|
| Play | The state machine actively runs. |
| Pause | Execution stops but the current state is preserved. |
| Reset | The 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
- Minimize allocations; states should not dynamically allocate memory during
Execute(). - Do not perform blocking operations inside a state.
- Ensure transitions are deterministic and predictable.
- Use
OnStateChanged()for cleanup/reset logic. - Keep
Execute()small and fast — it is executed every model cycle.