#include "ConfigurableModule.h" #include "grove/JsonDataNode.h" #include #include #include namespace grove { void ConfigurableModule::setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) { // Logger logger = spdlog::get("ConfigurableModule"); if (!logger) { logger = spdlog::stdout_color_mt("ConfigurableModule"); } logger->set_level(spdlog::level::debug); // Clone config en JSON const auto* jsonConfigNode = dynamic_cast(&configNode); if (jsonConfigNode) { this->configNode = std::make_unique("config", jsonConfigNode->getJsonData()); } else { this->configNode = std::make_unique("config"); } // Parse et appliquer config currentConfig = parseConfig(configNode); previousConfig = currentConfig; // Backup initial std::string errorMsg; if (!currentConfig.validate(errorMsg)) { logger->error("Invalid initial configuration: {}", errorMsg); // Fallback to defaults currentConfig = Config(); } logger->info("Initializing ConfigurableModule"); logger->info(" Spawn rate: {}/s", currentConfig.spawnRate); logger->info(" Max entities: {}", currentConfig.maxEntities); logger->info(" Entity speed: {}", currentConfig.entitySpeed); logger->info(" Colors: {}", currentConfig.colors.size()); frameCount = 0; } const IDataNode& ConfigurableModule::getConfiguration() { return *configNode; } void ConfigurableModule::process(const IDataNode& input) { float deltaTime = static_cast(input.getDouble("deltaTime", 1.0 / 60.0)); frameCount++; // Spawning logic if (static_cast(entities.size()) < currentConfig.maxEntities) { spawnAccumulator += deltaTime * currentConfig.spawnRate; while (spawnAccumulator >= 1.0f && static_cast(entities.size()) < currentConfig.maxEntities) { spawnEntity(); spawnAccumulator -= 1.0f; } } // Update entities for (auto& entity : entities) { updateEntity(entity, deltaTime); } // Log toutes les 60 frames (1 seconde) if (frameCount % 60 == 0) { logger->trace("Frame {}: {} entities active (max: {})", frameCount, entities.size(), currentConfig.maxEntities); } } std::unique_ptr ConfigurableModule::getHealthStatus() { nlohmann::json healthJson; healthJson["status"] = "healthy"; healthJson["entityCount"] = entities.size(); healthJson["frameCount"] = frameCount; healthJson["maxEntities"] = currentConfig.maxEntities; auto health = std::make_unique("health", healthJson); return health; } void ConfigurableModule::shutdown() { logger->info("Shutting down ConfigurableModule"); entities.clear(); } std::string ConfigurableModule::getType() const { return "configurable"; } std::unique_ptr ConfigurableModule::getState() { nlohmann::json json; json["frameCount"] = frameCount; json["nextEntityId"] = nextEntityId; json["spawnAccumulator"] = spawnAccumulator; // Sérialiser config actuelle json["config"] = configToJson(currentConfig); // Sérialiser entities nlohmann::json entitiesJson = nlohmann::json::array(); for (const auto& entity : entities) { entitiesJson.push_back({ {"id", entity.id}, {"x", entity.x}, {"y", entity.y}, {"vx", entity.vx}, {"vy", entity.vy}, {"color", entity.color}, {"speed", entity.speed} }); } json["entities"] = entitiesJson; return std::make_unique("state", json); } void ConfigurableModule::setState(const IDataNode& state) { const auto* jsonNode = dynamic_cast(&state); if (!jsonNode) { logger->error("setState: Invalid state (not JsonDataNode)"); return; } const auto& json = jsonNode->getJsonData(); // Ensure logger is initialized if (!logger) { logger = spdlog::get("ConfigurableModule"); if (!logger) { logger = spdlog::stdout_color_mt("ConfigurableModule"); } } // Ensure configNode is initialized if (!configNode) { configNode = std::make_unique("config"); } // Restaurer state frameCount = json.value("frameCount", 0); nextEntityId = json.value("nextEntityId", 0); spawnAccumulator = json.value("spawnAccumulator", 0.0f); // Restaurer entities entities.clear(); if (json.contains("entities") && json["entities"].is_array()) { for (const auto& entityJson : json["entities"]) { Entity entity; entity.id = entityJson.value("id", 0); entity.x = entityJson.value("x", 0.0f); entity.y = entityJson.value("y", 0.0f); entity.vx = entityJson.value("vx", 0.0f); entity.vy = entityJson.value("vy", 0.0f); entity.color = entityJson.value("color", "red"); entity.speed = entityJson.value("speed", 5.0f); entities.push_back(entity); } } logger->info("State restored: {} entities, frame {}", entities.size(), frameCount); } bool ConfigurableModule::updateConfig(const IDataNode& newConfigNode) { logger->info("Attempting config hot-reload..."); // Parse nouvelle config Config newConfig = parseConfig(newConfigNode); // Valider std::string errorMsg; if (!newConfig.validate(errorMsg)) { logger->error("Config validation failed: {}", errorMsg); return false; } // Backup current config (pour rollback potentiel) previousConfig = currentConfig; // Appliquer nouvelle config currentConfig = newConfig; // Update stored config node const auto* jsonConfigNode = dynamic_cast(&newConfigNode); if (jsonConfigNode) { configNode = std::make_unique("config", jsonConfigNode->getJsonData()); } logger->info("Config hot-reload successful!"); logger->info(" New spawn rate: {}/s", currentConfig.spawnRate); logger->info(" New max entities: {}", currentConfig.maxEntities); logger->info(" New entity speed: {}", currentConfig.entitySpeed); return true; } bool ConfigurableModule::updateConfigPartial(const IDataNode& partialConfigNode) { logger->info("Attempting partial config update..."); const auto* jsonNode = dynamic_cast(&partialConfigNode); if (!jsonNode) { logger->error("Partial config update failed: not a JsonDataNode"); return false; } const auto& partialJson = jsonNode->getJsonData(); // Créer une nouvelle config en fusionnant avec l'actuelle Config mergedConfig = currentConfig; // Merge chaque champ présent dans partialJson if (partialJson.contains("spawnRate")) { mergedConfig.spawnRate = partialJson["spawnRate"]; } if (partialJson.contains("maxEntities")) { mergedConfig.maxEntities = partialJson["maxEntities"]; } if (partialJson.contains("entitySpeed")) { mergedConfig.entitySpeed = partialJson["entitySpeed"]; } if (partialJson.contains("colors")) { mergedConfig.colors.clear(); for (const auto& color : partialJson["colors"]) { mergedConfig.colors.push_back(color); } } if (partialJson.contains("physics")) { if (partialJson["physics"].contains("gravity")) { mergedConfig.physics.gravity = partialJson["physics"]["gravity"]; } if (partialJson["physics"].contains("friction")) { mergedConfig.physics.friction = partialJson["physics"]["friction"]; } } // Valider config fusionnée std::string errorMsg; if (!mergedConfig.validate(errorMsg)) { logger->error("Partial config validation failed: {}", errorMsg); return false; } // Appliquer previousConfig = currentConfig; currentConfig = mergedConfig; // Update stored config node with merged config nlohmann::json mergedJson = configToJson(currentConfig); configNode = std::make_unique("config", mergedJson); logger->info("Partial config update successful!"); return true; } ConfigurableModule::Config ConfigurableModule::parseConfig(const IDataNode& configNode) { Config cfg; // Cast to JsonDataNode to access JSON const auto* jsonNode = dynamic_cast(&configNode); if (!jsonNode) { logger->warn("Config not a JsonDataNode, using defaults"); return cfg; } const auto& json = jsonNode->getJsonData(); cfg.spawnRate = json.value("spawnRate", 10); cfg.maxEntities = json.value("maxEntities", 50); cfg.entitySpeed = json.value("entitySpeed", 5.0f); // Parse colors cfg.colors.clear(); if (json.contains("colors") && json["colors"].is_array()) { for (const auto& colorJson : json["colors"]) { cfg.colors.push_back(colorJson.get()); } } if (cfg.colors.empty()) { cfg.colors = {"red", "blue"}; // Default } // Parse physics if (json.contains("physics") && json["physics"].is_object()) { cfg.physics.gravity = json["physics"].value("gravity", 9.8f); cfg.physics.friction = json["physics"].value("friction", 0.5f); } return cfg; } nlohmann::json ConfigurableModule::configToJson(const Config& cfg) const { nlohmann::json json; json["spawnRate"] = cfg.spawnRate; json["maxEntities"] = cfg.maxEntities; json["entitySpeed"] = cfg.entitySpeed; json["colors"] = cfg.colors; json["physics"]["gravity"] = cfg.physics.gravity; json["physics"]["friction"] = cfg.physics.friction; return json; } void ConfigurableModule::spawnEntity() { Entity entity; entity.id = nextEntityId++; // Position aléatoire std::uniform_real_distribution posDist(0.0f, 100.0f); entity.x = posDist(rng); entity.y = posDist(rng); // Vélocité aléatoire basée sur speed std::uniform_real_distribution angleDist(0.0f, 2.0f * 3.14159f); float angle = angleDist(rng); entity.vx = std::cos(angle) * currentConfig.entitySpeed; entity.vy = std::sin(angle) * currentConfig.entitySpeed; // Couleur aléatoire depuis la palette actuelle std::uniform_int_distribution colorDist(0, currentConfig.colors.size() - 1); entity.color = currentConfig.colors[colorDist(rng)]; // Snapshot de la config speed au moment de la création entity.speed = currentConfig.entitySpeed; entities.push_back(entity); logger->trace("Spawned entity #{} at ({:.1f}, {:.1f}) color={} speed={}", entity.id, entity.x, entity.y, entity.color, entity.speed); } void ConfigurableModule::updateEntity(Entity& entity, float dt) { // Update position entity.x += entity.vx * dt; entity.y += entity.vy * dt; // Bounce sur les bords (map 100x100) if (entity.x < 0.0f || entity.x > 100.0f) { entity.vx = -entity.vx; entity.x = std::clamp(entity.x, 0.0f, 100.0f); } if (entity.y < 0.0f || entity.y > 100.0f) { entity.vy = -entity.vy; entity.y = std::clamp(entity.y, 0.0f, 100.0f); } // Appliquer gravité et friction de la config actuelle entity.vy += currentConfig.physics.gravity * dt; entity.vx *= (1.0f - currentConfig.physics.friction * dt); entity.vy *= (1.0f - currentConfig.physics.friction * dt); } } // namespace grove // Export symbols extern "C" { grove::IModule* createModule() { return new grove::ConfigurableModule(); } void destroyModule(grove::IModule* module) { delete module; } }