GroveEngine/include/grove/IModule.h
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

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