Refactored step 1: Core Architecture Setup

This commit is contained in:
2025-08-17 10:22:21 +02:00
parent 2e4a981254
commit 36f849ba36
9 changed files with 952 additions and 53 deletions

View File

@ -0,0 +1,208 @@
#include "ApplicationManager.h"
#include "StateManager.h"
#include "../graphics/RenderManager.h"
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <iostream>
ApplicationManager::ApplicationManager() = default;
ApplicationManager::~ApplicationManager() {
if (m_initialized) {
shutdown();
}
}
bool ApplicationManager::initialize(int argc, char* argv[]) {
if (m_initialized) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager already initialized");
return true;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initializing ApplicationManager...");
// Initialize SDL first
if (!initializeSDL()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL");
return false;
}
// Initialize managers
if (!initializeManagers()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize managers");
cleanupSDL();
return false;
}
// Initialize game systems
if (!initializeGame()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize game systems");
cleanupManagers();
cleanupSDL();
return false;
}
m_initialized = true;
m_running = true;
m_lastFrameTime = SDL_GetTicks();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager initialized successfully");
return true;
}
void ApplicationManager::run() {
if (!m_initialized) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager not initialized");
return;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting main application loop");
while (m_running) {
// Calculate delta time
uint64_t currentTime = SDL_GetTicks();
float deltaTime = (currentTime - m_lastFrameTime) / 1000.0f;
m_lastFrameTime = currentTime;
// Limit delta time to prevent spiral of death
if (deltaTime > 0.05f) { // Cap at 20 FPS minimum
deltaTime = 0.05f;
}
// Main loop phases
processEvents();
if (m_running) {
update(deltaTime);
render();
}
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Main application loop ended");
}
void ApplicationManager::shutdown() {
if (!m_initialized) {
return;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shutting down ApplicationManager...");
m_running = false;
// Cleanup in reverse order of initialization
cleanupManagers();
cleanupSDL();
m_initialized = false;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager shutdown complete");
}
bool ApplicationManager::initializeSDL() {
// Initialize SDL subsystems
int sdlResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
if (sdlResult < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed: %s", SDL_GetError());
return false;
}
// Initialize SDL_ttf
int ttfResult = TTF_Init();
if (ttfResult < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TTF_Init failed: %s", SDL_GetError());
SDL_Quit();
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL initialized successfully");
return true;
}
bool ApplicationManager::initializeManagers() {
// Create and initialize RenderManager
m_renderManager = std::make_unique<RenderManager>();
if (!m_renderManager->initialize(m_windowWidth, m_windowHeight, m_windowTitle)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize RenderManager");
return false;
}
// Create StateManager (will be enhanced in next steps)
m_stateManager = std::make_unique<StateManager>(AppState::Loading);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers initialized successfully");
return true;
}
bool ApplicationManager::initializeGame() {
// TODO: Initialize game-specific systems
// For now, just set a basic loading state to test the window
// Initialize a basic test render handler
m_stateManager->registerRenderHandler(AppState::Loading,
[this](RenderManager& renderer) {
// Simple test render - just clear screen and show a colored rectangle
renderer.clear(20, 30, 40, 255);
SDL_FRect testRect = { 400, 300, 400, 200 };
renderer.renderRect(testRect, 255, 100, 100, 255);
});
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game systems initialized");
return true;
}
void ApplicationManager::processEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
requestShutdown();
return;
}
// TODO: Route events through InputManager and StateManager
// For now, handle basic window events
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
if (m_renderManager) {
m_renderManager->handleWindowResize(event.window.data1, event.window.data2);
}
}
}
}
void ApplicationManager::update(float deltaTime) {
// TODO: Update all game systems
// This will include StateManager updates, game logic, etc.
if (m_stateManager) {
m_stateManager->update(deltaTime);
}
}
void ApplicationManager::render() {
if (!m_renderManager) {
return;
}
m_renderManager->beginFrame();
// Delegate rendering to StateManager
if (m_stateManager) {
m_stateManager->render(*m_renderManager);
}
m_renderManager->endFrame();
}
void ApplicationManager::cleanupManagers() {
// Cleanup managers in reverse order
m_stateManager.reset();
m_renderManager.reset();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers cleaned up");
}
void ApplicationManager::cleanupSDL() {
TTF_Quit();
SDL_Quit();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL cleaned up");
}

View File

@ -0,0 +1,75 @@
#pragma once
#include <memory>
#include <string>
// Forward declarations
class RenderManager;
class InputManager;
class StateManager;
class AssetManager;
class Game;
class ScoreManager;
class Starfield;
class Starfield3D;
class FontAtlas;
class LineEffect;
/**
* ApplicationManager - Central coordinator for the entire application lifecycle
*
* Responsibilities:
* - Initialize and shutdown all subsystems
* - Coordinate the main application loop
* - Manage high-level application state
* - Provide clean separation between main() and application logic
*/
class ApplicationManager {
public:
ApplicationManager();
~ApplicationManager();
// Core lifecycle methods
bool initialize(int argc, char* argv[]);
void run();
void shutdown();
// Application state
bool isRunning() const { return m_running; }
void requestShutdown() { m_running = false; }
// Access to managers (for now, will be replaced with dependency injection later)
RenderManager* getRenderManager() const { return m_renderManager.get(); }
StateManager* getStateManager() const { return m_stateManager.get(); }
private:
// Initialization methods
bool initializeSDL();
bool initializeManagers();
bool initializeGame();
// Main loop methods
void processEvents();
void update(float deltaTime);
void render();
// Cleanup methods
void cleanupManagers();
void cleanupSDL();
// Core managers
std::unique_ptr<RenderManager> m_renderManager;
std::unique_ptr<StateManager> m_stateManager;
// Application state
bool m_running = false;
bool m_initialized = false;
// Timing
uint64_t m_lastFrameTime = 0;
// Configuration
int m_windowWidth = 1200;
int m_windowHeight = 1000;
std::string m_windowTitle = "Tetris (SDL3)";
};

View File

@ -1,46 +1,202 @@
#include "StateManager.h"
#include "../graphics/RenderManager.h"
#include <SDL3/SDL.h>
StateManager::StateManager(AppState initial)
: currentState(initial)
: m_currentState(initial)
, m_previousState(initial)
{
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "StateManager initialized with state: %s", getStateName(initial));
}
void StateManager::registerHandler(AppState s, EventHandler h) {
handlers[static_cast<int>(s)].push_back(std::move(h));
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::registerOnEnter(AppState s, Hook h) {
onEnter[static_cast<int>(s)].push_back(std::move(h));
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::registerOnExit(AppState s, Hook h) {
onExit[static_cast<int>(s)].push_back(std::move(h));
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));
}
// Overload accepting a no-arg function as handler (wraps it into an EventHandler)
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(); };
registerHandler(s, std::move(wrapper));
registerEventHandler(s, std::move(wrapper));
}
void StateManager::setState(AppState s) {
if (s == currentState) return;
// call exit hooks for current
auto it = onExit.find(static_cast<int>(currentState));
if (it != onExit.end()) {
for (auto &h : it->second) h();
bool StateManager::setState(AppState newState) {
if (!isValidState(newState)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Attempted to set invalid state");
return false;
}
currentState = s;
auto it2 = onEnter.find(static_cast<int>(currentState));
if (it2 != onEnter.end()) {
for (auto &h : it2->second) h();
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));
// 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);
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());
}
}
}
AppState StateManager::getState() const { return currentState; }
void StateManager::update(float deltaTime) {
auto it = m_updateHandlers.find(stateToInt(m_currentState));
if (it == m_updateHandlers.end()) {
return;
}
void StateManager::handleEvent(const SDL_Event& e) {
auto it = handlers.find(static_cast<int>(currentState));
if (it == handlers.end()) return;
for (auto &h : it->second) h(e);
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 {
// All enum values are currently valid
return static_cast<int>(state) >= static_cast<int>(AppState::Loading) &&
static_cast<int>(state) <= static_cast<int>(AppState::GameOver);
}
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::Menu: return "Menu";
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;
}
for (auto& hook : it->second) {
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());
}
}
}
void StateManager::executeExitHooks(AppState state) {
auto it = m_onExitHooks.find(stateToInt(state));
if (it == m_onExitHooks.end()) {
return;
}
for (auto& hook : it->second) {
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());
}
}
}

View File

@ -3,8 +3,12 @@
#include <functional>
#include <unordered_map>
#include <vector>
#include <string>
#include <SDL3/SDL.h>
// Forward declarations
class RenderManager;
// Application states used across the app
enum class AppState {
Loading,
@ -15,28 +19,68 @@ enum class AppState {
GameOver
};
// State manager used by main to route events and lifecycle hooks
/**
* Enhanced StateManager - Manages application state transitions and lifecycle
*
* Improvements over original:
* - Better error handling and validation
* - Support for update and render cycles
* - State transition validation
* - Logging for debugging
* - Cleaner API design
*/
class StateManager {
public:
using EventHandler = std::function<void(const SDL_Event&)>;
using UpdateHandler = std::function<void(float deltaTime)>;
using RenderHandler = std::function<void(RenderManager& renderer)>;
using Hook = std::function<void()>;
StateManager(AppState initial);
~StateManager() = default;
void registerHandler(AppState s, EventHandler h);
void registerOnEnter(AppState s, Hook h);
void registerOnExit(AppState s, Hook h);
// State registration
void registerEventHandler(AppState state, EventHandler handler);
void registerUpdateHandler(AppState state, UpdateHandler handler);
void registerRenderHandler(AppState state, RenderHandler handler);
void registerOnEnter(AppState state, Hook hook);
void registerOnExit(AppState state, Hook hook);
// Legacy compatibility
void registerHandler(AppState s, EventHandler h) { registerEventHandler(s, std::move(h)); }
void registerHandler(AppState s, std::function<void()> h); // overload used in some places
void setState(AppState s);
AppState getState() const;
// State management
bool setState(AppState newState);
AppState getState() const { return m_currentState; }
AppState getPreviousState() const { return m_previousState; }
void handleEvent(const SDL_Event& e);
// State operations
void handleEvent(const SDL_Event& event);
void update(float deltaTime);
void render(RenderManager& renderer);
// Validation
bool isValidState(AppState state) const;
bool canTransitionTo(AppState newState) const;
// Utility
const char* getStateName(AppState state) const;
private:
AppState currentState;
std::unordered_map<int, std::vector<EventHandler>> handlers;
std::unordered_map<int, std::vector<Hook>> onEnter;
std::unordered_map<int, std::vector<Hook>> onExit;
// State data
AppState m_currentState;
AppState m_previousState;
// Handler storage
std::unordered_map<int, std::vector<EventHandler>> m_eventHandlers;
std::unordered_map<int, std::vector<UpdateHandler>> m_updateHandlers;
std::unordered_map<int, std::vector<RenderHandler>> m_renderHandlers;
std::unordered_map<int, std::vector<Hook>> m_onEnterHooks;
std::unordered_map<int, std::vector<Hook>> m_onExitHooks;
// Helper methods
void executeEnterHooks(AppState state);
void executeExitHooks(AppState state);
int stateToInt(AppState state) const { return static_cast<int>(state); }
};