Initial commit: Grove Engine core architecture

- 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 <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-10-28 00:19:15 +08:00
commit e1cfa4513e
42 changed files with 8219 additions and 0 deletions

61
CMakeLists.txt Normal file
View File

@ -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"
)

174
README.md Normal file
View File

@ -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 <grove/IModule.h>
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 🌳*

View File

@ -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<IModuleSystem>) = 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<IModule>) = 0;
virtual void processModules(float deltaTime) = 0;
virtual void setIOLayer(std::unique_ptr<IIO>) = 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.

View File

@ -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<double>();
auto armor = stats["armor_thickness"].get<int>();
// 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.

View File

@ -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.*

View File

@ -0,0 +1,30 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
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

View File

@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <memory>
#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<IDataTree> create(const std::string& type, const std::string& sourcePath);
};
} // namespace warfactory

View File

@ -0,0 +1,89 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <chrono>
#include <thread>
#include <atomic>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<spdlog::logger> logger;
std::atomic<bool> running{false};
std::atomic<bool> debugPaused{false};
// Module management
std::vector<std::unique_ptr<IModuleSystem>> moduleSystems;
std::vector<std::string> moduleNames;
// Socket management
std::unique_ptr<IIO> coordinatorSocket;
std::vector<std::unique_ptr<IIO>> 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<IIO> coordinatorSocket) override;
void registerNewClientSocket(std::unique_ptr<IIO> 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

View File

@ -0,0 +1,105 @@
#pragma once
#include <memory>
#include <string>
#include <stdexcept>
#include <spdlog/spdlog.h>
#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<IEngine> 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<IEngine> 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<IEngine> createFromConfig(const std::string& configPath);
/**
* @brief Get list of available engine types
* @return Vector of supported engine type strings
*/
static std::vector<std::string> 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<spdlog::logger> getFactoryLogger();
static std::string toLowercase(const std::string& str);
};
} // namespace warfactory

View File

@ -0,0 +1,161 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
#include <functional>
#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<std::string> 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<IDataTree> 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

233
include/grove/IDataNode.h Normal file
View File

@ -0,0 +1,233 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <nlohmann/json.hpp>
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<IDataNode> getChild(const std::string& name) = 0;
/**
* @brief Get names of all direct children
* @return Vector of child names
*/
virtual std::vector<std::string> 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<IDataNode*> 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<IDataNode*> 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<int>() > 150;
* });
*/
virtual std::vector<IDataNode*> queryByProperty(const std::string& propName,
const std::function<bool(const json&)>& 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

69
include/grove/IDataTree.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include <string>
#include <memory>
#include <functional>
#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<IDataNode> 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<IDataNode> 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<void()> callback) = 0;
// ========================================
// METADATA
// ========================================
/**
* @brief Get tree implementation type
* @return Type identifier (e.g., "JSONDataTree", "DatabaseDataTree")
*/
virtual std::string getType() = 0;
};
} // namespace warfactory

125
include/grove/IEngine.h Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
// 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<IIO> 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<IIO> clientSocket) = 0;
/**
* @brief Get engine type identifier
* @return Engine type enum value for identification
*/
virtual EngineType getType() const = 0;
};
} // namespace warfactory

102
include/grove/IIO.h Normal file
View File

@ -0,0 +1,102 @@
#pragma once
#include <string>
#include <vector>
#include <functional>
#include <nlohmann/json.hpp>
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

113
include/grove/IModule.h Normal file
View File

@ -0,0 +1,113 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
#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

View File

@ -0,0 +1,95 @@
#pragma once
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
#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<IModule> 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<IIO> 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

134
include/grove/IOFactory.h Normal file
View File

@ -0,0 +1,134 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<IIO> 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<IIO> 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<IIO> createFromConfig(const json& config, const std::string& instanceId = "");
/**
* @brief Get list of available transport types
* @return Vector of supported transport strings
*/
static std::vector<std::string> 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<IIO> createWithEndpoint(const std::string& transportType,
const std::string& endpoint = "",
const std::string& instanceId = "");
private:
static std::shared_ptr<spdlog::logger> getFactoryLogger();
static std::string toLowercase(const std::string& str);
static std::string generateEndpoint(IOType ioType);
};
} // namespace warfactory

50
include/grove/IRegion.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <string>
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

View File

@ -0,0 +1,17 @@
#pragma once
#include <nlohmann/json.hpp>
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

View File

@ -0,0 +1,102 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
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

129
include/grove/IUI.h Normal file
View File

@ -0,0 +1,129 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
#include <functional>
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<void(const json&)> 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

340
include/grove/IUI_Enums.h Normal file
View File

@ -0,0 +1,340 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
#include <functional>
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<void(const json&)> 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<void(const json&)> 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

707
include/grove/ImGuiUI.h Normal file
View File

@ -0,0 +1,707 @@
#pragma once
#include "IUI_Enums.h"
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <GLFW/glfw3.h>
#include <GL/gl.h>
#include <map>
#include <vector>
#include <string>
#include <functional>
#include <chrono>
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<std::string, WindowInfo> 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<std::string> child_windows;
};
std::map<std::string, DockInfo> docks;
// ========================================
// CALLBACKS
// ========================================
std::map<RequestType, std::function<void(const json&)>> request_callbacks;
std::map<std::string, std::function<void(const json&)>> custom_request_callbacks;
// ========================================
// MESSAGE SYSTEM
// ========================================
struct LogMessage {
EventLevel level;
std::string message;
std::chrono::steady_clock::time_point timestamp;
};
std::vector<LogMessage> 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<int>(screen_size.x),
static_cast<int>(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<float>(fb_width), static_cast<float>(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<int>(screen_size.x), static_cast<int>(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<float>();
}
if (size_value.is_string()) {
std::string size_str = size_value.get<std::string>();
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<std::string>();
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<float>();
} 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<std::string>();
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<float>();
} 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<std::string>();
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<void(const json&)> callback) override;
void onRequestCustom(const std::string& customType, std::function<void(const json&)> 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

135
include/grove/IntraIO.h Normal file
View File

@ -0,0 +1,135 @@
#pragma once
#include <memory>
#include <string>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <regex>
#include <mutex>
#include <chrono>
#include <atomic>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<spdlog::logger> logger;
mutable std::mutex operationMutex; // Thread safety for all operations
// Instance identification for routing
std::string instanceId;
// Message storage
std::queue<Message> messageQueue;
std::queue<Message> lowFreqMessageQueue;
// Subscription management
struct Subscription {
std::regex pattern;
std::string originalPattern;
SubscriptionConfig config;
std::chrono::high_resolution_clock::time_point lastBatch;
std::unordered_map<std::string, Message> batchedMessages; // For replaceable messages
std::vector<Message> accumulatedMessages; // For non-replaceable messages
};
std::vector<Subscription> highFreqSubscriptions;
std::vector<Subscription> lowFreqSubscriptions;
// Health monitoring
mutable std::atomic<size_t> totalPublished{0};
mutable std::atomic<size_t> totalPulled{0};
mutable std::atomic<size_t> 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<std::string> 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

View File

@ -0,0 +1,91 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <regex>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<IntraIO> 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<spdlog::logger> logger;
mutable std::mutex managerMutex;
// Registry of IntraIO instances
std::unordered_map<std::string, std::shared_ptr<IIntraIODelivery>> instances;
// Subscription routing table
struct RouteEntry {
std::string instanceId;
std::regex pattern;
std::string originalPattern;
bool isLowFreq;
};
std::vector<RouteEntry> routingTable;
// Statistics
mutable std::atomic<size_t> totalRoutedMessages{0};
mutable std::atomic<size_t> totalRoutes{0};
public:
IntraIOManager();
~IntraIOManager();
// Instance management
std::shared_ptr<IntraIO> createInstance(const std::string& instanceId);
void registerInstance(const std::string& instanceId, std::shared_ptr<IIntraIODelivery> instance);
void removeInstance(const std::string& instanceId);
std::shared_ptr<IntraIO> 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<std::string> 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

View File

@ -0,0 +1,102 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<IModule*()> createFunc;
std::function<void(IModule*)> destroyFunc;
};
ModuleFactory();
~ModuleFactory();
// Module loading
std::unique_ptr<IModule> loadModule(const std::string& modulePath);
std::unique_ptr<IModule> 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<std::string> getAvailableModules() const;
std::vector<std::string> 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<spdlog::logger> logger;
std::string modulesDirectory;
bool hotReloadEnabled = false;
// Module registry
std::unordered_map<std::string, ModuleInfo> loadedModules;
std::unordered_map<std::string, std::string> availableModules; // type -> path
// Helper methods
std::shared_ptr<spdlog::logger> 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

View File

@ -0,0 +1,116 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<IModuleSystem> 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<IModuleSystem> 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<IModuleSystem> createFromConfig(const json& config);
/**
* @brief Get list of available ModuleSystem strategies
* @return Vector of supported strategy strings
*/
static std::vector<std::string> 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<spdlog::logger> getFactoryLogger();
static std::string toLowercase(const std::string& str);
static int detectCpuCores();
};
} // namespace warfactory

View File

@ -0,0 +1,89 @@
#pragma once
#include <random>
#include <cstdint>
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<float> 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<int> dis(min, max);
return dis(gen);
}
/**
* @brief Generate normal distribution float
*/
float normal(float mean, float stddev) {
std::normal_distribution<float> 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

37
include/grove/Resource.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
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

View File

@ -0,0 +1,113 @@
#pragma once
#include "Resource.h"
#include <vector>
#include <unordered_map>
#include <string>
#include <memory>
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<ResourceRegistry> instance;
static bool initialized;
std::vector<Resource> resources; // Indexed by ID (resources[id])
std::unordered_map<std::string, uint32_t> 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<uint32_t> 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

View File

@ -0,0 +1,87 @@
#pragma once
#include <memory>
#include <string>
#include <queue>
#include <chrono>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#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<spdlog::logger> logger;
std::unique_ptr<IModule> 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<IModule> module) override;
IModule* getModule() const override;
int processModule(float deltaTime) override;
ModuleSystemType getType() const override;
// Hot-reload support
std::unique_ptr<IModule> 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

View File

@ -0,0 +1,38 @@
#pragma once
#include <unordered_map>
#include <memory>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace warfactory {
class ASerializable;
class SerializationRegistry {
private:
std::unordered_map<std::string, ASerializable*> 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<std::string> getRegisteredIds() const;
void clear();
};
} // namespace warfactory

487
src/DebugEngine.cpp Normal file
View File

@ -0,0 +1,487 @@
#include <warfactory/DebugEngine.h>
#include <fstream>
#include <filesystem>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace warfactory {
DebugEngine::DebugEngine() {
// Create comprehensive logger with multiple sinks
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/debug_engine.log", true);
console_sink->set_level(spdlog::level::debug);
file_sink->set_level(spdlog::level::trace);
logger = std::make_shared<spdlog::logger>("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<void*>(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<std::chrono::milliseconds>(
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<float, std::milli>(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<float>(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<IIO> 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<int>(coordinatorSocket->getType()));
}
void DebugEngine::registerNewClientSocket(std::unique_ptr<IIO> clientSocket) {
logger->info("👥 Registering new client socket (client #{})", clientSockets.size() + 1);
logger->debug("🔍 Client socket type: {}", static_cast<int>(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<float>(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<float>(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

207
src/EngineFactory.cpp Normal file
View File

@ -0,0 +1,207 @@
#include <warfactory/EngineFactory.h>
#include <fstream>
#include <algorithm>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
using json = nlohmann::json;
namespace warfactory {
std::unique_ptr<IEngine> 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<IEngine> EngineFactory::createEngine(EngineType engineType) {
auto logger = getFactoryLogger();
std::string typeStr = engineTypeToString(engineType);
logger->info("🏭 EngineFactory: Creating engine of enum type '{}'", typeStr);
std::unique_ptr<IEngine> engine;
switch (engineType) {
case EngineType::DEBUG:
logger->debug("🔧 Creating DebugEngine instance");
engine = std::make_unique<DebugEngine>();
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<int>(engineType));
throw std::invalid_argument("Unknown engine type enum value: " + std::to_string(static_cast<int>(engineType)));
}
logger->debug("🎯 Engine type verification: created engine reports type '{}'",
engineTypeToString(engine->getType()));
return engine;
}
std::unique_ptr<IEngine> 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<DebugEngine*>(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<std::string> 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<spdlog::logger> EngineFactory::getFactoryLogger() {
static std::shared_ptr<spdlog::logger> logger = nullptr;
if (!logger) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::debug);
logger = std::make_shared<spdlog::logger>("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

311
src/IOFactory.cpp Normal file
View File

@ -0,0 +1,311 @@
#include <warfactory/IOFactory.h>
#include <algorithm>
#include <random>
#include <functional>
#include <spdlog/sinks/stdout_color_sinks.h>
// Include implemented transports
#include <warfactory/IntraIO.h>
#include <warfactory/IntraIOManager.h>
// Forward declarations for future implementations
// #include "LocalIO.h"
// #include "NetworkIO.h"
namespace warfactory {
std::unique_ptr<IIO> 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<IIO> 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<IIO> 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<IntraIO>(actualInstanceId);
// Manually register with manager for routing
auto& manager = IntraIOManager::getInstance();
manager.registerInstance(actualInstanceId,
std::static_pointer_cast<IIntraIODelivery>(
std::shared_ptr<IntraIO>(static_cast<IntraIO*>(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<LocalIO>();
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<NetworkIO>();
logger->error("❌ NetworkIO not yet implemented");
throw std::invalid_argument("NetworkIO not yet implemented");
default:
logger->error("❌ Unknown IOType enum value: {}", static_cast<int>(ioType));
throw std::invalid_argument("Unknown IOType enum value: " + std::to_string(static_cast<int>(ioType)));
}
logger->debug("🎯 IO type verification: created transport reports type '{}'",
transportToString(io->getType()));
return io;
}
std::unique_ptr<IIO> 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<std::string> 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<IIO> 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<spdlog::logger> IOFactory::getFactoryLogger() {
static std::shared_ptr<spdlog::logger> logger = nullptr;
if (!logger) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::debug);
logger = std::make_shared<spdlog::logger>("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

546
src/ImGuiUI.cpp Normal file
View File

@ -0,0 +1,546 @@
#include "warfactory/ImGuiUI.h"
#include <sstream>
#include <iomanip>
#include <iostream>
namespace warfactory {
// ========================================
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
// ========================================
void ImGuiUI::onRequest(RequestType requestType, std::function<void(const json&)> callback) {
request_callbacks[requestType] = callback;
}
void ImGuiUI::onRequestCustom(const std::string& customType, std::function<void(const json&)> 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<float>());
} 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<std::string>().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<std::string>().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<std::chrono::milliseconds>(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

484
src/IntraIO.cpp Normal file
View File

@ -0,0 +1,484 @@
#include <warfactory/IntraIO.h>
#include <warfactory/IntraIOManager.h>
#include <stdexcept>
#include <algorithm>
#include <thread>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace warfactory {
// Factory function for IntraIOManager to avoid circular include
std::shared_ptr<IntraIO> createIntraIOInstance(const std::string& instanceId) {
return std::make_shared<IntraIO>(instanceId);
}
IntraIO::IntraIO(const std::string& instanceId) : instanceId(instanceId) {
// Create logger with file and console output
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/intra_io.log", true);
console_sink->set_level(spdlog::level::debug);
file_sink->set_level(spdlog::level::trace);
logger = std::make_shared<spdlog::logger>("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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(operationMutex);
return highFreqSubscriptions.size() + lowFreqSubscriptions.size();
}
std::vector<std::string> IntraIO::getActiveTopics() const {
std::lock_guard<std::mutex> lock(operationMutex);
std::unordered_set<std::string> topicSet;
std::queue<Message> 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<std::string>(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::milliseconds>(
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<std::mutex> 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<void*>(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<std::chrono::milliseconds>(
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<float>(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<std::mutex> lock(operationMutex);
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
Message msg{topic, message, static_cast<uint64_t>(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

269
src/IntraIOManager.cpp Normal file
View File

@ -0,0 +1,269 @@
#include <warfactory/IntraIOManager.h>
#include <warfactory/IntraIO.h>
#include <stdexcept>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace warfactory {
IntraIOManager::IntraIOManager() {
// Create logger
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/intra_io_manager.log", true);
console_sink->set_level(spdlog::level::debug);
file_sink->set_level(spdlog::level::trace);
logger = std::make_shared<spdlog::logger>("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<std::mutex> 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<IntraIO> IntraIOManager::createInstance(const std::string& instanceId) {
std::lock_guard<std::mutex> 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<IntraIO>(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<IIntraIODelivery> instance) {
std::lock_guard<std::mutex> lock(managerMutex);
instances[instanceId] = instance;
logger->info("📋 Registered instance: '{}'", instanceId);
}
void IntraIOManager::removeInstance(const std::string& instanceId) {
std::lock_guard<std::mutex> 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<IntraIO> IntraIOManager::getInstance(const std::string& instanceId) const {
std::lock_guard<std::mutex> lock(managerMutex);
auto it = instances.find(instanceId);
if (it != instances.end()) {
return std::static_pointer_cast<IntraIO>(it->second);
}
return nullptr;
}
void IntraIOManager::routeMessage(const std::string& sourceId, const std::string& topic, const json& message) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(managerMutex);
auto clearedCount = routingTable.size();
routingTable.clear();
logger->info("🧹 Cleared {} routing entries", clearedCount);
}
size_t IntraIOManager::getInstanceCount() const {
std::lock_guard<std::mutex> lock(managerMutex);
return instances.size();
}
std::vector<std::string> IntraIOManager::getInstanceIds() const {
std::lock_guard<std::mutex> lock(managerMutex);
std::vector<std::string> ids;
for (const auto& pair : instances) {
ids.push_back(pair.first);
}
return ids;
}
json IntraIOManager::getRoutingStats() const {
std::lock_guard<std::mutex> 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

509
src/ModuleFactory.cpp Normal file
View File

@ -0,0 +1,509 @@
#include <warfactory/ModuleFactory.h>
#include <filesystem>
#include <dlfcn.h>
#include <algorithm>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace fs = std::filesystem;
namespace warfactory {
ModuleFactory::ModuleFactory() {
// Create logger with file and console output
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/module_factory.log", true);
console_sink->set_level(spdlog::level::info);
file_sink->set_level(spdlog::level::trace);
logger = std::make_shared<spdlog::logger>("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<IModule> 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<IModule>(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<IModule> 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<std::string> ModuleFactory::getAvailableModules() const {
std::vector<std::string> 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<std::string> ModuleFactory::getLoadedModules() const {
std::vector<std::string> 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<uintptr_t>(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<IModule>(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<spdlog::logger> 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

239
src/ModuleSystemFactory.cpp Normal file
View File

@ -0,0 +1,239 @@
#include <warfactory/ModuleSystemFactory.h>
#include <algorithm>
#include <thread>
#include <spdlog/sinks/stdout_color_sinks.h>
// Include implemented systems
#include <warfactory/SequentialModuleSystem.h>
// Forward declarations for future implementations
// #include "ThreadedModuleSystem.h"
// #include "ThreadPoolModuleSystem.h"
// #include "ClusterModuleSystem.h"
namespace warfactory {
std::unique_ptr<IModuleSystem> 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<IModuleSystem> ModuleSystemFactory::create(ModuleSystemType systemType) {
auto logger = getFactoryLogger();
std::string typeStr = strategyToString(systemType);
logger->info("⚙️ ModuleSystemFactory: Creating enum type '{}'", typeStr);
std::unique_ptr<IModuleSystem> moduleSystem;
switch (systemType) {
case ModuleSystemType::SEQUENTIAL:
logger->debug("🔧 Creating SequentialModuleSystem instance");
moduleSystem = std::make_unique<SequentialModuleSystem>();
logger->info("✅ SequentialModuleSystem created successfully");
break;
case ModuleSystemType::THREADED:
logger->debug("🔧 Creating ThreadedModuleSystem instance");
// TODO: Implement ThreadedModuleSystem
// moduleSystem = std::make_unique<ThreadedModuleSystem>();
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<ThreadPoolModuleSystem>();
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<ClusterModuleSystem>();
logger->error("❌ ClusterModuleSystem not yet implemented");
throw std::invalid_argument("ClusterModuleSystem not yet implemented");
default:
logger->error("❌ Unknown ModuleSystemType enum value: {}", static_cast<int>(systemType));
throw std::invalid_argument("Unknown ModuleSystemType enum value: " + std::to_string(static_cast<int>(systemType)));
}
logger->debug("🎯 ModuleSystem type verification: created system reports type '{}'",
strategyToString(moduleSystem->getType()));
return moduleSystem;
}
std::unique_ptr<IModuleSystem> 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<std::string> 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<spdlog::logger> ModuleSystemFactory::getFactoryLogger() {
static std::shared_ptr<spdlog::logger> logger = nullptr;
if (!logger) {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::debug);
logger = std::make_shared<spdlog::logger>("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

120
src/ResourceRegistry.cpp Normal file
View File

@ -0,0 +1,120 @@
#include "warfactory/ResourceRegistry.h"
#include <algorithm>
namespace warfactory {
// Static member initialization
std::unique_ptr<ResourceRegistry> ResourceRegistry::instance = nullptr;
bool ResourceRegistry::initialized = false;
ResourceRegistry& ResourceRegistry::getInstance() {
if (!initialized) {
initialize();
}
return *instance;
}
void ResourceRegistry::initialize() {
if (!initialized) {
instance = std::unique_ptr<ResourceRegistry>(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<std::string, uint32_t>::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<uint32_t> ResourceRegistry::getAllResourceIds() const {
std::vector<uint32_t> 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

View File

@ -0,0 +1,276 @@
#include <warfactory/SequentialModuleSystem.h>
#include <stdexcept>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace warfactory {
SequentialModuleSystem::SequentialModuleSystem() {
// Create logger with file and console output
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/sequential_system.log", true);
console_sink->set_level(spdlog::level::debug);
file_sink->set_level(spdlog::level::trace);
logger = std::make_shared<spdlog::logger>("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<IModule> 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<std::chrono::milliseconds>(
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<float, std::milli>(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<float, std::milli>(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<float>(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<void*>(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<IModule> 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