diff --git a/docs/02-systems/message-communication-system.md b/docs/02-systems/message-communication-system.md new file mode 100644 index 0000000..2cdb660 --- /dev/null +++ b/docs/02-systems/message-communication-system.md @@ -0,0 +1,419 @@ +# 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