Controllers, modèles et listeners runtime
Pattern général
Le projet sépare les objets de données sérialisables des controllers runtime. Les classes de Assets/Src/Game/Data stockent les champs; les classes de Assets/Src/Game/Controllers ajoutent chargement, callbacks, caches, listeners RTT et opérations métier côté client.
classDiagram
class BaseData
class BaseController
class SimulationBase
class SimulationBaseController
class ActivityBase
class ActivityBaseController
class TeamBase
class TeamBaseController
BaseData <|-- SimulationBase
BaseData <|-- ActivityBase
BaseData <|-- TeamBase
BaseController <|-- SimulationBaseController
BaseController <|-- ActivityBaseController
BaseController <|-- TeamBaseController
SimulationBaseController --> SimulationBase
ActivityBaseController --> ActivityBase
TeamBaseController --> TeamBase
BaseController
Le constructeur appelle SetListeners(). La classe définit les hooks Deserialize, PreLoad, PostLoad, SetListeners et RemoveListeners. Une sous-classe qui change de contexte doit donc retirer ses listeners pour éviter les doubles callbacks.
SimulationBaseController
Ce controller est le hub runtime de la simulation. Il maintient notamment :
- simulation courante, activité et équipe courantes;
- stubs d'étudiants et équipes;
- permissions, ponderations et custom evaluations;
- présence/heartbeat;
- caches de stubs d'évaluation;
- listeners pour join/remove team, changement de nom, activité suivante, notifications, ponderations, etc.
ActivityBaseController
Il charge ActDataGameSpec, expose GenerateResults(), écoute ACT_UPDATED_FIELDS et sert de base aux variantes By Module et By Year.
TeamBaseController
Il porte les membres, activités/décisions, cash, logbook, résultats, last chat time et TeamEvaluation. Il écoute notamment les changements de cash, membres, logbook et création d'activité d'équipe.
Factory
Les controllers de base ne construisent pas directement leurs modèles. Ils passent par Factory.CreateSimulationBase(), CreateActivityBase(), CreateTeamBase(), etc. C'est ce qui permet aux deux familles de partager le même contrat runtime.
Règle de maintenance
Une donnée pure appartient au modèle; un accès backend, cache, listener ou événement UI appartient au controller. Éviter de mettre des appels BrainCloud directement dans les classes Data.
Créer ou étendre un controller de A à Z
Le controller est la couche qui transforme une donnée sérialisée en objet runtime vivant. Il peut charger le backend, maintenir des caches et écouter RTT. La donnée sérialisable elle-même reste dans Data/.
1. Créer/identifier le modèle Data
Le modèle doit savoir Deserialize() ses champs sans appeler BrainCloud. Si une nouvelle propriété est persistée, ajouter d'abord le champ ici et vérifier sa clé JSON.
2. Créer le controller
Hériter du controller de base le plus proche. Le constructeur crée normalement le modèle concret via Factory.
3. Respecter le cycle Deserialize → PreLoad → modèle → PostLoad
Les controllers principaux suivent ce pattern pour invalider les caches et recalculer les dépendances autour d'une nouvelle payload. Ne pas mettre des listeners dans Deserialize().
4. Ajouter les listeners symétriquement
public override void SetListeners()
{
base.SetListeners();
Game.MessagesDispatcher.RegisterListener(
MessageTypeBase.MY_EVENT,
OnMyEvent
);
}
public override void RemoveListeners()
{
base.RemoveListeners();
Game.MessagesDispatcher.UnRegisterListener(
MessageTypeBase.MY_EVENT,
OnMyEvent
);
}
Toute inscription doit avoir sa désinscription miroir. Le remplacement d'une simulation appelle oldSim.RemoveListeners(); si une sous-classe oublie un listener, l'ancien controller continuera à recevoir des RTT.
5. Mettre à jour le modèle dans le handler avant de prévenir l'UI
Le handler doit interpréter la payload, vérifier l'UID de simulation/activity/team concerné, mettre à jour le modèle/cache, puis déclencher les events locaux nécessaires.
6. Brancher Factory si la classe varie par famille/jeu
Si le controller ou son modèle diffère entre By Module et By Year, ajouter la sélection dans Factory plutôt que des tests GameUid dispersés.
Quand utiliser un cache
Les stubs d'évaluation et listes lourdes sont mis en cache puis marqués dirty lorsqu'un RTT pertinent arrive. Appliquer ce pattern lorsqu'une donnée coûte cher à recharger mais peut être invalidée précisément.
Checklist controller
- Modèle pur séparé du controller.
- Deserialize tolère les champs optionnels/anciens.
- Chaque RegisterListener possède UnRegisterListener.
- Le handler vérifie le contexte/UID avant de modifier l'état.
- Les caches sont invalidés sur tous les messages pertinents.
- Factory mise à jour si polymorphisme.
- Relog/changement de simulation ne produit pas de double callback.
No comments to display
No comments to display