#include #include #include #include #include #include #include #include #include #include #include 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 m_lastModified; }; // Load JSON config file std::unique_ptr loadConfig(const std::string& path) { if (fs::exists(path)) { std::ifstream file(path); nlohmann::json j; file >> j; auto config = std::make_unique("config", j); spdlog::info("Config chargee: {}", path); return config; } else { spdlog::warn("Config non trouvee: {}, utilisation des defauts", path); return std::make_unique("config"); } } // Module entry in our simple manager struct ModuleEntry { std::string name; std::string configFile; std::string path; std::unique_ptr loader; std::unique_ptr 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 modules; FileWatcher watcher; // Liste des modules a charger std::vector> 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(); 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(frameStart - lastFrame).count(); auto gameTime = std::chrono::duration(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 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(gameTime / 60.0f); int seconds = static_cast(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; }