Files
Ransom-s-wake/docs/adr/0008-composition-owned-service-lifetimes.md
T

6.1 KiB

0008 — Services possédés et injectés par la composition

  • Statut : accepté
  • Date : 2026-08-10
  • Remplace : aucun

Contexte

Les réglages, l'accès aux sauvegardes, l'audio et une campagne chargée doivent survivre à plusieurs changements d'écran. Godot permet de résoudre ce besoin avec des autoloads, des singletons statiques ou un bus de signaux global. Ces solutions rendent toutefois les dépendances accessibles depuis n'importe quel script, masquent leur durée de vie et compliquent l'isolation des tests.

La verticale possède déjà une couche composition, seule autorisée à connaître les implémentations concrètes de toutes les autres couches. Elle peut donc maintenir les objets de longue durée sans les transformer en état global.

Décision

Aucun accès global de gameplay

Aucun autoload personnalisé, singleton statique, registre global, service locator ou bus de signaux global n'est introduit pour le gameplay de la verticale. Une longue durée de vie ne suffit pas à justifier un accès global.

Les singletons fournis par Godot restent des détails de plateforme. Seuls les adaptateurs autorisés les utilisent ; le domaine et les cas d'usage ne lisent pas directement Input, SceneTree, AudioServer, les fichiers ou l'horloge.

Racine persistante

vertical_slice_root.tscn est la racine de composition persistante de la verticale. Elle crée les implémentations concrètes, les relie à leurs ports et possède un emplacement destiné à l'écran ou au monde actif. Une transition remplace ce contenu enfant sans remplacer la racine et sans demander aux scènes de rechercher des services dans l'arbre.

La composition est responsable de :

  • construire les adaptateurs d'infrastructure et les cas d'usage ;
  • charger ou créer l'état de campagne ;
  • injecter les contrats requis avant d'activer une scène ;
  • connecter les sorties d'une scène aux commandes applicatives appropriées ;
  • déconnecter et libérer les dépendances de portée courte lors d'une transition ;
  • choisir l'écran suivant à partir d'un résultat applicatif explicite.

Une scène ne parcourt pas ses parents, /root, un groupe global ou un chemin absolu afin de découvrir une dépendance. Elle expose une méthode de configuration, des propriétés typées ou un constructeur de composition explicite. L'absence d'une dépendance requise échoue immédiatement en développement et dans les tests de chargement.

Durées de vie

Trois portées sont distinguées :

  1. Application : réglages, localisation, adaptateurs d'entrée et d'audio, horloge de plateforme, accès au stockage et liste des sauvegardes. Ils vivent tant que la racine existe.
  2. Campagne : CampaignState, progression de mission et cas d'usage liés à une partie. Ils sont créés lors d'une nouvelle partie ou d'un chargement et détruits lors de sa fermeture ou de son remplacement.
  3. Plongée : ResolvedDiveRules, DiveSession, acteurs, adaptateurs physiques et service sonar courant. Ils sont créés au départ d'une plongée et détruits après production de son résultat immuable.

Un objet de portée longue peut fournir un port à une portée plus courte. Il ne conserve pas en retour une référence implicite vers un acteur ou une scène déjà détruite. Les connexions de signaux suivent la même propriété et sont retirées avec leur portée.

Les services d'infrastructure ne deviennent pas propriétaires des règles. Par exemple, le dépôt de sauvegarde écrit un document demandé par un cas d'usage ; il ne décide pas quand une mission progresse ou combien d'argent est gagné.

Navigation et communications

La présentation traduit une interaction en commande ou appel de cas d'usage. Elle consomme ensuite un résultat, un snapshot ou un événement typé. Elle ne modifie jamais directement l'état de campagne ou un acteur du monde.

Les événements locaux peuvent utiliser les signaux Godot entre objets déjà reliés par la composition. Un signal décrit un fait passé et ne sert pas de canal global anonyme. Une intention reste une commande nommée.

Chaque écran et composition de plongée peut être instancié dans un test avec de faux ports, sans charger une campagne réelle, le stockage de la machine ou tous les autres écrans.

Évolution

Tout nouvel autoload, accès statique global ou exception durable à cette politique exige un ADR qui précise son propriétaire, sa durée de vie, son API, sa stratégie de réinitialisation et son isolation en test.

Conséquences

  • Les dépendances et leur durée de vie sont visibles à l'endroit où les scènes sont assemblées.
  • Un changement d'écran ne détruit pas les services applicatifs nécessaires à la session.
  • Une nouvelle campagne peut remplacer proprement la précédente sans conserver ses signaux, caches ou acteurs.
  • Les écrans sont testables avec des doubles légers et ne dépendent pas d'un ordre d'initialisation d'autoloads.
  • La composition contient davantage de code d'assemblage explicite et doit rester distincte des règles de gameplay.
  • Les transitions doivent libérer leurs objets de portée courte et seront couvertes par des tests d'intégration et de fuite de nœuds.
  • Les fonctionnalités transversales passent par des ports nommés plutôt que par une commodité globale.

Alternatives considérées

  • Un autoload par service : rejeté, car sauvegarde, campagne, audio et navigation deviendraient accessibles et mutables depuis toutes les couches.
  • Un unique GameManager autoload : rejeté, car il concentrerait assemblage, navigation et règles dans un propriétaire générique.
  • Un bus d'événements global : rejeté, car producteurs et consommateurs seraient reliés par des noms implicites et des durées de vie difficiles à vérifier.
  • Remplacer toute la scène courante à chaque écran : rejeté comme navigation principale, car les services persistants devraient alors être globaux ou reconstruits avec un état dupliqué.
  • Rechercher les services dans les parents ou par groupe Godot : rejeté, car la dépendance resterait cachée et dépendrait de la forme de l'arbre.