237 lines
8.1 KiB
C++
237 lines
8.1 KiB
C++
#include "StateManager.h"
|
|
#include "../../graphics/renderers/RenderManager.h"
|
|
#include <SDL3/SDL.h>
|
|
|
|
StateManager::StateManager(AppState initial)
|
|
: m_currentState(initial)
|
|
, m_previousState(initial)
|
|
{
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "StateManager initialized with state: %s", getStateName(initial));
|
|
}
|
|
|
|
void StateManager::registerEventHandler(AppState state, EventHandler handler) {
|
|
if (!isValidState(state)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid state for event handler registration");
|
|
return;
|
|
}
|
|
|
|
m_eventHandlers[stateToInt(state)].push_back(std::move(handler));
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Event handler registered for state: %s", getStateName(state));
|
|
}
|
|
|
|
void StateManager::registerUpdateHandler(AppState state, UpdateHandler handler) {
|
|
if (!isValidState(state)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid state for update handler registration");
|
|
return;
|
|
}
|
|
|
|
m_updateHandlers[stateToInt(state)].push_back(std::move(handler));
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Update handler registered for state: %s", getStateName(state));
|
|
}
|
|
|
|
void StateManager::registerRenderHandler(AppState state, RenderHandler handler) {
|
|
if (!isValidState(state)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid state for render handler registration");
|
|
return;
|
|
}
|
|
|
|
m_renderHandlers[stateToInt(state)].push_back(std::move(handler));
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Render handler registered for state: %s", getStateName(state));
|
|
}
|
|
|
|
void StateManager::registerOnEnter(AppState state, Hook hook) {
|
|
if (!isValidState(state)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid state for enter hook registration");
|
|
return;
|
|
}
|
|
|
|
m_onEnterHooks[stateToInt(state)].push_back(std::move(hook));
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Enter hook registered for state: %s", getStateName(state));
|
|
}
|
|
|
|
void StateManager::registerOnExit(AppState state, Hook hook) {
|
|
if (!isValidState(state)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid state for exit hook registration");
|
|
return;
|
|
}
|
|
|
|
m_onExitHooks[stateToInt(state)].push_back(std::move(hook));
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Exit hook registered for state: %s", getStateName(state));
|
|
}
|
|
|
|
// Legacy compatibility: overload accepting a no-arg function as handler
|
|
void StateManager::registerHandler(AppState s, std::function<void()> h) {
|
|
EventHandler wrapper = [h = std::move(h)](const SDL_Event&) { h(); };
|
|
registerEventHandler(s, std::move(wrapper));
|
|
}
|
|
|
|
bool StateManager::setState(AppState newState) {
|
|
if (!isValidState(newState)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Attempted to set invalid state");
|
|
return false;
|
|
}
|
|
|
|
if (newState == m_currentState) {
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Attempted to set same state: %s", getStateName(newState));
|
|
return true;
|
|
}
|
|
|
|
if (!canTransitionTo(newState)) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Invalid state transition from %s to %s",
|
|
getStateName(m_currentState), getStateName(newState));
|
|
return false;
|
|
}
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "State transition: %s -> %s",
|
|
getStateName(m_currentState), getStateName(newState));
|
|
// Persistent trace for debugging abrupt exits
|
|
{
|
|
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "setState start %s -> %s\n", getStateName(m_currentState), getStateName(newState)); fclose(f); }
|
|
}
|
|
|
|
// Execute exit hooks for current state
|
|
executeExitHooks(m_currentState);
|
|
|
|
// Update state
|
|
m_previousState = m_currentState;
|
|
m_currentState = newState;
|
|
|
|
// Execute enter hooks for new state
|
|
executeEnterHooks(m_currentState);
|
|
|
|
// Trace completion
|
|
{
|
|
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "setState end %s\n", getStateName(m_currentState)); fclose(f); }
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void StateManager::handleEvent(const SDL_Event& event) {
|
|
auto it = m_eventHandlers.find(stateToInt(m_currentState));
|
|
if (it == m_eventHandlers.end()) {
|
|
return;
|
|
}
|
|
|
|
for (auto& handler : it->second) {
|
|
try {
|
|
handler(event);
|
|
} catch (const std::exception& e) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in event handler for state %s: %s",
|
|
getStateName(m_currentState), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void StateManager::update(float deltaTime) {
|
|
auto it = m_updateHandlers.find(stateToInt(m_currentState));
|
|
if (it == m_updateHandlers.end()) {
|
|
return;
|
|
}
|
|
|
|
for (auto& handler : it->second) {
|
|
try {
|
|
handler(deltaTime);
|
|
} catch (const std::exception& e) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in update handler for state %s: %s",
|
|
getStateName(m_currentState), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void StateManager::render(RenderManager& renderer) {
|
|
auto it = m_renderHandlers.find(stateToInt(m_currentState));
|
|
if (it == m_renderHandlers.end()) {
|
|
return;
|
|
}
|
|
|
|
for (auto& handler : it->second) {
|
|
try {
|
|
handler(renderer);
|
|
} catch (const std::exception& e) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in render handler for state %s: %s",
|
|
getStateName(m_currentState), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StateManager::isValidState(AppState state) const {
|
|
switch (state) {
|
|
case AppState::Loading:
|
|
case AppState::Video:
|
|
case AppState::Menu:
|
|
case AppState::Options:
|
|
case AppState::LevelSelector:
|
|
case AppState::Playing:
|
|
case AppState::LevelSelect:
|
|
case AppState::GameOver:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool StateManager::canTransitionTo(AppState newState) const {
|
|
// For now, allow all transitions. Can be enhanced later with state machine validation
|
|
return isValidState(newState);
|
|
}
|
|
|
|
const char* StateManager::getStateName(AppState state) const {
|
|
switch (state) {
|
|
case AppState::Loading: return "Loading";
|
|
case AppState::Video: return "Video";
|
|
case AppState::Menu: return "Menu";
|
|
case AppState::Options: return "Options";
|
|
case AppState::LevelSelector: return "LevelSelector";
|
|
case AppState::Playing: return "Playing";
|
|
case AppState::LevelSelect: return "LevelSelect";
|
|
case AppState::GameOver: return "GameOver";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
void StateManager::executeEnterHooks(AppState state) {
|
|
auto it = m_onEnterHooks.find(stateToInt(state));
|
|
if (it == m_onEnterHooks.end()) {
|
|
return;
|
|
}
|
|
|
|
int idx = 0;
|
|
for (auto& hook : it->second) {
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing enter hook %d for state %s", idx, getStateName(state));
|
|
// Also write to trace file for persistent record
|
|
{
|
|
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "executeEnterHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
|
}
|
|
try {
|
|
hook();
|
|
} catch (const std::exception& e) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in enter hook for state %s: %s",
|
|
getStateName(state), e.what());
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
void StateManager::executeExitHooks(AppState state) {
|
|
auto it = m_onExitHooks.find(stateToInt(state));
|
|
if (it == m_onExitHooks.end()) {
|
|
return;
|
|
}
|
|
|
|
int idx = 0;
|
|
for (auto& hook : it->second) {
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing exit hook %d for state %s", idx, getStateName(state));
|
|
{
|
|
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "executeExitHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
|
}
|
|
try {
|
|
hook();
|
|
} catch (const std::exception& e) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Exception in exit hook for state %s: %s",
|
|
getStateName(state), e.what());
|
|
}
|
|
++idx;
|
|
}
|
|
}
|