From e1cfa4513e560b4070f2c7b224c6a7ae7e3c8b10 Mon Sep 17 00:00:00 2001 From: StillHammer Date: Tue, 28 Oct 2025 00:19:15 +0800 Subject: [PATCH] Initial commit: Grove Engine core architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Core interfaces for modular engine system - Resource management and registry system - Module system with sequential execution - ImGui-based UI implementation - Intra-process I/O communication - Data tree structures for hierarchical data - Serialization framework - Task scheduler interface - Debug engine implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CMakeLists.txt | 61 ++ README.md | 174 +++++ docs/architecture/architecture-modulaire.md | 423 +++++++++++ docs/architecture/claude-code-integration.md | 502 +++++++++++++ .../implementation/CLAUDE-HOT-RELOAD-GUIDE.md | 179 +++++ include/grove/ASerializable.h | 30 + include/grove/DataTreeFactory.h | 23 + include/grove/DebugEngine.h | 89 +++ include/grove/EngineFactory.h | 105 +++ include/grove/ICoordinationModule.h | 161 ++++ include/grove/IDataNode.h | 233 ++++++ include/grove/IDataTree.h | 69 ++ include/grove/IEngine.h | 125 ++++ include/grove/IIO.h | 102 +++ include/grove/IModule.h | 113 +++ include/grove/IModuleSystem.h | 95 +++ include/grove/IOFactory.h | 134 ++++ include/grove/IRegion.h | 50 ++ include/grove/ISerializable.h | 17 + include/grove/ITaskScheduler.h | 102 +++ include/grove/IUI.h | 129 ++++ include/grove/IUI_Enums.h | 340 +++++++++ include/grove/ImGuiUI.h | 707 ++++++++++++++++++ include/grove/IntraIO.h | 135 ++++ include/grove/IntraIOManager.h | 91 +++ include/grove/ModuleFactory.h | 102 +++ include/grove/ModuleSystemFactory.h | 116 +++ include/grove/RandomGenerator.h | 89 +++ include/grove/Resource.h | 37 + include/grove/ResourceRegistry.h | 113 +++ include/grove/SequentialModuleSystem.h | 87 +++ include/grove/SerializationRegistry.h | 38 + src/DebugEngine.cpp | 487 ++++++++++++ src/EngineFactory.cpp | 207 +++++ src/IOFactory.cpp | 311 ++++++++ src/ImGuiUI.cpp | 546 ++++++++++++++ src/IntraIO.cpp | 484 ++++++++++++ src/IntraIOManager.cpp | 269 +++++++ src/ModuleFactory.cpp | 509 +++++++++++++ src/ModuleSystemFactory.cpp | 239 ++++++ src/ResourceRegistry.cpp | 120 +++ src/SequentialModuleSystem.cpp | 276 +++++++ 42 files changed, 8219 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 docs/architecture/architecture-modulaire.md create mode 100644 docs/architecture/claude-code-integration.md create mode 100644 docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md create mode 100644 include/grove/ASerializable.h create mode 100644 include/grove/DataTreeFactory.h create mode 100644 include/grove/DebugEngine.h create mode 100644 include/grove/EngineFactory.h create mode 100644 include/grove/ICoordinationModule.h create mode 100644 include/grove/IDataNode.h create mode 100644 include/grove/IDataTree.h create mode 100644 include/grove/IEngine.h create mode 100644 include/grove/IIO.h create mode 100644 include/grove/IModule.h create mode 100644 include/grove/IModuleSystem.h create mode 100644 include/grove/IOFactory.h create mode 100644 include/grove/IRegion.h create mode 100644 include/grove/ISerializable.h create mode 100644 include/grove/ITaskScheduler.h create mode 100644 include/grove/IUI.h create mode 100644 include/grove/IUI_Enums.h create mode 100644 include/grove/ImGuiUI.h create mode 100644 include/grove/IntraIO.h create mode 100644 include/grove/IntraIOManager.h create mode 100644 include/grove/ModuleFactory.h create mode 100644 include/grove/ModuleSystemFactory.h create mode 100644 include/grove/RandomGenerator.h create mode 100644 include/grove/Resource.h create mode 100644 include/grove/ResourceRegistry.h create mode 100644 include/grove/SequentialModuleSystem.h create mode 100644 include/grove/SerializationRegistry.h create mode 100644 src/DebugEngine.cpp create mode 100644 src/EngineFactory.cpp create mode 100644 src/IOFactory.cpp create mode 100644 src/ImGuiUI.cpp create mode 100644 src/IntraIO.cpp create mode 100644 src/IntraIOManager.cpp create mode 100644 src/ModuleFactory.cpp create mode 100644 src/ModuleSystemFactory.cpp create mode 100644 src/ResourceRegistry.cpp create mode 100644 src/SequentialModuleSystem.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fadce18 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.20) +project(GroveEngine VERSION 1.0.0 LANGUAGES CXX) + +# C++ Standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Dependencies +include(FetchContent) + +# nlohmann_json for JSON handling +FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 +) +FetchContent_MakeAvailable(nlohmann_json) + +# Core library (INTERFACE - header-only pour les interfaces) +add_library(grove_core INTERFACE) + +target_include_directories(grove_core INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(grove_core INTERFACE + nlohmann_json::nlohmann_json +) + +# Alias for consistent naming +add_library(GroveEngine::core ALIAS grove_core) + +# Optional: Build implementations +option(GROVE_BUILD_IMPLEMENTATIONS "Build GroveEngine implementations" ON) + +if(GROVE_BUILD_IMPLEMENTATIONS) + add_library(grove_impl STATIC + src/ImGuiUI.cpp + src/ResourceRegistry.cpp + ) + + target_link_libraries(grove_impl PUBLIC + GroveEngine::core + ) + + add_library(GroveEngine::impl ALIAS grove_impl) +endif() + +# Testing +option(GROVE_BUILD_TESTS "Build GroveEngine tests" OFF) + +if(GROVE_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +# Installation +install(DIRECTORY include/grove + DESTINATION include + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8458a1 --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +# GroveEngine 🌳 + +**Modular C++ Engine Architecture for Rapid Development with Hot-Reload** + +GroveEngine is a lightweight, modular engine architecture designed for blazing-fast development iteration (0.4ms hot-reload validated) and optimized for Claude Code workflows. + +## Key Features + +- 🔥 **Hot-Reload 0.4ms** - Validated blazing-fast module reloading +- 🧩 **Modular Architecture** - Clean separation via interfaces (IEngine, IModule, IIO, IModuleSystem) +- 🚀 **Development Velocity** - Edit → Build → Hot-reload < 1 second total +- 🤖 **Claude Code Optimized** - 200-300 line modules for AI-friendly development +- 📦 **Autonomous Builds** - Each module builds independently (`cmake .`) +- 🔌 **Progressive Scaling** - Debug → Production → Cloud without rewriting + +## Architecture Overview + +``` +grove::IEngine (Orchestration) +├── grove::IModuleSystem (Execution strategy) +│ ├── SequentialModuleSystem (Debug/test - 1 module at a time) +│ ├── ThreadedModuleSystem (Each module in thread - TODO) +│ └── MultithreadedModuleSystem (Thread pool - TODO) +├── grove::IModule (Business logic - 200-300 lines) +│ └── Your modules (.so/.dll hot-reloadable) +└── grove::IIO (Communication) + ├── IntraIO (Same process - validated) + ├── LocalIO (Same machine - TODO) + └── NetworkIO (Distributed - TODO) +``` + +## Current Status + +### ✅ Implemented & Validated +- **Core Interfaces** (13): IEngine, IModule, IModuleSystem, IIO, ICoordinationModule, ITaskScheduler, IDataTree, IDataNode, IUI, ISerializable +- **Debug Implementations** (Phase 2 - Pre-IDataTree): + - `DebugEngine` - Comprehensive logging and health monitoring + - `SequentialModuleSystem` - Ultra-lightweight execution + - `IntraIO` + `IntraIOManager` - Sub-millisecond pub/sub with pattern matching + - `ModuleFactory` - Dynamic .so/.dll loading system + - `EngineFactory`, `ModuleSystemFactory`, `IOFactory` - Factory patterns +- **Hot-Reload System** - 0.4ms average, 0.055ms best performance, perfect state preservation +- **UI System** - ImGuiUI implementation with hybrid sizing + +### ⚠️ Compatibility Note +Current implementations use **pre-IDataTree API** (`json` config). The architecture evolved to use `IDataNode` for configuration. Implementations need adaptation or recreation for full IDataTree compatibility. + +### 🚧 TODO +- Adapt implementations to use IDataTree/IDataNode instead of json +- Implement ThreadedModuleSystem and MultithreadedModuleSystem +- Implement LocalIO and NetworkIO +- Create concrete IDataTree implementations (JSONDataTree, etc.) + +## Quick Start + +### Directory Structure +``` +GroveEngine/ +├── include/grove/ # 27 headers +│ ├── IEngine.h # Core interfaces +│ ├── IModule.h +│ ├── IModuleSystem.h +│ ├── IIO.h +│ ├── IDataTree.h # Configuration system +│ ├── IDataNode.h +│ └── ... +├── src/ # 10 implementations +│ ├── DebugEngine.cpp +│ ├── SequentialModuleSystem.cpp +│ ├── IntraIO.cpp +│ ├── ModuleFactory.cpp +│ └── ... +├── docs/ # Documentation +│ ├── architecture/ +│ │ ├── architecture-modulaire.md +│ │ └── claude-code-integration.md +│ └── implementation/ +│ └── CLAUDE-HOT-RELOAD-GUIDE.md +├── modules/ # Your application modules +├── tests/ # Tests +└── CMakeLists.txt # Build system +``` + +### Build + +```bash +cd GroveEngine +mkdir build && cd build +cmake .. +make + +# Or use the root CMakeLists.txt directly +cmake . +make +``` + +### Create a Module + +```cpp +// MyModule.h +#include + +class MyModule : public grove::IModule { +public: + json process(const json& input) override { + // Your logic here (200-300 lines max) + return {"result": "processed"}; + } + + void setConfiguration(const IDataNode& config, IIO* io, ITaskScheduler* scheduler) override { + // Configuration setup + } + + // ... other interface methods +}; +``` + +## Documentation + +- **[Architecture Modulaire](docs/architecture/architecture-modulaire.md)** - Core interface architecture +- **[Claude Code Integration](docs/architecture/claude-code-integration.md)** - AI-optimized development workflow +- **[Hot-Reload Guide](docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md)** - 0.4ms hot-reload system + +## Philosophy + +### Micro-Context Development +- **Small modules** (200-300 lines) for AI-friendly development +- **Autonomous builds** - Zero parent dependencies +- **Hot-swappable infrastructure** - Change performance without touching business logic + +### Progressive Evolution +```cpp +// Start simple (MVP) +DebugEngine + SequentialModuleSystem + IntraIO + +// Scale transparently (same module code) +HighPerfEngine + MultithreadedModuleSystem + NetworkIO +``` + +### Complexity Through Simplicity +Complex behavior emerges from the interaction of simple, well-defined modules. + +## Performance + +**Hot-Reload Benchmarks** (Validated): +- Average: **0.4ms** +- Best: **0.055ms** +- 5-cycle test: **2ms total** +- State persistence: **100% success rate** +- Classification: **🚀 BLAZING** (Theoretical maximum achieved) + +## Projects Using GroveEngine + +- **AISSIA** - AI Smart Schedule & Interactive Assistant (in development) +- **WarFactory** (original architecture source) + +## License + +To be defined + +## Contributing + +This engine uses an architecture optimized for Claude Code development. Each module is autonomous and can be developed independently. + +**Constraints:** +- ✅ Modules 200-300 lines maximum +- ✅ Autonomous build: `cmake .` from module directory +- ✅ JSON-only communication between modules +- ✅ Zero dependencies up (no `#include "../"`) +- ❌ Never `cmake ..` + +--- + +*GroveEngine - Where modules grow like trees in a grove 🌳* diff --git a/docs/architecture/architecture-modulaire.md b/docs/architecture/architecture-modulaire.md new file mode 100644 index 0000000..8f196dd --- /dev/null +++ b/docs/architecture/architecture-modulaire.md @@ -0,0 +1,423 @@ +# Architecture Modulaire - Warfactory + +## Concept Révolutionnaire + +L'architecture modulaire Warfactory transforme le développement de jeux complexes en utilisant une approche **micro-modules** optimisée pour Claude Code. Chaque module est un micro-contexte de 200-300 lignes de logique métier pure. + +## Core Interface Architecture + +### Architecture Fondamentale + +```cpp +ICoordinationModule → Orchestrateur global système +IEngine → Orchestration locale +IModuleSystem → Stratégies d'exécution +IModule → Logique métier pure +IIO → Communication et transport +ITaskScheduler → Délégation de tâches +``` + +### IEngine - Orchestration + +**Responsabilité** : Coordination générale du système et évolution performance + +```cpp +class IEngine { +public: + virtual void initialize() = 0; + virtual void update(float deltaTime) = 0; + virtual void shutdown() = 0; + virtual void setModuleSystem(std::unique_ptr) = 0; +}; +``` + +**Implémentations disponibles :** +- **DebugEngine** : Développement et test (step-by-step, verbose logging) +- **HighPerfEngine** : Production optimisée (threading, memory management) +- **DataOrientedEngine** : Scale massive (SIMD, cluster distribution) + +### IModuleSystem - Stratégies d'Exécution + +**Responsabilité** : Détermine comment et quand les modules s'exécutent + +```cpp +class IModuleSystem { +public: + virtual void registerModule(const std::string& name, std::unique_ptr) = 0; + virtual void processModules(float deltaTime) = 0; + virtual void setIOLayer(std::unique_ptr) = 0; + virtual json queryModule(const std::string& name, const json& input) = 0; +}; +``` + +**Stratégies d'exécution :** +- **SequentialModuleSystem** : Debug/test (1 module à la fois) +- **ThreadedModuleSystem** : Chaque module dans son thread +- **MultithreadedModuleSystem** : Pool de threads pour tasks +- **ClusterModuleSystem** : Distribution sur plusieurs machines + +### IModule - Logique Métier Pure + +**Responsabilité** : Logique de jeu spécialisée sans infrastructure + +```cpp +class IModule { +public: + virtual json process(const json& input) = 0; // PURE FUNCTION + virtual void initialize(const json& config) = 0; // Configuration + virtual void shutdown() = 0; // Cleanup + + // Hot-reload support + virtual json getState() = 0; // Save state + virtual void setState(const json& state) = 0; // Restore state +}; +``` + +**Contraintes strictes :** +- **200-300 lignes maximum** par module +- **Aucune dépendance infrastructure** (threading, network, etc.) +- **JSON in/out uniquement** pour communication +- **Logic métier pure** sans effets de bord + +### IIO - Communication + +**Responsabilité** : Abstraction transport entre modules + +```cpp +class IIO { +public: + virtual json send(const std::string& target, const json& message) = 0; + virtual json receive(const std::string& source) = 0; + virtual void broadcast(const json& message) = 0; +}; +``` + +**Implémentations transport :** +- **IntraIO** : Appel direct (même processus) +- **LocalIO** : Named pipes/sockets (même machine) +- **NetworkIO** : TCP/WebSocket (réseau) + +## Modules Spécialisés + +### ProductionModule (Exception Critique) + +**Particularité** : Belt+Inserter+Factory DOIVENT cohabiter pour performance + +```cpp +class ProductionModule : public IModule { + // EXCEPTION: 500-800 lignes acceptées + // Raison: ISocket overhead >1ms inacceptable pour 60 FPS + + Belt beltSystem; + Inserter inserterSystem; + Factory factorySystem; + +public: + json process(const json& input) override { + // Frame-perfect coordination required + auto beltData = beltSystem.update(input); + auto inserterData = inserterSystem.update(beltData); + auto factoryData = factorySystem.update(inserterData); + + return factoryData; + } +}; +``` + +### TankModule + +```cpp +class TankModule : public IModule { + // Targeting: 60Hz + // Movement: 30Hz + // Tactical: 1Hz + // Analytics: 0.1Hz + +public: + json process(const json& input) override { + auto context = getCurrentContext(input); + + if (shouldUpdateTargeting(context)) { + return processTargeting(input); // 60Hz + } + + if (shouldUpdateMovement(context)) { + return processMovement(input); // 30Hz + } + + if (shouldUpdateTactical(context)) { + return processTactical(input); // 1Hz + } + + return processAnalytics(input); // 0.1Hz + } +}; +``` + +### EconomyModule + +```cpp +class EconomyModule : public IModule { + // Economic cycles: 0.01-0.1Hz + +public: + json process(const json& input) override { + auto marketData = input["market"]; + + // Slow economic simulation + auto priceUpdates = calculatePriceDiscovery(marketData); + auto supplyDemand = updateSupplyDemand(marketData); + auto transportOptim = optimizeTransportCosts(marketData); + + return { + {"prices", priceUpdates}, + {"supply_demand", supplyDemand}, + {"transport", transportOptim} + }; + } +}; +``` + +### LogisticModule + +```cpp +class LogisticModule : public IModule { + // Variable frequency: 50ms → 1000ms + +public: + json process(const json& input) override { + auto context = input["context"]; + + if (context["urgent"]) { + return processRealTimeTransport(input); // 50ms + } + + return processPlanning(input); // 1000ms + } +}; +``` + +## Isolation et Communication + +### Règles d'Isolation Strictes + +**War Module Isolation :** +```cpp +// ✅ CORRECT - War assets via LogisticModule +LogisticModule → TurretSupply → Ammunition +LogisticModule → VehicleSupply → Fuel/Parts + +// ❌ FORBIDDEN - Direct factory interaction +ProductionModule → TankModule // ZERO interaction +FactoryInserter → Turret // NO direct supply +``` + +**Supply Chain Architecture :** +```cpp +// ✅ CORRECT - Unidirectional flow +ProductionModule ↔ LogisticModule // Export/Import only +LogisticModule ↔ WarModule // Supply war assets + +// ❌ FORBIDDEN - Any direct war interaction +ProductionModule ↔ TankModule // ZERO interaction +ProductionModule ↔ TurretModule // ZERO interaction +``` + +### Communication JSON + +**Standard Message Format :** +```json +{ + "timestamp": 1234567890, + "source": "TankModule", + "target": "LogisticModule", + "action": "request_supply", + "data": { + "item": "ammunition", + "quantity": 100, + "priority": "high" + } +} +``` + +**Response Format :** +```json +{ + "timestamp": 1234567891, + "source": "LogisticModule", + "target": "TankModule", + "status": "completed", + "data": { + "delivered": 100, + "eta": "30s", + "cost": 50.0 + } +} +``` + +## Hot-Reload Architecture + +### State Preservation + +```cpp +class TankModule : public IModule { +private: + json persistentState; + +public: + json getState() override { + return { + {"position", currentPosition}, + {"health", currentHealth}, + {"ammunition", ammunitionCount}, + {"target", currentTarget} + }; + } + + void setState(const json& state) override { + currentPosition = state["position"]; + currentHealth = state["health"]; + ammunitionCount = state["ammunition"]; + currentTarget = state["target"]; + } +}; +``` + +### Hot-Reload Workflow + +```cpp +class ModuleLoader { + void reloadModule(const std::string& modulePath) { + // 1. Save state + auto state = currentModule->getState(); + + // 2. Unload old module + unloadModule(modulePath); + + // 3. Load new module + auto newModule = loadModule(modulePath); + + // 4. Restore state + newModule->setState(state); + + // 5. Continue execution + registerModule(newModule); + } +}; +``` + +## Claude Code Development + +### Workflow Optimisé + +```bash +# 1. Claude travaille dans contexte isolé +cd modules/tank/ +# Context: CLAUDE.md (50 lignes) + TankModule.cpp (200 lignes) + IModule.h (30 lignes) +# Total: 280 lignes vs 50K+ dans architecture monolithique + +# 2. Development cycle ultra-rapide +edit("src/TankModule.cpp") # Modification logique pure +cmake . && make tank-module # Build autonome (5 secondes) +./build/tank-module # Test standalone + +# 3. Hot-reload dans jeu principal +# Engine détecte changement → Reload automatique → Game continue +``` + +### Parallel Development + +```bash +# Instance Claude A - Tank Logic +cd modules/tank/ +# Context: 200 lignes tank behavior + +# Instance Claude B - Economy Logic +cd modules/economy/ +# Context: 250 lignes market simulation + +# Instance Claude C - Factory Logic +cd modules/factory/ +# Context: 300 lignes production optimization + +# Zero conflicts, parallel commits, modular architecture +``` + +## Évolution Progressive + +### Phase 1 : Prototype (Debug) +```cpp +DebugEngine + SequentialModuleSystem + IntraIO +→ Développement ultra-rapide, Claude Code 100% focus logique +→ Step-by-step debugging, verbose logging +→ Validation concepts sans complexité infrastructure +``` + +### Phase 2 : Optimization (Threading) +```cpp +DebugEngine + ThreadedModuleSystem + IntraIO +→ Performance boost sans changer 1 ligne de game logic +→ Chaque module dans son thread dédié +→ Parallélisation automatique +``` + +### Phase 3 : Production (High Performance) +```cpp +HighPerfEngine + MultithreadedModuleSystem + LocalIO +→ Scale transparent, modules inchangés +→ Pool de threads optimisé +→ Communication inter-processus +``` + +### Phase 4 : Scale Massive (Distribution) +```cpp +DataOrientedEngine + ClusterModuleSystem + NetworkIO +→ Distribution multi-serveurs +→ SIMD optimization automatique +→ Claude Code développe toujours modules 200 lignes +``` + +## Avantages Architecture + +### Pour Claude Code +- **Micro-contexts** : 200-300 lignes vs 50K+ lignes +- **Focus logique** : Zéro infrastructure, pure game logic +- **Iteration speed** : 5 secondes vs 5-10 minutes +- **Parallel development** : 3+ instances simultanées +- **Hot-reload** : Feedback instantané + +### Pour Performance +- **Modular scaling** : Chaque module à sa fréquence optimale +- **Resource allocation** : CPU budget précis par module +- **Evolution path** : Debug → Production sans réécriture +- **Network tolerance** : Latence adaptée par module type + +### Pour Maintenance +- **Isolation complète** : Failures localisées +- **Testing granular** : Chaque module testable indépendamment +- **Code reuse** : Modules réutilisables entre projets +- **Documentation focused** : Chaque module auto-documenté + +## Implementation Roadmap + +### Étape 1 : Core Infrastructure +- Implémenter IEngine, IModuleSystem, IModule, IIO interfaces +- DebugEngine + SequentialModuleSystem + IntraIO +- Module loader avec hot-reload basique + +### Étape 2 : Premier Module +- TankModule.cpp (200 lignes) +- Test standalone +- Intégration avec core + +### Étape 3 : Modules Core +- EconomyModule, FactoryModule, LogisticModule +- Communication JSON entre modules +- State preservation + +### Étape 4 : Performance +- ThreadedModuleSystem +- Optimisation hot-reload +- Métriques performance + +Cette architecture révolutionnaire permet de développer des jeux AAA complexes avec Claude Code en utilisant des micro-contextes de 200 lignes, tout en conservant la puissance architecturale nécessaire pour des systèmes distribués massifs. \ No newline at end of file diff --git a/docs/architecture/claude-code-integration.md b/docs/architecture/claude-code-integration.md new file mode 100644 index 0000000..fcb8e1b --- /dev/null +++ b/docs/architecture/claude-code-integration.md @@ -0,0 +1,502 @@ +# Intégration Claude Code : Guide Technique + +## 🎯 Objectif : Développement IA-First + +Cette architecture est spécifiquement conçue pour **maximiser l'efficacité de Claude Code** dans le développement de jeux complexes. + +## 🧠 Contraintes Cognitives de l'IA + +### Problème Fondamental : Context Window +- **Claude Code limite** : ~200K tokens de contexte +- **Jeu AAA typique** : 500K+ lignes de code interconnectées +- **Résultat** : IA ne peut pas appréhender le système complet + +### Solution : Micro-Contexts Autonomes +```cpp +// Au lieu de ça (impossible pour l'IA) : +TankSystem.cpp (5000 lignes) + +PhysicsEngine.cpp (8000 lignes) + +NetworkLayer.cpp (3000 lignes) + +GraphicsRenderer.cpp (12000 lignes) += 28000 lignes interconnectées + +// On fait ça (parfait pour l'IA) : +TankModule.cpp (200 lignes) += Logique pure, zéro dépendance +``` + +## 🏗️ Architecture Claude Code Friendly + +### Structure Cognitive Optimale + +``` +warfactory/ +├── modules/tank/ # 🎯 Claude travaille ICI +│ ├── CLAUDE.md # Instructions spécialisées +│ ├── CMakeLists.txt # Build autonome (cmake .) +│ ├── shared/ # Headers locaux +│ ├── src/TankModule.cpp # 200 lignes PURE logic +│ └── build/ # → tank.so +└── [reste du projet invisible pour Claude] +``` + +### Principe : Information Hiding Cognitif +- **Claude voit SEULEMENT** : TankModule.cpp + CLAUDE.md + interfaces +- **Claude ne voit JAMAIS** : Engine architecture, networking, threading +- **Résultat** : Focus 100% sur logique métier + +## 📋 Workflow Claude Code Optimisé + +### 1. Session Initialization +```bash +# Claude démarre TOUJOURS dans un module spécifique +cd modules/tank/ + +# Context loading minimal +files_to_read = [ + "CLAUDE.md", # 50 lignes d'instructions + "src/TankModule.cpp", # 200 lignes de logic + "shared/IModule.h" # 30 lignes d'interface +] +# Total : 280 lignes vs 50K+ dans architecture classique +``` + +### 2. Development Loop +```bash +# 1. Claude lit le contexte micro +read("src/TankModule.cpp") + +# 2. Claude modifie la logique pure +edit("src/TankModule.cpp") + +# 3. Test instantané +cmake . && make tank-module +./build/tank-module + +# 4. Hot-reload dans le jeu +# Aucune recompilation complète ! +``` + +### 3. Parallel Development +**Point 8 - Développement Parallèle :** Multiple instances Claude Code simultanées sans conflits + +```bash +# Instance Claude A +cd modules/tank/ && work_on("tank logic") + +# Instance Claude B +cd modules/economy/ && work_on("market simulation") + +# Instance Claude C +cd modules/ai/ && work_on("behavior trees") + +# Zero conflicts, parallel development +``` + +**Architecture de non-conflit :** +- **Isolation complète** : Chaque module = contexte indépendant +- **Builds autonomes** : `cmake .` par module, zéro dépendance parent +- **État séparé** : Aucun fichier partagé entre modules +- **Git-friendly** : Commits isolés par module + +## 🎯 Instructions CLAUDE.md Spécialisées +**Point 9 - CLAUDE.md Spécialisés :** Instructions contextuelles limitées par module + +**Principe révolutionnaire :** +- **CLAUDE.md global** : Trop générique, 150+ lignes, inefficace +- **CLAUDE.md par module** : Ultra-spécialisé, 50 lignes max, efficacité maximale +- **Instructions contextuelles** : Tank ≠ Economy ≠ Factory ≠ War + +### Template Type par Module + +#### modules/tank/CLAUDE.md +```markdown +# Tank Module - Pure Combat Logic + +## Context +You work EXCLUSIVELY on tank behavior. No networking, no threading. + +## Responsibilities +- Movement: acceleration, turning, terrain interaction +- Combat: targeting, firing, armor calculations +- States: idle, moving, attacking, destroyed + +## Interface Contract +Input JSON: {"type": "move", "direction": "north", "speed": 0.8} +Output JSON: {"position": [x, y], "facing": angle, "status": "moving"} + +## File Limits +- TankModule.cpp: Max 250 lines +- Pure logic only: No sockets, threads, engine dependencies +- JSON in/out: All communication via JSON messages + +## Build Commands +cmake . && make tank-module # Builds tank.so +./build/tank-module # Test standalone + +NEVER leave this directory or reference parent paths! +``` + +#### modules/economy/CLAUDE.md +```markdown +# Economy Module - Pure Market Logic + +## Context +You work EXCLUSIVELY on economic simulation. No infrastructure. + +## Responsibilities +- Market dynamics: supply/demand, pricing +- Trading: buy/sell orders, market makers +- Economics: inflation, market cycles + +## Interface Contract +Input: {"type": "trade", "item": "steel", "quantity": 100, "action": "buy"} +Output: {"status": "executed", "price": 5.2, "total": 520.0} + +## Focus Areas +1. Market algorithms (supply/demand curves) +2. Price discovery mechanisms +3. Economic modeling + +NEVER reference networking, threading, or parent directories! +``` + +### Contraintes Strictes pour Claude +1. **NEVER `cd ..`** ou référence parent +2. **ALWAYS `cmake .`** (pas cmake ..) +3. **ONLY JSON communication** avec autres modules +4. **MAX 300 lignes** par fichier +5. **ZERO infrastructure code** dans le contexte + +## 🔧 Build System Cognitif + +### Autonomous Build : Zero Mental Overhead + +```cmake +# modules/tank/CMakeLists.txt +cmake_minimum_required(VERSION 3.20) +project(TankModule) # Self-contained + +# Everything local +include_directories(shared) +add_library(tank-module SHARED src/TankModule.cpp) + +# Local build directory +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/build) +``` + +**Avantage Claude** : Aucun concept de "projet parent" à comprendre ! + +### Hot-Reload pour Rapid Iteration +```cpp +// Engine hot-reload automatique +class ModuleLoader { + void reloadIfChanged(const std::string& modulePath) { + if(fileChanged(modulePath)) { + unloadModule(modulePath); + loadModule(modulePath); // Reload .so + // Game continue sans interruption ! + } + } +}; +``` + +**Workflow Claude** : Edit → Save → See changes instantly in game + +## 🧪 Testing Strategy : AI-Optimized + +### Unit Tests Intégrés +```cpp +// Dans TankModule.cpp +#ifdef TESTING +void runTests() { + // Test 1: Movement + auto input = json{{"type", "move"}, {"direction", "north"}}; + auto result = process(input); + assert(result["status"] == "moving"); + + // Test 2: Combat + input = json{{"type", "attack"}, {"target", "enemy_1"}}; + result = process(input); + assert(result["action"] == "fire"); + + std::cout << "✅ All tank tests passed!" << std::endl; +} +#endif +``` + +### Standalone Testing +```bash +# Claude peut tester sans le engine complet +cd modules/tank/ +make tank-module +./build/tank-module # Run standalone avec tests intégrés +``` + +**Avantage IA** : Testing sans infrastructure complexe ! + +## 🔄 Hot-Reload Architecture + +### Module State Preservation +```cpp +class TankModule { +private: + json persistentState; // Sauvegardé lors hot-reload + +public: + json getState() override { + return persistentState; // Engine sauvegarde l'état + } + + void setState(const json& state) override { + persistentState = state; // Restore après reload + } +}; +``` + +### Seamless Development +1. **Claude modifie** TankModule.cpp +2. **Engine détecte** file change +3. **Automatic save** module state +4. **Reload .so** avec nouveau code +5. **Restore state** → Game continue +6. **Test immediately** nouvelles modifications + +## 🎮 Debug Mode : IA Paradise + +### Debug Engine Features +```cpp +class DebugEngine : public IEngine { + // Execution step-by-step pour analyse + void stepMode() { processOneModule(); } + + // Logging détaillé pour Claude + void verboseLogging() { + log("Module tank input: " + input.dump()); + log("Module tank output: " + output.dump()); + } + + // Module isolation pour debugging + void isolateModule(const std::string& name) { + // Run ONLY this module, others = mock + } +}; +``` + +### Claude Debug Workflow +```bash +# 1. Set debug mode +echo '{"debug_mode": true, "isolated_module": "tank"}' > config/debug.json + +# 2. Run with step mode +./warfactory-engine --step-mode + +# 3. Claude voit EXACT input/output de son module +# Perfect pour comprendre les interactions ! +``` + +## 📊 Métriques Claude Code + +### Avant : Architecture Monolithique +- **Context size** : 50K+ lignes (impossible) +- **Build time** : 2-5 minutes +- **Iteration cycle** : Edit → Compile → Restart → Test (10+ min) +- **Bug localization** : Needle in haystack +- **Parallel work** : Impossible (conflicts) + +### Après : Architecture Modulaire +- **Context size** : 200-300 lignes (parfait) +- **Build time** : 5-10 secondes +- **Iteration cycle** : Edit → Hot-reload → Test (30 sec) +- **Bug localization** : Surgical precision +- **Parallel work** : 3+ Claude instances + +### ROI Development +- **Claude efficiency** : 10x improvement +- **Development speed** : 5x faster iteration +- **Code quality** : Higher (focused contexts) +- **Bug density** : Lower (isolated modules) + +## 🚀 Advanced Claude Patterns + +### Pattern 1: Progressive Complexity +```cpp +// Iteration 1: Basic tank (Claude commence simple) +class TankModule { + json process(const json& input) { + if(input["type"] == "move") return basicMove(); + return {{"status", "idle"}}; + } +}; + +// Iteration 2: Add combat (Claude étend) +json process(const json& input) { + if(input["type"] == "move") return advancedMove(); + if(input["type"] == "attack") return combat(); + return {{"status", "idle"}}; +} + +// Iteration 3: Add AI (Claude sophistique) +// Etc... Progression naturelle +``` + +### Pattern 2: Behavior Composition +```cpp +// Claude peut composer behaviors facilement +class TankModule { + MovementBehavior movement; + CombatBehavior combat; + AiBehavior ai; + + json process(const json& input) { + auto context = getCurrentContext(); + + if(ai.shouldMove(context)) return movement.process(input); + if(ai.shouldAttack(context)) return combat.process(input); + return ai.idle(context); + } +}; +``` + +### Pattern 3: Data-Driven Logic +```cpp +// Claude travaille avec config, pas hard-coding +class TankModule { + json tankConfig; // Loaded from config/tanks.json + + json process(const json& input) { + auto stats = tankConfig["tank_mk1"]; + auto speed = stats["max_speed"].get(); + auto armor = stats["armor_thickness"].get(); + + // Logic basée sur data, pas constantes + return processWithStats(input, speed, armor); + } +}; +``` + +## 🔮 Future Claude Integration + +### Point 48 : AI-Driven Development +**Claude Code génère modules complets via prompts naturels** + +```bash +# Future workflow: Natural language → Working module +User: "Create a tank module with advanced combat AI" + +Claude: +1. Generates TankModule.cpp (250 lines) +2. Includes behavior trees, targeting systems +3. Auto-generates unit tests +4. Configures build system +5. Hot-reloads into running game +6. Result: Fully functional tank AI in minutes +``` + +**Technical Implementation:** +```cpp +class AIModuleGenerator { + json generateModule(const std::string& prompt) { + auto spec = parseNaturalLanguage(prompt); + auto code = generateSourceCode(spec); + auto tests = generateUnitTests(spec); + auto config = generateConfig(spec); + + return { + {"source", code}, + {"tests", tests}, + {"config", config}, + {"build_commands", generateBuild(spec)} + }; + } +}; +``` + +**Benefits:** +- **Rapid prototyping** : Idée → Module fonctionnel en minutes +- **AI pair programming** : Claude comprend context et génère code adapté +- **Automatic optimization** : Claude optimise performance selon targets +- **Self-validating code** : Tests générés automatiquement + +### Point 49 : Natural Language Debugging +**Debug conversation Claude vs tools complexes** + +```bash +# Traditional debugging (avoid) +gdb ./warfactory +(gdb) break TankModule::process +(gdb) run +(gdb) print variables... +# 20+ commands, complex analysis + +# Natural Language Debugging (future) +User: "Tank moves too slowly in mud terrain" + +Claude Debug Session: +🔍 Analyzing TankModule... +📊 Current speed: 28 (expected: 35) +🎯 Issue found: terrain modifier not applied correctly +📝 Location: TankModule.cpp line 142 +⚡ Suggested fix: Update terrain calculation +✅ Fix applied: Tank speed now correct + +# Single conversation → Problem solved +``` + +**Technical Architecture:** +```cpp +class NaturalLanguageDebugger { + void analyzeIssue(const std::string& description) { + // 1. Parse natural language problem description + auto issue = parseIssueDescription(description); + + // 2. Analyze relevant module state + auto moduleState = getModuleState(issue.moduleName); + + // 3. Compare expected vs actual behavior + auto analysis = performAnalysis(issue, moduleState); + + // 4. Generate human-readable explanation + auto explanation = generateExplanation(analysis); + + // 5. Suggest specific fixes + auto suggestions = generateFixSuggestions(analysis); + + // 6. Apply fixes if approved + if(user.approves(suggestions)) { + applyFixes(suggestions); + } + } +}; +``` + +**Debug Conversation Examples:** +``` +User: "Economy module prices seem unstable" +Claude: Detected oscillation in price calculation. Market clearing frequency too high. + Suggested fix: Reduce clearing cycle from 1h to 4h. + +User: "Tank targeting is weird" +Claude: Found issue: Target selection prioritizes distance over threat. + Current: target = findClosest(enemies) + Better: target = findBestThreat(enemies, threatMatrix) + +User: "Factory belt isn't working" +Claude: Belt module shows input blockage. + Problem: Inserter rate 30/min > Belt capacity 20/min + Fix: Upgrade belt or reduce inserter speed +``` + +**Benefits:** +- **Intuitive debugging** : Description naturelle → Solution précise +- **Context-aware analysis** : Claude comprend module interactions +- **Proactive suggestions** : Fixes suggérés avant implementation +- **Learning system** : Claude améliore analysis avec experience + +--- + +## Conclusion + +Cette architecture transforme Claude Code d'un **assistant de développement** en **développeur principal** capable de créer des systèmes de jeu complexes de manière autonome. + +**Clé du succès** : Réduire la complexité cognitive à un niveau où l'IA peut exceller, tout en maintenant la puissance architecturale nécessaire pour un jeu AAA. \ No newline at end of file diff --git a/docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md b/docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md new file mode 100644 index 0000000..74b65be --- /dev/null +++ b/docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md @@ -0,0 +1,179 @@ +# 🔥 CLAUDE CODE HOT-RELOAD DEVELOPMENT GUIDE + +**Status**: PRODUCTION-READY - **0.4ms average reload time achieved!** + +This guide provides Claude Code sessions with everything needed for blazing-fast module development using the revolutionary hot-reload system. + +## 🚀 Performance Achievements + +### Benchmark Results (Validated) +- **Average Hot-Reload**: **0.4ms** +- **Best Time**: **0.055ms** +- **Complete 5-cycle test**: **2ms total** +- **Classification**: **🚀 BLAZING** (Sub-20ms target exceeded by 50x) +- **State Persistence**: **PERFECT** - all module state preserved + +### Comparison to Targets +- **Original Target**: Edit → Build → Test < 5 seconds +- **Achieved**: **Hot-reload < 1ms** +- **Improvement**: **5000x faster than target!** + +## 🏗️ System Architecture + +### Hot-Reload Pipeline +``` +Edit Module → cmake . → make → dlopen/dlsym → State Transfer → 0.4ms +``` + +### Key Components (All Implemented) +- **ModuleFactory**: Dynamic .so loading with dlopen/dlsym +- **SequentialModuleSystem**: Lightweight execution + hot-reload support +- **IntraIO**: Sub-millisecond pub/sub communication +- **State Management**: `getState()` / `setState()` with JSON serialization + +## 📁 Project Structure for Hot-Reload + +### Optimized Build Structure +``` +├── core/ +│ ├── include/warfactory/ # All interfaces implemented +│ ├── src/ # Lightweight implementations +│ └── CMakeLists.txt # Minimal deps (nlohmann_json only) +├── modules/ +│ ├── debug-world-gen/ # WORKING test module +│ │ ├── CMakeLists.txt # Autonomous build +│ │ ├── src/DebugWorldGenModuleLight.cpp # ~150 lines +│ │ └── debug-world-gen-light.so # Built artifact +└── focused-hot-reload-test # Performance validation +``` + +### Build Commands (Validated) +```bash +# Module build (3 seconds) +cd modules/debug-world-gen && cmake . && make -j4 + +# Test hot-reload (instant) +cd ../../core && ./bin/focused-hot-reload-test +``` + +## 🔧 Module Development Workflow + +### 1. Create New Module +```cpp +// Required entry points for hot-reload +extern "C" { + IModule* create_module() { return new YourModule(); } + void destroy_module(IModule* m) { delete m; } + const char* get_module_type() { return "your-module"; } + const char* get_module_version() { return "1.0.0"; } +} +``` + +### 2. Implement State Management +```cpp +// Hot-reload state preservation +json getState() override { + return { + {"config", config}, + {"work_done", workCounter}, + {"initialized", initialized} + }; +} + +void setState(const json& state) override { + if (state.contains("config")) config = state["config"]; + if (state.contains("work_done")) workCounter = state["work_done"]; + // State restored - hot-reload complete! +} +``` + +### 3. Lightning-Fast Iteration Cycle +1. **Edit** module source (any changes) +2. **Build**: `make -j4` (2-3 seconds) +3. **Hot-reload**: Automatic via test or ModuleFactory (0.4ms) +4. **Verify**: State preserved, new code active + +## 🧪 Testing System + +### Focused Performance Test +```bash +# Validates complete hot-reload pipeline +./bin/focused-hot-reload-test + +# Output example: +# 🚀 BLAZING: Sub-20ms average reload! +# ✅ STATE PERSISTENCE: PERFECT! +# 📊 Average reload time: 0.4ms +``` + +### Test Capabilities +- **Multiple reload cycles** (5x default) +- **State persistence validation** +- **Performance benchmarking** +- **Error detection and reporting** + +## 💡 Development Best Practices + +### Module Design for Hot-Reload +- **Lightweight**: 150-300 lines typical +- **State-aware**: All important state in JSON +- **Self-contained**: Minimal external dependencies +- **Error-resilient**: Graceful failure handling + +### Compilation Optimization +- **Skip heavy deps**: Use minimal CMakeLists.txt +- **Incremental builds**: Only recompile changed modules +- **Parallel compilation**: `-j4` for multi-core builds + +### Performance Validation +- **Always test hot-reload** after major changes +- **Monitor state preservation** - critical for gameplay +- **Benchmark regularly** to detect performance regression + +## 🚨 Critical Points + +### Interface Immutability +- **NEVER modify core interfaces**: IModule, IIO, ITaskScheduler, etc. +- **Extend via implementations** only +- **Breaking interface changes** destroy all modules + +### Common Pitfalls +- **Missing `break;`** in factory switch statements +- **Improper inheritance** for test mocks (use real inheritance!) +- **State not serializable** - use JSON-compatible data only +- **Heavy dependencies** in module CMakeLists.txt + +### Troubleshooting Hot-Reload Issues +- **Segfault on load**: Check interface inheritance +- **State lost**: Verify `getState()`/`setState()` implementation +- **Slow reload**: Remove heavy dependencies, use minimal build +- **Symbol not found**: Check `extern "C"` entry points + +## 🎯 Next Development Steps + +### Immediate Opportunities +1. **Create specialized modules**: Tank, Economy, Factory +2. **Real Engine integration**: Connect to DebugEngine +3. **Multi-module systems**: Test module interaction +4. **Advanced state management**: Binary state serialization + +### Performance Targets +- **Current**: 0.4ms average hot-reload ✅ +- **Next goal**: Sub-0.1ms reload (10x improvement) +- **Ultimate**: Hot-patching without restart (0ms perceived) + +## 📊 Success Metrics + +The hot-reload system has achieved **theoretical maximum performance** for Claude Code development: + +- ✅ **Sub-millisecond iteration** +- ✅ **Perfect state preservation** +- ✅ **Zero-dependency lightweight modules** +- ✅ **Autonomous module builds** +- ✅ **Production-ready reliability** + +**Status**: The hot-reload system enables **instantaneous module development** - the holy grail of rapid iteration for AI-driven coding. + +--- + +*This guide is maintained for Claude Code sessions. Update after major hot-reload system changes.* \ No newline at end of file diff --git a/include/grove/ASerializable.h b/include/grove/ASerializable.h new file mode 100644 index 0000000..c531e40 --- /dev/null +++ b/include/grove/ASerializable.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +class SerializationRegistry; + +class ASerializable { +private: + std::string instance_id; + +public: + ASerializable(const std::string& id); + virtual ~ASerializable(); + + const std::string& getInstanceId() const { return instance_id; } + + virtual json serialize() const = 0; + virtual void deserialize(const json& data) = 0; + +protected: + void registerForSerialization(); + void unregisterFromSerialization(); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/DataTreeFactory.h b/include/grove/DataTreeFactory.h new file mode 100644 index 0000000..ed08da7 --- /dev/null +++ b/include/grove/DataTreeFactory.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include "IDataTree.h" + +namespace warfactory { + +/** + * @brief Factory for creating data tree instances + */ +class DataTreeFactory { +public: + /** + * @brief Create data tree from configuration source + * @param type Tree type ("json", "database", etc.) + * @param sourcePath Path to configuration source + * @return Data tree instance + */ + static std::unique_ptr create(const std::string& type, const std::string& sourcePath); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/DebugEngine.h b/include/grove/DebugEngine.h new file mode 100644 index 0000000..067b6c9 --- /dev/null +++ b/include/grove/DebugEngine.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IEngine.h" +#include "IModuleSystem.h" +#include "IIO.h" + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Debug engine implementation with comprehensive logging + * + * DebugEngine provides maximum visibility into engine operations: + * - Verbose logging of all operations + * - Step-by-step execution capabilities + * - Module isolation and debugging + * - Performance metrics and timing + * - IIO health monitoring and reporting + * - Detailed socket management logging + */ +class DebugEngine : public IEngine { +private: + std::shared_ptr logger; + std::atomic running{false}; + std::atomic debugPaused{false}; + + // Module management + std::vector> moduleSystems; + std::vector moduleNames; + + // Socket management + std::unique_ptr coordinatorSocket; + std::vector> clientSockets; + + // Performance tracking + std::chrono::high_resolution_clock::time_point lastFrameTime; + std::chrono::high_resolution_clock::time_point engineStartTime; + size_t frameCount = 0; + + // Configuration + json engineConfig; + + // Helper methods + void logEngineStart(); + void logEngineShutdown(); + void logFrameStart(float deltaTime); + void logFrameEnd(float frameTime); + void logModuleHealth(); + void logSocketHealth(); + void processModuleSystems(float deltaTime); + void processClientMessages(); + void processCoordinatorMessages(); + float calculateDeltaTime(); + void validateConfiguration(); + +public: + DebugEngine(); + virtual ~DebugEngine(); + + // IEngine implementation + void initialize() override; + void run() override; + void step(float deltaTime) override; + void shutdown() override; + void loadModules(const std::string& configPath) override; + void registerMainSocket(std::unique_ptr coordinatorSocket) override; + void registerNewClientSocket(std::unique_ptr clientSocket) override; + EngineType getType() const override; + + // Debug-specific methods + void pauseExecution(); + void resumeExecution(); + void stepSingleFrame(); + bool isPaused() const; + json getDetailedStatus() const; + void setLogLevel(spdlog::level::level_enum level); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/EngineFactory.h b/include/grove/EngineFactory.h new file mode 100644 index 0000000..37c0679 --- /dev/null +++ b/include/grove/EngineFactory.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +#include "IEngine.h" +#include "DebugEngine.h" + +namespace warfactory { + +/** + * @brief Factory for creating engine implementations + * + * EngineFactory provides a centralized way to create different engine types + * based on configuration or runtime requirements. + * + * Supported engine types: + * - "debug" or "DEBUG" -> DebugEngine (maximum logging, step debugging) + * - "production" or "PRODUCTION" -> ProductionEngine (future implementation) + * - "high_performance" or "HIGH_PERFORMANCE" -> HighPerformanceEngine (future) + * + * Usage: + * ```cpp + * auto engine = EngineFactory::createEngine("debug"); + * auto engine = EngineFactory::createEngine(EngineType::DEBUG); + * auto engine = EngineFactory::createFromConfig("config/engine.json"); + * ``` + */ +class EngineFactory { +public: + /** + * @brief Create engine from string type + * @param engineType String representation of engine type + * @return Unique pointer to engine implementation + * @throws std::invalid_argument if engine type is unknown + */ + static std::unique_ptr createEngine(const std::string& engineType); + + /** + * @brief Create engine from enum type + * @param engineType Engine type enum value + * @return Unique pointer to engine implementation + * @throws std::invalid_argument if engine type is not implemented + */ + static std::unique_ptr createEngine(EngineType engineType); + + /** + * @brief Create engine from configuration file + * @param configPath Path to JSON configuration file + * @return Unique pointer to engine implementation + * @throws std::runtime_error if config file cannot be read + * @throws std::invalid_argument if engine type in config is invalid + * + * Expected config format: + * ```json + * { + * "engine": { + * "type": "debug", + * "log_level": "trace", + * "features": { + * "step_debugging": true, + * "performance_monitoring": true + * } + * } + * } + * ``` + */ + static std::unique_ptr createFromConfig(const std::string& configPath); + + /** + * @brief Get list of available engine types + * @return Vector of supported engine type strings + */ + static std::vector getAvailableEngineTypes(); + + /** + * @brief Check if engine type is supported + * @param engineType Engine type string to check + * @return True if engine type is supported + */ + static bool isEngineTypeSupported(const std::string& engineType); + + /** + * @brief Get engine type from string (case-insensitive) + * @param engineTypeStr String representation of engine type + * @return EngineType enum value + * @throws std::invalid_argument if string is not a valid engine type + */ + static EngineType parseEngineType(const std::string& engineTypeStr); + + /** + * @brief Convert engine type enum to string + * @param engineType Engine type enum value + * @return String representation of engine type + */ + static std::string engineTypeToString(EngineType engineType); + +private: + static std::shared_ptr getFactoryLogger(); + static std::string toLowercase(const std::string& str); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ICoordinationModule.h b/include/grove/ICoordinationModule.h new file mode 100644 index 0000000..0967fa6 --- /dev/null +++ b/include/grove/ICoordinationModule.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include +#include "IModule.h" +#include "IDataTree.h" + +// Forward declarations +namespace warfactory { + class IEngine; + class IModuleSystem; +} + +namespace warfactory { + +/** + * @brief Global system orchestrator - First launched, last shutdown + * + * The CoordinationModule is the main system orchestrator that manages the entire + * game system lifecycle, module deployment topology, and configuration synchronization. + * + * ARCHITECTURE FLOW: + * 1. MainServer launches CoordinationModule (first module) + * 2. CoordinationModule loads gameconfig.json via IDataTree + * 3. Parses deployment section to determine module topology + * 4. Deploys modules to local IEngine or remote servers + * 5. Synchronizes configuration across all deployed modules + * 6. Coordinates shutdown (last module to close) + * + * DESIGN DECISIONS: + * - No state persistence: behavior driven entirely by gameconfig.json + * - No network protocol: all communication via IIO abstraction + * - No security for now: local/trusted environment assumed + * - Module deployment via IModuleSystem delegation + * - Configuration immutability via const IDataNode references + */ +class ICoordinationModule : public IModule { +public: + virtual ~ICoordinationModule() = default; + + // ======================================== + // GAME LIFECYCLE MANAGEMENT + // ======================================== + + /** + * @brief Start new game session with configuration + * @param gameConfigPath Path to gameconfig.json file + * + * Complete startup sequence: + * 1. Load and parse gameconfig.json via IDataTree + * 2. Initialize local IEngine and IModuleSystem + * 3. Parse deployment topology from config + * 4. Deploy local modules (target: "local") + * 5. Launch remote servers and deploy remote modules + * 6. Synchronize all configurations + * 7. Return when system is ready + */ + virtual void startNewGame(const std::string& gameConfigPath) = 0; + + /** + * @brief Load existing game from save file + * @param savePath Path to save file + */ + virtual void loadGame(const std::string& savePath) = 0; + + /** + * @brief Shutdown entire game system gracefully + * + * Coordinates graceful shutdown: + * 1. Signal all modules to save state + * 2. Undeploy remote modules first + * 3. Undeploy local modules + * 4. Shutdown remote servers + * 5. Shutdown local IEngine + * 6. CoordinationModule shuts down last + */ + virtual void shutdownGame() = 0; + + // ======================================== + // MODULE DEPLOYMENT TOPOLOGY + // ======================================== + + /** + * @brief Deploy module according to gameconfig.json specification + * @param moduleInstanceId Module instance ID as defined in config + * + * Deployment process: + * 1. Read module config from gameconfig.json deployment section + * 2. Determine target: "local" vs "server:IP" vs "cluster:name" + * 3. Get module-specific configuration from modules section + * 4. For local: delegate to local IEngine->IModuleSystem + * 5. For remote: send deployment command to remote server + * 6. Pass const IDataNode& configuration to module + */ + virtual void deployModule(const std::string& moduleInstanceId) = 0; + + /** + * @brief Stop and undeploy module instance + * @param moduleInstanceId Module instance ID to undeploy + */ + virtual void undeployModule(const std::string& moduleInstanceId) = 0; + + /** + * @brief Get list of currently deployed module instances + * @return Vector of module instance IDs currently running + */ + virtual std::vector getDeployedModules() = 0; + + // ======================================== + // CONFIGURATION SYNCHRONIZATION + // ======================================== + + /** + * @brief Synchronize configuration changes to all deployed modules + * + * Process: + * 1. Reload gameconfig.json via IDataTree hot-reload + * 2. For each deployed module, get updated configuration + * 3. Call module->setConfiguration() with new const IDataNode& + * 4. Handle any modules that fail to reconfigure + */ + virtual void syncConfiguration() = 0; + + /** + * @brief Set configuration tree for the coordination system + * @param configTree Configuration data tree loaded from gameconfig.json + */ + virtual void setConfigurationTree(std::unique_ptr configTree) = 0; + + /** + * @brief Get current configuration tree + * @return Configuration tree pointer for accessing gameconfig.json data + */ + virtual IDataTree* getConfigurationTree() = 0; + + // ======================================== + // SYSTEM HEALTH AND MANAGEMENT + // ======================================== + + /** + * @brief Check if all deployed modules are healthy and responsive + * @return true if system is healthy, false if issues detected + * + * Aggregates health status from all deployed modules: + * - Calls getHealthStatus() on each module + * - Checks network connectivity to remote servers + * - Validates configuration consistency + * - Could trigger auto-save in future versions + */ + virtual bool isSystemHealthy() = 0; + + /** + * @brief Get detailed system health report + * @return JSON health report aggregating all module health status + */ + virtual json getSystemHealthReport() = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IDataNode.h b/include/grove/IDataNode.h new file mode 100644 index 0000000..1f1df78 --- /dev/null +++ b/include/grove/IDataNode.h @@ -0,0 +1,233 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace warfactory { + +using json = nlohmann::json; + +/** + * @brief Interface for a single node in the data tree + * + * Each node can have: + * - Children nodes (tree navigation) + * - Its own data blob (JSON) + * - Properties accessible by name with type safety + */ +class IDataNode { +public: + virtual ~IDataNode() = default; + + // ======================================== + // TREE NAVIGATION + // ======================================== + + /** + * @brief Get direct child by name + * @param name Exact name of the child + * @return Child node or nullptr if not found + */ + virtual std::unique_ptr getChild(const std::string& name) = 0; + + /** + * @brief Get names of all direct children + * @return Vector of child names + */ + virtual std::vector getChildNames() = 0; + + /** + * @brief Check if this node has any children + * @return true if children exist + */ + virtual bool hasChildren() = 0; + + // ======================================== + // EXACT SEARCH IN CHILDREN + // ======================================== + + /** + * @brief Find all children with exact name (direct children only) + * @param name Exact name to search for + * @return Vector of matching child nodes + */ + virtual std::vector getChildrenByName(const std::string& name) = 0; + + /** + * @brief Check if any children have the exact name + * @param name Exact name to search for + * @return true if found + */ + virtual bool hasChildrenByName(const std::string& name) const = 0; + + /** + * @brief Get first child with exact name + * @param name Exact name to search for + * @return First matching child or nullptr + */ + virtual IDataNode* getFirstChildByName(const std::string& name) = 0; + + // ======================================== + // PATTERN MATCHING SEARCH (DEEP SEARCH IN WHOLE SUBTREE) + // ======================================== + + /** + * @brief Find all nodes in subtree matching pattern + * @param pattern Pattern with wildcards (* supported) + * @return Vector of matching nodes in entire subtree + * + * Examples: + * - "component*" matches "component_armor", "component_engine" + * - "*heavy*" matches "tank_heavy_mk1", "artillery_heavy" + * - "model_*" matches "model_01", "model_02" + */ + virtual std::vector getChildrenByNameMatch(const std::string& pattern) = 0; + + /** + * @brief Check if any nodes in subtree match pattern + * @param pattern Pattern with wildcards + * @return true if any matches found + */ + virtual bool hasChildrenByNameMatch(const std::string& pattern) const = 0; + + /** + * @brief Get first node in subtree matching pattern + * @param pattern Pattern with wildcards + * @return First matching node or nullptr + */ + virtual IDataNode* getFirstChildByNameMatch(const std::string& pattern) = 0; + + // ======================================== + // QUERY BY PROPERTIES + // ======================================== + + /** + * @brief Query nodes in subtree by property value + * @param propName Property name to check + * @param predicate Function to test property value + * @return Vector of nodes where predicate returns true + * + * Example: + * // Find all tanks with armor > 150 + * queryByProperty("armor", [](const json& val) { + * return val.is_number() && val.get() > 150; + * }); + */ + virtual std::vector queryByProperty(const std::string& propName, + const std::function& predicate) = 0; + + // ======================================== + // NODE'S OWN DATA + // ======================================== + + /** + * @brief Get this node's data blob + * @return JSON data or empty JSON if no data + */ + virtual json getData() const = 0; + + /** + * @brief Check if this node has data + * @return true if data exists + */ + virtual bool hasData() const = 0; + + /** + * @brief Set this node's data + * @param data JSON data to set + */ + virtual void setData(const json& data) = 0; + + // ======================================== + // TYPED DATA ACCESS BY PROPERTY NAME + // ======================================== + + /** + * @brief Get string property from this node's data + * @param name Property name + * @param defaultValue Default if property not found or wrong type + * @return Property value or default + */ + virtual std::string getString(const std::string& name, const std::string& defaultValue = "") const = 0; + + /** + * @brief Get integer property from this node's data + * @param name Property name + * @param defaultValue Default if property not found or wrong type + * @return Property value or default + */ + virtual int getInt(const std::string& name, int defaultValue = 0) const = 0; + + /** + * @brief Get double property from this node's data + * @param name Property name + * @param defaultValue Default if property not found or wrong type + * @return Property value or default + */ + virtual double getDouble(const std::string& name, double defaultValue = 0.0) const = 0; + + /** + * @brief Get boolean property from this node's data + * @param name Property name + * @param defaultValue Default if property not found or wrong type + * @return Property value or default + */ + virtual bool getBool(const std::string& name, bool defaultValue = false) const = 0; + + /** + * @brief Check if property exists in this node's data + * @param name Property name + * @return true if property exists + */ + virtual bool hasProperty(const std::string& name) const = 0; + + // ======================================== + // HASH SYSTEM FOR VALIDATION & SYNCHRO + // ======================================== + + /** + * @brief Get hash of this node's data only + * @return SHA256 hash of data blob + */ + virtual std::string getDataHash() = 0; + + /** + * @brief Get recursive hash of this node and all children + * @return SHA256 hash of entire subtree + */ + virtual std::string getTreeHash() = 0; + + /** + * @brief Get hash of specific child subtree + * @param childPath Path to child from this node + * @return SHA256 hash of child subtree + */ + virtual std::string getSubtreeHash(const std::string& childPath) = 0; + + // ======================================== + // METADATA + // ======================================== + + /** + * @brief Get full path from root to this node + * @return Path string (e.g., "vehicles/tanks/heavy/model5") + */ + virtual std::string getPath() const = 0; + + /** + * @brief Get this node's name + * @return Node name + */ + virtual std::string getName() const = 0; + + /** + * @brief Get node type (extensible for templates/inheritance later) + * @return Node type identifier + */ + virtual std::string getNodeType() const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IDataTree.h b/include/grove/IDataTree.h new file mode 100644 index 0000000..cb3548a --- /dev/null +++ b/include/grove/IDataTree.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include "IDataNode.h" + +namespace warfactory { + +/** + * @brief Interface for the root data tree container + * + * Manages the entire tree structure and provides hot-reload capabilities + */ +class IDataTree { +public: + virtual ~IDataTree() = default; + + // ======================================== + // TREE ACCESS + // ======================================== + + /** + * @brief Get root node of the tree + * @return Root node + */ + virtual std::unique_ptr getRoot() = 0; + + /** + * @brief Get node by path from root + * @param path Path from root (e.g., "vehicles/tanks/heavy") + * @return Node at path or nullptr if not found + */ + virtual std::unique_ptr getNode(const std::string& path) = 0; + + // ======================================== + // MANUAL HOT-RELOAD (SIMPLE & EFFECTIVE) + // ======================================== + + /** + * @brief Check if source files have changed + * @return true if changes detected + */ + virtual bool checkForChanges() = 0; + + /** + * @brief Reload entire tree if changes detected + * @return true if reload was performed + */ + virtual bool reloadIfChanged() = 0; + + /** + * @brief Register callback for when tree is reloaded + * @param callback Function called after successful reload + */ + virtual void onTreeReloaded(std::function callback) = 0; + + // ======================================== + // METADATA + // ======================================== + + /** + * @brief Get tree implementation type + * @return Type identifier (e.g., "JSONDataTree", "DatabaseDataTree") + */ + virtual std::string getType() = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IEngine.h b/include/grove/IEngine.h new file mode 100644 index 0000000..9f1a236 --- /dev/null +++ b/include/grove/IEngine.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include + +// Forward declarations to avoid circular dependencies +namespace warfactory { + class IModuleSystem; +} + +using json = nlohmann::json; + +namespace warfactory { + +enum class EngineType { + DEBUG = 0, + PRODUCTION = 1, + HIGH_PERFORMANCE = 2 +}; + +/** + * @brief Engine orchestration interface - coordinates the entire system + * + * The engine is responsible for: + * - System initialization and lifecycle management + * - Main game loop coordination with delta time updates + * - Module system orchestration + * - IIO health monitoring and backpressure management + * + * IMPORTANT: Engine implementations must periodically check IIO health: + * - Monitor IOHealth.queueSize vs maxQueueSize (warn at 80% full) + * - Track IOHealth.dropping status (critical - consider module restart) + * - Log IOHealth.droppedMessageCount for debugging + * - Monitor IOHealth.averageProcessingRate for performance analysis + * + * Evolution path: + * - DebugEngine: Development/testing with step-by-step execution + * - HighPerfEngine: Production optimized with threading + * - DataOrientedEngine: Massive scale with SIMD and clustering + */ +class IEngine { +public: + virtual ~IEngine() = default; + + /** + * @brief Initialize engine systems + * + * Sets up the engine with basic configuration. + * Module system and other components are set separately. + */ + virtual void initialize() = 0; + + /** + * @brief Start main game loop + * + * Blocks until shutdown() called. Engine owns the main loop and handles: + * - Frame timing and delta time calculation + * - Module system coordination + * - Performance management and frame rate control + */ + virtual void run() = 0; + + /** + * @brief Process single frame/tick (for debugging) + * @param deltaTime Time elapsed since last update in seconds + * + * For step debugging and testing. Processes one iteration + * without entering the main loop. + */ + virtual void step(float deltaTime) = 0; + + /** + * @brief Shutdown engine and cleanup all resources + * + * Ensures proper cleanup of all systems in correct order. + * Should be safe to call multiple times. Stops run() loop. + */ + virtual void shutdown() = 0; + + /** + * @brief Load modules from configuration + * @param configPath Path to module configuration file + * + * Engine automatically: + * - Loads modules from .so/.dll files + * - Creates appropriate ModuleSystem for each module (performance strategy) + * - Configures execution frequency and coordination + * + * Config format: + * { + * "modules": [ + * {"path": "tank.so", "strategy": "threaded", "frequency": "60hz"}, + * {"path": "economy.so", "strategy": "sequential", "frequency": "0.1hz"} + * ] + * } + */ + virtual void loadModules(const std::string& configPath) = 0; + + /** + * @brief Register main coordinator socket + * @param coordinatorSocket Socket for system coordination communication + * + * Engine uses this socket for high-level system coordination, + * health monitoring, and administrative commands. + */ + virtual void registerMainSocket(std::unique_ptr coordinatorSocket) = 0; + + /** + * @brief Register new client/player socket + * @param clientSocket Socket for player communication + * + * Engine manages player connections as a priority channel. + * Players are the most important external connections. + */ + virtual void registerNewClientSocket(std::unique_ptr clientSocket) = 0; + + /** + * @brief Get engine type identifier + * @return Engine type enum value for identification + */ + virtual EngineType getType() const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IIO.h b/include/grove/IIO.h new file mode 100644 index 0000000..0f3edae --- /dev/null +++ b/include/grove/IIO.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +enum class IOType { + INTRA = 0, // Same process + LOCAL = 1, // Same machine + NETWORK = 2 // TCP/WebSocket +}; + +struct SubscriptionConfig { + bool replaceable = false; // Replace vs accumulate for low-freq + int batchInterval = 30000; // ms for low-freq batching + int maxBatchSize = 100; // Max messages per batch + bool compress = false; // Compress batched data +}; + +struct Message { + std::string topic; + json data; + uint64_t timestamp; +}; + +struct IOHealth { + int queueSize; + int maxQueueSize; + bool dropping = false; // Started dropping messages? + float averageProcessingRate; // Messages/second processed by module + int droppedMessageCount = 0; // Total dropped since last check +}; + +/** + * @brief Pub/Sub communication interface with pull-based synchronous design + * + * Pull-based pub/sub system optimized for game modules. Modules have full control + * over when they process messages, avoiding threading issues. + * + * Features: + * - Topic patterns with wildcards (e.g., "player:*", "economy:*") + * - Low-frequency subscriptions for bandwidth optimization + * - Message consumption (pull removes message from queue) + * - Engine health monitoring for backpressure management + */ +class IIO { +public: + virtual ~IIO() = default; + + /** + * @brief Publish message to a topic + * @param topic Topic name (e.g., "player:123", "economy:prices") + * @param message JSON message data + */ + virtual void publish(const std::string& topic, const json& message) = 0; + + /** + * @brief Subscribe to topic pattern (high-frequency) + * @param topicPattern Topic pattern with wildcards (e.g., "player:*") + * @param config Optional subscription configuration + */ + virtual void subscribe(const std::string& topicPattern, const SubscriptionConfig& config = {}) = 0; + + /** + * @brief Subscribe to topic pattern (low-frequency batched) + * @param topicPattern Topic pattern with wildcards + * @param config Subscription configuration (batchInterval, etc.) + */ + virtual void subscribeLowFreq(const std::string& topicPattern, const SubscriptionConfig& config = {}) = 0; + + /** + * @brief Get count of pending messages + * @return Number of messages waiting to be pulled + */ + virtual int hasMessages() const = 0; + + /** + * @brief Pull and consume one message + * @return Message from queue (oldest first). Message is removed from queue. + * @throws std::runtime_error if no messages available + */ + virtual Message pullMessage() = 0; + + /** + * @brief Get IO health status for Engine monitoring + * @return Health metrics including queue size, drop status, processing rate + */ + virtual IOHealth getHealth() const = 0; + + /** + * @brief Get IO type identifier + * @return IO type enum value for identification + */ + virtual IOType getType() const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IModule.h b/include/grove/IModule.h new file mode 100644 index 0000000..e5f600c --- /dev/null +++ b/include/grove/IModule.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include "IDataNode.h" +#include "ITaskScheduler.h" + +// Forward declarations +namespace warfactory { + class IIO; +} + +using json = nlohmann::json; + +namespace warfactory { + + + +/** + * @brief Pure business logic interface - optimized for Claude Code development + * + * This interface defines the contract for all game modules. Each module contains + * 200-300 lines of pure game logic with zero infrastructure code. + * + * Key design principles: + * - PURE FUNCTION: process() method has no side effects beyond return value + * - CONFIG VIA DATATREE: Configuration via immutable IDataNode references + * - JSON ONLY: All communication via JSON input/output + * - NO INFRASTRUCTURE: No threading, networking, or framework dependencies + * - HOT-RELOAD READY: State serialization for seamless module replacement + * - CLAUDE OPTIMIZED: Micro-context size for AI development efficiency + * + * BREAKING CHANGES: + * - Removed initialize() method - use setConfiguration() instead + * - Configuration via const IDataNode& for immutability + * - Health check returns detailed JSON status + * + * Module constraint: Maximum 300 lines per module (Exception: ProductionModule 500-800 lines) + */ +class IModule { +public: + virtual ~IModule() = default; + + /** + * @brief Process game logic + * @param input JSON input from other modules or the module system + * + * This is the core method where all module logic is implemented. + * Modules communicate via IIO pub/sub and can delegate tasks via ITaskScheduler. + * Must handle state properly through getState/setState for hot-reload. + */ + virtual void process(const json& input) = 0; + + /** + * @brief Set module configuration (replaces initialize) + * @param configNode Configuration node (immutable reference) + * @param io Pub/sub communication interface for messaging + * @param scheduler Task scheduling interface for delegating work + * + * Called when the module is loaded or configuration changes. + * Should setup internal state, validate configuration, and store service references. + */ + virtual void setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) = 0; + + /** + * @brief Get current module configuration + * @return Configuration node reference + */ + virtual const IDataNode& getConfiguration() = 0; + + /** + * @brief Get detailed health status of the module + * @return JSON health report with status, metrics, and diagnostics + */ + virtual json getHealthStatus() = 0; + + /** + * @brief Cleanup and shutdown the module + * + * Called when the module is being unloaded. Should clean up any + * resources and prepare for safe destruction. + */ + virtual void shutdown() = 0; + + /** + * @brief Get current module state for hot-reload support + * @return JSON representation of all module state + * + * Critical for hot-reload functionality. Must serialize all internal + * state that needs to be preserved when the module is replaced. + * The returned JSON should be sufficient to restore the module to + * its current state via setState(). + */ + virtual json getState() = 0; + + /** + * @brief Restore module state after hot-reload + * @param state JSON state previously returned by getState() + * + * Called after module replacement to restore the previous state. + * Must be able to reconstruct all internal state from the JSON + * to ensure seamless hot-reload without game disruption. + */ + virtual void setState(const json& state) = 0; + + /** + * @brief Get module type identifier + * @return Module type as string (e.g., "tank", "economy", "production") + */ + virtual std::string getType() const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IModuleSystem.h b/include/grove/IModuleSystem.h new file mode 100644 index 0000000..e618912 --- /dev/null +++ b/include/grove/IModuleSystem.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include "ITaskScheduler.h" + +// Forward declarations to avoid circular dependencies +namespace warfactory { + class IModule; + class IIO; +} + +using json = nlohmann::json; + +namespace warfactory { + +enum class ModuleSystemType { + SEQUENTIAL = 0, + THREADED = 1, + THREAD_POOL = 2, + CLUSTER = 3 +}; + + +/** + * @brief Module execution strategy interface - swappable performance architecture + * + * The module system manages module lifecycle and execution strategy. + * Different implementations provide different performance characteristics: + * + * - SequentialModuleSystem: Debug/test mode, processes modules one at a time + * - ThreadedModuleSystem: Each module in its own thread + * - MultithreadedModuleSystem: Module tasks distributed across thread pool + * - ClusterModuleSystem: Modules distributed across multiple machines + * + * This enables progressive evolution from debug to production to MMO scale + * without changing any module business logic code. + * + * Inherits from ITaskScheduler to provide task delegation capabilities. + */ +class IModuleSystem : public ITaskScheduler { +public: + virtual ~IModuleSystem() = default; + + /** + * @brief Register a module with the system + * @param name Unique identifier for the module + * @param module Module implementation (unique ownership) + * + * The module system takes ownership of the module and manages its lifecycle. + * Modules can be registered at any time and will participate in the next + * processing cycle. + */ + virtual void registerModule(const std::string& name, std::unique_ptr module) = 0; + + /** + * @brief Process all registered modules + * @param deltaTime Time elapsed since last processing cycle in seconds + * + * This is the core execution method that coordinates all modules according + * to the implemented strategy. Each module's process() method will be called + * with appropriate timing and coordination. + */ + virtual void processModules(float deltaTime) = 0; + + /** + * @brief Set the IO layer for inter-module communication + * @param ioLayer Communication transport implementation (unique ownership) + * + * The module system takes ownership of the IO layer and uses it to + * facilitate communication between modules. + */ + virtual void setIOLayer(std::unique_ptr ioLayer) = 0; + + /** + * @brief Query a specific module directly + * @param name Name of the module to query + * @param input JSON input to send to the module + * @return JSON response from the module + * + * This provides direct access to module functionality for debugging, + * testing, or administrative purposes. The query bypasses normal + * execution flow and calls the module's process() method directly. + */ + virtual json queryModule(const std::string& name, const json& input) = 0; + + /** + * @brief Get module system type identifier + * @return Module system type enum value for identification + */ + virtual ModuleSystemType getType() const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IOFactory.h b/include/grove/IOFactory.h new file mode 100644 index 0000000..10e1551 --- /dev/null +++ b/include/grove/IOFactory.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "IIO.h" + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Factory for creating IO transport implementations + * + * IOFactory provides centralized creation of different communication transports: + * - "intra" -> IntraIO (same-process direct function calls, zero network overhead) + * - "local" -> LocalIO (same-machine via named pipes/sockets, production single-server) + * - "network" -> NetworkIO (TCP/WebSocket for distributed deployment, MMO scale) + * + * Each IO type provides different performance and deployment characteristics while + * maintaining the same pub/sub interface, enabling progressive scaling from + * development to massive distributed systems. + * + * Usage: + * ```cpp + * auto io = IOFactory::create("intra"); + * auto io = IOFactory::create(IOType::NETWORK); + * auto io = IOFactory::createFromConfig(config); + * ``` + */ +class IOFactory { +public: + /** + * @brief Create IO transport from string type name + * @param transportType String representation of transport type + * @param instanceId Unique identifier for this IO instance (required for IntraIO) + * @return Unique pointer to IO implementation + * @throws std::invalid_argument if transport type is unknown + */ + static std::unique_ptr create(const std::string& transportType, const std::string& instanceId = ""); + + /** + * @brief Create IO transport from enum type + * @param ioType IOType enum value + * @param instanceId Unique identifier for this IO instance (required for IntraIO) + * @return Unique pointer to IO implementation + * @throws std::invalid_argument if type is not implemented + */ + static std::unique_ptr create(IOType ioType, const std::string& instanceId = ""); + + /** + * @brief Create IO transport from JSON configuration + * @param config JSON configuration object + * @param instanceId Unique identifier for this IO instance (required for IntraIO) + * @return Unique pointer to configured IO transport + * @throws std::invalid_argument if config is invalid + * + * Expected config format: + * ```json + * { + * "type": "network", + * "instance_id": "module-name", + * "host": "localhost", + * "port": 8080, + * "protocol": "tcp", + * "buffer_size": 4096, + * "timeout": 5000, + * "compression": true + * } + * ``` + */ + static std::unique_ptr createFromConfig(const json& config, const std::string& instanceId = ""); + + /** + * @brief Get list of available transport types + * @return Vector of supported transport strings + */ + static std::vector getAvailableTransports(); + + /** + * @brief Check if transport type is supported + * @param transportType Transport string to check + * @return True if transport type is supported + */ + static bool isTransportSupported(const std::string& transportType); + + /** + * @brief Parse transport string to enum (case-insensitive) + * @param transportStr String representation of transport + * @return IOType enum value + * @throws std::invalid_argument if string is invalid + */ + static IOType parseTransport(const std::string& transportStr); + + /** + * @brief Convert transport enum to string + * @param ioType IOType enum value + * @return String representation of transport + */ + static std::string transportToString(IOType ioType); + + /** + * @brief Get recommended transport for deployment scenario + * @param expectedClients Expected number of concurrent clients (0 = single-user) + * @param distributed Whether system will be distributed across machines + * @param development Whether this is for development/debugging + * @return Recommended IOType + */ + static IOType getRecommendedTransport(int expectedClients = 1, + bool distributed = false, + bool development = true); + + /** + * @brief Create IO transport with automatic endpoint discovery + * @param transportType Transport type to create + * @param endpoint Optional endpoint specification (auto-detected if empty) + * @param instanceId Unique identifier for this IO instance (required for IntraIO) + * @return Unique pointer to configured IO transport + */ + static std::unique_ptr createWithEndpoint(const std::string& transportType, + const std::string& endpoint = "", + const std::string& instanceId = ""); + +private: + static std::shared_ptr getFactoryLogger(); + static std::string toLowercase(const std::string& str); + static std::string generateEndpoint(IOType ioType); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IRegion.h b/include/grove/IRegion.h new file mode 100644 index 0000000..7a6f8f4 --- /dev/null +++ b/include/grove/IRegion.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +namespace warfactory { + +/** + * @brief Interface for geological regions during world generation + * + * Represents discrete regions with specific properties like resource deposits, + * volcanic activity, or tectonic formations. + */ +class IRegion { +public: + virtual ~IRegion() = default; + + // ======================================== + // IDENTIFICATION + // ======================================== + + virtual int getId() const = 0; + virtual const std::string& getType() const = 0; + + // ======================================== + // POSITION & SIZE + // ======================================== + + virtual float getX() const = 0; + virtual float getY() const = 0; + virtual float getRadius() const = 0; + virtual float getMass() const = 0; + + // ======================================== + // LIFECYCLE + // ======================================== + + virtual void update(float delta_time) = 0; + virtual bool isActive() const = 0; + + // ======================================== + // PROPERTIES + // ======================================== + + virtual float getIntensity() const = 0; + virtual void setIntensity(float intensity) = 0; + + virtual bool canFuseWith(const IRegion* other) const = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ISerializable.h b/include/grove/ISerializable.h new file mode 100644 index 0000000..d66739d --- /dev/null +++ b/include/grove/ISerializable.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +using json = nlohmann::json; + +namespace warfactory { + +class ISerializable { +public: + virtual ~ISerializable() = default; + + virtual json serialize() const = 0; + virtual void deserialize(const json& data) = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ITaskScheduler.h b/include/grove/ITaskScheduler.h new file mode 100644 index 0000000..d92aa7b --- /dev/null +++ b/include/grove/ITaskScheduler.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Task scheduling interface for module delegation to execution system + * + * ITaskScheduler allows modules to delegate computationally expensive or + * time-consuming tasks to the underlying execution system without knowing + * the implementation details (sequential, threaded, thread pool, cluster). + * + * CORE PURPOSE: + * - Modules stay lightweight (200-300 lines) by delegating heavy work + * - Execution strategy determined by IModuleSystem implementation + * - Modules remain thread-agnostic and infrastructure-free + * + * USAGE PATTERNS: + * - ProductionModule: Delegate belt pathfinding calculations + * - TankModule: Delegate A* pathfinding for unit movement + * - EconomyModule: Delegate market analysis and price calculations + * - FactoryModule: Delegate assembly line optimization + * + * EXECUTION STRATEGIES: + * - SequentialModuleSystem: Tasks executed immediately in same thread + * - ThreadedModuleSystem: Tasks executed in dedicated module thread + * - MultithreadedModuleSystem: Tasks distributed across thread pool + * - ClusterModuleSystem: Tasks distributed across remote workers + * + * PERFORMANCE BENEFIT: + * - Modules process() methods stay fast (< 1ms for 60Hz modules) + * - Heavy computation moved to background without blocking game loop + * - Automatic scaling based on IModuleSystem implementation + */ +class ITaskScheduler { +public: + virtual ~ITaskScheduler() = default; + + /** + * @brief Schedule a task for execution + * @param taskType Type of task (e.g., "pathfinding", "market_analysis", "belt_optimization") + * @param taskData JSON data for the task + * + * Example usage: + * ```cpp + * // TankModule delegates pathfinding + * scheduler->scheduleTask("pathfinding", { + * {"start", {x: 100, y: 200}}, + * {"target", {x: 500, y: 600}}, + * {"unit_id", "tank_001"} + * }); + * + * // ProductionModule delegates belt calculation + * scheduler->scheduleTask("belt_optimization", { + * {"factory_id", "main_base"}, + * {"item_type", "iron_plate"}, + * {"throughput_target", 240} + * }); + * ``` + */ + virtual void scheduleTask(const std::string& taskType, const json& taskData) = 0; + + /** + * @brief Check if completed tasks are available + * @return Number of completed tasks ready to be pulled + * + * Modules should check this before calling getCompletedTask() + * to avoid blocking or polling unnecessarily. + */ + virtual int hasCompletedTasks() const = 0; + + /** + * @brief Pull and consume one completed task + * @return Task result JSON. Task is removed from completed queue. + * + * Example results: + * ```cpp + * // Pathfinding result + * { + * "task_type": "pathfinding", + * "unit_id": "tank_001", + * "path": [{"x": 100, "y": 200}, {"x": 150, "y": 250}, ...], + * "cost": 42.5 + * } + * + * // Belt optimization result + * { + * "task_type": "belt_optimization", + * "factory_id": "main_base", + * "optimal_layout": [...], + * "efficiency_gain": 0.15 + * } + * ``` + */ + virtual json getCompletedTask() = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IUI.h b/include/grove/IUI.h new file mode 100644 index 0000000..b9a30af --- /dev/null +++ b/include/grove/IUI.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include + +namespace warfactory { + +using json = nlohmann::json; + +/** + * @brief Pure Generic UI Interface - Zero assumptions about content + * + * Completely data-agnostic. Implementation decides how to handle each data type. + */ +class IUI { +public: + virtual ~IUI() = default; + + // ======================================== + // LIFECYCLE + // ======================================== + + /** + * @brief Initialize UI system + * @param config Generic config, implementation interprets + */ + virtual void initialize(const json& config) = 0; + + /** + * @brief Update/render one frame + * @return true to continue, false to quit + */ + virtual bool update() = 0; + + /** + * @brief Clean shutdown + */ + virtual void shutdown() = 0; + + // ======================================== + // GENERIC DATA FLOW + // ======================================== + + /** + * @brief Display any data of any type with layout/windowing info + * @param dataType "economy", "map", "inventory", "status", whatever + * @param data JSON with content + layout: + * { + * "content": {...}, // Actual data to display + * "window": { // Window/layout configuration + * "id": "economy_main", // Unique window ID + * "title": "Economy Dashboard", + * "parent": "main_dock", // Parent window/dock ID (optional) + * "dock": "left", // Dock position: "left", "right", "top", "bottom", "center", "tab" + * "size": {"width": 400, "height": 300}, + * "position": {"x": 100, "y": 50}, + * "floating": false, // true = floating window, false = docked + * "resizable": true, + * "closeable": true + * } + * } + */ + virtual void showData(const std::string& dataType, const json& data) = 0; + + /** + * @brief Handle any user request of any type + * @param requestType "get_prices", "move_unit", "save_game", whatever + * @param callback Function to call when request happens + */ + virtual void onRequest(const std::string& requestType, std::function callback) = 0; + + /** + * @brief Show any event/message + * @param level "info", "error", "debug", whatever + * @param message Human readable text + */ + virtual void showEvent(const std::string& level, const std::string& message) = 0; + + // ======================================== + // WINDOW MANAGEMENT + // ======================================== + + /** + * @brief Create or configure a dock/container window + * @param dockId Unique dock identifier + * @param config Dock configuration: + * { + * "type": "dock", // "dock", "tabbed", "split" + * "orientation": "horizontal", // "horizontal", "vertical" (for splits) + * "parent": "main_window", // Parent dock (for nested docks) + * "position": "left", // Initial position + * "size": {"width": 300}, // Initial size + * "collapsible": true, // Can be collapsed + * "tabs": true // Enable tabbed interface + * } + */ + virtual void createDock(const std::string& dockId, const json& config) = 0; + + /** + * @brief Close/remove window or dock + * @param windowId Window or dock ID to close + */ + virtual void closeWindow(const std::string& windowId) = 0; + + /** + * @brief Focus/bring to front a specific window + * @param windowId Window ID to focus + */ + virtual void focusWindow(const std::string& windowId) = 0; + + // ======================================== + // GENERIC STATE + // ======================================== + + /** + * @brief Get current UI state + * @return JSON state, implementation defines structure + */ + virtual json getState() const = 0; + + /** + * @brief Restore UI state + * @param state JSON state from previous getState() + */ + virtual void setState(const json& state) = 0; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IUI_Enums.h b/include/grove/IUI_Enums.h new file mode 100644 index 0000000..78b76fa --- /dev/null +++ b/include/grove/IUI_Enums.h @@ -0,0 +1,340 @@ +#pragma once + +#include +#include +#include + +namespace warfactory { + +using json = nlohmann::json; + +// ======================================== +// ENUMS FOR TYPE SAFETY +// ======================================== + +/** + * @brief Data types for UI display + */ +enum class DataType { + ECONOMY, + MAP, + INVENTORY, + CONSOLE, + PERFORMANCE, + COMPANIES, + ALERTS, + PRODUCTION, + LOGISTICS, + PLAYER, + SETTINGS, + DEBUG, + CUSTOM // For extending with string fallback +}; + +/** + * @brief Request types from UI + */ +enum class RequestType { + GET_PRICES, + GET_CHUNK, + MOVE_PLAYER, + SAVE_GAME, + LOAD_GAME, + CLOSE_WINDOW, + FOCUS_WINDOW, + UPDATE_SETTINGS, + EXECUTE_COMMAND, + CUSTOM // For extending with string fallback +}; + +/** + * @brief Event/message levels + */ +enum class EventLevel { + INFO, + SUCCESS, + WARNING, + ERROR, + DEBUG, + TRACE +}; + +/** + * @brief Dock types for window management + */ +enum class DockType { + DOCK, // Standard dockable panel + SPLIT, // Horizontal/vertical split + TABBED, // Tabbed container + FLOATING // Floating window +}; + +/** + * @brief Dock positions + */ +enum class DockPosition { + LEFT, + RIGHT, + TOP, + BOTTOM, + CENTER, + TAB // Add as tab to parent +}; + +/** + * @brief Split orientations + */ +enum class Orientation { + HORIZONTAL, + VERTICAL +}; + +/** + * @brief Pure Generic UI Interface - Type-safe with enums + */ +class IUI { +public: + virtual ~IUI() = default; + + // ======================================== + // LIFECYCLE + // ======================================== + + virtual void initialize(const json& config) = 0; + virtual bool update() = 0; + virtual void shutdown() = 0; + + // ======================================== + // GENERIC DATA FLOW - ENUM VERSIONS + // ======================================== + + /** + * @brief Display data with type-safe enum + * @param dataType Enum data type + * @param data JSON with content + optional window config: + * { + * "content": {...}, // Actual data to display + * "window": { // Window configuration (optional) + * "id": "window_id", + * "title": "Window Title", + * "parent": "parent_dock_id", + * "dock": "left|right|top|bottom|center|tab", + * + * // SIZE SYSTEM - Hybrid percentage + absolute constraints + * "size": {"width": "20%", "height": 300}, // Target: 20% of parent width, 300px height + * "size": {"width": 400, "height": "50%"}, // Target: 400px width, 50% of parent height + * "size": {"width": "30%", "height": "40%"}, // Target: 30% width, 40% height + * + * "min_size": {"width": 200, "height": 150}, // ABSOLUTE minimum in pixels (always respected) + * "max_size": {"width": 800, "height": "80%"}, // Maximum: 800px width OR 80% of parent (whichever smaller) + * + * "position": {"x": 100, "y": 50}, + * "floating": false, + * "resizable": true, + * "closeable": true, + * "collapsible": false + * } + * } + */ + virtual void showData(DataType dataType, const json& data) = 0; + + /** + * @brief Display custom data type (fallback to string) + * @param customType Custom data type name + * @param data JSON data + */ + virtual void showDataCustom(const std::string& customType, const json& data) = 0; + + /** + * @brief Handle user request with type-safe enum + * @param requestType Enum request type + * @param callback Function to call when request happens + */ + virtual void onRequest(RequestType requestType, std::function callback) = 0; + + /** + * @brief Handle custom request type (fallback to string) + * @param customType Custom request type name + * @param callback Function to call when request happens + */ + virtual void onRequestCustom(const std::string& customType, std::function callback) = 0; + + /** + * @brief Show event with type-safe level + * @param level Event level enum + * @param message Human readable text + */ + virtual void showEvent(EventLevel level, const std::string& message) = 0; + + // ======================================== + // WINDOW MANAGEMENT - ENUM VERSIONS + // ======================================== + + /** + * @brief Create dock with type-safe enums + * @param dockId Unique dock identifier + * @param type Dock type enum + * @param position Dock position enum + * @param config Additional configuration: + * { + * "parent": "parent_dock_id", // Parent dock (optional) + * + * // HYBRID SIZE SYSTEM + * "size": {"width": "25%", "height": 200}, // Target: 25% of parent width, 200px height + * "min_size": {"width": 200, "height": 100}, // ABSOLUTE minimum pixels (overrides percentage) + * "max_size": {"width": "50%", "height": 600}, // Maximum: 50% of parent OR 600px (whichever smaller) + * + * "orientation": "horizontal", // For SPLIT type + * "collapsible": true, // Can be collapsed + * "resizable": true, // Can be resized + * "tabs": true // Enable tabbed interface + * } + */ + virtual void createDock(const std::string& dockId, DockType type, DockPosition position, const json& config = {}) = 0; + + /** + * @brief Create split dock with orientation + * @param dockId Unique dock identifier + * @param orientation Split orientation + * @param config Additional configuration: + * { + * "parent": "parent_dock_id", // Parent dock (optional) + * "size": {"width": 300, "height": 200}, // Initial size + * "min_size": {"width": 100, "height": 50}, // Minimum split size in pixels + * "max_size": {"width": 1000, "height": 800}, // Maximum split size in pixels + * "split_ratio": 0.5, // Split ratio (0.0 to 1.0) + * "min_panel_size": 80, // Minimum size for each panel in split + * "resizable": true // Can be resized by dragging splitter + * } + */ + virtual void createSplit(const std::string& dockId, Orientation orientation, const json& config = {}) = 0; + + /** + * @brief Close window or dock + * @param windowId Window/dock ID to close + */ + virtual void closeWindow(const std::string& windowId) = 0; + + /** + * @brief Focus window + * @param windowId Window ID to focus + */ + virtual void focusWindow(const std::string& windowId) = 0; + + // ======================================== + // GENERIC STATE + // ======================================== + + virtual json getState() const = 0; + virtual void setState(const json& state) = 0; + + // ======================================== + // CONVENIENCE METHODS WITH ENUMS + // ======================================== + + void info(const std::string& message) { + showEvent(EventLevel::INFO, message); + } + + void success(const std::string& message) { + showEvent(EventLevel::SUCCESS, message); + } + + void warning(const std::string& message) { + showEvent(EventLevel::WARNING, message); + } + + void error(const std::string& message) { + showEvent(EventLevel::ERROR, message); + } + + void debug(const std::string& message) { + showEvent(EventLevel::DEBUG, message); + } +}; + +// ======================================== +// ENUM TO STRING CONVERSIONS (for implementations) +// ======================================== + +/** + * @brief Convert DataType enum to string (for implementations that need strings) + */ +constexpr const char* toString(DataType type) { + switch (type) { + case DataType::ECONOMY: return "economy"; + case DataType::MAP: return "map"; + case DataType::INVENTORY: return "inventory"; + case DataType::CONSOLE: return "console"; + case DataType::PERFORMANCE: return "performance"; + case DataType::COMPANIES: return "companies"; + case DataType::ALERTS: return "alerts"; + case DataType::PRODUCTION: return "production"; + case DataType::LOGISTICS: return "logistics"; + case DataType::PLAYER: return "player"; + case DataType::SETTINGS: return "settings"; + case DataType::DEBUG: return "debug"; + case DataType::CUSTOM: return "custom"; + default: return "unknown"; + } +} + +constexpr const char* toString(RequestType type) { + switch (type) { + case RequestType::GET_PRICES: return "get_prices"; + case RequestType::GET_CHUNK: return "get_chunk"; + case RequestType::MOVE_PLAYER: return "move_player"; + case RequestType::SAVE_GAME: return "save_game"; + case RequestType::LOAD_GAME: return "load_game"; + case RequestType::CLOSE_WINDOW: return "close_window"; + case RequestType::FOCUS_WINDOW: return "focus_window"; + case RequestType::UPDATE_SETTINGS: return "update_settings"; + case RequestType::EXECUTE_COMMAND: return "execute_command"; + case RequestType::CUSTOM: return "custom"; + default: return "unknown"; + } +} + +constexpr const char* toString(EventLevel level) { + switch (level) { + case EventLevel::INFO: return "info"; + case EventLevel::SUCCESS: return "success"; + case EventLevel::WARNING: return "warning"; + case EventLevel::ERROR: return "error"; + case EventLevel::DEBUG: return "debug"; + case EventLevel::TRACE: return "trace"; + default: return "unknown"; + } +} + +constexpr const char* toString(DockType type) { + switch (type) { + case DockType::DOCK: return "dock"; + case DockType::SPLIT: return "split"; + case DockType::TABBED: return "tabbed"; + case DockType::FLOATING: return "floating"; + default: return "unknown"; + } +} + +constexpr const char* toString(DockPosition pos) { + switch (pos) { + case DockPosition::LEFT: return "left"; + case DockPosition::RIGHT: return "right"; + case DockPosition::TOP: return "top"; + case DockPosition::BOTTOM: return "bottom"; + case DockPosition::CENTER: return "center"; + case DockPosition::TAB: return "tab"; + default: return "unknown"; + } +} + +constexpr const char* toString(Orientation orient) { + switch (orient) { + case Orientation::HORIZONTAL: return "horizontal"; + case Orientation::VERTICAL: return "vertical"; + default: return "unknown"; + } +} + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ImGuiUI.h b/include/grove/ImGuiUI.h new file mode 100644 index 0000000..dbb2524 --- /dev/null +++ b/include/grove/ImGuiUI.h @@ -0,0 +1,707 @@ +#pragma once + +#include "IUI_Enums.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace warfactory { + +/** + * @brief ImGui implementation of IUI interface + * + * Provides full windowing system with docking, tabs, splits, and floating windows. + * Handles hybrid percentage + pixel sizing with automatic constraint enforcement. + */ +class ImGuiUI : public IUI { +private: + // ======================================== + // CORE STATE + // ======================================== + + GLFWwindow* window = nullptr; + bool initialized = false; + bool should_close = false; + int frame_count = 0; + + // Screen/parent sizes for percentage calculations + ImVec2 screen_size = {1400, 900}; + ImVec2 previous_screen_size = {0, 0}; + + // ======================================== + // WINDOW MANAGEMENT + // ======================================== + + struct WindowInfo { + std::string id; + std::string title; + std::string parent; + DockPosition dock_position = DockPosition::CENTER; + bool is_open = true; + bool is_floating = false; + bool resizable = true; + bool closeable = true; + + // Size system + ImVec2 size = {400, 300}; + ImVec2 min_size = {100, 100}; + ImVec2 max_size = {2000, 1500}; + ImVec2 position = {0, 0}; + + // Percentage tracking + std::string size_width_percent = ""; + std::string size_height_percent = ""; + std::string min_width_percent = ""; + std::string min_height_percent = ""; + std::string max_width_percent = ""; + std::string max_height_percent = ""; + + // Content + DataType data_type = DataType::CUSTOM; + json content_data; + }; + + std::map windows; + + struct DockInfo { + std::string id; + DockType type = DockType::DOCK; + DockPosition position = DockPosition::LEFT; + std::string parent; + bool collapsible = true; + bool resizable = true; + ImVec2 size = {300, 200}; + ImVec2 min_size = {100, 100}; + ImVec2 max_size = {1000, 800}; + std::vector child_windows; + }; + + std::map docks; + + // ======================================== + // CALLBACKS + // ======================================== + + std::map> request_callbacks; + std::map> custom_request_callbacks; + + // ======================================== + // MESSAGE SYSTEM + // ======================================== + + struct LogMessage { + EventLevel level; + std::string message; + std::chrono::steady_clock::time_point timestamp; + }; + + std::vector log_messages; + static constexpr size_t MAX_LOG_MESSAGES = 100; + +public: + ImGuiUI() = default; + ~ImGuiUI() override { shutdown(); } + + // ======================================== + // LIFECYCLE IMPLEMENTATION + // ======================================== + + void initialize(const json& config) override { + if (initialized) return; + + // Initialize GLFW + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW"); + } + + // OpenGL 3.3 Core + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Create window + std::string title = config.value("title", "Warfactory ImGui UI"); + auto window_size = config.value("window_size", json{{"width", 1400}, {"height", 900}}); + if (window_size.is_object()) { + screen_size.x = window_size.value("width", 1400); + screen_size.y = window_size.value("height", 900); + } else { + screen_size.x = 1400; + screen_size.y = 900; + } + + window = glfwCreateWindow( + static_cast(screen_size.x), + static_cast(screen_size.y), + title.c_str(), nullptr, nullptr + ); + + if (!window) { + glfwTerminate(); + throw std::runtime_error("Failed to create GLFW window"); + } + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // VSync + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + // Note: Docking features depend on ImGui docking branch + // Using manual docking simulation for compatibility + + // Basic style setup + ImGui::StyleColorsDark(); + + // Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 330 core"); + + initialized = true; + } + + bool update() override { + if (!initialized || !window) return false; + + if (glfwWindowShouldClose(window)) { + should_close = true; + return false; + } + + // Update screen size for percentage calculations + int fb_width, fb_height; + glfwGetFramebufferSize(window, &fb_width, &fb_height); + ImVec2 new_screen_size = {static_cast(fb_width), static_cast(fb_height)}; + + // Detect screen size changes and recalculate if needed + if (new_screen_size.x != previous_screen_size.x || new_screen_size.y != previous_screen_size.y) { + if (frame_count > 0) { // Skip first frame (initialization) + debug("🔄 Screen size changed: " + std::to_string((int)new_screen_size.x) + "x" + std::to_string((int)new_screen_size.y)); + recalculateAllSizes(); + } + previous_screen_size = screen_size; + } + screen_size = new_screen_size; + + frame_count++; + + // Poll events + glfwPollEvents(); + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Render all windows + renderAllWindows(); + + // Render ImGui + ImGui::Render(); + + // OpenGL rendering + glViewport(0, 0, static_cast(screen_size.x), static_cast(screen_size.y)); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + + return !should_close; + } + + void shutdown() override { + if (!initialized) return; + + if (window) { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); + window = nullptr; + } + + initialized = false; + } + +private: + // ======================================== + // SIZE CALCULATION HELPERS + // ======================================== + + /** + * @brief Parse size value - handles both pixels and percentages + */ + float parseSize(const json& size_value, float parent_size, float default_size) { + try { + if (size_value.is_number()) { + return size_value.get(); + } + + if (size_value.is_string()) { + std::string size_str = size_value.get(); + if (!size_str.empty() && size_str.back() == '%') { + float percent = std::stof(size_str.substr(0, size_str.length() - 1)); + return (percent / 100.0f) * parent_size; + } else { + // String but not percentage - try to parse as number + return std::stof(size_str); + } + } + } catch (...) { + // Any JSON or parsing error - return default + } + + // Neither number nor string or error - return default + return default_size; + } + + /** + * @brief Calculate effective size with hybrid constraints + */ + ImVec2 calculateEffectiveSize(const WindowInfo& win, ImVec2 parent_size) { + // Use already parsed sizes (converted in showData) + float target_width = win.size.x; + float target_height = win.size.y; + + // Calculate constraint bounds + float min_width = win.min_size.x; + float min_height = win.min_size.y; + float max_width = win.max_size.x; + float max_height = win.max_size.y; + + // Apply constraints (clamp) + float final_width = std::max(min_width, std::min(target_width, max_width)); + float final_height = std::max(min_height, std::min(target_height, max_height)); + + return {final_width, final_height}; + } + + /** + * @brief Calculate window position based on docking + */ + ImVec2 calculateDockedPosition(const WindowInfo& win, ImVec2 size) { + if (win.parent.empty() || win.is_floating) { + // For windows without parent, use explicit position or calculate smart default + debug("🔍 Checking position for '" + win.id + "': pos=" + + std::to_string(win.position.x) + "," + std::to_string(win.position.y) + + " floating=" + (win.is_floating ? "true" : "false")); + + // Only use explicit position if it was actually set by user (not just default values) + if (win.position.x > 10 && win.position.y > 10) { + debug("📌 Using explicit position for '" + win.id + "'"); + return win.position; // Use explicit position + } else { + // Simple approach: use actual window sizes from economy_main window + float left_edge_end = 252; // Real end of economy_main (we know it's 252px wide) + float top_edge_end = 88; // Real end of toolbar + margin + + // Find the right sidebar start by looking for info_panel_main + float right_edge_start = 1050; // We know info_panel starts at 1050px + + debug("🔧 Simple positioning for window '" + win.id + "': left_end=" + + std::to_string(left_edge_end) + "px, right_start=" + + std::to_string(right_edge_start) + "px, top_end=" + + std::to_string(top_edge_end) + "px"); + + // Position directly against the real edge of existing windows + float x = left_edge_end; // Directly touching end of left sidebar (252px) + float y = top_edge_end; // Directly below toolbar (88px) + + // If window would overlap with right sidebar, push it left to touch right edge + if (x + size.x > right_edge_start) { + x = right_edge_start - size.x; // Touch right sidebar windows + } + + debug("🎯 Calculated position for '" + win.id + "': " + + std::to_string(x) + "," + std::to_string(y) + + " (touching real window edges)"); + + return {x, y}; + } + } + + // Find parent dock + auto dock_it = docks.find(win.parent); + if (dock_it == docks.end()) { + return {0, 0}; // Parent dock not found + } + + const DockInfo& dock = dock_it->second; + + // Calculate dock area based on position + switch (dock.position) { + case DockPosition::LEFT: + return {0, 80}; // Left edge but below toolbar (72px + margin) + case DockPosition::RIGHT: + return {screen_size.x - dock.size.x, 80}; // Right edge but below toolbar + case DockPosition::TOP: + // Top edge - if dock spans full width, start at 0, else offset + if (dock.size.x >= screen_size.x * 0.9f) { + return {0, 0}; // Full width toolbar starts at screen edge + } else { + return {280, 0}; // Partial width toolbar starts after sidebar + } + case DockPosition::BOTTOM: + return {0, screen_size.y - dock.size.y}; // Bottom edge + case DockPosition::CENTER: + default: + return {screen_size.x * 0.5f - size.x * 0.5f, screen_size.y * 0.5f - size.y * 0.5f}; // Center + } + } + + // ======================================== + // RECALCULATION METHODS + // ======================================== + + void recalculateAllSizes() { + // Recalculate dock sizes + for (auto& [dock_id, dock] : docks) { + // Recalculate dock size if it uses percentages + recalculateDockSize(dock); + } + + // Recalculate window sizes + for (auto& [window_id, win] : windows) { + recalculateWindowSize(win); + } + } + + void recalculateDockSize(DockInfo& dock) { + // Re-parse dock size with new screen size + // This would need the original JSON config, for now just log + debug("📐 Recalculating dock: " + dock.id); + // TODO: Store original percentage strings to recalculate properly + } + + void recalculateWindowSize(WindowInfo& win) { + // Re-parse window size with new screen/parent sizes + debug("📐 Recalculating window: " + win.id); + + // Recalculate width if percentage + if (!win.size_width_percent.empty()) { + float parent_width = screen_size.x; + if (!win.parent.empty() && docks.find(win.parent) != docks.end()) { + parent_width = docks[win.parent].size.x; + } + win.size.x = parseSize(win.size_width_percent, parent_width, 400); + } + + // Recalculate height if percentage + if (!win.size_height_percent.empty()) { + float parent_height = screen_size.y; + if (!win.parent.empty() && docks.find(win.parent) != docks.end()) { + parent_height = docks[win.parent].size.y; + } + win.size.y = parseSize(win.size_height_percent, parent_height, 300); + } + } + + // ======================================== + // RENDERING IMPLEMENTATION + // ======================================== + + void renderAllWindows() { + // Log screen size for debugging (only first frame to avoid spam) + if (frame_count == 1) { + debug("🖥️ Screen Size: " + std::to_string((int)screen_size.x) + "x" + std::to_string((int)screen_size.y) + "px"); + info("🏗️ Manual docking system active (simulated docking layout)"); + } + + for (auto& [window_id, win] : windows) { + if (!win.is_open) continue; + + if (frame_count <= 5) { // Log first 5 frames for each window + debug("🪟 Window: " + window_id + " (" + win.title + ")"); + debug(" 📐 Target Size: " + std::to_string((int)win.size.x) + "x" + std::to_string((int)win.size.y) + "px"); + debug(" 📏 Size %: width='" + win.size_width_percent + "' height='" + win.size_height_percent + "'"); + debug(" ⚖️ Constraints: min=" + std::to_string((int)win.min_size.x) + "x" + std::to_string((int)win.min_size.y) + + " max=" + std::to_string((int)win.max_size.x) + "x" + std::to_string((int)win.max_size.y)); + debug(" 🔗 Docking: parent='" + win.parent + "' position=" + std::to_string((int)win.dock_position)); + } + + // Calculate effective size with constraints + ImVec2 effective_size = calculateEffectiveSize(win, screen_size); + if (frame_count <= 5) { + debug(" ✅ Effective Size: " + std::to_string((int)effective_size.x) + "x" + std::to_string((int)effective_size.y) + "px"); + } + + // Set window constraints + ImGui::SetNextWindowSizeConstraints(win.min_size, win.max_size); + + // Set window size + if (win.is_floating) { + // For floating windows, force initial size and position + ImGuiCond size_condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver; + ImGui::SetNextWindowSize(effective_size, size_condition); + + // Calculate smart position that avoids dock overlaps + ImVec2 floating_position = calculateDockedPosition(win, effective_size); + ImGuiCond position_condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver; + ImGui::SetNextWindowPos(floating_position, position_condition); + + if (frame_count <= 5) { + debug(" 🎈 Floating Position: " + std::to_string((int)floating_position.x) + "," + std::to_string((int)floating_position.y)); + } + } else { + // For docked windows, calculate position and force it during initial frames + ImVec2 dock_position = calculateDockedPosition(win, effective_size); + + ImGuiCond condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver; + ImGui::SetNextWindowSize(effective_size, condition); + ImGui::SetNextWindowPos(dock_position, condition); + + if (frame_count <= 5) { + debug(" 📍 Docked Position: " + std::to_string((int)dock_position.x) + "," + std::to_string((int)dock_position.y)); + } + } + + // Window flags + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + if (!win.resizable) flags |= ImGuiWindowFlags_NoResize; + + // Render window + if (ImGui::Begin(win.title.c_str(), win.closeable ? &win.is_open : nullptr, flags)) { + // Log actual ImGui window size after rendering (first 5 frames only) + if (frame_count <= 5) { + ImVec2 current_size = ImGui::GetWindowSize(); + ImVec2 current_pos = ImGui::GetWindowPos(); + debug(" 🎯 ImGui Actual: pos=" + std::to_string((int)current_pos.x) + "," + std::to_string((int)current_pos.y) + + " size=" + std::to_string((int)current_size.x) + "x" + std::to_string((int)current_size.y) + "px"); + } + + renderWindowContent(win); + } + ImGui::End(); + } + } + + void renderWindowContent(const WindowInfo& win) { + switch (win.data_type) { + case DataType::ECONOMY: + renderEconomyContent(win.content_data); + break; + case DataType::MAP: + renderMapContent(win.content_data); + break; + case DataType::INVENTORY: + renderInventoryContent(win.content_data); + break; + case DataType::CONSOLE: + renderConsoleContent(win.content_data); + break; + case DataType::PERFORMANCE: + renderPerformanceContent(win.content_data); + break; + case DataType::COMPANIES: + renderCompaniesContent(win.content_data); + break; + case DataType::ALERTS: + renderAlertsContent(win.content_data); + break; + case DataType::SETTINGS: + renderSettingsContent(win.content_data); + break; + default: + renderGenericContent(win.content_data); + break; + } + } + +public: + // ======================================== + // IUI INTERFACE IMPLEMENTATION - DATA DISPLAY + // ======================================== + + void showData(DataType dataType, const json& data) override { + // Extract window configuration + json window_config = data.value("window", json{}); + json content = data.value("content", data); + + // Generate ID if not provided + std::string window_id = window_config.value("id", "window_" + std::to_string(windows.size())); + + // Create or update window info + WindowInfo& win = windows[window_id]; + win.id = window_id; + win.title = window_config.value("title", toString(dataType)); + win.data_type = dataType; + win.content_data = content; + win.is_open = true; + + // Parse parent first (needed for size calculations) + win.parent = window_config.value("parent", ""); + + // Parse size configuration with percentage support + if (window_config.contains("size")) { + auto size_config = window_config["size"]; + if (size_config.is_object()) { + if (size_config.contains("width")) { + auto width_val = size_config["width"]; + if (width_val.is_string()) { + win.size_width_percent = width_val.get(); + debug("🔧 Processing width percentage '" + win.size_width_percent + + "' for window '" + win.id + "' with parent='" + win.parent + "'"); + + // Calculate parent size for percentage - use dock size if docked + float parent_width = screen_size.x; + if (!win.parent.empty() && docks.find(win.parent) != docks.end()) { + parent_width = docks[win.parent].size.x; + debug("🔍 Found parent dock '" + win.parent + "' with width=" + + std::to_string((int)parent_width) + "px"); + } else if (!win.parent.empty()) { + debug("❌ Parent dock '" + win.parent + "' not found! Using screen width."); + } + + win.size.x = parseSize(width_val, parent_width, 400); + } else if (width_val.is_number()) { + win.size.x = width_val.get(); + } else { + win.size.x = 400; // Default fallback + } + } + + if (size_config.contains("height")) { + auto height_val = size_config["height"]; + if (height_val.is_string()) { + win.size_height_percent = height_val.get(); + float parent_height = screen_size.y; + if (!win.parent.empty() && docks.find(win.parent) != docks.end()) { + parent_height = docks[win.parent].size.y; + } + win.size.y = parseSize(height_val, parent_height, 300); + } else if (height_val.is_number()) { + win.size.y = height_val.get(); + } else { + win.size.y = 300; // Default fallback + } + } + } + } + + // Parse constraints + if (window_config.contains("min_size")) { + auto min_config = window_config["min_size"]; + if (min_config.is_object()) { + if (min_config.contains("width")) { + win.min_size.x = parseSize(min_config["width"], screen_size.x, 100); + } else { + win.min_size.x = 100; + } + if (min_config.contains("height")) { + win.min_size.y = parseSize(min_config["height"], screen_size.y, 100); + } else { + win.min_size.y = 100; + } + } + } + + if (window_config.contains("max_size")) { + auto max_config = window_config["max_size"]; + if (max_config.is_object()) { + if (max_config.contains("width")) { + win.max_size.x = parseSize(max_config["width"], screen_size.x, 2000); + } else { + win.max_size.x = 2000; + } + if (max_config.contains("height")) { + win.max_size.y = parseSize(max_config["height"], screen_size.y, 1500); + } else { + win.max_size.y = 1500; + } + } + } + + // Parse other properties + win.is_floating = window_config.value("floating", false); + win.resizable = window_config.value("resizable", true); + win.closeable = window_config.value("closeable", true); + + // Parse dock position if specified + if (window_config.contains("dock")) { + std::string dock_str = window_config["dock"].get(); + if (dock_str == "left") win.dock_position = DockPosition::LEFT; + else if (dock_str == "right") win.dock_position = DockPosition::RIGHT; + else if (dock_str == "top") win.dock_position = DockPosition::TOP; + else if (dock_str == "bottom") win.dock_position = DockPosition::BOTTOM; + else if (dock_str == "tab") win.dock_position = DockPosition::CENTER; // tabs go in center + else win.dock_position = DockPosition::CENTER; + } + + if (window_config.contains("position")) { + auto pos = window_config["position"]; + if (pos.is_object()) { + win.position.x = pos.value("x", 0); + win.position.y = pos.value("y", 0); + } + } + } + + void showDataCustom(const std::string& customType, const json& data) override { + // Treat as generic data with custom type in title + json modified_data = data; + if (!modified_data.contains("window")) { + modified_data["window"] = json{}; + } + if (!modified_data["window"].contains("title")) { + modified_data["window"]["title"] = customType; + } + + showData(DataType::CUSTOM, modified_data); + } + + // ======================================== + // IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS + // ======================================== + + void onRequest(RequestType requestType, std::function callback) override; + void onRequestCustom(const std::string& customType, std::function callback) override; + void showEvent(EventLevel level, const std::string& message) override; + + // ======================================== + // WINDOW MANAGEMENT IMPLEMENTATION + // ======================================== + + void createDock(const std::string& dockId, DockType type, DockPosition position, const json& config = {}) override; + void createSplit(const std::string& dockId, Orientation orientation, const json& config = {}) override; + void closeWindow(const std::string& windowId) override; + void focusWindow(const std::string& windowId) override; + + // ======================================== + // STATE MANAGEMENT + // ======================================== + + json getState() const override; + void setState(const json& state) override; + +private: + // ======================================== + // CONTENT RENDERING IMPLEMENTATIONS + // ======================================== + + void renderEconomyContent(const json& content); + void renderMapContent(const json& content); + void renderInventoryContent(const json& content); + void renderConsoleContent(const json& content); + void renderPerformanceContent(const json& content); + void renderCompaniesContent(const json& content); + void renderAlertsContent(const json& content); + void renderSettingsContent(const json& content); + void renderGenericContent(const json& content); + void renderLogConsole(); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IntraIO.h b/include/grove/IntraIO.h new file mode 100644 index 0000000..981fa5f --- /dev/null +++ b/include/grove/IntraIO.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IIO.h" + +using json = nlohmann::json; + +namespace warfactory { + +// Interface for message delivery to avoid circular include +class IIntraIODelivery { +public: + virtual ~IIntraIODelivery() = default; + virtual void deliverMessage(const std::string& topic, const json& message, bool isLowFreq) = 0; + virtual const std::string& getInstanceId() const = 0; +}; + +/** + * @brief Intra-process IO implementation with central routing + * + * IntraIO provides same-process pub/sub communication with zero network overhead. + * Each module gets its own IntraIO instance, and messages are routed through + * IntraIOManager for proper multi-module delivery. + * + * Features: + * - Per-module isolation (one instance per module) + * - Central routing via IntraIOManager + * - Topic pattern matching with wildcards (e.g., "player:*", "economy:*") + * - Low-frequency batching with configurable intervals + * - Message replacement for reducible topics (latest-only semantics) + * - Comprehensive health monitoring and metrics + * - Thread-safe operations + * - Pull-based message consumption + * + * Performance characteristics: + * - Publish: ~10-50ns (direct memory copy + routing) + * - Subscribe: ~100-500ns (pattern registration) + * - Pull: ~50-200ns (queue operations) + * - Zero network serialization overhead + */ +class IntraIO : public IIO, public IIntraIODelivery { +private: + std::shared_ptr logger; + mutable std::mutex operationMutex; // Thread safety for all operations + + // Instance identification for routing + std::string instanceId; + + // Message storage + std::queue messageQueue; + std::queue lowFreqMessageQueue; + + // Subscription management + struct Subscription { + std::regex pattern; + std::string originalPattern; + SubscriptionConfig config; + std::chrono::high_resolution_clock::time_point lastBatch; + std::unordered_map batchedMessages; // For replaceable messages + std::vector accumulatedMessages; // For non-replaceable messages + }; + + std::vector highFreqSubscriptions; + std::vector lowFreqSubscriptions; + + // Health monitoring + mutable std::atomic totalPublished{0}; + mutable std::atomic totalPulled{0}; + mutable std::atomic totalDropped{0}; + mutable std::chrono::high_resolution_clock::time_point lastHealthCheck; + mutable float averageProcessingRate = 0.0f; + + // Configuration + static constexpr size_t DEFAULT_MAX_QUEUE_SIZE = 10000; + size_t maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + + // Helper methods + void logIOStart(); + bool matchesPattern(const std::string& topic, const std::regex& pattern) const; + std::regex compileTopicPattern(const std::string& pattern) const; + void processLowFreqSubscriptions(); + void flushBatchedMessages(Subscription& sub); + void updateHealthMetrics() const; + void enforceQueueLimits(); + void logPublish(const std::string& topic, const json& message) const; + void logSubscription(const std::string& pattern, bool isLowFreq) const; + void logPull(const Message& message) const; + +public: + IntraIO(const std::string& instanceId); + virtual ~IntraIO(); + + // IIO implementation + void publish(const std::string& topic, const json& message) override; + void subscribe(const std::string& topicPattern, const SubscriptionConfig& config = {}) override; + void subscribeLowFreq(const std::string& topicPattern, const SubscriptionConfig& config = {}) override; + int hasMessages() const override; + Message pullMessage() override; + IOHealth getHealth() const override; + IOType getType() const override; + + // Configuration and management + void setMaxQueueSize(size_t maxSize); + size_t getMaxQueueSize() const; + void clearAllMessages(); + void clearAllSubscriptions(); + + // Debug and monitoring + json getDetailedMetrics() const; + void setLogLevel(spdlog::level::level_enum level); + size_t getSubscriptionCount() const; + std::vector getActiveTopics() const; + + // Testing utilities + void simulateHighLoad(int messageCount, const std::string& topicPrefix = "test"); + void forceProcessLowFreqBatches(); + + // Manager interface (called by IntraIOManager) + void deliverMessage(const std::string& topic, const json& message, bool isLowFreq); + const std::string& getInstanceId() const; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/IntraIOManager.h b/include/grove/IntraIOManager.h new file mode 100644 index 0000000..3bb2b9e --- /dev/null +++ b/include/grove/IntraIOManager.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IIO.h" + +using json = nlohmann::json; + +namespace warfactory { + +class IntraIO; // Forward declaration +class IIntraIODelivery; // Forward declaration + +// Factory function for creating IntraIO (defined in IntraIO.cpp to avoid circular include) +std::shared_ptr createIntraIOInstance(const std::string& instanceId); + +/** + * @brief Central router for IntraIO instances + * + * IntraIOManager coordinates message passing between multiple IntraIO instances. + * Each module gets its own IntraIO instance, and the manager handles routing + * messages between them based on subscriptions. + * + * Architecture: + * - One IntraIO instance per module (isolation) + * - Central routing of messages between instances + * - Pattern-based subscription matching + * - Thread-safe operations + * + * Performance: + * - Direct memory routing (no serialization) + * - Pattern caching for fast lookup + * - Batched delivery for efficiency + */ +class IntraIOManager { +private: + std::shared_ptr logger; + mutable std::mutex managerMutex; + + // Registry of IntraIO instances + std::unordered_map> instances; + + // Subscription routing table + struct RouteEntry { + std::string instanceId; + std::regex pattern; + std::string originalPattern; + bool isLowFreq; + }; + std::vector routingTable; + + // Statistics + mutable std::atomic totalRoutedMessages{0}; + mutable std::atomic totalRoutes{0}; + +public: + IntraIOManager(); + ~IntraIOManager(); + + // Instance management + std::shared_ptr createInstance(const std::string& instanceId); + void registerInstance(const std::string& instanceId, std::shared_ptr instance); + void removeInstance(const std::string& instanceId); + std::shared_ptr getInstance(const std::string& instanceId) const; + + // Routing (called by IntraIO instances) + void routeMessage(const std::string& sourceid, const std::string& topic, const json& message); + void registerSubscription(const std::string& instanceId, const std::string& pattern, bool isLowFreq); + void unregisterSubscription(const std::string& instanceId, const std::string& pattern); + + // Management + void clearAllRoutes(); + size_t getInstanceCount() const; + std::vector getInstanceIds() const; + + // Debug and monitoring + json getRoutingStats() const; + void setLogLevel(spdlog::level::level_enum level); + + // Singleton access (for global routing) + static IntraIOManager& getInstance(); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ModuleFactory.h b/include/grove/ModuleFactory.h new file mode 100644 index 0000000..5c7cc29 --- /dev/null +++ b/include/grove/ModuleFactory.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "IModule.h" + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Factory for loading and creating modules from shared libraries (.so files) + * + * ModuleFactory handles dynamic loading of module implementations from .so files. + * It manages symbol resolution, error handling, and module lifecycle. + * + * Features: + * - Dynamic loading of .so files with dlopen/dlsym + * - Automatic symbol resolution for module entry points + * - Hot-reload support with proper cleanup + * - Comprehensive error reporting and logging + * - Module registration and discovery + * - Thread-safe operations + * + * Expected module .so structure: + * - extern "C" IModule* create_module() + * - extern "C" void destroy_module(IModule*) + * - extern "C" const char* get_module_type() + * - extern "C" const char* get_module_version() + */ +class ModuleFactory { +public: + struct ModuleInfo { + std::string path; + std::string type; + std::string version; + void* handle = nullptr; + std::function createFunc; + std::function destroyFunc; + }; + + ModuleFactory(); + ~ModuleFactory(); + + // Module loading + std::unique_ptr loadModule(const std::string& modulePath); + std::unique_ptr createModule(const std::string& moduleType); + + // Module discovery and registration + void scanModulesDirectory(const std::string& directory); + void registerModule(const std::string& modulePath); + void unloadModule(const std::string& moduleType); + void unloadAllModules(); + + // Information and diagnostics + std::vector getAvailableModules() const; + std::vector getLoadedModules() const; + ModuleInfo getModuleInfo(const std::string& moduleType) const; + bool isModuleLoaded(const std::string& moduleType) const; + bool isModuleAvailable(const std::string& moduleType) const; + + // Configuration + void setModulesDirectory(const std::string& directory); + std::string getModulesDirectory() const; + + // Hot-reload support + bool reloadModule(const std::string& moduleType); + void enableHotReload(bool enable); + bool isHotReloadEnabled() const; + + // Diagnostics and debugging + json getDetailedStatus() const; + void validateModule(const std::string& modulePath); + void setLogLevel(spdlog::level::level_enum level); + +private: + std::shared_ptr logger; + std::string modulesDirectory; + bool hotReloadEnabled = false; + + // Module registry + std::unordered_map loadedModules; + std::unordered_map availableModules; // type -> path + + // Helper methods + std::shared_ptr getFactoryLogger(); + bool loadSharedLibrary(const std::string& path, ModuleInfo& info); + void unloadSharedLibrary(ModuleInfo& info); + bool resolveSymbols(ModuleInfo& info); + std::string extractModuleTypeFromPath(const std::string& path) const; + bool isValidModuleFile(const std::string& path) const; + void logModuleLoad(const std::string& type, const std::string& path) const; + void logModuleUnload(const std::string& type) const; + void logModuleError(const std::string& operation, const std::string& details) const; +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ModuleSystemFactory.h b/include/grove/ModuleSystemFactory.h new file mode 100644 index 0000000..9ccd84a --- /dev/null +++ b/include/grove/ModuleSystemFactory.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "IModuleSystem.h" + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Factory for creating ModuleSystem implementations + * + * ModuleSystemFactory provides centralized creation of different execution strategies: + * - "sequential" -> SequentialModuleSystem (debug/test, one-at-a-time execution) + * - "threaded" -> ThreadedModuleSystem (each module in own thread) + * - "thread_pool" -> ThreadPoolModuleSystem (tasks distributed across pool) + * - "cluster" -> ClusterModuleSystem (distributed across machines) + * + * Each ModuleSystem type provides different performance characteristics while + * maintaining the same interface, enabling progressive scaling. + * + * Usage: + * ```cpp + * auto moduleSystem = ModuleSystemFactory::create("sequential"); + * auto moduleSystem = ModuleSystemFactory::create(ModuleSystemType::THREAD_POOL); + * auto moduleSystem = ModuleSystemFactory::createFromConfig(config); + * ``` + */ +class ModuleSystemFactory { +public: + /** + * @brief Create ModuleSystem from string strategy name + * @param strategy String representation of execution strategy + * @return Unique pointer to ModuleSystem implementation + * @throws std::invalid_argument if strategy is unknown + */ + static std::unique_ptr create(const std::string& strategy); + + /** + * @brief Create ModuleSystem from enum type + * @param systemType ModuleSystemType enum value + * @return Unique pointer to ModuleSystem implementation + * @throws std::invalid_argument if type is not implemented + */ + static std::unique_ptr create(ModuleSystemType systemType); + + /** + * @brief Create ModuleSystem from JSON configuration + * @param config JSON configuration object + * @return Unique pointer to configured ModuleSystem + * @throws std::invalid_argument if config is invalid + * + * Expected config format: + * ```json + * { + * "strategy": "thread_pool", + * "thread_count": 4, + * "queue_size": 1000, + * "priority": "normal" + * } + * ``` + */ + static std::unique_ptr createFromConfig(const json& config); + + /** + * @brief Get list of available ModuleSystem strategies + * @return Vector of supported strategy strings + */ + static std::vector getAvailableStrategies(); + + /** + * @brief Check if strategy is supported + * @param strategy Strategy string to check + * @return True if strategy is supported + */ + static bool isStrategySupported(const std::string& strategy); + + /** + * @brief Parse strategy string to enum (case-insensitive) + * @param strategyStr String representation of strategy + * @return ModuleSystemType enum value + * @throws std::invalid_argument if string is invalid + */ + static ModuleSystemType parseStrategy(const std::string& strategyStr); + + /** + * @brief Convert strategy enum to string + * @param systemType ModuleSystemType enum value + * @return String representation of strategy + */ + static std::string strategyToString(ModuleSystemType systemType); + + /** + * @brief Get recommended strategy for given performance requirements + * @param targetFPS Target frames per second (0 = no preference) + * @param moduleCount Expected number of modules + * @param cpuCores Available CPU cores (0 = auto-detect) + * @return Recommended ModuleSystemType + */ + static ModuleSystemType getRecommendedStrategy(int targetFPS = 60, + int moduleCount = 1, + int cpuCores = 0); + +private: + static std::shared_ptr getFactoryLogger(); + static std::string toLowercase(const std::string& str); + static int detectCpuCores(); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/RandomGenerator.h b/include/grove/RandomGenerator.h new file mode 100644 index 0000000..8d62bb5 --- /dev/null +++ b/include/grove/RandomGenerator.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +namespace warfactory { + +/** + * @brief Centralized random number generator singleton + * + * Provides consistent, seedable random number generation across all modules. + * Ensures reproducibility for testing and debugging while maintaining + * high-quality random distribution. + */ +class RandomGenerator { +private: + std::mt19937 gen; + + RandomGenerator() : gen(std::random_device{}()) {} + +public: + // Singleton access + static RandomGenerator& getInstance() { + static RandomGenerator instance; + return instance; + } + + // Delete copy/move constructors and operators + RandomGenerator(const RandomGenerator&) = delete; + RandomGenerator& operator=(const RandomGenerator&) = delete; + RandomGenerator(RandomGenerator&&) = delete; + RandomGenerator& operator=(RandomGenerator&&) = delete; + + /** + * @brief Seed the random generator for reproducible sequences + * @param seed Seed value (use same seed for identical results) + */ + void seed(uint32_t seed) { + gen.seed(seed); + } + + /** + * @brief Generate uniform float in range [min, max] + */ + float uniform(float min, float max) { + std::uniform_real_distribution dis(min, max); + return dis(gen); + } + + /** + * @brief Generate uniform int in range [min, max] (inclusive) + */ + int uniformInt(int min, int max) { + std::uniform_int_distribution dis(min, max); + return dis(gen); + } + + /** + * @brief Generate normal distribution float + */ + float normal(float mean, float stddev) { + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + /** + * @brief Generate uniform float in range [0.0, 1.0] + */ + float unit() { + return uniform(0.0f, 1.0f); + } + + /** + * @brief Generate boolean with given probability + * @param probability Probability of returning true [0.0, 1.0] + */ + bool boolean(float probability = 0.5f) { + return unit() < probability; + } + + /** + * @brief Direct access to underlying generator for custom distributions + */ + std::mt19937& getGenerator() { + return gen; + } +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/Resource.h b/include/grove/Resource.h new file mode 100644 index 0000000..7ffcc8e --- /dev/null +++ b/include/grove/Resource.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +class Resource { +private: + std::string resource_id; + std::string name; + std::string category; + std::string logistic_category; + float density; + int stack_size; + std::string container_type; + json ui_data; + +public: + Resource() = default; + Resource(const json& resource_data); + + const std::string& getResourceId() const { return resource_id; } + const std::string& getName() const { return name; } + const std::string& getCategory() const { return category; } + const std::string& getLogisticCategory() const { return logistic_category; } + float getDensity() const { return density; } + int getStackSize() const { return stack_size; } + const std::string& getContainerType() const { return container_type; } + const json& getUIData() const { return ui_data; } + + static Resource loadFromJson(const std::string& resource_id, const json& resource_data); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/ResourceRegistry.h b/include/grove/ResourceRegistry.h new file mode 100644 index 0000000..f1fb853 --- /dev/null +++ b/include/grove/ResourceRegistry.h @@ -0,0 +1,113 @@ +#pragma once + +#include "Resource.h" +#include +#include +#include +#include + +namespace warfactory { + +/** + * @brief Singleton registry for all game resources with fast uint32_t ID lookup + * + * Centralizes resource management with O(1) access by numeric ID. + * Resources are loaded once at startup and accessed by ID throughout the game. + */ +class ResourceRegistry { +private: + static std::unique_ptr instance; + static bool initialized; + + std::vector resources; // Indexed by ID (resources[id]) + std::unordered_map name_to_id; // String -> ID mapping (init only) + uint32_t next_id = 1; // Start at 1 (0 = invalid/null resource) + + ResourceRegistry() = default; + +public: + // Singleton access + static ResourceRegistry& getInstance(); + static void initialize(); + static void shutdown(); + + // ======================================== + // REGISTRATION (Initialization Phase) + // ======================================== + + /** + * @brief Register a resource and get its assigned ID + * @param resource The resource to register + * @return The assigned uint32_t ID for this resource + */ + uint32_t registerResource(const Resource& resource); + + /** + * @brief Load resources from JSON configuration + * @param resources_json JSON object containing all resources + */ + void loadResourcesFromJson(const json& resources_json); + + // ======================================== + // RUNTIME ACCESS (Performance Critical) + // ======================================== + + /** + * @brief Get resource by ID (O(1) access) + * @param id The resource ID + * @return Pointer to resource or nullptr if not found + */ + const Resource* getResource(uint32_t id) const; + + /** + * @brief Get resource ID by name (use sparingly - prefer caching IDs) + * @param name The resource name/identifier + * @return The resource ID or 0 if not found + */ + uint32_t getResourceId(const std::string& name) const; + + /** + * @brief Check if resource ID is valid + */ + bool isValidResourceId(uint32_t id) const; + + // ======================================== + // BULK OPERATIONS + // ======================================== + + /** + * @brief Get all registered resource IDs + */ + std::vector getAllResourceIds() const; + + /** + * @brief Get total number of registered resources + */ + size_t getResourceCount() const; + + /** + * @brief Clear all registered resources (testing/reset) + */ + void clear(); + + // ======================================== + // CONVENIENCE CONSTANTS + // ======================================== + + static constexpr uint32_t INVALID_RESOURCE_ID = 0; + static constexpr uint32_t MAX_RESOURCES = 1000000; // 1M resources max + + // Prevent copy/assignment + ResourceRegistry(const ResourceRegistry&) = delete; + ResourceRegistry& operator=(const ResourceRegistry&) = delete; +}; + +// ======================================== +// CONVENIENCE MACROS FOR PERFORMANCE +// ======================================== + +#define RESOURCE_ID(name) warfactory::ResourceRegistry::getInstance().getResourceId(name) +#define GET_RESOURCE(id) warfactory::ResourceRegistry::getInstance().getResource(id) +#define VALID_RESOURCE(id) warfactory::ResourceRegistry::getInstance().isValidResourceId(id) + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/SequentialModuleSystem.h b/include/grove/SequentialModuleSystem.h new file mode 100644 index 0000000..709eec9 --- /dev/null +++ b/include/grove/SequentialModuleSystem.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "IModuleSystem.h" +#include "IModule.h" + +using json = nlohmann::json; + +namespace warfactory { + +/** + * @brief Sequential module system implementation for debug and testing + * + * SequentialModuleSystem processes modules one at a time in a simple, predictable manner. + * Perfect for development, debugging, and testing scenarios where deterministic execution + * is more important than performance. + * + * Features: + * - Single-threaded execution (thread-safe by design) + * - Immediate task execution (no actual scheduling) + * - Comprehensive logging of all operations + * - Simple state management + * - Perfect for step-by-step debugging + * + * Task scheduling behavior: + * - scheduleTask() executes immediately (no queue) + * - hasCompletedTasks() always returns 0 (tasks complete immediately) + * - getCompletedTask() throws (no queued results) + */ +class SequentialModuleSystem : public IModuleSystem { +private: + std::shared_ptr logger; + std::unique_ptr module; + std::string moduleName = "unknown"; + + // Performance tracking + std::chrono::high_resolution_clock::time_point lastProcessTime; + size_t processCallCount = 0; + float totalProcessTime = 0.0f; + float lastProcessDuration = 0.0f; + + // Task execution tracking (for logging purposes) + size_t taskExecutionCount = 0; + + // Helper methods + void logSystemStart(); + void logProcessStart(float deltaTime); + void logProcessEnd(float processTime); + void logTaskExecution(const std::string& taskType, const json& taskData); + void validateModule() const; + +public: + SequentialModuleSystem(); + virtual ~SequentialModuleSystem(); + + // IModuleSystem implementation + void setModule(std::unique_ptr module) override; + IModule* getModule() const override; + int processModule(float deltaTime) override; + ModuleSystemType getType() const override; + + // Hot-reload support + std::unique_ptr extractModule(); + + // ITaskScheduler implementation (inherited) + void scheduleTask(const std::string& taskType, const json& taskData) override; + int hasCompletedTasks() const override; + json getCompletedTask() override; + + // Debug and monitoring methods + json getPerformanceMetrics() const; + void resetPerformanceMetrics(); + float getAverageProcessTime() const; + size_t getProcessCallCount() const; + size_t getTaskExecutionCount() const; + + // Configuration + void setLogLevel(spdlog::level::level_enum level); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/include/grove/SerializationRegistry.h b/include/grove/SerializationRegistry.h new file mode 100644 index 0000000..fdd7eb6 --- /dev/null +++ b/include/grove/SerializationRegistry.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +class ASerializable; + +class SerializationRegistry { +private: + std::unordered_map registered_objects; + + SerializationRegistry() = default; + +public: + static SerializationRegistry& getInstance(); + + void registerObject(const std::string& instance_id, ASerializable* object); + void unregisterObject(const std::string& instance_id); + + json serializeAll() const; + void deserializeAll(const json& data); + + json serializeObject(const std::string& instance_id) const; + void deserializeObject(const std::string& instance_id, const json& data); + + size_t getRegisteredCount() const { return registered_objects.size(); } + std::vector getRegisteredIds() const; + + void clear(); +}; + +} // namespace warfactory \ No newline at end of file diff --git a/src/DebugEngine.cpp b/src/DebugEngine.cpp new file mode 100644 index 0000000..dca6b2c --- /dev/null +++ b/src/DebugEngine.cpp @@ -0,0 +1,487 @@ +#include +#include +#include +#include +#include + +namespace warfactory { + +DebugEngine::DebugEngine() { + // Create comprehensive logger with multiple sinks + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/debug_engine.log", true); + + console_sink->set_level(spdlog::level::debug); + file_sink->set_level(spdlog::level::trace); + + logger = std::make_shared("DebugEngine", + spdlog::sinks_init_list{console_sink, file_sink}); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::debug); + + // Register logger globally + spdlog::register_logger(logger); + + logger->info("🔧 DebugEngine constructor - Maximum logging enabled"); + logger->debug("📊 Console sink level: DEBUG, File sink level: TRACE"); + logger->trace("🏗️ DebugEngine object created at address: {}", static_cast(this)); +} + +DebugEngine::~DebugEngine() { + logger->info("🔧 DebugEngine destructor called"); + if (running.load()) { + logger->warn("⚠️ Engine still running during destruction - forcing shutdown"); + shutdown(); + } + logger->trace("🏗️ DebugEngine object destroyed"); +} + +void DebugEngine::initialize() { + logger->info("🚀 Initializing DebugEngine..."); + logEngineStart(); + + // Create logs directory if it doesn't exist + std::filesystem::create_directories("logs"); + logger->debug("📁 Ensured logs directory exists"); + + engineStartTime = std::chrono::high_resolution_clock::now(); + lastFrameTime = engineStartTime; + frameCount = 0; + + logger->info("✅ DebugEngine initialization complete"); + logger->debug("🕐 Engine start time recorded: {}", + std::chrono::duration_cast( + engineStartTime.time_since_epoch()).count()); +} + +void DebugEngine::run() { + logger->info("🏃 Starting DebugEngine main loop"); + logger->debug("🔄 Engine loop type: Continuous with debug capabilities"); + + if (!coordinatorSocket) { + logger->warn("⚠️ No coordinator socket registered - running in isolated mode"); + } + + if (clientSockets.empty()) { + logger->warn("⚠️ No client sockets registered - no players will connect"); + } + + running.store(true); + logger->info("✅ DebugEngine marked as running"); + + while (running.load()) { + if (debugPaused.load()) { + logger->trace("⏸️ Engine paused - waiting for resume or step command"); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + + float deltaTime = calculateDeltaTime(); + step(deltaTime); + + // Log every 60 frames (roughly every second at 60fps) + if (frameCount % 60 == 0) { + logger->debug("📊 Frame {}: Running smoothly, deltaTime: {:.3f}ms", + frameCount, deltaTime * 1000); + } + } + + logger->info("🏁 DebugEngine main loop ended"); +} + +void DebugEngine::step(float deltaTime) { + logFrameStart(deltaTime); + + auto frameStartTime = std::chrono::high_resolution_clock::now(); + + try { + // Process coordinator messages + if (coordinatorSocket) { + logger->trace("📨 Processing coordinator messages"); + processCoordinatorMessages(); + } + + // Process client messages + if (!clientSockets.empty()) { + logger->trace("👥 Processing {} client socket(s)", clientSockets.size()); + processClientMessages(); + } + + // Process all module systems + if (!moduleSystems.empty()) { + logger->trace("🔧 Processing {} module system(s)", moduleSystems.size()); + processModuleSystems(deltaTime); + } + + // Health monitoring every 30 frames + if (frameCount % 30 == 0) { + logModuleHealth(); + logSocketHealth(); + } + + frameCount++; + + } catch (const std::exception& e) { + logger->error("❌ Exception during step execution: {}", e.what()); + logger->error("🔍 Frame: {}, deltaTime: {:.3f}ms", frameCount, deltaTime * 1000); + throw; // Re-throw to allow caller to handle + } + + auto frameEndTime = std::chrono::high_resolution_clock::now(); + float frameTime = std::chrono::duration(frameEndTime - frameStartTime).count(); + + logFrameEnd(frameTime); +} + +void DebugEngine::shutdown() { + logger->info("🛑 DebugEngine shutdown initiated"); + logEngineShutdown(); + + running.store(false); + logger->debug("🔄 Running flag set to false"); + + // Shutdown all module systems + if (!moduleSystems.empty()) { + logger->info("🔧 Shutting down {} module system(s)", moduleSystems.size()); + for (size_t i = 0; i < moduleSystems.size(); ++i) { + logger->debug("🔧 Shutting down module system: {}", moduleNames[i]); + // Note: ModuleSystems don't have shutdown in interface yet + // This would be added when implementing IModuleSystem + } + moduleSystems.clear(); + moduleNames.clear(); + logger->info("✅ All module systems shut down"); + } + + // Clear sockets + if (coordinatorSocket) { + logger->debug("🔌 Clearing coordinator socket"); + coordinatorSocket.reset(); + } + + if (!clientSockets.empty()) { + logger->info("👥 Clearing {} client socket(s)", clientSockets.size()); + clientSockets.clear(); + } + + logger->info("✅ DebugEngine shutdown complete"); + + // Final statistics + auto shutdownTime = std::chrono::high_resolution_clock::now(); + auto totalRunTime = std::chrono::duration(shutdownTime - engineStartTime).count(); + logger->info("📊 Total engine runtime: {:.2f} seconds", totalRunTime); + logger->info("📊 Total frames processed: {}", frameCount); + if (totalRunTime > 0) { + logger->info("📊 Average FPS: {:.2f}", frameCount / totalRunTime); + } +} + +void DebugEngine::loadModules(const std::string& configPath) { + logger->info("📦 Loading modules from config: {}", configPath); + + try { + // Read configuration file + std::ifstream configFile(configPath); + if (!configFile.is_open()) { + logger->error("❌ Cannot open config file: {}", configPath); + throw std::runtime_error("Config file not found: " + configPath); + } + + json config; + configFile >> config; + logger->debug("✅ Config file parsed successfully"); + logger->trace("📄 Config content: {}", config.dump(2)); + + // Validate configuration + validateConfiguration(); + + if (!config.contains("modules")) { + logger->warn("⚠️ No 'modules' section in config - no modules to load"); + return; + } + + auto modules = config["modules"]; + logger->info("🔍 Found {} module(s) to load", modules.size()); + + for (size_t i = 0; i < modules.size(); ++i) { + const auto& moduleConfig = modules[i]; + logger->info("📦 Loading module {}/{}", i + 1, modules.size()); + + if (!moduleConfig.contains("path") || !moduleConfig.contains("strategy")) { + logger->error("❌ Module config missing 'path' or 'strategy': {}", moduleConfig.dump()); + continue; + } + + std::string modulePath = moduleConfig["path"]; + std::string strategy = moduleConfig["strategy"]; + std::string frequency = moduleConfig.value("frequency", "60hz"); + + logger->info("📂 Module path: {}", modulePath); + logger->info("⚙️ Module strategy: {}", strategy); + logger->info("⏱️ Module frequency: {}", frequency); + + // TODO: Create appropriate ModuleSystem based on strategy + // For now, we'll log what would be created + logger->info("🚧 TODO: Create {} ModuleSystem for {}", strategy, modulePath); + logger->debug("🔮 Future: Load dynamic library from {}", modulePath); + logger->debug("🔮 Future: Instantiate module and wrap in {} system", strategy); + + // Store module name for tracking + moduleNames.push_back(modulePath); + } + + logger->info("✅ Module loading configuration processed"); + + } catch (const std::exception& e) { + logger->error("❌ Failed to load modules: {}", e.what()); + throw; + } +} + +void DebugEngine::registerMainSocket(std::unique_ptr socket) { + logger->info("🔌 Registering main coordinator socket"); + + if (coordinatorSocket) { + logger->warn("⚠️ Coordinator socket already exists - replacing"); + } + + coordinatorSocket = std::move(socket); + logger->info("✅ Main coordinator socket registered"); + logger->debug("🔍 Socket type: {}", static_cast(coordinatorSocket->getType())); +} + +void DebugEngine::registerNewClientSocket(std::unique_ptr clientSocket) { + logger->info("👥 Registering new client socket (client #{})", clientSockets.size() + 1); + + logger->debug("🔍 Client socket type: {}", static_cast(clientSocket->getType())); + clientSockets.push_back(std::move(clientSocket)); + + logger->info("✅ Client socket registered - Total clients: {}", clientSockets.size()); +} + +EngineType DebugEngine::getType() const { + logger->trace("🏷️ Engine type requested: DEBUG"); + return EngineType::DEBUG; +} + +// Debug-specific methods +void DebugEngine::pauseExecution() { + logger->info("⏸️ Pausing engine execution"); + debugPaused.store(true); + logger->debug("🔄 Debug pause flag set to true"); +} + +void DebugEngine::resumeExecution() { + logger->info("▶️ Resuming engine execution"); + debugPaused.store(false); + logger->debug("🔄 Debug pause flag set to false"); +} + +void DebugEngine::stepSingleFrame() { + logger->info("👣 Executing single frame step"); + if (debugPaused.load()) { + float deltaTime = calculateDeltaTime(); + step(deltaTime); + logger->debug("✅ Single frame step completed"); + } else { + logger->warn("⚠️ Cannot step single frame - engine not paused"); + } +} + +bool DebugEngine::isPaused() const { + bool paused = debugPaused.load(); + logger->trace("🔍 Pause status requested: {}", paused ? "PAUSED" : "RUNNING"); + return paused; +} + +json DebugEngine::getDetailedStatus() const { + logger->debug("📊 Detailed status requested"); + + json status = { + {"type", "DEBUG"}, + {"running", running.load()}, + {"paused", debugPaused.load()}, + {"frame_count", frameCount}, + {"modules_loaded", moduleNames.size()}, + {"client_sockets", clientSockets.size()}, + {"has_coordinator", coordinatorSocket != nullptr} + }; + + // Add runtime info + if (frameCount > 0) { + auto currentTime = std::chrono::high_resolution_clock::now(); + auto totalTime = std::chrono::duration(currentTime - engineStartTime).count(); + status["runtime_seconds"] = totalTime; + status["average_fps"] = frameCount / totalTime; + } + + logger->trace("📄 Status JSON: {}", status.dump()); + return status; +} + +void DebugEngine::setLogLevel(spdlog::level::level_enum level) { + logger->info("🔧 Setting log level to: {}", spdlog::level::to_string_view(level)); + logger->set_level(level); + logger->debug("✅ Log level updated"); +} + +// Private helper methods +void DebugEngine::logEngineStart() { + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🏭 WARFACTORY DEBUG ENGINE STARTING"); + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🎯 Engine Type: DEBUG (Maximum visibility mode)"); + logger->info("📊 Logging Level: TRACE (Everything logged)"); + logger->info("🔧 Features: Step debugging, health monitoring, performance tracking"); +} + +void DebugEngine::logEngineShutdown() { + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🏭 WARFACTORY DEBUG ENGINE SHUTTING DOWN"); + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); +} + +void DebugEngine::logFrameStart(float deltaTime) { + logger->trace("🎬 Frame {} START - deltaTime: {:.3f}ms", frameCount, deltaTime * 1000); +} + +void DebugEngine::logFrameEnd(float frameTime) { + logger->trace("🏁 Frame {} END - frameTime: {:.3f}ms", frameCount, frameTime); + + // Warn about slow frames + if (frameTime > 16.67f) { // More than 60fps target + logger->warn("🐌 Slow frame detected: {:.2f}ms (target: <16.67ms for 60fps)", frameTime); + } +} + +void DebugEngine::logModuleHealth() { + if (moduleSystems.empty()) { + logger->debug("🏥 Module health check: No modules loaded"); + return; + } + + logger->debug("🏥 Module health check: {} module system(s)", moduleSystems.size()); + + for (size_t i = 0; i < moduleSystems.size(); ++i) { + // TODO: When IModuleSystem has health methods, check them here + logger->trace("🔍 Module '{}': Status unknown (health interface not implemented)", moduleNames[i]); + } +} + +void DebugEngine::logSocketHealth() { + logger->debug("🌐 Socket health check:"); + + if (coordinatorSocket) { + auto health = coordinatorSocket->getHealth(); + logger->debug("📡 Coordinator socket: Queue={}/{}, Dropping={}, Rate={:.1f}msg/s", + health.queueSize, health.maxQueueSize, health.dropping, health.averageProcessingRate); + + if (health.dropping) { + logger->warn("⚠️ Coordinator socket dropping messages!"); + } + if (health.queueSize > health.maxQueueSize * 0.8) { + logger->warn("⚠️ Coordinator socket queue 80% full ({}/{})", health.queueSize, health.maxQueueSize); + } + } + + for (size_t i = 0; i < clientSockets.size(); ++i) { + auto health = clientSockets[i]->getHealth(); + logger->debug("👤 Client socket {}: Queue={}/{}, Dropping={}, Rate={:.1f}msg/s", + i, health.queueSize, health.maxQueueSize, health.dropping, health.averageProcessingRate); + + if (health.dropping) { + logger->warn("⚠️ Client socket {} dropping messages!", i); + } + } +} + +void DebugEngine::processModuleSystems(float deltaTime) { + logger->trace("⚙️ Processing {} module system(s)", moduleSystems.size()); + + for (size_t i = 0; i < moduleSystems.size(); ++i) { + logger->trace("🔧 Processing module system: {}", moduleNames[i]); + + try { + // TODO: Call moduleSystem->processModule(deltaTime) when implemented + logger->trace("🚧 TODO: Call processModule() on {}", moduleNames[i]); + + } catch (const std::exception& e) { + logger->error("❌ Error processing module '{}': {}", moduleNames[i], e.what()); + } + } +} + +void DebugEngine::processClientMessages() { + for (size_t i = 0; i < clientSockets.size(); ++i) { + auto& socket = clientSockets[i]; + int messageCount = socket->hasMessages(); + + if (messageCount > 0) { + logger->trace("📨 Client {} has {} pending message(s)", i, messageCount); + + // Process a few messages per frame to avoid blocking + int messagesToProcess = std::min(messageCount, 5); + + for (int j = 0; j < messagesToProcess; ++j) { + try { + auto message = socket->pullMessage(); + logger->debug("📩 Client {} message: topic='{}', data size={}", + i, message.topic, message.data.dump().size()); + + // TODO: Route message to appropriate module or process it + logger->trace("🚧 TODO: Route client message to modules"); + + } catch (const std::exception& e) { + logger->error("❌ Error processing client {} message: {}", i, e.what()); + } + } + } + } +} + +void DebugEngine::processCoordinatorMessages() { + int messageCount = coordinatorSocket->hasMessages(); + + if (messageCount > 0) { + logger->trace("📨 Coordinator has {} pending message(s)", messageCount); + + // Process coordinator messages with higher priority + int messagesToProcess = std::min(messageCount, 10); + + for (int i = 0; i < messagesToProcess; ++i) { + try { + auto message = coordinatorSocket->pullMessage(); + logger->debug("📩 Coordinator message: topic='{}', data size={}", + message.topic, message.data.dump().size()); + + // TODO: Handle coordinator commands (shutdown, config reload, etc.) + logger->trace("🚧 TODO: Handle coordinator commands"); + + } catch (const std::exception& e) { + logger->error("❌ Error processing coordinator message: {}", e.what()); + } + } + } +} + +float DebugEngine::calculateDeltaTime() { + auto currentTime = std::chrono::high_resolution_clock::now(); + float deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); + lastFrameTime = currentTime; + + // Cap delta time to avoid huge jumps (e.g., after debugging pause) + if (deltaTime > 0.1f) { + logger->trace("⏱️ Large deltaTime detected: {:.3f}s - capping to 100ms", deltaTime); + deltaTime = 0.1f; + } + + return deltaTime; +} + +void DebugEngine::validateConfiguration() { + logger->debug("✅ Configuration validation passed"); + // TODO: Add actual validation logic + logger->trace("🚧 TODO: Implement comprehensive config validation"); +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/EngineFactory.cpp b/src/EngineFactory.cpp new file mode 100644 index 0000000..1127cf7 --- /dev/null +++ b/src/EngineFactory.cpp @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace warfactory { + +std::unique_ptr EngineFactory::createEngine(const std::string& engineType) { + auto logger = getFactoryLogger(); + logger->info("🏭 EngineFactory: Creating engine of type '{}'", engineType); + + EngineType type = parseEngineType(engineType); + return createEngine(type); +} + +std::unique_ptr EngineFactory::createEngine(EngineType engineType) { + auto logger = getFactoryLogger(); + std::string typeStr = engineTypeToString(engineType); + logger->info("🏭 EngineFactory: Creating engine of enum type '{}'", typeStr); + + std::unique_ptr engine; + + switch (engineType) { + case EngineType::DEBUG: + logger->debug("🔧 Creating DebugEngine instance"); + engine = std::make_unique(); + logger->info("✅ DebugEngine created successfully"); + break; + + case EngineType::PRODUCTION: + logger->error("❌ ProductionEngine not yet implemented"); + throw std::invalid_argument("ProductionEngine not yet implemented - use DEBUG for now"); + + case EngineType::HIGH_PERFORMANCE: + logger->error("❌ HighPerformanceEngine not yet implemented"); + throw std::invalid_argument("HighPerformanceEngine not yet implemented - use DEBUG for now"); + + default: + logger->error("❌ Unknown engine type enum value: {}", static_cast(engineType)); + throw std::invalid_argument("Unknown engine type enum value: " + std::to_string(static_cast(engineType))); + } + + logger->debug("🎯 Engine type verification: created engine reports type '{}'", + engineTypeToString(engine->getType())); + + return engine; +} + +std::unique_ptr EngineFactory::createFromConfig(const std::string& configPath) { + auto logger = getFactoryLogger(); + logger->info("🏭 EngineFactory: Creating engine from config '{}'", configPath); + + try { + // Read configuration file + std::ifstream configFile(configPath); + if (!configFile.is_open()) { + logger->error("❌ Cannot open config file: {}", configPath); + throw std::runtime_error("Cannot open engine config file: " + configPath); + } + + json config; + configFile >> config; + logger->debug("✅ Config file parsed successfully"); + + // Extract engine configuration + if (!config.contains("engine")) { + logger->error("❌ Config file missing 'engine' section"); + throw std::runtime_error("Config file missing 'engine' section"); + } + + auto engineConfig = config["engine"]; + + if (!engineConfig.contains("type")) { + logger->error("❌ Engine config missing 'type' field"); + throw std::runtime_error("Engine config missing 'type' field"); + } + + std::string engineType = engineConfig["type"]; + logger->info("📋 Config specifies engine type: '{}'", engineType); + + // Create engine + auto engine = createEngine(engineType); + + // Apply additional configuration if available + if (engineConfig.contains("log_level")) { + std::string logLevel = engineConfig["log_level"]; + logger->info("🔧 Config specifies log level: '{}'", logLevel); + + // Apply log level if engine supports it (DebugEngine does) + if (engine->getType() == EngineType::DEBUG) { + auto debugEngine = static_cast(engine.get()); + + if (logLevel == "trace") debugEngine->setLogLevel(spdlog::level::trace); + else if (logLevel == "debug") debugEngine->setLogLevel(spdlog::level::debug); + else if (logLevel == "info") debugEngine->setLogLevel(spdlog::level::info); + else if (logLevel == "warn") debugEngine->setLogLevel(spdlog::level::warn); + else if (logLevel == "error") debugEngine->setLogLevel(spdlog::level::err); + else { + logger->warn("⚠️ Unknown log level '{}' - using default", logLevel); + } + } + } + + if (engineConfig.contains("features")) { + auto features = engineConfig["features"]; + logger->debug("🎛️ Engine features configuration found: {}", features.dump()); + // TODO: Apply feature configuration when engines support it + } + + logger->info("✅ Engine created from config successfully"); + return engine; + + } catch (const json::exception& e) { + logger->error("❌ JSON parsing error in config file: {}", e.what()); + throw std::runtime_error("Invalid JSON in engine config file: " + std::string(e.what())); + } catch (const std::exception& e) { + logger->error("❌ Error creating engine from config: {}", e.what()); + throw; + } +} + +std::vector EngineFactory::getAvailableEngineTypes() { + return { + "debug", + "production", // Not yet implemented + "high_performance" // Not yet implemented + }; +} + +bool EngineFactory::isEngineTypeSupported(const std::string& engineType) { + try { + parseEngineType(engineType); + return true; + } catch (const std::invalid_argument&) { + return false; + } +} + +EngineType EngineFactory::parseEngineType(const std::string& engineTypeStr) { + auto logger = getFactoryLogger(); + std::string lowerType = toLowercase(engineTypeStr); + + logger->trace("🔍 Parsing engine type: '{}' -> '{}'", engineTypeStr, lowerType); + + if (lowerType == "debug") { + return EngineType::DEBUG; + } else if (lowerType == "production") { + return EngineType::PRODUCTION; + } else if (lowerType == "high_performance" || lowerType == "high-performance" || lowerType == "highperformance") { + return EngineType::HIGH_PERFORMANCE; + } else { + logger->error("❌ Unknown engine type: '{}'", engineTypeStr); + auto availableTypes = getAvailableEngineTypes(); + std::string availableStr = "["; + for (size_t i = 0; i < availableTypes.size(); ++i) { + availableStr += availableTypes[i]; + if (i < availableTypes.size() - 1) availableStr += ", "; + } + availableStr += "]"; + + throw std::invalid_argument("Unknown engine type '" + engineTypeStr + "'. Available types: " + availableStr); + } +} + +std::string EngineFactory::engineTypeToString(EngineType engineType) { + switch (engineType) { + case EngineType::DEBUG: + return "debug"; + case EngineType::PRODUCTION: + return "production"; + case EngineType::HIGH_PERFORMANCE: + return "high_performance"; + default: + return "unknown"; + } +} + +// Private helper methods +std::shared_ptr EngineFactory::getFactoryLogger() { + static std::shared_ptr logger = nullptr; + + if (!logger) { + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::debug); + + logger = std::make_shared("EngineFactory", console_sink); + logger->set_level(spdlog::level::debug); + logger->flush_on(spdlog::level::debug); + + // Register globally + spdlog::register_logger(logger); + } + + return logger; +} + +std::string EngineFactory::toLowercase(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](char c) { return std::tolower(c); }); + return result; +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/IOFactory.cpp b/src/IOFactory.cpp new file mode 100644 index 0000000..e373fc9 --- /dev/null +++ b/src/IOFactory.cpp @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include + +// Include implemented transports +#include +#include +// Forward declarations for future implementations +// #include "LocalIO.h" +// #include "NetworkIO.h" + +namespace warfactory { + +std::unique_ptr IOFactory::create(const std::string& transportType, const std::string& instanceId) { + auto logger = getFactoryLogger(); + logger->info("🌐 IOFactory: Creating transport '{}' with instanceId '{}'", transportType, instanceId); + + IOType type = parseTransport(transportType); + return create(type, instanceId); +} + +std::unique_ptr IOFactory::create(IOType ioType, const std::string& instanceId) { + auto logger = getFactoryLogger(); + std::string typeStr = transportToString(ioType); + logger->info("🌐 IOFactory: Creating enum type '{}' with instanceId '{}'", typeStr, instanceId); + + std::unique_ptr io; + + switch (ioType) { + case IOType::INTRA: { + logger->debug("🔧 Creating IntraIO instance"); + + // Generate instanceId if not provided + std::string actualInstanceId = instanceId; + if (actualInstanceId.empty()) { + actualInstanceId = "intra-" + std::to_string(std::random_device{}() % 10000); + logger->debug("🔧 Generated instanceId: '{}'", actualInstanceId); + } + + // TEMPORARY SOLUTION: Create direct IntraIO instance + // TODO: Properly integrate with IntraIOManager without type issues + io = std::make_unique(actualInstanceId); + + // Manually register with manager for routing + auto& manager = IntraIOManager::getInstance(); + manager.registerInstance(actualInstanceId, + std::static_pointer_cast( + std::shared_ptr(static_cast(io.get()), [](IntraIO*) { + // Don't delete - unique_ptr will handle it + }) + ) + ); + + logger->info("✅ IntraIO created successfully with instanceId '{}'", actualInstanceId); + break; + } + + case IOType::LOCAL: + logger->debug("🔧 Creating LocalIO instance"); + // TODO: Implement LocalIO + // io = std::make_unique(); + logger->error("❌ LocalIO not yet implemented"); + throw std::invalid_argument("LocalIO not yet implemented"); + + case IOType::NETWORK: + logger->debug("🔧 Creating NetworkIO instance"); + // TODO: Implement NetworkIO + // io = std::make_unique(); + logger->error("❌ NetworkIO not yet implemented"); + throw std::invalid_argument("NetworkIO not yet implemented"); + + default: + logger->error("❌ Unknown IOType enum value: {}", static_cast(ioType)); + throw std::invalid_argument("Unknown IOType enum value: " + std::to_string(static_cast(ioType))); + } + + logger->debug("🎯 IO type verification: created transport reports type '{}'", + transportToString(io->getType())); + + return io; +} + +std::unique_ptr IOFactory::createFromConfig(const json& config, const std::string& instanceId) { + auto logger = getFactoryLogger(); + logger->info("🌐 IOFactory: Creating from config with instanceId '{}'", instanceId); + logger->trace("📄 Config: {}", config.dump()); + + try { + if (!config.contains("type")) { + logger->error("❌ Config missing 'type' field"); + throw std::invalid_argument("IO config missing 'type' field"); + } + + std::string transportType = config["type"]; + logger->info("📋 Config specifies transport: '{}'", transportType); + + // Get instanceId from config or parameter + std::string actualInstanceId = instanceId; + if (actualInstanceId.empty() && config.contains("instance_id")) { + actualInstanceId = config["instance_id"]; + logger->debug("🔧 Using instanceId from config: '{}'", actualInstanceId); + } + + // Create base IO transport + auto io = create(transportType, actualInstanceId); + auto ioType = io->getType(); + + // Apply transport-specific configuration + if (ioType == IOType::NETWORK) { + if (config.contains("host")) { + std::string host = config["host"]; + logger->info("🔧 Network config: host '{}'", host); + // TODO: Apply host when NetworkIO is implemented + } + + if (config.contains("port")) { + int port = config["port"]; + logger->info("🔧 Network config: port {}", port); + // TODO: Apply port when NetworkIO is implemented + } + + if (config.contains("protocol")) { + std::string protocol = config["protocol"]; + logger->info("🔧 Network config: protocol '{}'", protocol); + // TODO: Apply protocol when NetworkIO is implemented + } + + if (config.contains("timeout")) { + int timeout = config["timeout"]; + logger->info("🔧 Network config: timeout {}ms", timeout); + // TODO: Apply timeout when NetworkIO is implemented + } + } + + if (ioType == IOType::LOCAL) { + if (config.contains("socket_path")) { + std::string socketPath = config["socket_path"]; + logger->info("🔧 Local config: socket path '{}'", socketPath); + // TODO: Apply socket path when LocalIO is implemented + } + } + + if (config.contains("buffer_size")) { + int bufferSize = config["buffer_size"]; + logger->info("🔧 IO config: buffer size {} bytes", bufferSize); + // TODO: Apply buffer size when implementations support it + } + + if (config.contains("compression")) { + bool compression = config["compression"]; + logger->info("🔧 IO config: compression {}", compression ? "enabled" : "disabled"); + // TODO: Apply compression settings when implementations support it + } + + logger->info("✅ IO transport created from config successfully"); + return io; + + } catch (const json::exception& e) { + logger->error("❌ JSON parsing error in config: {}", e.what()); + throw std::invalid_argument("Invalid JSON in IO config: " + std::string(e.what())); + } catch (const std::exception& e) { + logger->error("❌ Error creating IO from config: {}", e.what()); + throw; + } +} + +std::vector IOFactory::getAvailableTransports() { + return { + "intra", + "local", + "network" + }; +} + +bool IOFactory::isTransportSupported(const std::string& transportType) { + try { + parseTransport(transportType); + return true; + } catch (const std::invalid_argument&) { + return false; + } +} + +IOType IOFactory::parseTransport(const std::string& transportStr) { + auto logger = getFactoryLogger(); + std::string lowerTransport = toLowercase(transportStr); + + logger->trace("🔍 Parsing transport: '{}' -> '{}'", transportStr, lowerTransport); + + if (lowerTransport == "intra") { + return IOType::INTRA; + } else if (lowerTransport == "local") { + return IOType::LOCAL; + } else if (lowerTransport == "network" || lowerTransport == "net" || lowerTransport == "tcp") { + return IOType::NETWORK; + } else { + logger->error("❌ Unknown transport: '{}'", transportStr); + auto availableTransports = getAvailableTransports(); + std::string availableStr = "["; + for (size_t i = 0; i < availableTransports.size(); ++i) { + availableStr += availableTransports[i]; + if (i < availableTransports.size() - 1) availableStr += ", "; + } + availableStr += "]"; + + throw std::invalid_argument("Unknown transport '" + transportStr + "'. Available transports: " + availableStr); + } +} + +std::string IOFactory::transportToString(IOType ioType) { + switch (ioType) { + case IOType::INTRA: + return "intra"; + case IOType::LOCAL: + return "local"; + case IOType::NETWORK: + return "network"; + default: + return "unknown"; + } +} + +IOType IOFactory::getRecommendedTransport(int expectedClients, bool distributed, bool development) { + auto logger = getFactoryLogger(); + + logger->debug("🎯 Recommending transport for: {} clients, distributed={}, dev={}", + expectedClients, distributed, development); + + if (development || expectedClients <= 1) { + logger->debug("💡 Development/single-user -> INTRA"); + return IOType::INTRA; + } else if (!distributed && expectedClients <= 10) { + logger->debug("💡 Local deployment, few clients -> LOCAL"); + return IOType::LOCAL; + } else if (distributed || expectedClients > 10) { + logger->debug("💡 Distributed/many clients -> NETWORK"); + return IOType::NETWORK; + } else { + logger->debug("💡 Default fallback -> INTRA"); + return IOType::INTRA; + } +} + +std::unique_ptr IOFactory::createWithEndpoint(const std::string& transportType, const std::string& endpoint, const std::string& instanceId) { + auto logger = getFactoryLogger(); + logger->info("🌐 IOFactory: Creating '{}' with endpoint '{}' and instanceId '{}'", transportType, endpoint, instanceId); + + IOType ioType = parseTransport(transportType); + auto io = create(ioType, instanceId); + + std::string actualEndpoint = endpoint; + if (endpoint.empty()) { + actualEndpoint = generateEndpoint(ioType); + logger->info("🔧 Auto-generated endpoint: '{}'", actualEndpoint); + } + + // TODO: Configure endpoint when implementations support it + logger->debug("🚧 TODO: Configure endpoint '{}' on {} transport", actualEndpoint, transportType); + + return io; +} + +// Private helper methods +std::shared_ptr IOFactory::getFactoryLogger() { + static std::shared_ptr logger = nullptr; + + if (!logger) { + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::debug); + + logger = std::make_shared("IOFactory", console_sink); + logger->set_level(spdlog::level::debug); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + } + + return logger; +} + +std::string IOFactory::toLowercase(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](char c) { return std::tolower(c); }); + return result; +} + +std::string IOFactory::generateEndpoint(IOType ioType) { + switch (ioType) { + case IOType::INTRA: + return "intra://localhost"; + + case IOType::LOCAL: + return "/tmp/warfactory_" + std::to_string(std::random_device{}()); + + case IOType::NETWORK: { + // Generate random port between 8000-9000 + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(8000, 9000); + return "tcp://localhost:" + std::to_string(dis(gen)); + } + + default: + return "unknown://endpoint"; + } +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/ImGuiUI.cpp b/src/ImGuiUI.cpp new file mode 100644 index 0000000..7fd2715 --- /dev/null +++ b/src/ImGuiUI.cpp @@ -0,0 +1,546 @@ +#include "warfactory/ImGuiUI.h" +#include +#include +#include + +namespace warfactory { + +// ======================================== +// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS +// ======================================== + +void ImGuiUI::onRequest(RequestType requestType, std::function callback) { + request_callbacks[requestType] = callback; +} + +void ImGuiUI::onRequestCustom(const std::string& customType, std::function callback) { + custom_request_callbacks[customType] = callback; +} + +void ImGuiUI::showEvent(EventLevel level, const std::string& message) { + LogMessage log_msg; + log_msg.level = level; + log_msg.message = message; + log_msg.timestamp = std::chrono::steady_clock::now(); + + log_messages.push_back(log_msg); + + // Keep only last MAX_LOG_MESSAGES + if (log_messages.size() > MAX_LOG_MESSAGES) { + log_messages.erase(log_messages.begin()); + } + + // Also output to console for debugging + const char* level_str = toString(level); + std::cout << "[" << level_str << "] " << message << std::endl; +} + +// ======================================== +// WINDOW MANAGEMENT IMPLEMENTATION +// ======================================== + +void ImGuiUI::createDock(const std::string& dockId, DockType type, DockPosition position, const json& config) { + DockInfo& dock = docks[dockId]; + dock.id = dockId; + dock.type = type; + dock.position = position; + dock.parent = config.value("parent", ""); + + // Parse size with percentage support + if (config.contains("size")) { + auto size_config = config["size"]; + if (size_config.is_object()) { + if (size_config.contains("width")) { + dock.size.x = parseSize(size_config["width"], screen_size.x, 300); + } else { + dock.size.x = 300; // Default + } + if (size_config.contains("height")) { + dock.size.y = parseSize(size_config["height"], screen_size.y, 200); + } else { + dock.size.y = 200; // Default + } + } + } + + if (config.contains("min_size")) { + auto min_config = config["min_size"]; + if (min_config.is_object()) { + if (min_config.contains("width")) { + dock.min_size.x = parseSize(min_config["width"], screen_size.x, 100); + } else { + dock.min_size.x = 100; + } + if (min_config.contains("height")) { + dock.min_size.y = parseSize(min_config["height"], screen_size.y, 100); + } else { + dock.min_size.y = 100; + } + } + } + + if (config.contains("max_size")) { + auto max_config = config["max_size"]; + if (max_config.is_object()) { + if (max_config.contains("width")) { + dock.max_size.x = parseSize(max_config["width"], screen_size.x, 1000); + } else { + dock.max_size.x = 1000; + } + if (max_config.contains("height")) { + dock.max_size.y = parseSize(max_config["height"], screen_size.y, 800); + } else { + dock.max_size.y = 800; + } + } + } + + dock.collapsible = config.value("collapsible", true); + dock.resizable = config.value("resizable", true); + + // Debug logging for dock creation + showEvent(EventLevel::DEBUG, "🏗️ Created dock '" + dockId + "': " + std::string(toString(type)) + + " size=" + std::to_string((int)dock.size.x) + "x" + std::to_string((int)dock.size.y) + "px"); + + showEvent(EventLevel::INFO, "Created " + std::string(toString(type)) + " dock: " + dockId); +} + +void ImGuiUI::createSplit(const std::string& dockId, Orientation orientation, const json& config) { + // Create as a split dock + json split_config = config; + split_config["orientation"] = toString(orientation); + createDock(dockId, DockType::SPLIT, DockPosition::CENTER, split_config); +} + +void ImGuiUI::closeWindow(const std::string& windowId) { + auto it = windows.find(windowId); + if (it != windows.end()) { + it->second.is_open = false; + showEvent(EventLevel::INFO, "Closed window: " + windowId); + } +} + +void ImGuiUI::focusWindow(const std::string& windowId) { + auto it = windows.find(windowId); + if (it != windows.end()) { + ImGui::SetWindowFocus(it->second.title.c_str()); + showEvent(EventLevel::DEBUG, "Focused window: " + windowId); + } +} + +// ======================================== +// STATE MANAGEMENT +// ======================================== + +json ImGuiUI::getState() const { + json state; + state["frame_count"] = frame_count; + state["window_open"] = !should_close; + state["screen_size"] = {{"width", screen_size.x}, {"height", screen_size.y}}; + + // Save window states + json window_states = json::object(); + for (const auto& [id, win] : windows) { + window_states[id] = { + {"is_open", win.is_open}, + {"size", {{"width", win.size.x}, {"height", win.size.y}}}, + {"position", {{"x", win.position.x}, {"y", win.position.y}}}, + {"floating", win.is_floating} + }; + } + state["windows"] = window_states; + + return state; +} + +void ImGuiUI::setState(const json& state) { + if (state.contains("windows")) { + for (const auto& [id, win_state] : state["windows"].items()) { + auto it = windows.find(id); + if (it != windows.end()) { + auto& win = it->second; + win.is_open = win_state.value("is_open", true); + + if (win_state.contains("size")) { + auto size_state = win_state["size"]; + if (size_state.is_object()) { + win.size.x = size_state.value("width", win.size.x); + win.size.y = size_state.value("height", win.size.y); + } + } + + if (win_state.contains("position")) { + auto pos_state = win_state["position"]; + if (pos_state.is_object()) { + win.position.x = pos_state.value("x", win.position.x); + win.position.y = pos_state.value("y", win.position.y); + } + } + + win.is_floating = win_state.value("floating", win.is_floating); + } + } + } +} + +// ======================================== +// CONTENT RENDERING IMPLEMENTATIONS +// ======================================== + +void ImGuiUI::renderEconomyContent(const json& content) { + ImGui::Text("💰 Economy Dashboard"); + ImGui::Separator(); + + if (content.contains("prices")) { + ImGui::Text("Market Prices:"); + ImGui::BeginTable("prices_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg); + + ImGui::TableSetupColumn("Item"); + ImGui::TableSetupColumn("Price"); + ImGui::TableSetupColumn("Trend"); + ImGui::TableHeadersRow(); + + for (const auto& [item, price] : content["prices"].items()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", item.c_str()); + ImGui::TableNextColumn(); + if (price.is_number()) { + ImGui::Text("%.2f", price.get()); + } else { + ImGui::Text("%s", price.dump().c_str()); + } + ImGui::TableNextColumn(); + + // Show trend if available + if (content.contains("trends") && content["trends"].contains(item)) { + std::string trend = content["trends"][item]; + if (trend[0] == '+') { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s", trend.c_str()); + } else if (trend[0] == '-') { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "%s", trend.c_str()); + } else { + ImGui::Text("%s", trend.c_str()); + } + } else { + ImGui::Text("--"); + } + } + + ImGui::EndTable(); + } + + ImGui::Spacing(); + + // Action buttons + if (ImGui::Button("🔄 Refresh Prices")) { + if (request_callbacks.count(RequestType::GET_PRICES)) { + request_callbacks[RequestType::GET_PRICES]({}); + } + } + + ImGui::SameLine(); + if (ImGui::Button("📊 Market Analysis")) { + if (custom_request_callbacks.count("market_analysis")) { + custom_request_callbacks["market_analysis"]({}); + } + } +} + +void ImGuiUI::renderMapContent(const json& content) { + ImGui::Text("🗺️ Global Map"); + ImGui::Separator(); + + if (content.contains("current_chunk")) { + auto chunk = content["current_chunk"]; + if (chunk.is_object()) { + ImGui::Text("Current Chunk: (%d, %d)", chunk.value("x", 0), chunk.value("y", 0)); + } + } + + if (content.contains("tiles")) { + ImGui::Text("Map Display:"); + + // Navigation controls + if (ImGui::Button("⬆️")) { + if (request_callbacks.count(RequestType::GET_CHUNK)) { + request_callbacks[RequestType::GET_CHUNK]({{"action", "move_up"}}); + } + } + + if (ImGui::Button("⬅️")) { + if (request_callbacks.count(RequestType::GET_CHUNK)) { + request_callbacks[RequestType::GET_CHUNK]({{"action", "move_left"}}); + } + } + ImGui::SameLine(); + if (ImGui::Button("➡️")) { + if (request_callbacks.count(RequestType::GET_CHUNK)) { + request_callbacks[RequestType::GET_CHUNK]({{"action", "move_right"}}); + } + } + + if (ImGui::Button("⬇️")) { + if (request_callbacks.count(RequestType::GET_CHUNK)) { + request_callbacks[RequestType::GET_CHUNK]({{"action", "move_down"}}); + } + } + + // Simple tile grid representation + ImGui::Text("Tile Grid (sample):"); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 8; x++) { + if (x > 0) ImGui::SameLine(); + + // Generate simple tile representation + char tile_str[2] = "."; // Null-terminated string + if ((x + y) % 3 == 0) tile_str[0] = 'I'; // Iron + else if ((x + y) % 5 == 0) tile_str[0] = 'C'; // Copper + else if ((x + y) % 7 == 0) tile_str[0] = 'T'; // Tree + + ImGui::Button(tile_str, ImVec2(20, 20)); + } + } + } + + ImGui::Spacing(); + if (ImGui::Button("🔄 Refresh Map")) { + if (request_callbacks.count(RequestType::GET_CHUNK)) { + request_callbacks[RequestType::GET_CHUNK]({{"type", "refresh"}}); + } + } +} + +void ImGuiUI::renderInventoryContent(const json& content) { + ImGui::Text("🎒 Inventory"); + ImGui::Separator(); + + if (content.contains("items")) { + ImGui::BeginTable("inventory_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg); + ImGui::TableSetupColumn("Item"); + ImGui::TableSetupColumn("Quantity"); + ImGui::TableSetupColumn("Reserved"); + ImGui::TableHeadersRow(); + + for (const auto& item : content["items"]) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", item.value("name", "Unknown").c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%d", item.value("quantity", 0)); + ImGui::TableNextColumn(); + ImGui::Text("%d", item.value("reserved", 0)); + } + + ImGui::EndTable(); + } +} + +void ImGuiUI::renderConsoleContent(const json& content) { + ImGui::Text("🖥️ Console"); + ImGui::Separator(); + + // Console output area + ImGui::BeginChild("console_output", ImVec2(0, -30), true); + + if (content.contains("logs")) { + for (const auto& log : content["logs"]) { + std::string level = log.value("level", "info"); + std::string message = log.value("message", ""); + std::string timestamp = log.value("timestamp", ""); + + // Color based on level + if (level == "error") { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "[%s] %s - %s", + timestamp.c_str(), level.c_str(), message.c_str()); + } else if (level == "warning") { + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "[%s] %s - %s", + timestamp.c_str(), level.c_str(), message.c_str()); + } else if (level == "success") { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "[%s] %s - %s", + timestamp.c_str(), level.c_str(), message.c_str()); + } else { + ImGui::Text("[%s] %s - %s", timestamp.c_str(), level.c_str(), message.c_str()); + } + } + } + + ImGui::EndChild(); + + // Command input + static char command_buffer[256] = ""; + ImGui::SetNextItemWidth(-1); + if (ImGui::InputText("##command", command_buffer, sizeof(command_buffer), + ImGuiInputTextFlags_EnterReturnsTrue)) { + if (custom_request_callbacks.count("console_command")) { + custom_request_callbacks["console_command"]({{"command", std::string(command_buffer)}}); + } + command_buffer[0] = '\0'; // Clear buffer + } +} + +void ImGuiUI::renderPerformanceContent(const json& content) { + ImGui::Text("📊 Performance Monitor"); + ImGui::Separator(); + + if (content.contains("fps")) { + ImGui::Text("FPS: %d", content.value("fps", 0)); + } + if (content.contains("frame_time")) { + ImGui::Text("Frame Time: %s", content.value("frame_time", "0ms").c_str()); + } + if (content.contains("memory_usage")) { + ImGui::Text("Memory: %s", content.value("memory_usage", "0MB").c_str()); + } + if (content.contains("entities")) { + ImGui::Text("Entities: %d", content.value("entities", 0)); + } + + // Real-time FPS display + ImGui::Spacing(); + ImGui::Text("Real-time FPS: %.1f", ImGui::GetIO().Framerate); +} + +void ImGuiUI::renderCompaniesContent(const json& content) { + ImGui::Text("🏢 Companies"); + ImGui::Separator(); + + for (const auto& [company_name, company_data] : content.items()) { + if (ImGui::CollapsingHeader(company_name.c_str())) { + if (company_data.contains("cash")) { + ImGui::Text("💰 Cash: $%d", company_data.value("cash", 0)); + } + if (company_data.contains("status")) { + ImGui::Text("📊 Status: %s", company_data.value("status", "unknown").c_str()); + } + if (company_data.contains("strategy")) { + ImGui::Text("🎯 Strategy: %s", company_data.value("strategy", "none").c_str()); + } + } + } +} + +void ImGuiUI::renderAlertsContent(const json& content) { + ImGui::Text("⚠️ Alerts"); + ImGui::Separator(); + + if (content.contains("urgent_alerts")) { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "🚨 URGENT:"); + for (const auto& alert : content["urgent_alerts"]) { + if (alert.is_string()) { + ImGui::BulletText("%s", alert.get().c_str()); + } else { + ImGui::BulletText("%s", alert.dump().c_str()); + } + } + } + + if (content.contains("warnings")) { + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "⚠️ Warnings:"); + for (const auto& warning : content["warnings"]) { + if (warning.is_string()) { + ImGui::BulletText("%s", warning.get().c_str()); + } else { + ImGui::BulletText("%s", warning.dump().c_str()); + } + } + } + + ImGui::Spacing(); + if (ImGui::Button("✅ Acknowledge All")) { + if (custom_request_callbacks.count("acknowledge_alerts")) { + custom_request_callbacks["acknowledge_alerts"]({}); + } + } +} + +void ImGuiUI::renderSettingsContent(const json& content) { + ImGui::Text("⚙️ Settings"); + ImGui::Separator(); + + if (content.contains("graphics")) { + if (ImGui::CollapsingHeader("🖥️ Graphics")) { + auto graphics = content["graphics"]; + if (graphics.is_object()) { + ImGui::Text("Resolution: %s", graphics.value("resolution", "Unknown").c_str()); + bool fullscreen = graphics.value("fullscreen", false); + if (ImGui::Checkbox("Fullscreen", &fullscreen)) { + // Handle setting change + } + bool vsync = graphics.value("vsync", true); + if (ImGui::Checkbox("VSync", &vsync)) { + // Handle setting change + } + } + } + } + + if (content.contains("audio")) { + if (ImGui::CollapsingHeader("🔊 Audio")) { + auto audio = content["audio"]; + if (audio.is_object()) { + float master_vol = audio.value("master_volume", 1.0f); + if (ImGui::SliderFloat("Master Volume", &master_vol, 0.0f, 1.0f)) { + // Handle setting change + } + float effects_vol = audio.value("effects_volume", 1.0f); + if (ImGui::SliderFloat("Effects Volume", &effects_vol, 0.0f, 1.0f)) { + // Handle setting change + } + } + } + } +} + +void ImGuiUI::renderGenericContent(const json& content) { + ImGui::Text("📄 Data"); + ImGui::Separator(); + + // Generic JSON display + std::ostringstream oss; + oss << content.dump(2); // Pretty print with 2-space indent + ImGui::TextWrapped("%s", oss.str().c_str()); +} + +void ImGuiUI::renderLogConsole() { + // Always visible log console at bottom + ImGui::SetNextWindowSize(ImVec2(screen_size.x, 200), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(0, screen_size.y - 200), ImGuiCond_FirstUseEver); + + if (ImGui::Begin("📜 System Log", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize)) { + + ImGui::BeginChild("log_scroll", ImVec2(0, 150), true); + + for (const auto& log_msg : log_messages) { + auto duration = log_msg.timestamp.time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count() % 100000; + + const char* level_str = toString(log_msg.level); + + // Color based on level + ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f}; // Default white + switch (log_msg.level) { + case EventLevel::ERROR: color = {1.0f, 0.0f, 0.0f, 1.0f}; break; + case EventLevel::WARNING: color = {1.0f, 1.0f, 0.0f, 1.0f}; break; + case EventLevel::SUCCESS: color = {0.0f, 1.0f, 0.0f, 1.0f}; break; + case EventLevel::DEBUG: color = {0.7f, 0.7f, 0.7f, 1.0f}; break; + case EventLevel::INFO: + default: color = {1.0f, 1.0f, 1.0f, 1.0f}; break; + } + + ImGui::TextColored(color, "[%05lld] [%s] %s", + millis, level_str, log_msg.message.c_str()); + } + + // Auto-scroll to bottom + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); + } + ImGui::End(); +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/IntraIO.cpp b/src/IntraIO.cpp new file mode 100644 index 0000000..640aa38 --- /dev/null +++ b/src/IntraIO.cpp @@ -0,0 +1,484 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace warfactory { + +// Factory function for IntraIOManager to avoid circular include +std::shared_ptr createIntraIOInstance(const std::string& instanceId) { + return std::make_shared(instanceId); +} + +IntraIO::IntraIO(const std::string& instanceId) : instanceId(instanceId) { + // Create logger with file and console output + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/intra_io.log", true); + + console_sink->set_level(spdlog::level::debug); + file_sink->set_level(spdlog::level::trace); + + logger = std::make_shared("IntraIO[" + instanceId + "]", + spdlog::sinks_init_list{console_sink, file_sink}); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + + logIOStart(); + lastHealthCheck = std::chrono::high_resolution_clock::now(); +} + +IntraIO::~IntraIO() { + logger->info("🌐 IntraIO[{}] destructor called", instanceId); + + // Unregister from manager + try { + IntraIOManager::getInstance().removeInstance(instanceId); + } catch (const std::exception& e) { + logger->warn("⚠️ Failed to unregister from manager: {}", e.what()); + } + + auto finalMetrics = getDetailedMetrics(); + logger->info("📊 Final IntraIO[{}] metrics:", instanceId); + logger->info(" Total published: {}", finalMetrics["total_published"]); + logger->info(" Total pulled: {}", finalMetrics["total_pulled"]); + logger->info(" Total dropped: {}", finalMetrics["total_dropped"]); + logger->info(" Final queue size: {}", finalMetrics["queue_size"]); + + logger->trace("🏗️ IntraIO[{}] destroyed", instanceId); +} + +void IntraIO::publish(const std::string& topic, const json& message) { + std::lock_guard lock(operationMutex); + + logPublish(topic, message); + totalPublished++; + + try { + // Route message through manager to all interested instances + IntraIOManager::getInstance().routeMessage(instanceId, topic, message); + logger->trace("📤 Message routed through manager: '{}'", topic); + + } catch (const std::exception& e) { + logger->error("❌ Error publishing message to topic '{}': {}", topic, e.what()); + throw; + } +} + +void IntraIO::subscribe(const std::string& topicPattern, const SubscriptionConfig& config) { + std::lock_guard lock(operationMutex); + + logSubscription(topicPattern, false); + + try { + // Register with manager for routing + IntraIOManager::getInstance().registerSubscription(instanceId, topicPattern, false); + + Subscription sub; + sub.pattern = compileTopicPattern(topicPattern); + sub.originalPattern = topicPattern; + sub.config = config; + sub.lastBatch = std::chrono::high_resolution_clock::now(); + + highFreqSubscriptions.push_back(std::move(sub)); + + logger->info("✅ High-frequency subscription added: '{}'", topicPattern); + logger->debug("🔧 Subscription config: replaceable={}, compress={}", + config.replaceable, config.compress); + + } catch (const std::exception& e) { + logger->error("❌ Error creating subscription for pattern '{}': {}", topicPattern, e.what()); + throw; + } +} + +void IntraIO::subscribeLowFreq(const std::string& topicPattern, const SubscriptionConfig& config) { + std::lock_guard lock(operationMutex); + + logSubscription(topicPattern, true); + + try { + // Register with manager for routing + IntraIOManager::getInstance().registerSubscription(instanceId, topicPattern, true); + + Subscription sub; + sub.pattern = compileTopicPattern(topicPattern); + sub.originalPattern = topicPattern; + sub.config = config; + sub.lastBatch = std::chrono::high_resolution_clock::now(); + + lowFreqSubscriptions.push_back(std::move(sub)); + + logger->info("✅ Low-frequency subscription added: '{}' (interval: {}ms)", + topicPattern, config.batchInterval); + logger->debug("🔧 LowFreq config: replaceable={}, batchSize={}, interval={}ms", + config.replaceable, config.maxBatchSize, config.batchInterval); + + } catch (const std::exception& e) { + logger->error("❌ Error creating low-freq subscription for pattern '{}': {}", topicPattern, e.what()); + throw; + } +} + +int IntraIO::hasMessages() const { + std::lock_guard lock(operationMutex); + + int totalMessages = messageQueue.size() + lowFreqMessageQueue.size(); + + logger->trace("🔍 Messages available: {} (high-freq: {}, low-freq: {})", + totalMessages, messageQueue.size(), lowFreqMessageQueue.size()); + + return totalMessages; +} + +Message IntraIO::pullMessage() { + std::lock_guard lock(operationMutex); + + Message msg; + + // Pull from high-frequency queue first (priority) + if (!messageQueue.empty()) { + msg = messageQueue.front(); + messageQueue.pop(); + logger->trace("📥 Pulled high-frequency message from topic: '{}'", msg.topic); + } else if (!lowFreqMessageQueue.empty()) { + msg = lowFreqMessageQueue.front(); + lowFreqMessageQueue.pop(); + logger->trace("📥 Pulled low-frequency message from topic: '{}'", msg.topic); + } else { + logger->error("❌ No messages available to pull"); + throw std::runtime_error("No messages available in IntraIO"); + } + + totalPulled++; + logPull(msg); + updateHealthMetrics(); + + return msg; +} + +IOHealth IntraIO::getHealth() const { + std::lock_guard lock(operationMutex); + updateHealthMetrics(); + + IOHealth health; + health.queueSize = messageQueue.size() + lowFreqMessageQueue.size(); + health.maxQueueSize = maxQueueSize; + health.dropping = health.queueSize >= maxQueueSize; + health.averageProcessingRate = averageProcessingRate; + health.droppedMessageCount = totalDropped.load(); + + logger->trace("🏥 Health check: queue={}/{}, dropping={}, rate={:.1f}msg/s", + health.queueSize, health.maxQueueSize, health.dropping, health.averageProcessingRate); + + return health; +} + +IOType IntraIO::getType() const { + logger->trace("🏷️ IO type requested: INTRA"); + return IOType::INTRA; +} + +void IntraIO::setMaxQueueSize(size_t maxSize) { + std::lock_guard lock(operationMutex); + + logger->info("🔧 Setting max queue size: {} -> {}", maxQueueSize, maxSize); + maxQueueSize = maxSize; +} + +size_t IntraIO::getMaxQueueSize() const { + return maxQueueSize; +} + +void IntraIO::clearAllMessages() { + std::lock_guard lock(operationMutex); + + size_t clearedCount = messageQueue.size() + lowFreqMessageQueue.size(); + + while (!messageQueue.empty()) messageQueue.pop(); + while (!lowFreqMessageQueue.empty()) lowFreqMessageQueue.pop(); + + logger->info("🧹 Cleared all messages: {} messages removed", clearedCount); +} + +void IntraIO::clearAllSubscriptions() { + std::lock_guard lock(operationMutex); + + size_t clearedCount = highFreqSubscriptions.size() + lowFreqSubscriptions.size(); + + highFreqSubscriptions.clear(); + lowFreqSubscriptions.clear(); + + logger->info("🧹 Cleared all subscriptions: {} subscriptions removed", clearedCount); +} + +json IntraIO::getDetailedMetrics() const { + std::lock_guard lock(operationMutex); + + json metrics = { + {"io_type", "intra"}, + {"queue_size", messageQueue.size() + lowFreqMessageQueue.size()}, + {"high_freq_queue_size", messageQueue.size()}, + {"low_freq_queue_size", lowFreqMessageQueue.size()}, + {"max_queue_size", maxQueueSize}, + {"total_published", totalPublished.load()}, + {"total_pulled", totalPulled.load()}, + {"total_dropped", totalDropped.load()}, + {"high_freq_subscriptions", highFreqSubscriptions.size()}, + {"low_freq_subscriptions", lowFreqSubscriptions.size()}, + {"average_processing_rate", averageProcessingRate} + }; + + logger->trace("📊 Detailed metrics: {}", metrics.dump()); + return metrics; +} + +void IntraIO::setLogLevel(spdlog::level::level_enum level) { + logger->info("🔧 Setting log level to: {}", spdlog::level::to_string_view(level)); + logger->set_level(level); +} + +size_t IntraIO::getSubscriptionCount() const { + std::lock_guard lock(operationMutex); + return highFreqSubscriptions.size() + lowFreqSubscriptions.size(); +} + +std::vector IntraIO::getActiveTopics() const { + std::lock_guard lock(operationMutex); + + std::unordered_set topicSet; + std::queue tempQueue = messageQueue; + + while (!tempQueue.empty()) { + topicSet.insert(tempQueue.front().topic); + tempQueue.pop(); + } + + tempQueue = lowFreqMessageQueue; + while (!tempQueue.empty()) { + topicSet.insert(tempQueue.front().topic); + tempQueue.pop(); + } + + return std::vector(topicSet.begin(), topicSet.end()); +} + +void IntraIO::simulateHighLoad(int messageCount, const std::string& topicPrefix) { + logger->info("🧪 Simulating high load: {} messages with prefix '{}'", messageCount, topicPrefix); + + for (int i = 0; i < messageCount; ++i) { + json testMessage = { + {"test_id", i}, + {"payload", "test_data_" + std::to_string(i)}, + {"timestamp", std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count()} + }; + + publish(topicPrefix + ":" + std::to_string(i), testMessage); + } + + logger->info("✅ High load simulation completed"); +} + +void IntraIO::forceProcessLowFreqBatches() { + std::lock_guard lock(operationMutex); + logger->debug("🔧 Force processing all low-frequency batches"); + + for (auto& sub : lowFreqSubscriptions) { + flushBatchedMessages(sub); + } +} + +// Private helper methods +void IntraIO::logIOStart() { + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🌐 INTRA-PROCESS IO INITIALIZED"); + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🎯 Transport Type: INTRA (Same-process)"); + logger->info("🔧 Features: Direct function calls, zero latency"); + logger->info("📊 Performance: ~10-50ns publish, thread-safe"); + logger->info("🔧 Max queue size: {}", maxQueueSize); + logger->trace("🏗️ IntraIO object created at: {}", static_cast(this)); +} + +bool IntraIO::matchesPattern(const std::string& topic, const std::regex& pattern) const { + return std::regex_match(topic, pattern); +} + +std::regex IntraIO::compileTopicPattern(const std::string& pattern) const { + // Convert wildcard pattern to regex + std::string regexPattern = pattern; + + // Escape special regex characters except our wildcards + std::string specialChars = ".^$+()[]{}|\\"; + for (char c : specialChars) { + std::string from = std::string(1, c); + std::string to = "\\" + from; + + size_t pos = 0; + while ((pos = regexPattern.find(from, pos)) != std::string::npos) { + regexPattern.replace(pos, 1, to); + pos += 2; + } + } + + // Convert * to regex equivalent + size_t pos2 = 0; + while ((pos2 = regexPattern.find("*", pos2)) != std::string::npos) { + regexPattern.replace(pos2, 1, ".*"); + pos2 += 2; + } + + logger->trace("🔍 Compiled pattern '{}' -> '{}'", pattern, regexPattern); + + return std::regex(regexPattern); +} + +void IntraIO::processLowFreqSubscriptions() { + auto currentTime = std::chrono::high_resolution_clock::now(); + + for (auto& sub : lowFreqSubscriptions) { + auto elapsed = std::chrono::duration_cast( + currentTime - sub.lastBatch).count(); + + if (elapsed >= sub.config.batchInterval) { + logger->trace("⏰ Processing low-freq batch for pattern '{}' ({}ms elapsed)", + sub.originalPattern, elapsed); + flushBatchedMessages(sub); + sub.lastBatch = currentTime; + } + } +} + +void IntraIO::flushBatchedMessages(Subscription& sub) { + size_t flushedCount = 0; + + // Flush replaceable messages (latest only) + for (auto& [topic, message] : sub.batchedMessages) { + lowFreqMessageQueue.push(message); + flushedCount++; + logger->trace("📤 Flushed replaceable message: topic '{}', data size {}", + topic, message.data.dump().size()); + } + sub.batchedMessages.clear(); + + // Flush accumulated messages (all) + for (const auto& message : sub.accumulatedMessages) { + lowFreqMessageQueue.push(message); + flushedCount++; + logger->trace("📤 Flushed accumulated message: topic '{}', data size {}", + message.topic, message.data.dump().size()); + } + sub.accumulatedMessages.clear(); + + if (flushedCount > 0) { + logger->debug("📦 Flushed {} low-freq messages for pattern '{}'", + flushedCount, sub.originalPattern); + } +} + +void IntraIO::updateHealthMetrics() const { + auto currentTime = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration(currentTime - lastHealthCheck).count(); + + if (elapsed >= 1.0f) { // Update every second + size_t currentPulled = totalPulled.load(); + static size_t lastPulledCount = 0; + + averageProcessingRate = (currentPulled - lastPulledCount) / elapsed; + lastPulledCount = currentPulled; + lastHealthCheck = currentTime; + + logger->trace("📊 Health metrics updated: rate={:.1f}msg/s", averageProcessingRate); + } +} + +void IntraIO::enforceQueueLimits() { + size_t totalSize = messageQueue.size() + lowFreqMessageQueue.size(); + + if (totalSize >= maxQueueSize) { + logger->warn("⚠️ Queue size limit reached: {}/{} - dropping oldest messages", totalSize, maxQueueSize); + + // Drop oldest messages to make room + size_t toDrop = totalSize - maxQueueSize + 1; + + for (size_t i = 0; i < toDrop && !messageQueue.empty(); ++i) { + messageQueue.pop(); + totalDropped++; + } + + logger->warn("🗑️ Dropped {} messages to enforce queue limit", toDrop); + } +} + +void IntraIO::logPublish(const std::string& topic, const json& message) const { + logger->trace("📡 Publishing to topic '{}', data size: {} bytes", + topic, message.dump().size()); +} + +void IntraIO::logSubscription(const std::string& pattern, bool isLowFreq) const { + logger->debug("📨 {} subscription request: pattern '{}'", + isLowFreq ? "Low-frequency" : "High-frequency", pattern); +} + +void IntraIO::logPull(const Message& message) const { + logger->trace("📥 Message pulled: topic '{}', timestamp {}, data size {} bytes", + message.topic, message.timestamp, message.data.dump().size()); +} + +void IntraIO::deliverMessage(const std::string& topic, const json& message, bool isLowFreq) { + std::lock_guard lock(operationMutex); + + auto timestamp = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + + Message msg{topic, message, static_cast(timestamp)}; + + try { + if (isLowFreq) { + // Handle low-frequency message delivery + for (auto& sub : lowFreqSubscriptions) { + if (matchesPattern(topic, sub.pattern)) { + if (sub.config.replaceable) { + sub.batchedMessages[topic] = msg; + logger->trace("🔄 Low-freq replaceable message delivered: '{}'", topic); + } else { + sub.accumulatedMessages.push_back(msg); + logger->trace("📚 Low-freq message accumulated: '{}'", topic); + } + break; + } + } + } else { + // Handle high-frequency message delivery + logger->info("🔍 deliverMessage: looking for high-freq subscriptions for '{}', have {} subs", topic, highFreqSubscriptions.size()); + for (const auto& sub : highFreqSubscriptions) { + logger->info("🔍 deliverMessage: testing pattern '{}' vs topic '{}'", sub.originalPattern, topic); + if (matchesPattern(topic, sub.pattern)) { + messageQueue.push(msg); + logger->info("📨 High-freq message delivered to queue: '{}'", topic); + break; + } else { + logger->info("❌ Pattern '{}' did not match topic '{}'", sub.originalPattern, topic); + } + } + } + + // Enforce queue limits + enforceQueueLimits(); + + } catch (const std::exception& e) { + logger->error("❌ Error delivering message to topic '{}': {}", topic, e.what()); + throw; + } +} + +const std::string& IntraIO::getInstanceId() const { + return instanceId; +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/IntraIOManager.cpp b/src/IntraIOManager.cpp new file mode 100644 index 0000000..ebfd646 --- /dev/null +++ b/src/IntraIOManager.cpp @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include + +namespace warfactory { + +IntraIOManager::IntraIOManager() { + // Create logger + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/intra_io_manager.log", true); + + console_sink->set_level(spdlog::level::debug); + file_sink->set_level(spdlog::level::trace); + + logger = std::make_shared("IntraIOManager", + spdlog::sinks_init_list{console_sink, file_sink}); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + + logger->info("🌐🔗 IntraIOManager created - Central message router initialized"); +} + +IntraIOManager::~IntraIOManager() { + std::lock_guard lock(managerMutex); + + auto stats = getRoutingStats(); + logger->info("📊 Final routing stats:"); + logger->info(" Total routed messages: {}", stats["total_routed_messages"]); + logger->info(" Total routes: {}", stats["total_routes"]); + logger->info(" Active instances: {}", stats["active_instances"]); + + instances.clear(); + routingTable.clear(); + + logger->info("🌐🔗 IntraIOManager destroyed"); +} + +std::shared_ptr IntraIOManager::createInstance(const std::string& instanceId) { + std::lock_guard lock(managerMutex); + + auto it = instances.find(instanceId); + if (it != instances.end()) { + logger->warn("⚠️ Instance '{}' already exists, returning existing", instanceId); + // Need to cast back to IntraIO + return std::static_pointer_cast(it->second); + } + + // Create new IntraIO instance via factory function + auto instance = createIntraIOInstance(instanceId); + instances[instanceId] = instance; + + logger->info("✅ Created IntraIO instance: '{}'", instanceId); + logger->debug("📊 Total instances: {}", instances.size()); + + return instance; +} + +void IntraIOManager::registerInstance(const std::string& instanceId, std::shared_ptr instance) { + std::lock_guard lock(managerMutex); + instances[instanceId] = instance; + logger->info("📋 Registered instance: '{}'", instanceId); +} + +void IntraIOManager::removeInstance(const std::string& instanceId) { + std::lock_guard lock(managerMutex); + + auto it = instances.find(instanceId); + if (it == instances.end()) { + logger->warn("⚠️ Instance '{}' not found for removal", instanceId); + return; + } + + // Remove all routing entries for this instance + routingTable.erase( + std::remove_if(routingTable.begin(), routingTable.end(), + [&instanceId](const RouteEntry& entry) { + return entry.instanceId == instanceId; + }), + routingTable.end() + ); + + instances.erase(it); + + logger->info("🗑️ Removed IntraIO instance: '{}'", instanceId); + logger->debug("📊 Remaining instances: {}", instances.size()); +} + +std::shared_ptr IntraIOManager::getInstance(const std::string& instanceId) const { + std::lock_guard lock(managerMutex); + + auto it = instances.find(instanceId); + if (it != instances.end()) { + return std::static_pointer_cast(it->second); + } + return nullptr; +} + +void IntraIOManager::routeMessage(const std::string& sourceId, const std::string& topic, const json& message) { + std::lock_guard lock(managerMutex); + + totalRoutedMessages++; + size_t deliveredCount = 0; + + logger->info("📨 Routing message: {} → '{}'", sourceId, topic); + + // Find all matching routes + for (const auto& route : routingTable) { + // Don't deliver back to sender + if (route.instanceId == sourceId) { + continue; + } + + // Check pattern match + logger->info(" 🔍 Testing pattern '{}' against topic '{}'", route.originalPattern, topic); + if (std::regex_match(topic, route.pattern)) { + auto targetInstance = instances.find(route.instanceId); + if (targetInstance != instances.end()) { + // Direct delivery to target instance's queue + targetInstance->second->deliverMessage(topic, message, route.isLowFreq); + deliveredCount++; + logger->info(" ↪️ Delivered to '{}' ({})", + route.instanceId, + route.isLowFreq ? "low-freq" : "high-freq"); + } else { + logger->warn("⚠️ Target instance '{}' not found for route", route.instanceId); + } + } else { + logger->info(" ❌ Pattern '{}' did not match topic '{}'", route.originalPattern, topic); + } + } + + if (deliveredCount > 0) { + logger->debug("📤 Message '{}' delivered to {} instances", topic, deliveredCount); + } else { + logger->trace("📪 No subscribers for topic '{}'", topic); + } +} + +void IntraIOManager::registerSubscription(const std::string& instanceId, const std::string& pattern, bool isLowFreq) { + std::lock_guard lock(managerMutex); + + try { + // Convert topic pattern to regex - use same logic as IntraIO + std::string regexPattern = pattern; + + // Escape special regex characters except our wildcards (: is NOT special) + std::string specialChars = ".^$+()[]{}|\\"; + for (char c : specialChars) { + std::string from = std::string(1, c); + std::string to = "\\" + from; + + size_t pos = 0; + while ((pos = regexPattern.find(from, pos)) != std::string::npos) { + regexPattern.replace(pos, 1, to); + pos += 2; + } + } + + // Convert * to regex equivalent + size_t pos2 = 0; + while ((pos2 = regexPattern.find("*", pos2)) != std::string::npos) { + regexPattern.replace(pos2, 1, ".*"); + pos2 += 2; + } + + logger->info("🔍 Pattern conversion: '{}' → '{}'", pattern, regexPattern); + + RouteEntry entry; + entry.instanceId = instanceId; + entry.pattern = std::regex(regexPattern); + entry.originalPattern = pattern; + entry.isLowFreq = isLowFreq; + + routingTable.push_back(entry); + totalRoutes++; + + logger->info("📋 Registered subscription: '{}' → '{}' ({})", + instanceId, pattern, isLowFreq ? "low-freq" : "high-freq"); + logger->debug("📊 Total routes: {}", routingTable.size()); + + } catch (const std::exception& e) { + logger->error("❌ Failed to register subscription '{}' for '{}': {}", + pattern, instanceId, e.what()); + throw; + } +} + +void IntraIOManager::unregisterSubscription(const std::string& instanceId, const std::string& pattern) { + std::lock_guard lock(managerMutex); + + auto oldSize = routingTable.size(); + routingTable.erase( + std::remove_if(routingTable.begin(), routingTable.end(), + [&instanceId, &pattern](const RouteEntry& entry) { + return entry.instanceId == instanceId && entry.originalPattern == pattern; + }), + routingTable.end() + ); + + auto removed = oldSize - routingTable.size(); + if (removed > 0) { + logger->info("🗑️ Unregistered {} subscription(s): '{}' → '{}'", removed, instanceId, pattern); + } else { + logger->warn("⚠️ Subscription not found for removal: '{}' → '{}'", instanceId, pattern); + } +} + +void IntraIOManager::clearAllRoutes() { + std::lock_guard lock(managerMutex); + + auto clearedCount = routingTable.size(); + routingTable.clear(); + + logger->info("🧹 Cleared {} routing entries", clearedCount); +} + +size_t IntraIOManager::getInstanceCount() const { + std::lock_guard lock(managerMutex); + return instances.size(); +} + +std::vector IntraIOManager::getInstanceIds() const { + std::lock_guard lock(managerMutex); + + std::vector ids; + for (const auto& pair : instances) { + ids.push_back(pair.first); + } + return ids; +} + +json IntraIOManager::getRoutingStats() const { + std::lock_guard lock(managerMutex); + + json stats; + stats["total_routed_messages"] = totalRoutedMessages.load(); + stats["total_routes"] = totalRoutes.load(); + stats["active_instances"] = instances.size(); + stats["routing_entries"] = routingTable.size(); + + // Instance details + json instanceDetails = json::object(); + for (const auto& pair : instances) { + instanceDetails[pair.first] = { + {"active", true}, + {"type", "IntraIO"} + }; + } + stats["instances"] = instanceDetails; + + return stats; +} + +void IntraIOManager::setLogLevel(spdlog::level::level_enum level) { + logger->set_level(level); + logger->info("📝 Log level set to: {}", spdlog::level::to_string_view(level)); +} + +// Singleton implementation +IntraIOManager& IntraIOManager::getInstance() { + static IntraIOManager instance; + return instance; +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/ModuleFactory.cpp b/src/ModuleFactory.cpp new file mode 100644 index 0000000..dfe54db --- /dev/null +++ b/src/ModuleFactory.cpp @@ -0,0 +1,509 @@ +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace warfactory { + +ModuleFactory::ModuleFactory() { + // Create logger with file and console output + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/module_factory.log", true); + + console_sink->set_level(spdlog::level::info); + file_sink->set_level(spdlog::level::trace); + + logger = std::make_shared("ModuleFactory", + spdlog::sinks_init_list{console_sink, file_sink}); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🏭 MODULE FACTORY INITIALIZED"); + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🔧 Dynamic module loading with dlopen/dlsym"); + logger->info("🔥 Hot-reload support available"); + logger->info("📁 Default modules directory: ./modules/"); + + modulesDirectory = "./modules/"; +} + +ModuleFactory::~ModuleFactory() { + logger->info("🏭 ModuleFactory destructor called"); + unloadAllModules(); + logger->trace("🏗️ ModuleFactory destroyed"); +} + +std::unique_ptr ModuleFactory::loadModule(const std::string& modulePath) { + logger->info("🏭 Loading module from path: '{}'", modulePath); + + if (!fs::exists(modulePath)) { + logger->error("❌ Module file not found: '{}'", modulePath); + throw std::runtime_error("Module file not found: " + modulePath); + } + + if (!isValidModuleFile(modulePath)) { + logger->error("❌ Invalid module file: '{}'", modulePath); + throw std::runtime_error("Invalid module file: " + modulePath); + } + + ModuleInfo info; + info.path = modulePath; + + try { + if (!loadSharedLibrary(modulePath, info)) { + logger->error("❌ Failed to load shared library: '{}'", modulePath); + throw std::runtime_error("Failed to load shared library: " + modulePath); + } + + if (!resolveSymbols(info)) { + logger->error("❌ Failed to resolve symbols: '{}'", modulePath); + unloadSharedLibrary(info); + throw std::runtime_error("Failed to resolve symbols: " + modulePath); + } + + // Create module instance + auto module = std::unique_ptr(info.createFunc()); + if (!module) { + logger->error("❌ Module creation function returned nullptr: '{}'", modulePath); + unloadSharedLibrary(info); + throw std::runtime_error("Module creation failed: " + modulePath); + } + + // Verify module type consistency + std::string actualType = module->getType(); + if (actualType != info.type) { + logger->warn("⚠️ Module type mismatch: expected '{}', got '{}'", info.type, actualType); + } + + // Register loaded module + loadedModules[info.type] = info; + availableModules[info.type] = modulePath; + + logModuleLoad(info.type, modulePath); + logger->info("✅ Module '{}' loaded successfully from '{}'", info.type, modulePath); + + return module; + + } catch (const std::exception& e) { + logModuleError("load", e.what()); + unloadSharedLibrary(info); + throw; + } +} + +std::unique_ptr ModuleFactory::createModule(const std::string& moduleType) { + logger->info("🏭 Creating module of type: '{}'", moduleType); + + auto it = availableModules.find(moduleType); + if (it == availableModules.end()) { + logger->error("❌ Module type '{}' not available", moduleType); + + auto available = getAvailableModules(); + std::string availableStr = "["; + for (size_t i = 0; i < available.size(); ++i) { + availableStr += available[i]; + if (i < available.size() - 1) availableStr += ", "; + } + availableStr += "]"; + + throw std::invalid_argument("Module type '" + moduleType + "' not available. Available: " + availableStr); + } + + return loadModule(it->second); +} + +void ModuleFactory::scanModulesDirectory(const std::string& directory) { + logger->info("🔍 Scanning modules directory: '{}'", directory); + + if (!fs::exists(directory) || !fs::is_directory(directory)) { + logger->warn("⚠️ Modules directory does not exist: '{}'", directory); + return; + } + + size_t foundCount = 0; + + for (const auto& entry : fs::directory_iterator(directory)) { + if (entry.is_regular_file() && isValidModuleFile(entry.path().string())) { + try { + registerModule(entry.path().string()); + foundCount++; + } catch (const std::exception& e) { + logger->warn("⚠️ Failed to register module '{}': {}", entry.path().string(), e.what()); + } + } + } + + logger->info("✅ Scan complete: {} modules found in '{}'", foundCount, directory); +} + +void ModuleFactory::registerModule(const std::string& modulePath) { + logger->debug("📝 Registering module: '{}'", modulePath); + + if (!fs::exists(modulePath)) { + throw std::runtime_error("Module file not found: " + modulePath); + } + + if (!isValidModuleFile(modulePath)) { + throw std::runtime_error("Invalid module file: " + modulePath); + } + + // Extract module type from the path for registration + std::string moduleType = extractModuleTypeFromPath(modulePath); + + // Quick validation - try to load and get type + ModuleInfo tempInfo; + tempInfo.path = modulePath; + + if (loadSharedLibrary(modulePath, tempInfo)) { + if (resolveSymbols(tempInfo)) { + // Get the actual type from the module + typedef const char* (*GetTypeFunc)(); + auto getTypeFunc = (GetTypeFunc)dlsym(tempInfo.handle, "get_module_type"); + if (getTypeFunc) { + moduleType = getTypeFunc(); + } + } + unloadSharedLibrary(tempInfo); + } + + availableModules[moduleType] = modulePath; + logger->debug("✅ Module '{}' registered from '{}'", moduleType, modulePath); +} + +void ModuleFactory::unloadModule(const std::string& moduleType) { + logger->info("🗑️ Unloading module: '{}'", moduleType); + + auto it = loadedModules.find(moduleType); + if (it == loadedModules.end()) { + logger->warn("⚠️ Module '{}' is not loaded", moduleType); + return; + } + + unloadSharedLibrary(it->second); + loadedModules.erase(it); + + logModuleUnload(moduleType); + logger->info("✅ Module '{}' unloaded successfully", moduleType); +} + +void ModuleFactory::unloadAllModules() { + logger->info("🗑️ Unloading all modules ({} loaded)", loadedModules.size()); + + for (auto& [type, info] : loadedModules) { + logger->debug("🗑️ Unloading module: '{}'", type); + unloadSharedLibrary(info); + } + + loadedModules.clear(); + logger->info("✅ All modules unloaded"); +} + +std::vector ModuleFactory::getAvailableModules() const { + std::vector modules; + modules.reserve(availableModules.size()); + + for (const auto& [type, path] : availableModules) { + modules.push_back(type); + } + + std::sort(modules.begin(), modules.end()); + return modules; +} + +std::vector ModuleFactory::getLoadedModules() const { + std::vector modules; + modules.reserve(loadedModules.size()); + + for (const auto& [type, info] : loadedModules) { + modules.push_back(type); + } + + std::sort(modules.begin(), modules.end()); + return modules; +} + +ModuleFactory::ModuleInfo ModuleFactory::getModuleInfo(const std::string& moduleType) const { + auto it = loadedModules.find(moduleType); + if (it != loadedModules.end()) { + return it->second; + } + + // Return empty info if not loaded + return ModuleInfo{}; +} + +bool ModuleFactory::isModuleLoaded(const std::string& moduleType) const { + return loadedModules.find(moduleType) != loadedModules.end(); +} + +bool ModuleFactory::isModuleAvailable(const std::string& moduleType) const { + return availableModules.find(moduleType) != availableModules.end(); +} + +void ModuleFactory::setModulesDirectory(const std::string& directory) { + logger->info("📁 Setting modules directory: '{}'", directory); + modulesDirectory = directory; + + // Auto-scan new directory + if (fs::exists(directory)) { + scanModulesDirectory(directory); + } +} + +std::string ModuleFactory::getModulesDirectory() const { + return modulesDirectory; +} + +bool ModuleFactory::reloadModule(const std::string& moduleType) { + logger->info("🔄 Reloading module: '{}'", moduleType); + + if (!hotReloadEnabled) { + logger->warn("⚠️ Hot-reload is disabled"); + return false; + } + + auto it = loadedModules.find(moduleType); + if (it == loadedModules.end()) { + logger->warn("⚠️ Module '{}' is not loaded, cannot reload", moduleType); + return false; + } + + std::string modulePath = it->second.path; + + try { + unloadModule(moduleType); + auto reloadedModule = loadModule(modulePath); + + logger->info("✅ Module '{}' reloaded successfully", moduleType); + return true; + + } catch (const std::exception& e) { + logger->error("❌ Failed to reload module '{}': {}", moduleType, e.what()); + return false; + } +} + +void ModuleFactory::enableHotReload(bool enable) { + logger->info("🔧 Hot-reload {}", enable ? "enabled" : "disabled"); + hotReloadEnabled = enable; +} + +bool ModuleFactory::isHotReloadEnabled() const { + return hotReloadEnabled; +} + +json ModuleFactory::getDetailedStatus() const { + json status = { + {"modules_directory", modulesDirectory}, + {"hot_reload_enabled", hotReloadEnabled}, + {"available_modules_count", availableModules.size()}, + {"loaded_modules_count", loadedModules.size()} + }; + + json availableList = json::array(); + for (const auto& [type, path] : availableModules) { + availableList.push_back({ + {"type", type}, + {"path", path} + }); + } + status["available_modules"] = availableList; + + json loadedList = json::array(); + for (const auto& [type, info] : loadedModules) { + loadedList.push_back({ + {"type", type}, + {"path", info.path}, + {"version", info.version}, + {"handle", reinterpret_cast(info.handle)} + }); + } + status["loaded_modules"] = loadedList; + + return status; +} + +void ModuleFactory::validateModule(const std::string& modulePath) { + logger->info("🔍 Validating module: '{}'", modulePath); + + if (!fs::exists(modulePath)) { + throw std::runtime_error("Module file not found: " + modulePath); + } + + if (!isValidModuleFile(modulePath)) { + throw std::runtime_error("Invalid module file extension: " + modulePath); + } + + ModuleInfo tempInfo; + tempInfo.path = modulePath; + + if (!loadSharedLibrary(modulePath, tempInfo)) { + throw std::runtime_error("Failed to load shared library: " + modulePath); + } + + if (!resolveSymbols(tempInfo)) { + unloadSharedLibrary(tempInfo); + throw std::runtime_error("Failed to resolve required symbols: " + modulePath); + } + + // Test module creation + auto testModule = std::unique_ptr(tempInfo.createFunc()); + if (!testModule) { + unloadSharedLibrary(tempInfo); + throw std::runtime_error("Module creation function returned nullptr"); + } + + // Test module type + std::string moduleType = testModule->getType(); + if (moduleType.empty()) { + tempInfo.destroyFunc(testModule.release()); + unloadSharedLibrary(tempInfo); + throw std::runtime_error("Module getType() returned empty string"); + } + + // Cleanup + tempInfo.destroyFunc(testModule.release()); + unloadSharedLibrary(tempInfo); + + logger->info("✅ Module validation passed: '{}' (type: '{}')", modulePath, moduleType); +} + +void ModuleFactory::setLogLevel(spdlog::level::level_enum level) { + logger->info("🔧 Setting log level to: {}", spdlog::level::to_string_view(level)); + logger->set_level(level); +} + +// Private helper methods +std::shared_ptr ModuleFactory::getFactoryLogger() { + return logger; +} + +bool ModuleFactory::loadSharedLibrary(const std::string& path, ModuleInfo& info) { + logger->trace("📚 Loading shared library: '{}'", path); + + // Clear any existing error + dlerror(); + + // Load the shared library + info.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); + if (!info.handle) { + const char* error = dlerror(); + logger->error("❌ dlopen failed for '{}': {}", path, error ? error : "unknown error"); + return false; + } + + logger->trace("✅ Shared library loaded: '{}'", path); + return true; +} + +void ModuleFactory::unloadSharedLibrary(ModuleInfo& info) { + if (info.handle) { + logger->trace("🗑️ Unloading shared library: '{}'", info.path); + + int result = dlclose(info.handle); + if (result != 0) { + const char* error = dlerror(); + logger->warn("⚠️ dlclose warning for '{}': {}", info.path, error ? error : "unknown error"); + } + + info.handle = nullptr; + info.createFunc = nullptr; + info.destroyFunc = nullptr; + } +} + +bool ModuleFactory::resolveSymbols(ModuleInfo& info) { + logger->trace("🔍 Resolving symbols for: '{}'", info.path); + + // Clear any existing error + dlerror(); + + // Resolve create_module function + typedef IModule* (*CreateFunc)(); + auto createFunc = (CreateFunc)dlsym(info.handle, "create_module"); + const char* error = dlerror(); + if (error || !createFunc) { + logger->error("❌ Failed to resolve 'create_module': {}", error ? error : "symbol not found"); + return false; + } + info.createFunc = createFunc; + + // Resolve destroy_module function + typedef void (*DestroyFunc)(IModule*); + auto destroyFunc = (DestroyFunc)dlsym(info.handle, "destroy_module"); + error = dlerror(); + if (error || !destroyFunc) { + logger->error("❌ Failed to resolve 'destroy_module': {}", error ? error : "symbol not found"); + return false; + } + info.destroyFunc = destroyFunc; + + // Resolve get_module_type function + typedef const char* (*GetTypeFunc)(); + auto getTypeFunc = (GetTypeFunc)dlsym(info.handle, "get_module_type"); + error = dlerror(); + if (error || !getTypeFunc) { + logger->error("❌ Failed to resolve 'get_module_type': {}", error ? error : "symbol not found"); + return false; + } + info.type = getTypeFunc(); + + // Resolve get_module_version function + typedef const char* (*GetVersionFunc)(); + auto getVersionFunc = (GetVersionFunc)dlsym(info.handle, "get_module_version"); + error = dlerror(); + if (error || !getVersionFunc) { + logger->warn("⚠️ Failed to resolve 'get_module_version': {}", error ? error : "symbol not found"); + info.version = "unknown"; + } else { + info.version = getVersionFunc(); + } + + logger->trace("✅ All symbols resolved for '{}' (type: '{}', version: '{}')", + info.path, info.type, info.version); + return true; +} + +std::string ModuleFactory::extractModuleTypeFromPath(const std::string& path) const { + fs::path p(path); + std::string filename = p.stem().string(); // Remove extension + + // Remove common prefixes + if (filename.find("lib") == 0) { + filename = filename.substr(3); + } + if (filename.find("warfactory-") == 0) { + filename = filename.substr(11); + } + + return filename; +} + +bool ModuleFactory::isValidModuleFile(const std::string& path) const { + fs::path p(path); + std::string extension = p.extension().string(); + + // Check for valid shared library extensions + return extension == ".so" || extension == ".dylib" || extension == ".dll"; +} + +void ModuleFactory::logModuleLoad(const std::string& type, const std::string& path) const { + logger->debug("📦 Module loaded: type='{}', path='{}'", type, path); +} + +void ModuleFactory::logModuleUnload(const std::string& type) const { + logger->debug("📤 Module unloaded: type='{}'", type); +} + +void ModuleFactory::logModuleError(const std::string& operation, const std::string& details) const { + logger->error("❌ Module {} error: {}", operation, details); +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/ModuleSystemFactory.cpp b/src/ModuleSystemFactory.cpp new file mode 100644 index 0000000..1f63220 --- /dev/null +++ b/src/ModuleSystemFactory.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include + +// Include implemented systems +#include +// Forward declarations for future implementations +// #include "ThreadedModuleSystem.h" +// #include "ThreadPoolModuleSystem.h" +// #include "ClusterModuleSystem.h" + +namespace warfactory { + +std::unique_ptr ModuleSystemFactory::create(const std::string& strategy) { + auto logger = getFactoryLogger(); + logger->info("⚙️ ModuleSystemFactory: Creating strategy '{}'", strategy); + + ModuleSystemType type = parseStrategy(strategy); + return create(type); +} + +std::unique_ptr ModuleSystemFactory::create(ModuleSystemType systemType) { + auto logger = getFactoryLogger(); + std::string typeStr = strategyToString(systemType); + logger->info("⚙️ ModuleSystemFactory: Creating enum type '{}'", typeStr); + + std::unique_ptr moduleSystem; + + switch (systemType) { + case ModuleSystemType::SEQUENTIAL: + logger->debug("🔧 Creating SequentialModuleSystem instance"); + moduleSystem = std::make_unique(); + logger->info("✅ SequentialModuleSystem created successfully"); + break; + + case ModuleSystemType::THREADED: + logger->debug("🔧 Creating ThreadedModuleSystem instance"); + // TODO: Implement ThreadedModuleSystem + // moduleSystem = std::make_unique(); + logger->error("❌ ThreadedModuleSystem not yet implemented"); + throw std::invalid_argument("ThreadedModuleSystem not yet implemented"); + + case ModuleSystemType::THREAD_POOL: + logger->debug("🔧 Creating ThreadPoolModuleSystem instance"); + // TODO: Implement ThreadPoolModuleSystem + // moduleSystem = std::make_unique(); + logger->error("❌ ThreadPoolModuleSystem not yet implemented"); + throw std::invalid_argument("ThreadPoolModuleSystem not yet implemented"); + + case ModuleSystemType::CLUSTER: + logger->debug("🔧 Creating ClusterModuleSystem instance"); + // TODO: Implement ClusterModuleSystem + // moduleSystem = std::make_unique(); + logger->error("❌ ClusterModuleSystem not yet implemented"); + throw std::invalid_argument("ClusterModuleSystem not yet implemented"); + + default: + logger->error("❌ Unknown ModuleSystemType enum value: {}", static_cast(systemType)); + throw std::invalid_argument("Unknown ModuleSystemType enum value: " + std::to_string(static_cast(systemType))); + } + + logger->debug("🎯 ModuleSystem type verification: created system reports type '{}'", + strategyToString(moduleSystem->getType())); + + return moduleSystem; +} + +std::unique_ptr ModuleSystemFactory::createFromConfig(const json& config) { + auto logger = getFactoryLogger(); + logger->info("⚙️ ModuleSystemFactory: Creating from config"); + logger->trace("📄 Config: {}", config.dump()); + + try { + if (!config.contains("strategy")) { + logger->error("❌ Config missing 'strategy' field"); + throw std::invalid_argument("ModuleSystem config missing 'strategy' field"); + } + + std::string strategy = config["strategy"]; + logger->info("📋 Config specifies strategy: '{}'", strategy); + + // Create base ModuleSystem + auto moduleSystem = create(strategy); + + // Apply additional configuration based on strategy type + auto systemType = moduleSystem->getType(); + + if (systemType == ModuleSystemType::THREAD_POOL) { + if (config.contains("thread_count")) { + int threadCount = config["thread_count"]; + logger->info("🔧 Thread pool config: {} threads", threadCount); + // TODO: Apply thread count when ThreadPoolModuleSystem is implemented + } + + if (config.contains("queue_size")) { + int queueSize = config["queue_size"]; + logger->info("🔧 Thread pool config: queue size {}", queueSize); + // TODO: Apply queue size when ThreadPoolModuleSystem is implemented + } + } + + if (config.contains("priority")) { + std::string priority = config["priority"]; + logger->info("🔧 ModuleSystem priority: {}", priority); + // TODO: Apply priority settings when implementations support it + } + + logger->info("✅ ModuleSystem created from config successfully"); + return moduleSystem; + + } catch (const json::exception& e) { + logger->error("❌ JSON parsing error in config: {}", e.what()); + throw std::invalid_argument("Invalid JSON in ModuleSystem config: " + std::string(e.what())); + } catch (const std::exception& e) { + logger->error("❌ Error creating ModuleSystem from config: {}", e.what()); + throw; + } +} + +std::vector ModuleSystemFactory::getAvailableStrategies() { + return { + "sequential", + "threaded", + "thread_pool", + "cluster" + }; +} + +bool ModuleSystemFactory::isStrategySupported(const std::string& strategy) { + try { + parseStrategy(strategy); + return true; + } catch (const std::invalid_argument&) { + return false; + } +} + +ModuleSystemType ModuleSystemFactory::parseStrategy(const std::string& strategyStr) { + auto logger = getFactoryLogger(); + std::string lowerStrategy = toLowercase(strategyStr); + + logger->trace("🔍 Parsing strategy: '{}' -> '{}'", strategyStr, lowerStrategy); + + if (lowerStrategy == "sequential") { + return ModuleSystemType::SEQUENTIAL; + } else if (lowerStrategy == "threaded") { + return ModuleSystemType::THREADED; + } else if (lowerStrategy == "thread_pool" || lowerStrategy == "threadpool" || lowerStrategy == "thread-pool") { + return ModuleSystemType::THREAD_POOL; + } else if (lowerStrategy == "cluster") { + return ModuleSystemType::CLUSTER; + } else { + logger->error("❌ Unknown strategy: '{}'", strategyStr); + auto availableStrategies = getAvailableStrategies(); + std::string availableStr = "["; + for (size_t i = 0; i < availableStrategies.size(); ++i) { + availableStr += availableStrategies[i]; + if (i < availableStrategies.size() - 1) availableStr += ", "; + } + availableStr += "]"; + + throw std::invalid_argument("Unknown strategy '" + strategyStr + "'. Available strategies: " + availableStr); + } +} + +std::string ModuleSystemFactory::strategyToString(ModuleSystemType systemType) { + switch (systemType) { + case ModuleSystemType::SEQUENTIAL: + return "sequential"; + case ModuleSystemType::THREADED: + return "threaded"; + case ModuleSystemType::THREAD_POOL: + return "thread_pool"; + case ModuleSystemType::CLUSTER: + return "cluster"; + default: + return "unknown"; + } +} + +ModuleSystemType ModuleSystemFactory::getRecommendedStrategy(int targetFPS, int moduleCount, int cpuCores) { + auto logger = getFactoryLogger(); + + if (cpuCores == 0) { + cpuCores = detectCpuCores(); + } + + logger->debug("🎯 Recommending strategy for: {}fps, {} modules, {} cores", + targetFPS, moduleCount, cpuCores); + + // Simple recommendation logic + if (moduleCount <= 1) { + logger->debug("💡 Single module -> SEQUENTIAL"); + return ModuleSystemType::SEQUENTIAL; + } else if (moduleCount <= cpuCores && targetFPS <= 30) { + logger->debug("💡 Few modules, low FPS -> THREADED"); + return ModuleSystemType::THREADED; + } else if (targetFPS > 30 || moduleCount > cpuCores) { + logger->debug("💡 High performance needs -> THREAD_POOL"); + return ModuleSystemType::THREAD_POOL; + } else { + logger->debug("💡 Default fallback -> SEQUENTIAL"); + return ModuleSystemType::SEQUENTIAL; + } +} + +// Private helper methods +std::shared_ptr ModuleSystemFactory::getFactoryLogger() { + static std::shared_ptr logger = nullptr; + + if (!logger) { + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::debug); + + logger = std::make_shared("ModuleSystemFactory", console_sink); + logger->set_level(spdlog::level::debug); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + } + + return logger; +} + +std::string ModuleSystemFactory::toLowercase(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](char c) { return std::tolower(c); }); + return result; +} + +int ModuleSystemFactory::detectCpuCores() { + int cores = std::thread::hardware_concurrency(); + if (cores == 0) cores = 4; // Fallback + return cores; +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/ResourceRegistry.cpp b/src/ResourceRegistry.cpp new file mode 100644 index 0000000..5331e52 --- /dev/null +++ b/src/ResourceRegistry.cpp @@ -0,0 +1,120 @@ +#include "warfactory/ResourceRegistry.h" +#include + +namespace warfactory { + +// Static member initialization +std::unique_ptr ResourceRegistry::instance = nullptr; +bool ResourceRegistry::initialized = false; + +ResourceRegistry& ResourceRegistry::getInstance() { + if (!initialized) { + initialize(); + } + return *instance; +} + +void ResourceRegistry::initialize() { + if (!initialized) { + instance = std::unique_ptr(new ResourceRegistry()); + initialized = true; + } +} + +void ResourceRegistry::shutdown() { + if (initialized) { + instance.reset(); + initialized = false; + } +} + +// ======================================== +// REGISTRATION (Initialization Phase) +// ======================================== + +uint32_t ResourceRegistry::registerResource(const Resource& resource) { + if (next_id >= MAX_RESOURCES) { + // Handle overflow - could throw or return INVALID_RESOURCE_ID + return INVALID_RESOURCE_ID; + } + + const uint32_t assigned_id = next_id++; + + // Ensure vector is large enough + if (resources.size() <= assigned_id) { + resources.resize(assigned_id + 1); + } + + // Store resource at index = ID + resources[assigned_id] = resource; + + // Map name to ID for lookup + name_to_id[resource.getResourceId()] = assigned_id; + + return assigned_id; +} + +void ResourceRegistry::loadResourcesFromJson(const json& resources_json) { + for (json::const_iterator it = resources_json.begin(); it != resources_json.end(); ++it) { + const std::string& resource_name = it.key(); + const json& resource_data = it.value(); + + // Create resource from JSON + Resource resource = Resource::loadFromJson(resource_name, resource_data); + + // Register it + const uint32_t resource_id = registerResource(resource); + + // Log or handle registration result if needed + (void)resource_id; // Suppress unused variable warning + } +} + +// ======================================== +// RUNTIME ACCESS (Performance Critical) +// ======================================== + +const Resource* ResourceRegistry::getResource(uint32_t id) const { + if (id == INVALID_RESOURCE_ID || id >= resources.size()) { + return nullptr; + } + return &resources[id]; +} + +uint32_t ResourceRegistry::getResourceId(const std::string& name) const { + const std::unordered_map::const_iterator it = name_to_id.find(name); + return (it != name_to_id.end()) ? it->second : INVALID_RESOURCE_ID; +} + +bool ResourceRegistry::isValidResourceId(uint32_t id) const { + return id != INVALID_RESOURCE_ID && id < resources.size(); +} + +// ======================================== +// BULK OPERATIONS +// ======================================== + +std::vector ResourceRegistry::getAllResourceIds() const { + std::vector ids; + ids.reserve(next_id - 1); // -1 because we start at 1 + + for (uint32_t id = 1; id < next_id; ++id) { + if (id < resources.size()) { + ids.push_back(id); + } + } + + return ids; +} + +size_t ResourceRegistry::getResourceCount() const { + return next_id - 1; // -1 because we start at 1 +} + +void ResourceRegistry::clear() { + resources.clear(); + name_to_id.clear(); + next_id = 1; +} + +} // namespace warfactory \ No newline at end of file diff --git a/src/SequentialModuleSystem.cpp b/src/SequentialModuleSystem.cpp new file mode 100644 index 0000000..f6badbb --- /dev/null +++ b/src/SequentialModuleSystem.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include + +namespace warfactory { + +SequentialModuleSystem::SequentialModuleSystem() { + // Create logger with file and console output + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/sequential_system.log", true); + + console_sink->set_level(spdlog::level::debug); + file_sink->set_level(spdlog::level::trace); + + logger = std::make_shared("SequentialModuleSystem", + spdlog::sinks_init_list{console_sink, file_sink}); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::debug); + + spdlog::register_logger(logger); + + logSystemStart(); + lastProcessTime = std::chrono::high_resolution_clock::now(); +} + +SequentialModuleSystem::~SequentialModuleSystem() { + logger->info("🔧 SequentialModuleSystem destructor called"); + + if (module) { + logger->info("📊 Final performance metrics:"); + logger->info(" Total process calls: {}", processCallCount); + logger->info(" Total process time: {:.2f}ms", totalProcessTime); + logger->info(" Average process time: {:.3f}ms", getAverageProcessTime()); + logger->info(" Total task executions: {}", taskExecutionCount); + } + + logger->trace("🏗️ SequentialModuleSystem destroyed"); +} + +void SequentialModuleSystem::setModule(std::unique_ptr newModule) { + logger->info("🔧 Setting module in SequentialModuleSystem"); + + if (module) { + logger->warn("⚠️ Replacing existing module '{}' with new module", moduleName); + try { + module->shutdown(); + logger->debug("✅ Previous module shut down successfully"); + } catch (const std::exception& e) { + logger->error("❌ Error shutting down previous module: {}", e.what()); + } + } + + if (!newModule) { + logger->error("❌ Cannot set null module"); + throw std::invalid_argument("Cannot set null module"); + } + + module = std::move(newModule); + + // Get module type for better logging + try { + moduleName = module->getType(); + logger->info("✅ Module set successfully: type '{}'", moduleName); + } catch (const std::exception& e) { + logger->warn("⚠️ Could not get module type: {} - using 'unknown'", e.what()); + moduleName = "unknown"; + } + + // Reset performance metrics for new module + resetPerformanceMetrics(); + logger->debug("📊 Performance metrics reset for new module"); +} + +IModule* SequentialModuleSystem::getModule() const { + logger->trace("🔍 Module pointer requested"); + return module.get(); +} + +int SequentialModuleSystem::processModule(float deltaTime) { + logProcessStart(deltaTime); + + auto processStartTime = std::chrono::high_resolution_clock::now(); + + try { + validateModule(); + + // Create input JSON for module + json moduleInput = { + {"deltaTime", deltaTime}, + {"frameCount", processCallCount}, + {"system", "sequential"}, + {"timestamp", std::chrono::duration_cast( + processStartTime.time_since_epoch()).count()} + }; + + logger->trace("📥 Calling module process() with input: {}", moduleInput.dump()); + + // Process the module + module->process(moduleInput); + + processCallCount++; + + auto processEndTime = std::chrono::high_resolution_clock::now(); + lastProcessDuration = std::chrono::duration(processEndTime - processStartTime).count(); + totalProcessTime += lastProcessDuration; + + logProcessEnd(lastProcessDuration); + + // Check for performance warnings + if (lastProcessDuration > 16.67f) { // More than 60fps budget + logger->warn("🐌 Slow module processing: {:.2f}ms (target: <16.67ms for 60fps)", lastProcessDuration); + } + + logger->trace("✅ Module processing completed successfully"); + return 0; // Success + + } catch (const std::exception& e) { + logger->error("❌ Error processing module '{}': {}", moduleName, e.what()); + logger->error("🔍 Error occurred at frame {}, deltaTime: {:.3f}ms", processCallCount, deltaTime * 1000); + + auto processEndTime = std::chrono::high_resolution_clock::now(); + lastProcessDuration = std::chrono::duration(processEndTime - processStartTime).count(); + + logProcessEnd(lastProcessDuration); + + return 1; // Error + } +} + +ModuleSystemType SequentialModuleSystem::getType() const { + logger->trace("🏷️ ModuleSystem type requested: SEQUENTIAL"); + return ModuleSystemType::SEQUENTIAL; +} + +void SequentialModuleSystem::scheduleTask(const std::string& taskType, const json& taskData) { + logger->debug("⚙️ Task scheduled for immediate execution: '{}'", taskType); + logTaskExecution(taskType, taskData); + + try { + // In sequential system, tasks execute immediately + // This is just a placeholder - real task execution would happen here + logger->trace("🔧 Executing task '{}' immediately", taskType); + + // TODO: Implement actual task execution + // For now, we just log and count + taskExecutionCount++; + + logger->debug("✅ Task '{}' completed immediately", taskType); + + } catch (const std::exception& e) { + logger->error("❌ Error executing task '{}': {}", taskType, e.what()); + throw; + } +} + +int SequentialModuleSystem::hasCompletedTasks() const { + // Sequential system executes tasks immediately, so no completed tasks queue + logger->trace("🔍 Completed tasks count requested: 0 (sequential execution)"); + return 0; +} + +json SequentialModuleSystem::getCompletedTask() { + logger->warn("⚠️ getCompletedTask() called on sequential system - no queued tasks"); + throw std::runtime_error("SequentialModuleSystem executes tasks immediately - no completed tasks queue"); +} + +json SequentialModuleSystem::getPerformanceMetrics() const { + logger->debug("📊 Performance metrics requested"); + + json metrics = { + {"system_type", "sequential"}, + {"module_name", moduleName}, + {"process_calls", processCallCount}, + {"total_process_time_ms", totalProcessTime}, + {"average_process_time_ms", getAverageProcessTime()}, + {"last_process_time_ms", lastProcessDuration}, + {"task_executions", taskExecutionCount} + }; + + if (processCallCount > 0) { + auto currentTime = std::chrono::high_resolution_clock::now(); + auto totalRunTime = std::chrono::duration(currentTime - lastProcessTime).count(); + metrics["total_runtime_seconds"] = totalRunTime; + metrics["average_fps"] = totalRunTime > 0 ? processCallCount / totalRunTime : 0.0f; + } + + logger->trace("📄 Metrics JSON: {}", metrics.dump()); + return metrics; +} + +void SequentialModuleSystem::resetPerformanceMetrics() { + logger->debug("📊 Resetting performance metrics"); + + processCallCount = 0; + totalProcessTime = 0.0f; + lastProcessDuration = 0.0f; + taskExecutionCount = 0; + lastProcessTime = std::chrono::high_resolution_clock::now(); + + logger->trace("✅ Performance metrics reset"); +} + +float SequentialModuleSystem::getAverageProcessTime() const { + if (processCallCount == 0) return 0.0f; + return totalProcessTime / processCallCount; +} + +size_t SequentialModuleSystem::getProcessCallCount() const { + return processCallCount; +} + +size_t SequentialModuleSystem::getTaskExecutionCount() const { + return taskExecutionCount; +} + +void SequentialModuleSystem::setLogLevel(spdlog::level::level_enum level) { + logger->info("🔧 Setting log level to: {}", spdlog::level::to_string_view(level)); + logger->set_level(level); +} + +// Private helper methods +void SequentialModuleSystem::logSystemStart() { + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("⚙️ SEQUENTIAL MODULE SYSTEM INITIALIZED"); + logger->info("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="); + logger->info("🎯 System Type: SEQUENTIAL (Debug/Test mode)"); + logger->info("🔧 Features: Immediate execution, comprehensive logging"); + logger->info("📊 Performance: Single-threaded, deterministic"); + logger->trace("🏗️ SequentialModuleSystem object created at: {}", static_cast(this)); +} + +void SequentialModuleSystem::logProcessStart(float deltaTime) { + logger->trace("🎬 Process call {} START - deltaTime: {:.3f}ms, module: '{}'", + processCallCount, deltaTime * 1000, moduleName); +} + +void SequentialModuleSystem::logProcessEnd(float processTime) { + logger->trace("🏁 Process call {} END - processTime: {:.3f}ms", processCallCount, processTime); + + // Log performance summary every 60 calls + if (processCallCount > 0 && processCallCount % 60 == 0) { + logger->debug("📊 Performance summary (frame {}): Avg: {:.3f}ms, Total: {:.1f}ms", + processCallCount, getAverageProcessTime(), totalProcessTime); + } +} + +void SequentialModuleSystem::logTaskExecution(const std::string& taskType, const json& taskData) { + logger->trace("⚙️ Task execution {} - type: '{}', data size: {} bytes", + taskExecutionCount + 1, taskType, taskData.dump().size()); + logger->trace("📄 Task data: {}", taskData.dump()); +} + +std::unique_ptr SequentialModuleSystem::extractModule() { + logger->info("🔓 Extracting module from system"); + + if (!module) { + logger->warn("⚠️ No module to extract"); + return nullptr; + } + + auto extractedModule = std::move(module); + moduleName = "unknown"; + + logger->info("✅ Module extracted successfully"); + return extractedModule; +} + +void SequentialModuleSystem::validateModule() const { + if (!module) { + logger->error("❌ No module set - cannot process"); + throw std::runtime_error("No module set in SequentialModuleSystem"); + } +} + +} // namespace warfactory \ No newline at end of file