═══════════════════════════════════════════════════════════════════════ StorageModule - Pub/Sub State Collection Flow (Game-Agnostic) ═══════════════════════════════════════════════════════════════════════ SAVE OPERATION FLOW ═══════════════════════════════════════════════════════════════════════ ┌──────────────┐ │ User/Game │ └──────┬───────┘ │ │ (1) User presses F5 (quick save) │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ GameModule │ │ │ │ auto request = make_unique("request"); │ │ request->setString("filename", "quicksave"); │ │ io->publish("game:request_save", move(request)); │ └────────────────────────────┬─────────────────────────────────────────┘ │ │ Topic: "game:request_save" │ Payload: {filename: "quicksave"} ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ StorageModule │ │ │ │ void onRequestSave(const IDataNode& data) { │ │ collectModuleStates(); // Broadcast state request │ │ } │ └────────────────────────────┬─────────────────────────────────────────┘ │ │ (2) Broadcast to ALL modules │ ▼ ┌───────────────────────────────────────┐ │ Topic: "storage:collect_states" │ │ Payload: {action: "collect_state"} │ └───────────┬───────┬───────┬───────────┘ │ │ │ ┌──────────┘ │ └──────────┐ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ResourceModule │ │ TrainModule │ │ GameModule │ │ │ │ │ │ │ │ (subscribed) │ │ (subscribed) │ │ (subscribed) │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ (3) Each module sends its state │ │ │ │ │ Topic: "storage:module_state" │ │ Payload: {moduleName: "ResourceModule", │ │ inventory: {...}} │ │ │ │ └────────────────────┼────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ StorageModule │ │ │ │ void onModuleState(const IDataNode& data) { │ │ string moduleName = data.getString("moduleName"); │ │ m_collectedStates[moduleName] = move(stateNode); │ │ } │ │ │ │ // After collection complete: │ │ m_collectedStates = { │ │ "ResourceModule": {inventory: {...}, craftQueue: [...]}, │ │ "TrainModule": {wagons: [...], balance: {...}}, │ │ "GameModule": {frameCount: 54321, gameState: "InGame"} │ │ } │ └────────────────────────────┬─────────────────────────────────────────┘ │ │ (4) Aggregate and serialize to JSON │ ▼ ┌───────────────┐ │ Save File │ │ quicksave │ │ .json │ └───────┬───────┘ │ │ (5) Notify completion │ ▼ ┌──────────────────────────────────┐ │ Topic: "storage:save_complete" │ │ Payload: {filename, timestamp} │ └──────────────┬───────────────────┘ │ ▼ ┌──────────────┐ │ GameModule │ │ (displays │ │ "Saved!") │ └──────────────┘ LOAD OPERATION FLOW ═══════════════════════════════════════════════════════════════════════ ┌──────────────┐ │ User/Game │ └──────┬───────┘ │ │ (1) User selects "Load Game" │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ GameModule │ │ │ │ auto request = make_unique("request"); │ │ request->setString("filename", "quicksave"); │ │ io->publish("game:request_load", move(request)); │ └────────────────────────────┬─────────────────────────────────────────┘ │ │ Topic: "game:request_load" │ Payload: {filename: "quicksave"} ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ StorageModule │ │ │ │ void onRequestLoad(const IDataNode& data) { │ │ loadGame(filename); │ │ } │ │ │ │ // Read and parse JSON file: │ │ savedData = { │ │ version: "0.1.0", │ │ modules: { │ │ "ResourceModule": {...}, │ │ "TrainModule": {...}, │ │ "GameModule": {...} │ │ } │ │ } │ └────────────────────────────┬─────────────────────────────────────────┘ │ │ (2) Publish restore for EACH module │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ Topic: │ │ Topic: │ │ Topic: │ │ "storage: │ │ "storage: │ │ "storage: │ │ restore_state: │ │ restore_state: │ │ restore_state: │ │ ResourceModule" │ │ TrainModule" │ │ GameModule" │ │ │ │ │ │ │ │ Payload: │ │ Payload: │ │ Payload: │ │ {inventory: {...}│ │ {wagons: [...], │ │ {frameCount: │ │ craftQueue: [..]│ │ balance: {...}} │ │ 54321, ...} │ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │ │ │ │ (3) Each module restores its own state │ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ResourceModule │ │ TrainModule │ │ GameModule │ │ │ │ │ │ │ │ setState(data) │ │ setState(data) │ │ setState(data) │ │ │ │ │ │ │ │ m_inventory = │ │ m_wagons = │ │ m_frameCount = │ │ {scrap: 150} │ │ [loco, cargo] │ │ 54321 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ (4) Notify completion │ ▼ ┌──────────────────────────────────┐ │ Topic: "storage:load_complete" │ │ Payload: {filename, version} │ └──────────────┬───────────────────┘ │ ▼ ┌──────────────┐ │ GameModule │ │ (displays │ │ "Loaded!") │ └──────────────┘ KEY ARCHITECTURAL PRINCIPLES ═══════════════════════════════════════════════════════════════════════ 1. DECOUPLING - StorageModule never directly calls other modules - Modules never directly call StorageModule - All communication via IIO pub/sub 2. GAME-AGNOSTIC - StorageModule doesn't know what "train" or "factory" means - It just collects/restores arbitrary JSON data - Same code works for Mobile Command AND WarFactory 3. SCALABILITY - Add new module? It auto-participates if it subscribes - Remove module? Save/load still works - No code changes in StorageModule 4. MODULE RESPONSIBILITY - Each module knows how to serialize its OWN state - StorageModule just orchestrates the collection - Separation of concerns 5. TOPIC NAMING CONVENTION - "game:*" = User-initiated actions (save, load, quit) - "storage:*" = Storage system events (save_complete, collect_states) - "storage:restore_state:{name}" = Module-specific restore MOBILE COMMAND EXAMPLE ═══════════════════════════════════════════════════════════════════════ Module: TrainBuilderModule Subscribe: - "storage:collect_states" - "storage:restore_state:TrainBuilderModule" On "storage:collect_states": auto state = make_unique("state"); state->setString("moduleName", "TrainBuilderModule"); // Serialize train for (auto& wagon : m_wagons) { // ... add wagon data ... } io->publish("storage:module_state", move(state)); On "storage:restore_state:TrainBuilderModule": // Restore train from data m_wagons.clear(); auto wagonsNode = data.getChild("wagons"); for (auto& wagonData : wagonsNode) { Wagon wagon; wagon.type = wagonData.getString("type"); wagon.health = wagonData.getInt("health"); m_wagons.push_back(wagon); } WARFACTORY EXAMPLE (Future) ═══════════════════════════════════════════════════════════════════════ Module: ProductionModule Subscribe: - "storage:collect_states" - "storage:restore_state:ProductionModule" On "storage:collect_states": auto state = make_unique("state"); state->setString("moduleName", "ProductionModule"); // Serialize production lines for (auto& line : m_productionLines) { // ... add line data ... } io->publish("storage:module_state", move(state)); On "storage:restore_state:ProductionModule": // Restore production lines from data m_productionLines.clear(); auto linesNode = data.getChild("productionLines"); for (auto& lineData : linesNode) { ProductionLine line; line.recipe = lineData.getString("recipe"); line.progress = lineData.getDouble("progress"); m_productionLines.push_back(line); } SAME StorageModule.cpp CODE FOR BOTH GAMES! ═══════════════════════════════════════════════════════════════════════