GroveEngine/tests/modules/GameLogicModuleV3.cpp
StillHammer 9105610b29 feat: Add integration tests 8-10 & fix CTest configuration
Added three new integration test scenarios:
- Test 08: Config Hot-Reload (dynamic configuration updates)
- Test 09: Module Dependencies (dependency injection & cascade reload)
- Test 10: Multi-Version Coexistence (canary deployment & progressive migration)

Fixes:
- Fixed CTest working directory for all tests (add WORKING_DIRECTORY)
- Fixed module paths to use relative paths (./ prefix)
- Fixed IModule.h comments for clarity

New test modules:
- ConfigurableModule (for config reload testing)
- BaseModule, DependentModule, IndependentModule (for dependency testing)
- GameLogicModuleV1/V2/V3 (for multi-version testing)

Test coverage now includes 10 comprehensive integration scenarios covering
hot-reload, chaos testing, stress testing, race conditions, memory leaks,
error recovery, limits, config reload, dependencies, and multi-versioning.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 07:34:15 +08:00

229 lines
6.9 KiB
C++

#include "GameLogicModuleV3.h"
#include "grove/JsonDataNode.h"
#include <nlohmann/json.hpp>
#include <sstream>
#include <cmath>
using json = nlohmann::json;
namespace grove {
GameLogicModuleV3::GameLogicModuleV3() {
config_ = std::make_unique<JsonDataNode>("config", json::object());
}
void GameLogicModuleV3::process(const IDataNode& input) {
if (!initialized_) return;
processCount_++;
float deltaTime = 0.016f;
const auto* jsonInput = dynamic_cast<const JsonDataNode*>(&input);
if (jsonInput) {
const auto& jsonData = jsonInput->getJsonData();
if (jsonData.contains("deltaTime")) {
deltaTime = jsonData["deltaTime"];
}
}
updateEntities(deltaTime);
}
void GameLogicModuleV3::updateEntities(float dt) {
for (auto& e : entities_) {
// V3: Advanced physics
applyPhysics(e, dt);
checkCollisions(e);
// Wrapping
if (e.x < 0.0f) e.x += 1000.0f;
if (e.x > 1000.0f) e.x -= 1000.0f;
if (e.y < 0.0f) e.y += 1000.0f;
if (e.y > 1000.0f) e.y -= 1000.0f;
}
}
void GameLogicModuleV3::applyPhysics(EntityV3& e, float dt) {
float ay = gravity_ * (1.0f / e.mass);
e.vx *= friction_;
e.vy *= friction_;
e.vy += ay * dt;
e.x += e.vx * dt;
e.y += e.vy * dt;
}
void GameLogicModuleV3::checkCollisions(EntityV3& e) {
bool wasCollided = e.collided;
e.collided = (e.x < 50.0f || e.x > 950.0f || e.y < 50.0f || e.y > 950.0f);
if (e.collided && !wasCollided) {
collisionCount_++;
if (e.x < 50.0f || e.x > 950.0f) {
e.vx *= -0.8f;
}
if (e.y < 50.0f || e.y > 950.0f) {
e.vy *= -0.8f;
}
}
}
void GameLogicModuleV3::setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) {
io_ = io;
scheduler_ = scheduler;
const auto* jsonConfigNode = dynamic_cast<const JsonDataNode*>(&configNode);
if (jsonConfigNode) {
config_ = std::make_unique<JsonDataNode>("config", jsonConfigNode->getJsonData());
} else {
config_ = std::make_unique<JsonDataNode>("config", json::object());
}
int entityCount = 100;
const auto* jsonConfig = dynamic_cast<const JsonDataNode*>(&configNode);
if (jsonConfig) {
const auto& jsonData = jsonConfig->getJsonData();
if (jsonData.contains("entityCount")) {
entityCount = jsonData["entityCount"];
}
if (jsonData.contains("gravity")) {
gravity_ = jsonData["gravity"];
}
if (jsonData.contains("friction")) {
friction_ = jsonData["friction"];
}
}
entities_.clear();
entities_.reserve(entityCount);
for (int i = 0; i < entityCount; ++i) {
float x = std::fmod(i * 123.456f, 1000.0f);
float y = std::fmod(i * 789.012f, 1000.0f);
float vx = ((i % 10) - 5) * 10.0f;
float vy = ((i % 7) - 3) * 10.0f;
EntityV3 e(i, x, y, vx, vy);
e.mass = 0.5f + (i % 5) * 0.5f;
entities_.push_back(e);
}
initialized_ = true;
}
const IDataNode& GameLogicModuleV3::getConfiguration() {
return *config_;
}
std::unique_ptr<IDataNode> GameLogicModuleV3::getHealthStatus() {
json health;
health["status"] = "healthy";
health["version"] = getVersion();
health["entityCount"] = static_cast<int>(entities_.size());
health["processCount"] = processCount_.load();
health["collisionCount"] = collisionCount_.load();
health["gravity"] = gravity_;
health["friction"] = friction_;
return std::make_unique<JsonDataNode>("health", health);
}
void GameLogicModuleV3::shutdown() {
initialized_ = false;
entities_.clear();
}
std::unique_ptr<IDataNode> GameLogicModuleV3::getState() {
json state;
state["version"] = getVersion();
state["processCount"] = processCount_.load();
state["collisionCount"] = collisionCount_.load();
state["entityCount"] = static_cast<int>(entities_.size());
json entitiesJson = json::object();
for (size_t i = 0; i < entities_.size(); ++i) {
const auto& e = entities_[i];
json entityJson;
entityJson["id"] = e.id;
entityJson["x"] = e.x;
entityJson["y"] = e.y;
entityJson["vx"] = e.vx;
entityJson["vy"] = e.vy;
entityJson["collided"] = e.collided;
entityJson["mass"] = e.mass;
std::string key = "entity_" + std::to_string(i);
entitiesJson[key] = entityJson;
}
state["entities"] = entitiesJson;
return std::make_unique<JsonDataNode>("state", state);
}
void GameLogicModuleV3::setState(const IDataNode& state) {
const auto* jsonState = dynamic_cast<const JsonDataNode*>(&state);
if (!jsonState) return;
const auto& jsonData = jsonState->getJsonData();
if (!jsonData.contains("version")) return;
processCount_ = jsonData["processCount"];
if (jsonData.contains("collisionCount")) {
collisionCount_ = jsonData["collisionCount"];
}
int entityCount = jsonData["entityCount"];
entities_.clear();
entities_.reserve(entityCount);
if (jsonData.contains("entities")) {
const auto& entitiesJson = jsonData["entities"];
for (int i = 0; i < entityCount; ++i) {
std::string key = "entity_" + std::to_string(i);
if (entitiesJson.contains(key)) {
const auto& entityJson = entitiesJson[key];
EntityV3 e;
e.id = entityJson["id"];
e.x = entityJson["x"];
e.y = entityJson["y"];
e.vx = entityJson["vx"];
e.vy = entityJson["vy"];
e.collided = entityJson.contains("collided") ? entityJson["collided"].get<bool>() : false;
e.mass = entityJson.contains("mass") ? entityJson["mass"].get<float>() : 1.0f;
entities_.push_back(e);
}
}
}
initialized_ = true;
}
bool GameLogicModuleV3::migrateStateFrom(int fromVersion, const IDataNode& oldState) {
if (fromVersion == 1 || fromVersion == 2 || fromVersion == 3) {
setState(oldState);
// Initialize mass for entities migrated from older versions
for (size_t i = 0; i < entities_.size(); ++i) {
if (entities_[i].mass == 1.0f) { // Default value, likely from migration
entities_[i].mass = 0.5f + (i % 5) * 0.5f;
}
}
// Re-check collisions
for (auto& e : entities_) {
checkCollisions(e);
}
return true;
}
return false;
}
} // namespace grove
extern "C" {
grove::IModule* createModule() {
return new grove::GameLogicModuleV3();
}
void destroyModule(grove::IModule* module) {
delete module;
}
}