#include "HeavyStateModule.h" #include "grove/JsonDataNode.h" #include #include #include #include #include #include #include #include #include namespace grove { void HeavyStateModule::setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) { // Logger logger = spdlog::get("HeavyStateModule"); if (!logger) { logger = spdlog::stdout_color_mt("HeavyStateModule"); } logger->set_level(spdlog::level::info); // Clone config const auto* jsonConfigNode = dynamic_cast(&configNode); if (jsonConfigNode) { config = std::make_unique("config", jsonConfigNode->getJsonData()); } else { config = std::make_unique("config"); } // Extraire configuration version = configNode.getString("version", "v1.0"); particleTargetCount = configNode.getInt("particleCount", 1000000); terrainWidth = configNode.getInt("terrainSize", 10000); terrainHeight = terrainWidth; initDuration = static_cast(configNode.getDouble("initDuration", 8.0)); initTimeout = static_cast(configNode.getDouble("initTimeout", 15.0)); incrementalMode = configNode.getBool("incrementalState", false); logger->info("Initializing HeavyStateModule {}", version); logger->info(" Particles: {}", particleTargetCount); logger->info(" Terrain: {}x{}", terrainWidth, terrainHeight); logger->info(" Init duration: {}s", initDuration); // Simuler initialisation longue avec timeout check auto startTime = std::chrono::high_resolution_clock::now(); // Spawner particules progressivement const int batchSize = 10000; // Batches plus petits pour vérifier le timeout plus souvent for (int spawned = 0; spawned < particleTargetCount; spawned += batchSize) { int toSpawn = std::min(batchSize, particleTargetCount - spawned); spawnParticles(toSpawn); // Check timeout auto currentTime = std::chrono::high_resolution_clock::now(); float elapsed = std::chrono::duration(currentTime - startTime).count(); if (elapsed > initTimeout) { throw std::runtime_error("Module initialization exceeded timeout (" + std::to_string(elapsed) + "s > " + std::to_string(initTimeout) + "s)"); } // Simuler travail (proportionnel à initDuration) float sleepTime = (initDuration / (particleTargetCount / (float)batchSize)) * 1000.0f; std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(sleepTime))); } // Initialiser terrain initializeTerrain(terrainWidth, terrainHeight); auto endTime = std::chrono::high_resolution_clock::now(); float totalTime = std::chrono::duration(endTime - startTime).count(); logger->info("Initialization completed in {}s", totalTime); logger->info(" Particles spawned: {}", particles.size()); logger->info(" Terrain cells: {}", terrain.size()); frameCount = 0; } const IDataNode& HeavyStateModule::getConfiguration() { return *config; } void HeavyStateModule::process(const IDataNode& input) { float deltaTime = static_cast(input.getDouble("deltaTime", 1.0 / 60.0)); frameCount++; // Update particules updateParticles(deltaTime); // Record frame snapshot if (history.size() >= static_cast(historyMaxSize)) { history.pop_front(); } FrameSnapshot snapshot; snapshot.frameId = frameCount; snapshot.avgFPS = 1.0f / deltaTime; snapshot.particleCount = particles.size(); snapshot.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); history.push_back(snapshot); if (frameCount % 300 == 0) { logger->trace("Frame {}: {} particles", frameCount, particles.size()); } } std::unique_ptr HeavyStateModule::getHealthStatus() { nlohmann::json healthJson; healthJson["status"] = "healthy"; healthJson["particleCount"] = particles.size(); healthJson["terrainCells"] = terrain.size(); healthJson["frameCount"] = frameCount; healthJson["historySize"] = history.size(); return std::make_unique("health", healthJson); } void HeavyStateModule::shutdown() { logger->info("Shutting down HeavyStateModule"); particles.clear(); terrain.clear(); history.clear(); textureCache.clear(); } std::string HeavyStateModule::getType() const { return "heavystate"; } std::unique_ptr HeavyStateModule::getState() { auto startTime = std::chrono::high_resolution_clock::now(); nlohmann::json state; state["version"] = version; state["frameCount"] = frameCount; // Config state["config"]["particleCount"] = particleTargetCount; state["config"]["terrainWidth"] = terrainWidth; state["config"]["terrainHeight"] = terrainHeight; state["config"]["historySize"] = historyMaxSize; // Particles (compressé) state["particles"]["count"] = particles.size(); state["particles"]["data"] = compressParticleData(); // Terrain (compressé) state["terrain"]["width"] = terrainWidth; state["terrain"]["height"] = terrainHeight; state["terrain"]["compressed"] = true; state["terrain"]["data"] = compressTerrainData(); // History nlohmann::json historyArray = nlohmann::json::array(); for (const auto& snap : history) { historyArray.push_back({ {"frame", snap.frameId}, {"fps", snap.avgFPS}, {"particles", snap.particleCount}, {"ts", snap.timestamp} }); } state["history"] = historyArray; // Texture cache metadata state["textureCache"]["count"] = textureCache.size(); size_t totalCacheSize = 0; for (const auto& [id, data] : textureCache) { totalCacheSize += data.size(); } state["textureCache"]["totalSize"] = totalCacheSize; auto endTime = std::chrono::high_resolution_clock::now(); float elapsed = std::chrono::duration(endTime - startTime).count(); logger->info("getState() completed in {}ms", elapsed); return std::make_unique("state", state); } void HeavyStateModule::setState(const IDataNode& stateNode) { // Initialiser logger si nécessaire (peut être appelé avant setConfiguration lors du reload) if (!logger) { logger = spdlog::get("HeavyStateModule"); if (!logger) { logger = spdlog::stdout_color_mt("HeavyStateModule"); } logger->set_level(spdlog::level::info); } auto startTime = std::chrono::high_resolution_clock::now(); // Valider avant d'appliquer if (!validateState(stateNode)) { throw std::runtime_error("State validation failed - corrupted or invalid state"); } const auto* jsonNode = dynamic_cast(&stateNode); if (!jsonNode) { throw std::runtime_error("HeavyStateModule requires JsonDataNode for state"); } const auto& data = jsonNode->getJsonData(); // Restaurer version version = data["version"].get(); frameCount = data["frameCount"].get(); // Restaurer config particleTargetCount = data["config"]["particleCount"]; terrainWidth = data["config"]["terrainWidth"]; terrainHeight = data["config"]["terrainHeight"]; // Restaurer particules decompressParticleData(data["particles"]["data"].get()); // Restaurer terrain decompressTerrainData(data["terrain"]["data"].get()); // Restaurer historique history.clear(); for (const auto& snap : data["history"]) { FrameSnapshot snapshot; snapshot.frameId = snap["frame"]; snapshot.avgFPS = snap["fps"]; snapshot.particleCount = snap["particles"]; snapshot.timestamp = snap["ts"]; history.push_back(snapshot); } auto endTime = std::chrono::high_resolution_clock::now(); float elapsed = std::chrono::duration(endTime - startTime).count(); logger->info("setState() completed in {}ms", elapsed); logger->info(" Particles restored: {}", particles.size()); logger->info(" Terrain cells: {}", terrain.size()); logger->info(" History entries: {}", history.size()); } void HeavyStateModule::updateParticles(float dt) { for (auto& p : particles) { // Update position p.x += p.vx * dt; p.y += p.vy * dt; // Update lifetime p.lifetime -= dt; // Respawn si mort if (p.lifetime <= 0) { static std::mt19937 rng(42); static std::uniform_real_distribution distPos(0.0f, 100.0f); static std::uniform_real_distribution distVel(-5.0f, 5.0f); static std::uniform_real_distribution distLife(1.0f, 10.0f); p.x = distPos(rng); p.y = distPos(rng); p.vx = distVel(rng); p.vy = distVel(rng); p.lifetime = distLife(rng); } } } void HeavyStateModule::spawnParticles(size_t count) { static std::mt19937 rng(42); // Seed fixe pour reproductibilité static std::uniform_real_distribution distPos(0.0f, 100.0f); static std::uniform_real_distribution distVel(-5.0f, 5.0f); static std::uniform_real_distribution distLife(1.0f, 10.0f); static std::uniform_int_distribution distColor(0x00000000, 0xFFFFFFFF); for (size_t i = 0; i < count; i++) { Particle p; p.x = distPos(rng); p.y = distPos(rng); p.vx = distVel(rng); p.vy = distVel(rng); p.lifetime = distLife(rng); p.color = distColor(rng); particles.push_back(p); } } void HeavyStateModule::initializeTerrain(int width, int height) { static std::mt19937 rng(1337); // Seed différent du spawn particules static std::uniform_int_distribution distHeight(0, 255); static std::uniform_int_distribution distType(0, 5); size_t totalCells = static_cast(width) * static_cast(height); terrain.reserve(totalCells); for (size_t i = 0; i < totalCells; i++) { TerrainCell cell; cell.height = static_cast(distHeight(rng)); cell.type = static_cast(distType(rng)); cell.metadata = 0; cell.reserved = 0; terrain.push_back(cell); } } bool HeavyStateModule::validateState(const IDataNode& stateNode) const { const auto* jsonNode = dynamic_cast(&stateNode); if (!jsonNode) { logger->error("State is not JsonDataNode"); return false; } const auto& data = jsonNode->getJsonData(); // Vérifier champs requis if (!data.contains("version") || !data.contains("config") || !data.contains("particles") || !data.contains("terrain")) { logger->error("Missing required fields"); return false; } // Vérifier types if (!data["frameCount"].is_number_integer()) { logger->error("frameCount must be integer"); return false; } // Vérifier limites int particleCount = data["config"]["particleCount"]; if (particleCount < 0 || particleCount > 10000000) { logger->error("Invalid particle count: {}", particleCount); return false; } // Vérifier NaN/Infinity int terrainW = data["config"]["terrainWidth"]; int terrainH = data["config"]["terrainHeight"]; if (std::isnan(static_cast(terrainW)) || std::isinf(static_cast(terrainW)) || std::isnan(static_cast(terrainH)) || std::isinf(static_cast(terrainH))) { logger->error("Terrain dimensions are NaN/Inf"); return false; } if (terrainW < 0 || terrainH < 0 || terrainW > 20000 || terrainH > 20000) { logger->error("Invalid terrain dimensions"); return false; } return true; } std::string HeavyStateModule::compressParticleData() const { // Pour simplifier, on encode en hexadécimal // Dans une vraie implémentation, on utiliserait zlib ou autre std::ostringstream oss; oss << std::hex << std::setfill('0'); // Encoder seulement un échantillon pour ne pas créer un JSON énorme size_t sampleSize = std::min(particles.size(), size_t(1000)); for (size_t i = 0; i < sampleSize; i++) { const auto& p = particles[i]; oss << std::setw(8) << *reinterpret_cast(&p.x); oss << std::setw(8) << *reinterpret_cast(&p.y); oss << std::setw(8) << *reinterpret_cast(&p.vx); oss << std::setw(8) << *reinterpret_cast(&p.vy); oss << std::setw(8) << *reinterpret_cast(&p.lifetime); oss << std::setw(8) << p.color; } return oss.str(); } void HeavyStateModule::decompressParticleData(const std::string& compressed) { // Recréer les particules à partir de l'échantillon // (simplifié - dans la vraie vie on décompresserait tout) particles.clear(); spawnParticles(particleTargetCount); // Recréer avec seed fixe } std::string HeavyStateModule::compressTerrainData() const { // Similaire - échantillon seulement std::ostringstream oss; oss << std::hex << std::setfill('0'); size_t sampleSize = std::min(terrain.size(), size_t(1000)); for (size_t i = 0; i < sampleSize; i++) { const auto& cell = terrain[i]; oss << std::setw(2) << static_cast(cell.height); oss << std::setw(2) << static_cast(cell.type); } return oss.str(); } void HeavyStateModule::decompressTerrainData(const std::string& compressed) { // Recréer terrain avec seed fixe terrain.clear(); initializeTerrain(terrainWidth, terrainHeight); } } // namespace grove // Export symbols extern "C" { grove::IModule* createModule() { return new grove::HeavyStateModule(); } void destroyModule(grove::IModule* module) { delete module; } }