Pourrais-je devenir fou avec les gestionnaires d'événements? Suis-je dans la «mauvaise direction» avec mon design?

12

Je suppose que j'ai décidé que j'aime vraiment les gestionnaires d'événements. Je souffre peut-être un peu de paralysie de l'analyse, mais je crains de rendre ma conception lourde ou de rencontrer d'autres conséquences imprévues pour mes décisions de conception.

Mon moteur de jeu fait actuellement un rendu de base basé sur les sprites avec une caméra panoramique. Ma conception ressemble un peu à ceci:

SceneHandler

Contient une liste de classes qui implémentent l'interface SceneListener (actuellement uniquement des Sprites). Appelle render () une fois par tick et envoie onCameraUpdate (); messages à SceneListeners.

InputHandler

Interroge l'entrée une fois par tick et envoie un simple message "onKeyPressed" aux InputListeners. J'ai un Camera InputListener qui contient une instance SceneHandler et déclenche updateCamera (); événements en fonction de ce que l'entrée est.

AgentHandler

Appelle les actions par défaut sur tous les agents (AI) une fois par tick, et vérifiera une pile pour tout nouvel événement enregistré, en les envoyant à des agents spécifiques si nécessaire.

J'ai donc des objets sprite de base qui peuvent se déplacer dans une scène et utiliser des comportements de pilotage rudimentaires pour voyager. Je suis passé à la détection des collisions, et c'est là que je ne suis pas sûr que la direction que prend ma conception soit bonne. Est-ce une bonne pratique d'avoir de nombreux petits gestionnaires d'événements? J'imagine que je suis comme ça que je devrais implémenter une sorte de CollisionHandler.

Serais-je mieux avec un EntityHandler plus consolidé qui gère l'IA, les mises à jour de collision et d'autres interactions d'entité dans une classe? Ou vais-je bien mettre en œuvre de nombreux sous-systèmes de gestion d'événements différents qui se transmettent des messages en fonction du type d'événement? Dois-je écrire un EntityHandler qui est simplement responsable de la coordination de tous ces gestionnaires d'événements secondaires?

Je me rends compte que dans certains cas, comme mes InputHandler et SceneHandler, ce sont des types d'événements très spécifiques. Une grande partie de mon code de jeu ne se souciera pas de la saisie, et une grande partie ne se souciera pas des mises à jour qui se produisent uniquement dans le rendu de la scène. Je pense donc que mon isolement de ces systèmes est justifié. Cependant, je pose cette question en abordant spécifiquement les événements de type logique de jeu.

sensae
la source

Réponses:

21

La conception basée sur les événements implique principalement la mise en œuvre du modèle de conception du réacteur .

Avant de commencer à concevoir des composants, vous devez examiner les limites d'un tel modèle (vous pouvez trouver de nombreuses informations à ce sujet).

Le premier et le plus important des problèmes est que les gestionnaires doivent revenir rapidement , comme tous ceux qui ont travaillé sérieusement sur les cadres basés sur l'interface graphique et les applications basées sur javascript le savent bien.

Lorsque vous développez chaque application avec une complexité décente, vous serez tôt ou tard confronté à un gestionnaire qui effectue un travail complexe qui nécessite du temps .

Dans ces cas, vous pouvez distinguer deux situations (pas nécessairement mutuellement exclusives):

Complexes liés à l' événement responsabilités et complexes , sans rapport avec l' événement responsabilités.

Responsabilités liées aux événements complexes:

Dans le premier cas, vous avez fusionné trop de responsabilités en un seul gestionnaire de composants : trop d'actions sont prises en réponse à un événement ou il y a des synchronisations par exemple.

Dans ce cas, la solution consiste à diviser le gestionnaire en différents gestionnaires (dans différents objets si nécessaire) et à laisser une poignée pour déclencher des événements au lieu d'appeler directement le code de gestion. Cela vous permettra d'autoriser la gestion d'un événement de priorité supérieure, car votre gestionnaire principal est revenu après avoir ajouté des événements à la file d'attente d'événements.

Une autre solution consiste à laisser une entité externe déclencher des événements et à laisser les autres s'abonner si cela vous intéresse (l'événement de synchronisation est l'exemple le plus trivial que vous puissiez généraliser).

Responsabilités complexes non liées aux événements:

Le deuxième cas se produit lorsqu'il y a une complexité intrinsèque dans l'action à entreprendre après un événement: il faut par exemple calculer un nombre de fibonacci après un événement.

Ici, le modèle Reactor échoue , ne valant pas grand-chose pour diviser l'algorithme de génération de fibonacci en petits morceaux qui déclenchent des événements à la fin afin de déclencher l'étape suivante.

Ici, la solution est de mélanger une conception filetée au réacteur , la bonne nouvelle est que si la complexité est intrinsèque (vous lisez donc la bonne section), il est très probable qu'il soit possible de démarrer et un fil indépendant qui fait le travail et qui doit en savoir peu ou rien sur le reste des composants liés au réacteur.

Pour gérer ce genre de situations, j'ai trouvé utile d'adopter une file d'attente de travaux sur un pool de threads flexible .

Lorsqu'un gestionnaire doit démarrer une action longue, il encapsule cette action dans une unité de travail à placer dans la file d'attente des travaux. Cette action encapsulée doit avoir un moyen de déclencher des événements dans le fil du réacteur. Le gestionnaire de files d'attente de travaux peut s'exécuter dans son propre thread ou dans le thread du réacteur et réagir à l'événement "newJob".

Le gestionnaire de files d'attente de travaux effectue les opérations suivantes:

  • il alloue une unité de travail à un thread du pool de threads flexibles à l'aide de son algorithme de planification si le pool est en mesure de fournir un thread (il existe un thread libre ou une nouvelle création de thread est autorisée)

  • écoutez le pool lui-même pour voir si une unité de travail est terminée afin qu'un thread puisse être récupéré pour exécuter une unité en attente, le cas échéant.

En utilisant le mécanisme de déclenchement d'événement, une unité de travail peut déclencher des événements pour notifier la fin, l'état de mise à jour, les avertissements, les erreurs ou chaque fois que cela est nécessaire. Le pool de threads flexible assure une bonne utilisation des ressources tout en évitant les threads morts pour bloquer l'ensemble du système; la file d'attente des travaux vous permet de choisir le type de priorité à attribuer aux différents types d'actions, car vous savez qu'ils nécessiteront une quantité cohérente de ressources de calcul.

FxIII
la source
3
+1 pour avoir été très minutieux. Quelques liens pour le lecteur curieux seraient géniaux.
sam hocevar
1

Dans la plupart des situations, l'utilisation d'événements sur d'autres options peut souvent améliorer la vitesse et la décentralisation. Je pense que si vous pouvez utiliser beaucoup d'événements, vous devriez y aller.

de manière annonciatrice
la source