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:
commit
e1cfa4513e
61
CMakeLists.txt
Normal file
61
CMakeLists.txt
Normal 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
174
README.md
Normal 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 🌳*
|
||||||
423
docs/architecture/architecture-modulaire.md
Normal file
423
docs/architecture/architecture-modulaire.md
Normal 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.
|
||||||
502
docs/architecture/claude-code-integration.md
Normal file
502
docs/architecture/claude-code-integration.md
Normal 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.
|
||||||
179
docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md
Normal file
179
docs/implementation/CLAUDE-HOT-RELOAD-GUIDE.md
Normal 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.*
|
||||||
30
include/grove/ASerializable.h
Normal file
30
include/grove/ASerializable.h
Normal 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
|
||||||
23
include/grove/DataTreeFactory.h
Normal file
23
include/grove/DataTreeFactory.h
Normal 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
|
||||||
89
include/grove/DebugEngine.h
Normal file
89
include/grove/DebugEngine.h
Normal 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
|
||||||
105
include/grove/EngineFactory.h
Normal file
105
include/grove/EngineFactory.h
Normal 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
|
||||||
161
include/grove/ICoordinationModule.h
Normal file
161
include/grove/ICoordinationModule.h
Normal 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
233
include/grove/IDataNode.h
Normal 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
69
include/grove/IDataTree.h
Normal 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
125
include/grove/IEngine.h
Normal 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
102
include/grove/IIO.h
Normal 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
113
include/grove/IModule.h
Normal 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
|
||||||
95
include/grove/IModuleSystem.h
Normal file
95
include/grove/IModuleSystem.h
Normal 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
134
include/grove/IOFactory.h
Normal 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
50
include/grove/IRegion.h
Normal 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
|
||||||
17
include/grove/ISerializable.h
Normal file
17
include/grove/ISerializable.h
Normal 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
|
||||||
102
include/grove/ITaskScheduler.h
Normal file
102
include/grove/ITaskScheduler.h
Normal 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
129
include/grove/IUI.h
Normal 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
340
include/grove/IUI_Enums.h
Normal 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
707
include/grove/ImGuiUI.h
Normal 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
135
include/grove/IntraIO.h
Normal 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
|
||||||
91
include/grove/IntraIOManager.h
Normal file
91
include/grove/IntraIOManager.h
Normal 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
|
||||||
102
include/grove/ModuleFactory.h
Normal file
102
include/grove/ModuleFactory.h
Normal 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
|
||||||
116
include/grove/ModuleSystemFactory.h
Normal file
116
include/grove/ModuleSystemFactory.h
Normal 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
|
||||||
89
include/grove/RandomGenerator.h
Normal file
89
include/grove/RandomGenerator.h
Normal 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
37
include/grove/Resource.h
Normal 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
|
||||||
113
include/grove/ResourceRegistry.h
Normal file
113
include/grove/ResourceRegistry.h
Normal 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
|
||||||
87
include/grove/SequentialModuleSystem.h
Normal file
87
include/grove/SequentialModuleSystem.h
Normal 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
|
||||||
38
include/grove/SerializationRegistry.h
Normal file
38
include/grove/SerializationRegistry.h
Normal 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
487
src/DebugEngine.cpp
Normal 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
207
src/EngineFactory.cpp
Normal 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
311
src/IOFactory.cpp
Normal 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
546
src/ImGuiUI.cpp
Normal 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
484
src/IntraIO.cpp
Normal 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
269
src/IntraIOManager.cpp
Normal 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
509
src/ModuleFactory.cpp
Normal 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
239
src/ModuleSystemFactory.cpp
Normal 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
120
src/ResourceRegistry.cpp
Normal 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
|
||||||
276
src/SequentialModuleSystem.cpp
Normal file
276
src/SequentialModuleSystem.cpp
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user