# Scénario 9: Module Dependencies **Priorité**: ⭐ CRITICAL **Phase**: 2 (CRITICAL) **Durée estimée**: ~2 minutes **Effort implémentation**: ~4-5 heures --- ## 🎯 Objectif Valider que le système peut gérer des modules avec dépendances explicites, où un module A dépend d'un module B, avec: - Cascade reload automatique (reload de B → force reload de A) - Ordre de reload correct (B avant A) - Protection contre déchargement si dépendance active - Détection de cycles de dépendances - Préservation des données partagées entre modules --- ## 📋 Description ### Architecture des Modules de Test **BaseModule**: Module de base sans dépendances - Expose des services/données (ex: génère des nombres aléatoires) - Trackable pour valider les reloads - Version incrémentale **DependentModule**: Module qui dépend de BaseModule - Déclare explicitement BaseModule comme dépendance - Utilise les services de BaseModule - Doit être rechargé si BaseModule est rechargé **IndependentModule**: Module sans dépendances - Sert de témoin (ne devrait jamais être affecté) - Permet de valider que seules les bonnes dépendances sont reload ### Setup Initial 1. Charger **BaseModule** (version 1) - Expose service: `generateNumber()` → retourne 42 - Pas de dépendances 2. Charger **DependentModule** (version 1) - Dépend de: `["BaseModule"]` - Utilise `BaseModule.generateNumber()` - Accumule les résultats 3. Charger **IndependentModule** (version 1) - Pas de dépendances - Témoin indépendant 4. Vérifier que tous les modules sont chargés et fonctionnent ### Phase 1: Cascade Reload (BaseModule → DependentModule) **Durée**: 30 secondes 1. À t=10s, modifier et reloader **BaseModule** (version 2) - `generateNumber()` → retourne maintenant 100 2. Vérifier cascade reload: - ✅ BaseModule rechargé automatiquement - ✅ DependentModule **forcé** à reloader (cascade) - ✅ IndependentModule **non** rechargé (isolé) 3. Vérifier ordre de reload: - BaseModule reload **avant** DependentModule - État cohérent (pas de références cassées) 4. Vérifier continuité: - State de DependentModule préservé - Nouvelles valeurs de BaseModule utilisées (100 au lieu de 42) **Métriques**: - Cascade reload time: temps total pour recharger B + A - Dependency resolution time: temps pour identifier dépendants ### Phase 2: Protection contre Déchargement **Durée**: 10 secondes 1. À t=40s, tenter de **décharger BaseModule** 2. Vérifier rejet: - ❌ Déchargement **refusé** (DependentModule en dépend) - ⚠️ Erreur claire: "Cannot unload BaseModule: required by DependentModule" - ✅ BaseModule reste chargé et fonctionnel 3. Vérifier stabilité: - Tous les modules continuent de fonctionner - Aucune corruption de state ### Phase 3: Reload Module Dépendant (sans cascade inverse) **Durée**: 20 secondes 1. À t=50s, modifier et reloader **DependentModule** (version 2) - Change comportement interne - Garde même dépendance sur BaseModule 2. Vérifier isolation: - ✅ DependentModule rechargé - ✅ BaseModule **non** rechargé (pas de cascade inverse) - ✅ IndependentModule toujours isolé 3. Vérifier connexion: - DependentModule peut toujours utiliser BaseModule - Pas de références cassées ### Phase 4: Détection de Cycle de Dépendances **Durée**: 20 secondes 1. À t=70s, créer module **CyclicModule** avec dépendance circulaire: - CyclicModuleA dépend de CyclicModuleB - CyclicModuleB dépend de CyclicModuleA 2. Tenter de charger les deux modules 3. Vérifier détection: - ❌ Chargement **refusé** - ⚠️ Erreur claire: "Cyclic dependency detected: A → B → A" - ✅ Aucun module partiellement chargé (transactional) 4. Vérifier stabilité: - Modules existants non affectés - Système reste stable ### Phase 5: Déchargement en Cascade **Durée**: 20 secondes 1. À t=90s, décharger **DependentModule** (libère dépendance) 2. Vérifier libération: - ✅ DependentModule déchargé - ✅ BaseModule toujours chargé (encore utilisable) 3. À t=100s, décharger **BaseModule** (maintenant possible) 4. Vérifier succès: - ✅ BaseModule déchargé (plus de dépendants) - ✅ IndependentModule toujours actif (isolé) --- ## 🏗️ Implémentation ### Extension de IModule.h ```cpp // include/grove/IModule.h class IModule { public: // Méthodes existantes virtual void initialize(std::shared_ptr config) = 0; virtual void process(float deltaTime) = 0; virtual std::shared_ptr getState() const = 0; virtual void setState(std::shared_ptr state) = 0; virtual bool isIdle() const = 0; // NOUVELLE: Config hot-reload (Scenario 8) virtual bool updateConfig(std::shared_ptr newConfig) { return false; } // NOUVELLE: Dépendances (Scenario 9) virtual std::vector getDependencies() const { return {}; // Par défaut: aucune dépendance } // NOUVELLE: Version du module (pour tracking) virtual int getVersion() const { return 1; // Par défaut: version 1 } virtual ~IModule() = default; }; ``` ### BaseModule Structure ```cpp // tests/modules/BaseModule.h #pragma once #include "grove/IModule.h" #include #include class BaseModule : public IModule { public: void initialize(std::shared_ptr config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr state) override; bool isIdle() const override { return true; } std::vector getDependencies() const override { return {}; // Pas de dépendances } int getVersion() const override { return version_; } // Service exposé aux autres modules int generateNumber() const; private: int version_ = 1; std::atomic processCount_{0}; int generatedValue_ = 42; // V1: 42, V2: 100 }; // Module factory extern "C" IModule* createModule() { return new BaseModule(); } extern "C" void destroyModule(IModule* module) { delete module; } ``` ### DependentModule Structure ```cpp // tests/modules/DependentModule.h #pragma once #include "grove/IModule.h" #include "BaseModule.h" #include #include class DependentModule : public IModule { public: void initialize(std::shared_ptr config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr state) override; bool isIdle() const override { return true; } std::vector getDependencies() const override { return {"BaseModule"}; // Dépend de BaseModule } int getVersion() const override { return version_; } // Setter pour injecter la référence à BaseModule void setBaseModule(BaseModule* baseModule) { baseModule_ = baseModule; } private: int version_ = 1; BaseModule* baseModule_ = nullptr; std::vector collectedNumbers_; // Accumule les valeurs de BaseModule }; extern "C" IModule* createModule() { return new DependentModule(); } extern "C" void destroyModule(IModule* module) { delete module; } ``` ### IndependentModule Structure ```cpp // tests/modules/IndependentModule.h #pragma once #include "grove/IModule.h" #include #include class IndependentModule : public IModule { public: void initialize(std::shared_ptr config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr state) override; bool isIdle() const override { return true; } std::vector getDependencies() const override { return {}; // Témoin: aucune dépendance } int getVersion() const override { return version_; } private: int version_ = 1; std::atomic processCount_{0}; }; extern "C" IModule* createModule() { return new IndependentModule(); } extern "C" void destroyModule(IModule* module) { delete module; } ``` ### ModuleLoader Extensions Le `ModuleLoader` doit être étendu pour: 1. **Dependency Resolution**: ```cpp // Lors du chargement std::vector resolveDependencies(const std::string& moduleName); bool checkCyclicDependencies(const std::string& moduleName, std::set& visited); ``` 2. **Cascade Reload**: ```cpp // Lors du reload std::vector findDependents(const std::string& moduleName); void reloadWithDependents(const std::string& moduleName); ``` 3. **Unload Protection**: ```cpp // Lors du déchargement bool canUnload(const std::string& moduleName, std::string& errorMsg); ``` ### Dependency Graph ``` Phase 1-5: ┌─────────────────┐ │ IndependentModule│ (isolated, never reloaded) └─────────────────┘ ┌──────────────┐ │ BaseModule │ (no dependencies) └──────┬───────┘ │ │ depends on ▼ ┌──────────────────┐ │ DependentModule │ (depends on BaseModule) └──────────────────┘ Cascade reload: BaseModule reload → DependentModule reload (cascade) DependentModule reload → BaseModule NOT reloaded (no reverse cascade) Phase 4 (rejected): ┌────────────────┐ ┌────────────────┐ │ CyclicModuleA │───────▶│ CyclicModuleB │ └────────────────┘ └────────┬───────┘ ▲ │ └───────────────────────────┘ CYCLE! → REJECTED ``` --- ## 📊 Métriques Collectées | Métrique | Description | Seuil | |----------|-------------|-------| | **cascade_reload_time_ms** | Temps pour reload B + tous dépendants | < 200ms | | **dependency_resolution_time_ms** | Temps pour identifier dépendants | < 10ms | | **cycle_detection_time_ms** | Temps pour détecter cycle | < 50ms | | **memory_growth_mb** | Croissance mémoire totale | < 5MB | | **reload_count** | Nombre total de reloads | 3 (Base v2, Dep cascade, Dep v2) | --- ## ✅ Critères de Succès ### MUST PASS 1. ✅ Cascade reload: BaseModule reload → DependentModule reload 2. ✅ Ordre correct: BaseModule avant DependentModule 3. ✅ Isolation: IndependentModule jamais reload 4. ✅ Unload protection: BaseModule non déchargeable si DependentModule actif 5. ✅ Cycle detection: Dépendances circulaires détectées et rejetées 6. ✅ State preservation: State préservé après cascade reload 7. ✅ No reverse cascade: DependentModule reload ne force pas BaseModule reload 8. ✅ Aucun crash durant tout le test ### NICE TO HAVE 1. ✅ Cascade reload en < 100ms 2. ✅ Erreurs explicites avec noms de modules 3. ✅ Support de dépendances multiples (A dépend de B et C) 4. ✅ Visualisation du graphe de dépendances --- ## 🔧 API Nécessaires dans DebugEngine ### Nouvelles méthodes ```cpp class DebugEngine { public: // Méthodes existantes... // NOUVELLES pour Scenario 9 bool canUnloadModule(const std::string& moduleName, std::string& errorMsg); std::vector getModuleDependents(const std::string& moduleName); std::vector getModuleDependencies(const std::string& moduleName); bool hasCircularDependencies(const std::string& moduleName); // Reload avec cascade bool reloadModuleWithDependents(const std::string& moduleName); }; ``` --- ## 🐛 Cas d'Erreur Attendus | Erreur | Cause | Action | |--------|-------|--------| | DependentModule non rechargé après BaseModule reload | Cascade pas implémentée | FAIL - implémenter cascade | | BaseModule rechargé après DependentModule reload | Reverse cascade incorrecte | FAIL - vérifier direction | | IndependentModule rechargé | Isolation cassée | FAIL - fix isolation | | BaseModule déchargé alors que DependentModule actif | Protection pas implémentée | FAIL - ajouter check | | Cycle non détecté | Algorithme DFS manquant | FAIL - implémenter cycle detection | | Ordre reload incorrect (A avant B) | Topological sort manquant | FAIL - trier deps | | State corrompu après cascade | Mauvaise gestion | FAIL - préserver state | --- ## 📝 Output Attendu ``` ================================================================================ TEST: Module Dependencies ================================================================================ === Setup: Load modules with dependencies === ✓ BaseModule loaded (v1, no dependencies) ✓ DependentModule loaded (v1, depends on: BaseModule) ✓ IndependentModule loaded (v1, no dependencies) Dependency graph: IndependentModule → (none) BaseModule → (none) DependentModule → BaseModule === Phase 1: Cascade Reload (30s) === Reloading BaseModule... → BaseModule reload triggered → Cascade reload triggered for DependentModule ✓ BaseModule reloaded: v1 → v2 ✓ DependentModule cascade reloaded: v1 → v1 (state preserved) ✓ IndependentModule NOT reloaded (isolated) ✓ generateNumber() changed: 42 → 100 ✓ DependentModule using new value: 100 Metrics: Cascade reload time: 85ms ✓ Dependency resolution time: 4ms ✓ === Phase 2: Unload Protection (10s) === Attempting to unload BaseModule... ✗ Unload rejected: Cannot unload BaseModule: required by DependentModule ✓ BaseModule still loaded and functional ✓ All modules stable === Phase 3: Reload Dependent Only (20s) === Reloading DependentModule... ✓ DependentModule reloaded: v1 → v2 ✓ BaseModule NOT reloaded (no reverse cascade) ✓ IndependentModule still isolated ✓ DependentModule still connected to BaseModule === Phase 4: Cyclic Dependency Detection (20s) === Attempting to load CyclicModuleA and CyclicModuleB... ✗ Load rejected: Cyclic dependency detected: CyclicModuleA → CyclicModuleB → CyclicModuleA ✓ No modules partially loaded ✓ Existing modules unaffected Metrics: Cycle detection time: 12ms ✓ === Phase 5: Cascade Unload (20s) === Unloading DependentModule... ✓ DependentModule unloaded (dependency released) ✓ BaseModule still loaded Attempting to unload BaseModule... ✓ BaseModule unload succeeded (no dependents) Final state: IndependentModule: loaded (v1) BaseModule: unloaded DependentModule: unloaded ================================================================================ METRICS ================================================================================ Cascade reload time: 85ms (threshold: < 200ms) ✓ Dep resolution time: 4ms (threshold: < 10ms) ✓ Cycle detection time: 12ms (threshold: < 50ms) ✓ Memory growth: 2.1MB (threshold: < 5MB) ✓ Total reloads: 3 (expected: 3) ✓ ================================================================================ ASSERTIONS ================================================================================ ✓ modules_loaded ✓ cascade_reload_triggered ✓ reload_order_correct ✓ independent_isolated ✓ unload_protection_works ✓ no_reverse_cascade ✓ cycle_detected ✓ cascade_unload_works ✓ state_preserved ✓ no_crashes Result: ✅ PASSED ================================================================================ ``` --- ## 📅 Planning **Jour 10 (3h):** - Étendre IModule avec `getDependencies()` et `getVersion()` - Implémenter BaseModule, DependentModule, IndependentModule - Étendre ModuleLoader avec dependency resolution **Jour 11 (2h):** - Implémenter cascade reload dans ModuleLoader - Implémenter cycle detection (DFS) - Implémenter unload protection **Jour 12 (1h):** - Implémenter test_09_module_dependencies.cpp - Debug + validation - Intégration CMake --- ## 🎯 Valeur Ajoutée Ce test valide une fonctionnalité **critique pour les systèmes complexes** : - **Modularité avancée** : Permet de construire des architectures avec dépendances claires - **Sécurité** : Empêche les déchargements dangereux qui casseraient des références - **Maintenabilité** : Cascade reload automatique garantit cohérence - **Robustesse** : Détection de cycles évite les deadlocks et états invalides Dans un moteur de jeu réel, typiquement: - **PhysicsModule** dépend de **MathModule** - **RenderModule** dépend de **ResourceModule** - **GameplayModule** dépend de **PhysicsModule** et **AudioModule** Le hot-reload de `MathModule` doit automatiquement recharger `PhysicsModule` et tous ses dépendants (`GameplayModule`), dans l'ordre topologique correct. --- **Prochaine étape**: Implémentation