# Message Communication System ## Vue d'ensemble Le système de communication inter-modules utilise des **classes de messages typées** au lieu de JSON brut pour garantir la fiabilité et la maintenabilité. ## Concept fondamental **Une classe = un type de message** **Une instance = un message individuel** Exemple: La classe `TankMovedMessage` définit le type "mouvement de tank". Chaque fois qu'un tank bouge, on crée une nouvelle instance de cette classe. ## Pourquoi des classes? ### Problème avec JSON brut - Erreurs découvertes uniquement au runtime - Pas de validation automatique - Typos dans les clés non détectées - Difficile à maintenir et refactoriser ### Solution avec classes - Validation au compile-time - Contrat clair entre modules - IDE autocomplétion et refactoring - Hot-reload friendly (petites classes isolées) - Évolutif (ajout de champs sans casser l'existant) ## Architecture ### IMessage - Interface de base Interface pure définissant le contrat minimal pour tous les messages: - **Type identification**: Chaque message déclare son type via enum - **Serialization**: Conversion vers JSON pour transport via IIO - **Deserialization**: Reconstruction depuis JSON reçu ### AMessage - Classe abstraite avec métadata Classe abstraite obligatoire fournissant l'implémentation partielle commune à tous les messages. **Métadata immutables (enforced):** - `timestamp` - Horodatage de création (const) - `sender` - Module émetteur (const) - `messageId` - ID unique pour tracking et reassemblage fragments (const) - `partId` - ID de fragment pour messages multi-parts (const) **Caractéristiques:** - Constructeur protégé force passage par classes enfants - Métadata initialisées automatiquement à la construction - Impossible de modifier métadata après création - Tous messages garantis d'avoir timestamp/sender/messageId ### MessageType - Enum central Enum listant tous les types de messages du système: - `TANK_MOVED`, `TANK_FIRED`, `TANK_DESTROYED` - `PRICE_UPDATED`, `TRADE_EXECUTED` - `ITEM_PRODUCED`, `BELT_CONGESTION` - etc. **Pourquoi enum plutôt que strings?** - Performance (comparaison d'entiers) - Type safety (typos détectées au compile) - Centralisation (tous les types visibles) ### Messages concrets Chaque type de message est une classe dédiée héritant de `AMessage`: **TankMovedMessage**: Position, vitesse, ID tank **PriceUpdatedMessage**: Item, ancien prix, nouveau prix **ItemProducedMessage**: Item type, quantité, factory ID Chaque classe: - Hérite obligatoirement de `AMessage` - Appelle constructeur `AMessage(senderModule)` - Stocke ses données spécifiques - Valide à la construction - Sérialise/désérialise son propre format ## Flow de communication ### Publication **Exemple complet:** ```cpp void TankModule::process(const json& input) { // 1. Calculer nouvelle position Vector2 newPos = calculatePosition(); float currentSpeed = getSpeed(); // 2. Créer message (validation automatique à la construction) TankMovedMessage msg(newPos, currentSpeed, tankId); // 3. Sérialiser en JSON json serialized = msg.serialize(); // 4. Publier via IIO io->publish("tank:" + std::to_string(tankId), serialized); } ``` **Étapes:** 1. Module crée instance de message (validation à la construction) 2. Message sérialisé en JSON via `serialize()` 3. Publié via `IIO::publish(topic, json)` 4. IIO route vers subscribers du topic ### Réception **Méthode 1: Type-safe template helper (recommandé)** ```cpp // Clean syntax avec type safety automatique auto tankMsg = io->pullMessageAs(); if (tankMsg) { // nullptr si type mismatch ou queue vide Vector2 pos = tankMsg->getPosition(); float speed = tankMsg->getSpeed(); } ``` **Méthode 2: Manuelle (si besoin de flexibilité)** ```cpp // Récupère message brut Message rawMsg = io->pullMessage(); // Désérialise vers base std::unique_ptr baseMsg = IMessage::deserialize(rawMsg.data); // Cast vers type concret if (TankMovedMessage* tankMsg = dynamic_cast(baseMsg.get())) { // Accès type-safe } ``` **Template helper implementation:** ```cpp class IIO { // Template helper inline (zero overhead) template std::unique_ptr pullMessageAs() { if (!hasMessages()) return nullptr; Message raw = pullMessage(); std::unique_ptr base = IMessage::deserialize(raw.data); T* casted = dynamic_cast(base.get()); if (!casted) return nullptr; // Type mismatch base.release(); return std::unique_ptr(casted); } }; ``` **Performance:** - Template inlined = zero function call overhead - `dynamic_cast` = ~5-10ns (négligeable vs JSON parsing) - Bottleneck réel = JSON serialization, pas le cast ### Désérialisation centralisée **Factory pattern avec routing par type:** ```cpp // IMessage base class std::unique_ptr IMessage::deserialize(const json& data) { // Extract type from JSON int typeInt = data.value("type", -1); MessageType type = static_cast(typeInt); // Route to concrete deserializer switch (type) { case MessageType::TANK_MOVED: return TankMovedMessage::deserialize(data); case MessageType::PRICE_UPDATED: return PriceUpdatedMessage::deserialize(data); case MessageType::ITEM_PRODUCED: return ItemProducedMessage::deserialize(data); // Add new messages here default: return nullptr; // Unknown type } } ``` **Chaque message implémente sa propre désérialisation:** ```cpp class TankMovedMessage : public AMessage { static std::unique_ptr deserialize(const json& data) { try { Vector2 pos{data["position"]["x"], data["position"]["y"]}; float speed = data["speed"]; int tankId = data["tankId"]; return std::make_unique(pos, speed, tankId); } catch (const json::exception& e) { return nullptr; // Malformed JSON } } }; ``` **Gestion des erreurs:** - JSON malformé → retourne `nullptr` - Type inconnu → retourne `nullptr` - Validation échoue → exception à la construction - Modules doivent vérifier `if (msg != nullptr)` ## Organisation du code ### Location des messages ``` modules/shared/messages/ ├── IMessage.h # Interface pure ├── AMessage.h # Classe abstraite avec métadata ├── MessageType.h # Enum des types ├── TankMovedMessage.h ├── PriceUpdatedMessage.h └── ... ``` **Rationale:** - Single source of truth (pas de duplication) - Contrat partagé entre émetteur et récepteur - Facile à trouver et maintenir ### Validation **À la construction**: Invariants validés immédiatement - Vitesse négative → exception - ID invalide → exception - Fail fast pour détecter bugs tôt **À la désérialisation**: Données réseau/externes validées - JSON malformé → retourne nullptr - Champs manquants → retourne nullptr - Protège contre données corrompues ## Décisions de design finalisées ### Versioning - Breaking changes assumées **Décision:** Types versionnés avec breaking changes strictes Changement de format = nouveau type + nouvelle classe: - `TANK_MOVED_V1` → `TANK_MOVED_V2` = types différents - Pas de backward compatibility - Pas de forward compatibility - Migration forcée de tous les modules **Rationale:** - Zéro ambiguïté sur le format attendu - Pas de logique conditionnelle complexe - Hot-reload force mise à jour synchronisée - Détection immédiate des incompatibilités - Code simple et clair **Conséquence:** - Format change → tous modules doivent migrer - Ancien code ne compile plus → migration manuelle obligatoire - Breaking changes explicites et visibles ### Message inheritance - AMessage obligatoire **Décision:** Classe abstraite `AMessage` avec métadata immutables enforced Architecture: ``` IMessage (interface pure) ↓ AMessage (classe abstraite - métadata immutables) ↓ TankMovedMessage, PriceUpdatedMessage... (classes concrètes) ``` **Enforcement:** - Constructeur `AMessage` protégé → impossible de créer message sans passer par enfant - Métadata const → immutables après construction - Tous messages garantis d'avoir timestamp/sender/messageId - Format uniforme pour tous messages du système **Rationale:** - Pas de duplication (métadata dans AMessage) - Impossible d'oublier métadata - Contrat strict et enforced au compile-time - Simplicité pour messages concrets (métadata automatique) ## Décisions de design finalisées (suite) ### Size limits - Fragmentation automatique par IO **Décision:** Pas de limite au niveau message, fragmentation automatique par couche transport **Architecture:** - **Modules**: Publient/reçoivent messages complets (transparence totale) - **IIO**: Gère fragmentation/défragmentation automatiquement - **Transport adaptatif**: Fragmentation si nécessaire selon type IO **Fragmentation par type:** - **IntraIO**: Pas de fragmentation (copie mémoire directe) - **LocalIO**: Fragmentation si > seuil (ex: 64KB chunks) - **NetworkIO**: Fragmentation si > MTU (~1500 bytes) **Mécanisme:** 1. Module publie message → sérialisé en JSON 2. IO détecte si taille > seuil transport 3. Si oui: découpe en fragments avec `messageId` + `partId` 4. Transport des fragments 5. IO destination reassemble via `messageId` (collect tous `partId`) 6. Module reçoit message complet via `pullMessage()` **Robustesse:** - **Packet loss**: Timeout si fragments incomplets (ex: 30s) - **Ordering**: `partId` garantit ordre de reassemblage - **Monitoring**: Log warning si message > 100KB (design smell probable) **Conséquence:** - Messages peuvent être arbitrairement gros - Complexité cachée dans couche IO - Performance optimisée par type de transport ### Async handling - Délégation au module **Décision:** Pas de gestion async spéciale au niveau messages **Rationale:** - Messages = transport de données uniquement - `ITaskScheduler` existe déjà pour opérations coûteuses - Module décide si déléguer ou traiter directement - Keep messages simple et stupid **Responsabilité:** - **Message**: Transport de données (aucune intelligence) - **Module**: Décide de déléguer opérations coûteuses au scheduler - **ITaskScheduler**: Gère threading et async **Exemple:** ```cpp // Module reçoit message déclenchant opération coûteuse if (PathfindingRequestMessage* req = dynamic_cast<...>) { // Module délègue au TaskScheduler scheduler->scheduleTask(PATHFINDING_TASK, req->serialize()); // Continue traitement autres messages } // Plus tard, récupère résultat if (scheduler->hasCompletedTasks() > 0) { TaskResult result = scheduler->getCompletedTask(); // Publie résultat via message } ``` ### Message ordering - Pas de garantie + Messages remplaçables **Décision:** Pas de garantie d'ordre, modules gèrent via timestamp si critique **Approche:** - **Pas de FIFO enforced**: Messages peuvent arriver dans ordre arbitraire - **Messages remplaçables par défaut**: `SubscriptionConfig.replaceable = true` pour majorité - **Timestamp disponible**: `msg->getTimestamp()` permet tri manuel si nécessaire - **Utilité questionnable**: Architecture modulaire rend ordre moins critique **Rationale:** - Performance maximale (pas de garanties coûteuses) - Messages remplaçables optimisent bandwidth (garde juste dernier) - Timestamp dans `AMessage` permet tri si vraiment nécessaire - Architecture découple les dépendances temporelles **Exemples d'usage:** ```cpp // Prix - remplaçable (dernière valeur suffit) io->subscribeLowFreq("economy:*", {.replaceable = true}); // Tank position - remplaçable (position actuelle suffit) io->subscribe("tank:*", {.replaceable = true}); // Events critiques - non remplaçable + tri si nécessaire io->subscribe("combat:*", {.replaceable = false}); // Si ordre vraiment critique (rare) std::vector messages = collectAllMessages(); std::sort(messages.begin(), messages.end(), [](const Message& a, const Message& b) { return a.getTimestamp() < b.getTimestamp(); }); ``` **Conséquence:** - Modules ne doivent pas assumer ordre de réception - Messages remplaçables = dernière valeur seulement - Tri par timestamp disponible mais rarement nécessaire ## Questions ouvertes Aucune question ouverte restante. Toutes les décisions de design ont été finalisées. ## Statut d'implémentation **Phase actuelle:** Design et documentation **Prochaines étapes:** 1. Créer interface `IMessage.h` 2. Créer classe abstraite `AMessage.h` avec métadata immutables 3. Créer enum `MessageType.h` 4. Implémenter 5-10 messages d'exemple 5. Tester avec modules existants 6. Itérer selon usage réel ## Références - `src/core/include/warfactory/IIO.h` - Couche pub/sub - `docs/01-architecture/architecture-technique.md` - Patterns de communication - `docs/03-implementation/CLAUDE-HOT-RELOAD-GUIDE.md` - Impact sur design messages