Setup initial: Mobile Command project with GroveEngine
- Architecture basée sur GroveEngine (hot-reload C++17) - Structure du projet (src, config, external) - CMakeLists.txt avec support MinGW - GameModule de base (hot-reloadable) - Main loop 10Hz avec file watcher - Configuration via JSON - Documentation README et CLAUDE.md ✅ Build fonctionnel ✅ Hot-reload validé 🚧 Prochaine étape: Prototype gameplay 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d10b285a56
commit
3dfaffc75a
66
.gitignore
vendored
66
.gitignore
vendored
@ -1,50 +1,40 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
vendor/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
# Build directories
|
||||
build/
|
||||
out/
|
||||
build-*/
|
||||
|
||||
# CMake files
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
*.cmake
|
||||
!CMakeLists.txt
|
||||
|
||||
# Compiled binaries
|
||||
*.exe
|
||||
*.dll
|
||||
*.out
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# IDE
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
# Data files
|
||||
data/
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Assets (work files)
|
||||
*.psd
|
||||
*.ai
|
||||
*.blend1
|
||||
*.blend2
|
||||
|
||||
# Game specific
|
||||
saves/
|
||||
cache/
|
||||
screenshots/
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
external/GroveEngine
|
||||
|
||||
133
CLAUDE.md
Normal file
133
CLAUDE.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Claude - Mobile Command Development
|
||||
|
||||
## Rôle
|
||||
Je suis l'assistant de développement pour le projet Mobile Command.
|
||||
|
||||
## Contexte du Projet
|
||||
|
||||
**Mobile Command** est un jeu de gestion/survie où le joueur commande un train blindé mobile à travers l'Ukraine en guerre (2022-2025).
|
||||
|
||||
### Stack Technique
|
||||
- **Engine**: GroveEngine (C++17 hot-reload module system)
|
||||
- **Build**: CMake 3.20+ avec MinGW/GCC
|
||||
- **Architecture**: Main loop + modules hot-reloadable
|
||||
|
||||
### Structure du Projet
|
||||
```
|
||||
mobilecommand/
|
||||
├── external/GroveEngine/ # Lien symbolique vers ../groveengine
|
||||
├── src/
|
||||
│ ├── main.cpp # Boucle principale (10Hz)
|
||||
│ └── modules/ # Modules hot-reloadable (.dll)
|
||||
│ └── GameModule.* # Module de jeu principal
|
||||
├── config/
|
||||
│ └── game.json # Configuration
|
||||
└── build/ # Dossier de build
|
||||
```
|
||||
|
||||
## Workflow de Développement
|
||||
|
||||
### Build Initial
|
||||
```bash
|
||||
cmake -B build -G "MinGW Makefiles"
|
||||
cmake --build build -j4
|
||||
```
|
||||
|
||||
### Hot-Reload Workflow
|
||||
1. Le jeu tourne: `./build/mobilecommand.exe`
|
||||
2. Éditer un module: `src/modules/GameModule.cpp`
|
||||
3. Rebuild: `cmake --build build --target modules`
|
||||
4. Le module se recharge automatiquement avec préservation d'état
|
||||
|
||||
### Commandes Utiles
|
||||
```bash
|
||||
# Build complet
|
||||
cmake --build build -j4
|
||||
|
||||
# Build modules seulement (rapide)
|
||||
cmake --build build --target modules
|
||||
|
||||
# Run
|
||||
cd build && ./mobilecommand.exe
|
||||
```
|
||||
|
||||
## Principes GroveEngine
|
||||
|
||||
### Modules
|
||||
- Héritent de `grove::IModule`
|
||||
- Implémentent: `process()`, `getState()`, `setState()`, `shutdown()`
|
||||
- Chargés dynamiquement (.dll/.so)
|
||||
- Hot-reload < 1ms avec état préservé
|
||||
|
||||
### Communication
|
||||
- `grove::IIO` pour pub/sub entre modules
|
||||
- Messages via `grove::IDataNode` (JSON)
|
||||
- Topics: `module:event` (ex: `game:tick`, `train:damaged`)
|
||||
|
||||
### Configuration
|
||||
- Fichiers JSON dans `config/`
|
||||
- Chargés via `grove::JsonDataNode`
|
||||
- Hot-reload de config supporté
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Phase 1 - Setup (COMPLÉTÉ)
|
||||
- [x] Structure projet
|
||||
- [x] GroveEngine intégré
|
||||
- [x] Build system
|
||||
- [x] Hot-reload validé
|
||||
|
||||
### Phase 2 - Prototype Gameplay (EN COURS)
|
||||
- [ ] Train basique (3 wagons)
|
||||
- [ ] Système de craft simple
|
||||
- [ ] 1 mission de combat
|
||||
- [ ] 3-5 événements
|
||||
- [ ] Boucle de jeu complète
|
||||
|
||||
### Phase 3 - MVP
|
||||
- [ ] Train complet
|
||||
- [ ] Système d'expéditions
|
||||
- [ ] Campagne Act 1
|
||||
|
||||
## Conventions de Code
|
||||
|
||||
### Modules
|
||||
- Nom: `XyzModule` (PascalCase)
|
||||
- Fichiers: `XyzModule.cpp` + `XyzModule.h` (optionnel)
|
||||
- Exports: `createModule()` et `destroyModule()`
|
||||
|
||||
### Logging
|
||||
```cpp
|
||||
#include <spdlog/spdlog.h>
|
||||
spdlog::info("[ModuleName] Message");
|
||||
spdlog::debug("[ModuleName] Debug info");
|
||||
spdlog::error("[ModuleName] Error message");
|
||||
```
|
||||
|
||||
### Configuration
|
||||
```cpp
|
||||
auto config = loadConfig("config/module.json");
|
||||
int value = config->getInt("key", defaultValue);
|
||||
```
|
||||
|
||||
## Objectifs du Projet
|
||||
|
||||
### Primaire
|
||||
Créer un jeu de gestion/survie autour d'un train blindé mobile
|
||||
|
||||
### Secondaire
|
||||
Valider GroveEngine en conditions réelles de production
|
||||
|
||||
## Notes Importantes
|
||||
|
||||
- Le projet est en phase prototype
|
||||
- Focus sur la validation des mécaniques de base
|
||||
- Hot-reload est essentiel au workflow
|
||||
- Garder les modules simples et focalisés
|
||||
|
||||
## Ressources
|
||||
|
||||
- **Concept complet**: `../couple-repo/Projects/CONCEPT/mobile_command_v2.md`
|
||||
- **GroveEngine docs**: `external/GroveEngine/README.md`
|
||||
- **Architecture GroveEngine**: `external/GroveEngine/docs/architecture/`
|
||||
|
||||
77
CMakeLists.txt
Normal file
77
CMakeLists.txt
Normal file
@ -0,0 +1,77 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(MobileCommand VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Export compile commands for IDE support
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# ============================================================================
|
||||
# GroveEngine Integration
|
||||
# ============================================================================
|
||||
set(GROVE_BUILD_TESTS OFF CACHE BOOL "Disable GroveEngine tests" FORCE)
|
||||
set(GROVE_BUILD_MODULES OFF CACHE BOOL "Disable GroveEngine modules" FORCE)
|
||||
add_subdirectory(external/GroveEngine)
|
||||
|
||||
# ============================================================================
|
||||
# Dependencies
|
||||
# ============================================================================
|
||||
include(FetchContent)
|
||||
|
||||
# ============================================================================
|
||||
# Main Executable
|
||||
# ============================================================================
|
||||
add_executable(mobilecommand
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(mobilecommand PRIVATE
|
||||
GroveEngine::impl
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
target_include_directories(mobilecommand PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# Hot-Reloadable Modules (.so/.dll) - Game modules
|
||||
# ============================================================================
|
||||
|
||||
# GameModule - Core game loop
|
||||
add_library(GameModule SHARED
|
||||
src/modules/GameModule.cpp
|
||||
)
|
||||
target_link_libraries(GameModule PRIVATE
|
||||
GroveEngine::impl
|
||||
spdlog::spdlog
|
||||
)
|
||||
set_target_properties(GameModule PROPERTIES
|
||||
PREFIX "lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# Copy config files to build directory
|
||||
# ============================================================================
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/config/
|
||||
DESTINATION ${CMAKE_BINARY_DIR}/config)
|
||||
|
||||
# ============================================================================
|
||||
# Development targets
|
||||
# ============================================================================
|
||||
|
||||
# Quick rebuild of modules only (for hot-reload workflow)
|
||||
add_custom_target(modules
|
||||
DEPENDS GameModule
|
||||
COMMENT "Building hot-reloadable modules only"
|
||||
)
|
||||
|
||||
# Run MobileCommand
|
||||
add_custom_target(run
|
||||
COMMAND $<TARGET_FILE:mobilecommand>
|
||||
DEPENDS mobilecommand modules
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Running Mobile Command"
|
||||
)
|
||||
84
README.md
84
README.md
@ -17,10 +17,68 @@ Le jeu force une évolution : De commandant infantry-heavy (2022) à opérateur
|
||||
|
||||
## Stack Technique
|
||||
|
||||
- **Engine** : GroveEngine (validation engine = objectif secondaire)
|
||||
- **Engine** : GroveEngine (hot-reload C++17 module system)
|
||||
- **Plateforme** : PC (Steam)
|
||||
- **Rendu** : 2D (sprites ou vector)
|
||||
- **Public** : 16+ (Thèmes de guerre, violence, choix moraux)
|
||||
- **Build** : CMake 3.20+, MinGW/GCC
|
||||
|
||||
## Architecture Projet
|
||||
|
||||
```
|
||||
mobilecommand/
|
||||
├── external/
|
||||
│ └── GroveEngine/ # GroveEngine (symlink vers ../groveengine)
|
||||
├── src/
|
||||
│ ├── main.cpp # Main application loop
|
||||
│ └── modules/
|
||||
│ └── GameModule.* # Core game loop (hot-reloadable)
|
||||
├── config/
|
||||
│ └── game.json # Game configuration
|
||||
├── build/ # Build directory
|
||||
│ ├── mobilecommand.exe # Main executable
|
||||
│ └── modules/ # Hot-reloadable modules (.dll)
|
||||
└── CMakeLists.txt
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.20+
|
||||
- C++17 compiler (MinGW/GCC on Windows)
|
||||
- GroveEngine (included via symlink)
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Configure (first time)
|
||||
cmake -B build -G "MinGW Makefiles"
|
||||
|
||||
# Build everything
|
||||
cmake --build build -j4
|
||||
|
||||
# Build modules only (for hot-reload workflow)
|
||||
cmake --build build --target modules
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
# Run from build directory
|
||||
cd build
|
||||
./mobilecommand.exe
|
||||
|
||||
# Or use the run script
|
||||
./run.sh
|
||||
```
|
||||
|
||||
### Hot-Reload Workflow
|
||||
|
||||
1. Start Mobile Command: `./build/mobilecommand.exe`
|
||||
2. Edit a module: `src/modules/GameModule.cpp`
|
||||
3. Rebuild: `cmake --build build --target modules`
|
||||
4. **Module reloads automatically with state preserved**
|
||||
|
||||
## Piliers de Design
|
||||
|
||||
@ -56,12 +114,15 @@ Le jeu force une évolution : De commandant infantry-heavy (2022) à opérateur
|
||||
|
||||
## Roadmap Développement
|
||||
|
||||
### Prototype (3-6 mois)
|
||||
- Train basique (3 wagons)
|
||||
- Craft simple (1 ressource → 1 drone)
|
||||
- 1 mission combat Rimworld-style
|
||||
- 3-5 events
|
||||
- Loop complet minimal
|
||||
### Prototype (3-6 mois) - EN COURS
|
||||
- [x] Setup projet GroveEngine
|
||||
- [x] Build system fonctionnel
|
||||
- [x] Hot-reload validé
|
||||
- [ ] Train basique (3 wagons)
|
||||
- [ ] Craft simple (1 ressource → 1 drone)
|
||||
- [ ] 1 mission combat Rimworld-style
|
||||
- [ ] 3-5 events
|
||||
- [ ] Loop complet minimal
|
||||
|
||||
### MVP (12-18 mois)
|
||||
- Train complet (tous wagons)
|
||||
@ -83,9 +144,12 @@ Le jeu force une évolution : De commandant infantry-heavy (2022) à opérateur
|
||||
|
||||
## Status
|
||||
|
||||
**CONCEPT** - Version 2.4 (1er décembre 2025)
|
||||
- Setup initial du projet
|
||||
- Prochaine étape : Prototype planning
|
||||
**PROTOTYPE SETUP** - Version 0.1.0 (1er décembre 2025)
|
||||
- ✅ Setup initial du projet
|
||||
- ✅ GroveEngine intégré
|
||||
- ✅ Build system configuré
|
||||
- ✅ Hot-reload fonctionnel
|
||||
- 🚧 Prochaine étape : Prototype gameplay
|
||||
|
||||
---
|
||||
|
||||
|
||||
11
config/game.json
Normal file
11
config/game.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"game": {
|
||||
"name": "Mobile Command",
|
||||
"targetFrameRate": 10
|
||||
},
|
||||
"debug": {
|
||||
"logLevel": "debug",
|
||||
"showFrameCount": true
|
||||
}
|
||||
}
|
||||
259
src/main.cpp
Normal file
259
src/main.cpp
Normal file
@ -0,0 +1,259 @@
|
||||
#include <grove/ModuleLoader.h>
|
||||
#include <grove/JsonDataNode.h>
|
||||
#include <grove/IOFactory.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Global flag for clean shutdown
|
||||
static volatile bool g_running = true;
|
||||
|
||||
void signalHandler(int signal) {
|
||||
spdlog::info("Signal {} recu, arret en cours...", signal);
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
// Simple file watcher for hot-reload
|
||||
class FileWatcher {
|
||||
public:
|
||||
void watch(const std::string& path) {
|
||||
if (fs::exists(path)) {
|
||||
m_lastModified[path] = fs::last_write_time(path);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasChanged(const std::string& path) {
|
||||
if (!fs::exists(path)) return false;
|
||||
|
||||
auto currentTime = fs::last_write_time(path);
|
||||
auto it = m_lastModified.find(path);
|
||||
|
||||
if (it == m_lastModified.end()) {
|
||||
m_lastModified[path] = currentTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTime != it->second) {
|
||||
it->second = currentTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, fs::file_time_type> m_lastModified;
|
||||
};
|
||||
|
||||
// Load JSON config file
|
||||
std::unique_ptr<grove::JsonDataNode> loadConfig(const std::string& path) {
|
||||
if (fs::exists(path)) {
|
||||
std::ifstream file(path);
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
auto config = std::make_unique<grove::JsonDataNode>("config", j);
|
||||
spdlog::info("Config chargee: {}", path);
|
||||
return config;
|
||||
} else {
|
||||
spdlog::warn("Config non trouvee: {}, utilisation des defauts", path);
|
||||
return std::make_unique<grove::JsonDataNode>("config");
|
||||
}
|
||||
}
|
||||
|
||||
// Module entry in our simple manager
|
||||
struct ModuleEntry {
|
||||
std::string name;
|
||||
std::string configFile;
|
||||
std::string path;
|
||||
std::unique_ptr<grove::ModuleLoader> loader;
|
||||
std::unique_ptr<grove::IIO> io;
|
||||
grove::IModule* module = nullptr;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Setup logging
|
||||
auto console = spdlog::stdout_color_mt("MobileCommand");
|
||||
spdlog::set_default_logger(console);
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
spdlog::set_pattern("[%H:%M:%S.%e] [%n] [%^%l%$] %v");
|
||||
|
||||
spdlog::info("========================================");
|
||||
spdlog::info(" MOBILE COMMAND");
|
||||
spdlog::info(" Survival Management / Base Building");
|
||||
spdlog::info(" Powered by GroveEngine");
|
||||
spdlog::info("========================================");
|
||||
|
||||
// Signal handling
|
||||
std::signal(SIGINT, signalHandler);
|
||||
std::signal(SIGTERM, signalHandler);
|
||||
|
||||
// Paths - try ./modules/ first, fallback to ./build/modules/
|
||||
std::string modulesDir = "./modules/";
|
||||
if (!fs::exists(modulesDir) || fs::is_empty(modulesDir)) {
|
||||
if (fs::exists("./build/modules/")) {
|
||||
modulesDir = "./build/modules/";
|
||||
spdlog::info("Using modules from: {}", modulesDir);
|
||||
}
|
||||
}
|
||||
const std::string configDir = "./config/";
|
||||
|
||||
// =========================================================================
|
||||
// Hot-Reloadable Modules
|
||||
// =========================================================================
|
||||
std::map<std::string, ModuleEntry> modules;
|
||||
FileWatcher watcher;
|
||||
|
||||
// Liste des modules a charger
|
||||
std::vector<std::pair<std::string, std::string>> moduleList = {
|
||||
{"GameModule", "game.json"},
|
||||
};
|
||||
|
||||
// Charger les modules
|
||||
for (const auto& [moduleName, configFile] : moduleList) {
|
||||
std::string modulePath = modulesDir + "lib" + moduleName + ".so";
|
||||
|
||||
if (!fs::exists(modulePath)) {
|
||||
spdlog::warn("{} non trouve: {}", moduleName, modulePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
ModuleEntry entry;
|
||||
entry.name = moduleName;
|
||||
entry.configFile = configFile;
|
||||
entry.path = modulePath;
|
||||
entry.loader = std::make_unique<grove::ModuleLoader>();
|
||||
entry.io = grove::IOFactory::create("intra", moduleName);
|
||||
|
||||
auto modulePtr = entry.loader->load(modulePath, moduleName);
|
||||
if (!modulePtr) {
|
||||
spdlog::error("Echec du chargement: {}", moduleName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Configure
|
||||
auto config = loadConfig(configDir + configFile);
|
||||
modulePtr->setConfiguration(*config, entry.io.get(), nullptr);
|
||||
|
||||
entry.module = modulePtr.release();
|
||||
watcher.watch(modulePath);
|
||||
|
||||
spdlog::info("{} charge et configure", moduleName);
|
||||
modules[moduleName] = std::move(entry);
|
||||
}
|
||||
|
||||
if (modules.empty()) {
|
||||
spdlog::warn("Aucun module charge! Build les modules: cmake --build build --target modules");
|
||||
spdlog::info("Demarrage sans modules (mode minimal)...");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Main Loop
|
||||
// =========================================================================
|
||||
spdlog::info("Demarrage de la boucle principale (Ctrl+C pour quitter)");
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
auto startTime = Clock::now();
|
||||
auto lastFrame = Clock::now();
|
||||
const auto targetFrameTime = std::chrono::milliseconds(100); // 10 Hz
|
||||
|
||||
grove::JsonDataNode frameInput("frame");
|
||||
uint64_t frameCount = 0;
|
||||
|
||||
while (g_running) {
|
||||
auto frameStart = Clock::now();
|
||||
|
||||
// Calculate times
|
||||
auto deltaTime = std::chrono::duration<float>(frameStart - lastFrame).count();
|
||||
auto gameTime = std::chrono::duration<float>(frameStart - startTime).count();
|
||||
lastFrame = frameStart;
|
||||
|
||||
// Prepare frame input
|
||||
frameInput.setDouble("deltaTime", deltaTime);
|
||||
frameInput.setInt("frameCount", frameCount);
|
||||
frameInput.setDouble("gameTime", gameTime);
|
||||
|
||||
// =====================================================================
|
||||
// Hot-Reload Check (every 10 frames = ~1 second)
|
||||
// =====================================================================
|
||||
if (frameCount % 10 == 0) {
|
||||
for (auto& [name, entry] : modules) {
|
||||
if (watcher.hasChanged(entry.path)) {
|
||||
spdlog::info("Modification detectee: {}, hot-reload...", name);
|
||||
|
||||
// Get state before reload
|
||||
std::unique_ptr<grove::IDataNode> state;
|
||||
if (entry.module) {
|
||||
state = entry.module->getState();
|
||||
}
|
||||
|
||||
// Reload
|
||||
auto reloaded = entry.loader->load(entry.path, name, true);
|
||||
if (reloaded) {
|
||||
auto config = loadConfig(configDir + entry.configFile);
|
||||
reloaded->setConfiguration(*config, entry.io.get(), nullptr);
|
||||
|
||||
if (state) {
|
||||
reloaded->setState(*state);
|
||||
}
|
||||
|
||||
entry.module = reloaded.release();
|
||||
spdlog::info("{} recharge avec succes!", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Process Modules (hot-reloadable)
|
||||
// =====================================================================
|
||||
for (auto& [name, entry] : modules) {
|
||||
if (entry.module) {
|
||||
entry.module->process(frameInput);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Frame timing
|
||||
// =====================================================================
|
||||
frameCount++;
|
||||
|
||||
auto frameEnd = Clock::now();
|
||||
auto frameDuration = frameEnd - frameStart;
|
||||
|
||||
if (frameDuration < targetFrameTime) {
|
||||
std::this_thread::sleep_for(targetFrameTime - frameDuration);
|
||||
}
|
||||
|
||||
// Status log every 30 seconds
|
||||
if (frameCount % 300 == 0) {
|
||||
int minutes = static_cast<int>(gameTime / 60.0f);
|
||||
int seconds = static_cast<int>(gameTime) % 60;
|
||||
spdlog::debug("Session: {}m{}s, {} modules actifs",
|
||||
minutes, seconds, modules.size());
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Shutdown
|
||||
// =========================================================================
|
||||
spdlog::info("Arret en cours...");
|
||||
|
||||
// Shutdown modules
|
||||
for (auto& [name, entry] : modules) {
|
||||
if (entry.module) {
|
||||
entry.module->shutdown();
|
||||
spdlog::info("{} arrete", name);
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("A bientot!");
|
||||
return 0;
|
||||
}
|
||||
89
src/modules/GameModule.cpp
Normal file
89
src/modules/GameModule.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include <grove/IModule.h>
|
||||
#include <grove/JsonDataNode.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* GameModule - Core game loop module
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Main game state management
|
||||
* - Game loop coordination
|
||||
* - Event dispatching
|
||||
*/
|
||||
class GameModule : public grove::IModule {
|
||||
public:
|
||||
GameModule() = default;
|
||||
~GameModule() override = default;
|
||||
|
||||
void setConfiguration(const grove::IDataNode& config, grove::IIO* io, grove::ITaskScheduler* scheduler) override {
|
||||
m_io = io;
|
||||
m_scheduler = scheduler;
|
||||
|
||||
// Load configuration
|
||||
auto* jsonNode = dynamic_cast<const grove::JsonDataNode*>(&config);
|
||||
if (jsonNode) {
|
||||
spdlog::info("[GameModule] Configuration loaded");
|
||||
}
|
||||
}
|
||||
|
||||
void process(const grove::IDataNode& input) override {
|
||||
// Main game loop processing
|
||||
m_frameCount++;
|
||||
|
||||
if (m_frameCount % 100 == 0) {
|
||||
spdlog::debug("[GameModule] Frame {}", m_frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
spdlog::info("[GameModule] Shutdown");
|
||||
}
|
||||
|
||||
std::unique_ptr<grove::IDataNode> getState() override {
|
||||
auto state = std::make_unique<grove::JsonDataNode>("state");
|
||||
state->setInt("frameCount", m_frameCount);
|
||||
return state;
|
||||
}
|
||||
|
||||
void setState(const grove::IDataNode& state) override {
|
||||
m_frameCount = state.getInt("frameCount", 0);
|
||||
spdlog::info("[GameModule] State restored: frame {}", m_frameCount);
|
||||
}
|
||||
|
||||
const grove::IDataNode& getConfiguration() override {
|
||||
return m_config;
|
||||
}
|
||||
|
||||
std::unique_ptr<grove::IDataNode> getHealthStatus() override {
|
||||
auto health = std::make_unique<grove::JsonDataNode>("health");
|
||||
health->setString("status", "healthy");
|
||||
health->setInt("frameCount", m_frameCount);
|
||||
return health;
|
||||
}
|
||||
|
||||
std::string getType() const override {
|
||||
return "GameModule";
|
||||
}
|
||||
|
||||
bool isIdle() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
grove::IIO* m_io = nullptr;
|
||||
grove::ITaskScheduler* m_scheduler = nullptr;
|
||||
int m_frameCount = 0;
|
||||
grove::JsonDataNode m_config{"config"};
|
||||
};
|
||||
|
||||
// Module factory function
|
||||
extern "C" {
|
||||
grove::IModule* createModule() {
|
||||
return new GameModule();
|
||||
}
|
||||
|
||||
void destroyModule(grove::IModule* module) {
|
||||
delete module;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user