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>
249 lines
9.4 KiB
C++
249 lines
9.4 KiB
C++
#pragma once
|
|
|
|
#include <string>
|
|
#include <memory>
|
|
#include "IDataNode.h"
|
|
#include "ITaskScheduler.h"
|
|
|
|
// Forward declarations
|
|
namespace grove {
|
|
class IIO;
|
|
}
|
|
|
|
namespace grove {
|
|
|
|
|
|
|
|
/**
|
|
* @brief Pure business logic interface - optimized for Claude Code development
|
|
*
|
|
* This interface defines the contract for all game modules. Each module contains
|
|
* 200-300 lines of pure game logic with zero infrastructure code.
|
|
*
|
|
* Key design principles:
|
|
* - PURE FUNCTION: process() method has minimal side effects
|
|
* - CONFIG VIA IDATANODE: Configuration via immutable IDataNode references
|
|
* - IDATANODE COMMUNICATION: All data via IDataNode abstraction (backend agnostic)
|
|
* - IIO FOR PERSISTENCE: Save requests via IIO publish (Engine handles persistence)
|
|
* - NO INFRASTRUCTURE: No threading, networking, or framework dependencies
|
|
* - HOT-RELOAD READY: State serialization for seamless module replacement
|
|
* - CLAUDE OPTIMIZED: Micro-context size for AI development efficiency
|
|
*
|
|
* DATA FLOW:
|
|
* - Configuration: Read-only via setConfiguration(const IDataNode&)
|
|
* - Input: Read-only via process(const IDataNode&)
|
|
* - Save: Publish via IIO: m_io->publish("save:module:state", data)
|
|
* - State: Serialized via getState() for hot-reload
|
|
*
|
|
* Module constraint: Maximum 300 lines per module (Exception: ProductionModule 500-800 lines)
|
|
*/
|
|
class IModule {
|
|
public:
|
|
virtual ~IModule() = default;
|
|
|
|
/**
|
|
* @brief Process game logic
|
|
* @param input Data input from other modules or the module system
|
|
*
|
|
* This is the core method where all module logic is implemented.
|
|
* Modules communicate via IIO pub/sub and can delegate tasks via ITaskScheduler.
|
|
* Must handle state properly through getState/setState for hot-reload.
|
|
*/
|
|
virtual void process(const IDataNode& input) = 0;
|
|
|
|
/**
|
|
* @brief Set module configuration (replaces initialize)
|
|
* @param configNode Configuration node (immutable reference)
|
|
* @param io Pub/sub communication interface for messaging
|
|
* @param scheduler Task scheduling interface for delegating work
|
|
*
|
|
* Called when the module is loaded or configuration changes.
|
|
* Should setup internal state, validate configuration, and store service references.
|
|
*/
|
|
virtual void setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) = 0;
|
|
|
|
/**
|
|
* @brief Get current module configuration
|
|
* @return Configuration node reference
|
|
*/
|
|
virtual const IDataNode& getConfiguration() = 0;
|
|
|
|
/**
|
|
* @brief Get detailed health status of the module
|
|
* @return Health report with status, metrics, and diagnostics
|
|
*/
|
|
virtual std::unique_ptr<IDataNode> getHealthStatus() = 0;
|
|
|
|
/**
|
|
* @brief Cleanup and shutdown the module
|
|
*
|
|
* Called when the module is being unloaded. Should clean up any
|
|
* resources and prepare for safe destruction.
|
|
*/
|
|
virtual void shutdown() = 0;
|
|
|
|
/**
|
|
* @brief Get current module state for hot-reload support
|
|
* @return Data representation of all module state
|
|
*
|
|
* Critical for hot-reload functionality. Must serialize all internal
|
|
* state that needs to be preserved when the module is replaced.
|
|
* The returned data should be sufficient to restore the module to
|
|
* its current state via setState().
|
|
*/
|
|
virtual std::unique_ptr<IDataNode> getState() = 0;
|
|
|
|
/**
|
|
* @brief Restore module state after hot-reload
|
|
* @param state State previously returned by getState()
|
|
*
|
|
* Called after module replacement to restore the previous state.
|
|
* Must be able to reconstruct all internal state from the data
|
|
* to ensure seamless hot-reload without game disruption.
|
|
*/
|
|
virtual void setState(const IDataNode& state) = 0;
|
|
|
|
/**
|
|
* @brief Get module type identifier
|
|
* @return Module type as string (e.g., "tank", "economy", "production")
|
|
*/
|
|
virtual std::string getType() const = 0;
|
|
|
|
/**
|
|
* @brief Check if module is idle (no processing in progress)
|
|
* @return True if module has no active processing and can be safely hot-reloaded
|
|
*
|
|
* Used by hot-reload system to ensure safe reload timing.
|
|
* A module is considered idle when:
|
|
* - No synchronous processing in progress
|
|
* - Not waiting for critical state updates
|
|
* - Safe to extract state via getState()
|
|
*
|
|
* Note: Async tasks scheduled via ITaskScheduler are tracked separately
|
|
* by the module system and don't affect idle status.
|
|
*
|
|
* Default implementation should return true unless module explicitly
|
|
* tracks long-running synchronous operations.
|
|
*/
|
|
virtual bool isIdle() const = 0;
|
|
|
|
/**
|
|
* @brief Update module configuration at runtime (config hot-reload)
|
|
* @param newConfigNode New configuration to apply
|
|
* @return True if configuration was successfully applied, false if rejected
|
|
*
|
|
* This method enables runtime configuration changes without code reload.
|
|
* Unlike setConfiguration(), this is called on an already-initialized module.
|
|
*
|
|
* Implementation should:
|
|
* - Validate new configuration
|
|
* - Reject invalid configurations and return false
|
|
* - Preserve previous config for rollback if needed
|
|
* - Apply changes atomically if possible
|
|
*
|
|
* Default implementation rejects all updates (returns false).
|
|
* Modules that support config hot-reload must override this method.
|
|
*/
|
|
virtual bool updateConfig(const IDataNode& newConfigNode) {
|
|
// Default: reject config updates
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Update module configuration partially (merge with current config)
|
|
* @param partialConfigNode Configuration fragment to merge
|
|
* @return True if configuration was successfully merged and applied
|
|
*
|
|
* This method enables partial configuration updates where only specified
|
|
* fields are changed while others remain unchanged.
|
|
*
|
|
* Implementation should:
|
|
* - Merge partial config with current config
|
|
* - Validate merged result
|
|
* - Apply atomically
|
|
*
|
|
* Default implementation delegates to updateConfig() (full replacement).
|
|
* Modules can override for smarter partial merging.
|
|
*/
|
|
virtual bool updateConfigPartial(const IDataNode& partialConfigNode) {
|
|
// Default: delegate to full update
|
|
return updateConfig(partialConfigNode);
|
|
}
|
|
|
|
/**
|
|
* @brief Get list of module dependencies
|
|
* @return Vector of module names that this module depends on
|
|
*
|
|
* Declares explicit dependencies on other modules. The module system
|
|
* uses this to:
|
|
* - Verify dependencies are loaded before this module
|
|
* - Cascade reload when a dependency is reloaded
|
|
* - Prevent unloading dependencies while this module is active
|
|
* - Detect circular dependencies
|
|
*
|
|
* Dependencies are specified by module name (not file path).
|
|
*
|
|
* Example:
|
|
* - PhysicsModule might return {"MathModule"}
|
|
* - GameplayModule might return {"PhysicsModule", "AudioModule"}
|
|
*
|
|
* Default implementation returns empty vector (no dependencies).
|
|
*/
|
|
virtual std::vector<std::string> getDependencies() const {
|
|
return {}; // Default: no dependencies
|
|
}
|
|
|
|
/**
|
|
* @brief Get module version number
|
|
* @return Integer version number (increments with each reload)
|
|
*
|
|
* Used for tracking and debugging hot-reload behavior.
|
|
* Helps verify that modules are actually being reloaded and
|
|
* allows tracking version mismatches.
|
|
*
|
|
* Typically incremented manually during development or
|
|
* auto-generated during build process.
|
|
*
|
|
* Default implementation returns 1.
|
|
*/
|
|
virtual int getVersion() const {
|
|
return 1; // Default: version 1
|
|
}
|
|
|
|
/**
|
|
* @brief Migrate state from a different version of this module
|
|
* @param fromVersion Version number of the source module
|
|
* @param oldState State data from the previous version
|
|
* @return True if migration was successful, false if incompatible
|
|
*
|
|
* Enables multi-version coexistence by allowing state migration
|
|
* between different versions of the same module. Critical for:
|
|
* - Canary deployments (v1 → v2 progressive migration)
|
|
* - Blue/Green deployments (switch traffic between versions)
|
|
* - Rollback scenarios (v2 → v1 state restoration)
|
|
*
|
|
* Implementation should:
|
|
* - Check if migration from fromVersion is supported
|
|
* - Transform old state format to new format
|
|
* - Handle missing/new fields gracefully
|
|
* - Validate migrated state
|
|
* - Return false if migration is impossible
|
|
*
|
|
* Example:
|
|
* - v2 can migrate from v1 by adding default collision flags
|
|
* - v3 can migrate from v2 by initializing physics parameters
|
|
* - v1 cannot migrate from v2 (missing fields) → return false
|
|
*
|
|
* Default implementation accepts same version only (simple copy).
|
|
*/
|
|
virtual bool migrateStateFrom(int fromVersion, const IDataNode& oldState) {
|
|
// Default: only accept same version (simple setState)
|
|
if (fromVersion == getVersion()) {
|
|
setState(oldState);
|
|
return true;
|
|
}
|
|
return false; // Override for cross-version migration
|
|
}
|
|
};
|
|
|
|
} // namespace grove
|