Existe-t-il une meilleure façon de mettre en place un système d'événements?

9

Les systèmes d'événements sont incroyables, ils rendent le code extrêmement difficile à apprivoiser et permettent vraiment la création dynamique de jeux grâce à une communication facile des objets et de la boucle de jeu. J'ai du mal avec l'efficacité de ma mise en œuvre actuelle. Actuellement, ma légère optimisation de la séparation des listes d'objets dans les événements auxquels ils répondent a fait des merveilles, mais je devrais faire plus.

Actuellement, j'ai deux méthodes:

  1. Le plus simple: tous les objets sont ajoutés à un vecteur lorsqu'un événement est envoyé tous les objets sont envoyés à l'événement via sa méthode handle_event ()

  2. Plus complexe: j'ai une carte avec chaîne comme clé et int comme valeur. Lorsqu'un type d'événement est ajouté, il est ajouté à cette carte, l'int entier étant simplement incrémenté (il doit y avoir une meilleure façon)
    le vecteur de vecteurs d'objets repousse ensuite un nouveau vecteur pour gérer ce type d'événement.
    Lorsqu'un événement est appelé, il appelle simplement l'int correspondant dans la mappe eventTypes au type à l'intérieur du vecteur de vecteurs d'objets et envoie cet événement à chaque objet gérant ce type d'événement.

Cette première méthode est assez lente (évidemment) pour beaucoup d'objets, mais assez rapide pour très peu d'objets. Alors que la deuxième méthode est assez rapide avec des objets volumineux qui souhaitent gérer différents types d'événements, mais plus lente que la première méthode par objet avec des objets gérant le même type d'événement.

Existe-t-il un moyen plus rapide (au niveau de l'exécution)? Existe-t-il un moyen plus rapide de rechercher un entier dans le type de chaîne? (Au départ, j'avais une énumération, mais elle n'autorisait pas les types personnalisés, qui sont nécessaires en raison du niveau de dynamisme souhaité.)

ultifinitus
la source
1
C'est en quelque sorte à quoi servent les Hash Maps (ou Hash Table), la chaîne est calculée jusqu'à un nombre de hachage qui est ensuite utilisé pour rechercher directement dans un tableau. en.wikipedia.org/wiki/Hash_table
Patrick Hughes
Une bonne question est: est-ce vraiment un goulot d'étranglement en matière de performances, ou vous inquiétez-vous juste prématurément?
Jari Komppa
Il est déjà implémenté et il existe un goulot d'étranglement lorsque de nombreux objets sont utilisés. Mon commentaire précédent sur l'absence de goulot d'étranglement n'était pas dans l'application elle-même mais en fait la différence de vitesse entre les deux implémentations ci-dessus où le même nombre d'objets était réellement géré. J'espérais d'autres méthodes pour créer des systèmes d'événements ... Cependant, certaines des idées de ce fil augmenteront un peu la vitesse, en particulier dans le chargement du système d'événements (11-25 secondes de temps de chargement avec 100000 objets)
ultifinitus
Les objets 100K semblent terriblement élevés juste pour un jeu. S'agit-il d'un serveur ou d'une application cliente d'utilisateur final?
Patrick Hughes
Ceci est mon moteur, donc je vais vraiment pour la flexibilité. Il sera utilisé dans les applications serveur et les applications utilisateur final (moins souvent la première, l'optimisation)
ultifinitus

Réponses:

5

Il semble que vous disiez que le gros goulot d'étranglement des performances consiste à rechercher des ID d'événement (entiers) à partir de leurs noms de chaîne. Vous pouvez prétraiter vos données de jeu pour convertir tous les noms d'événements en entiers avant d'exécuter le jeu, ou éventuellement pendant le chargement du niveau; alors vous n'auriez pas à faire de conversions pendant le jeu.

Si des objets sont fréquemment créés et détruits, il peut y avoir beaucoup de désabonnement dans les vecteurs d'objets. Dans ce cas, vous pourriez bénéficier en utilisant des listes chaînées au lieu de vecteurs; ils sont plus rapides pour l'insertion et la suppression.

Nathan Reed
la source
Le goulot d'étranglement n'est pas grand (pour 100 000 objets, il perd 0,000076 ms / objet) mais je pense que votre idée est une excellente idée! Je pense que je vais en fait faire une recherche unique de l'id et que l'ID d'événement sera stocké sous forme d'int plutôt que les données de chaîne d'origine. Et je n'avais en fait pas pensé aux listes chaînées, bonne idée.
ultifinitus
1
+1 Pour le prétraitement des ID. Vous pouvez également le faire paresseusement en ayant un type EventType que chaque module pourrait obtenir via quelque chose comme EventType takeDamageEvent = EventSystem::CacheEventType("takeDamageEvent");. Faites-en un membre statique des classes et vous n'aurez qu'une seule copie flottant dans chaque classe qui en a besoin.
michael.bartnett
2
Notez que le stockage des identifiants au lieu des chaînes rendra le débogage quelque peu difficile. Il est toujours utile de pouvoir voir du texte spécifiant d'où vient un objet. Vous pouvez aller à mi-chemin en stockant à la fois l'ID et la chaîne, que vous conservez à des fins de débogage (peut-être même supprimée dans les versions).
Nicol Bolas
2

Eh bien, débarrassons-nous d'abord des choses simples. Vous avez cela mapentre des chaînes (le nom de l'événement, probablement) et des entiers (l'index des écouteurs d'événements enregistrés).

Le temps de recherche dans un mapest basé sur deux choses: le nombre d'éléments dans la carte et le temps qu'il faut pour faire la comparaison entre deux clés (en ignorant les problèmes de cache). Si le temps de recherche est un problème, une façon de le gérer consiste à modifier la fonction de comparaison en changeant le type de chaîne.

Supposons que vous utilisez std::stringet operator<pour comparaison. C'est très inefficace; il fait une comparaison d'octets. Vous ne vous souciez pas de la vraie chaîne moins que la comparaison; vous avez juste besoin d'une sorte de comparaison qui donne un ordre strict-faible (car mapcela ne fonctionne pas autrement).

Vous devez donc utiliser une chaîne de longueur fixe de 32 octets au lieu de std::string. Je les utilise pour les identifiants. Les tests de comparaison pour ces chaînes fixes ne font pas de comparaisons d'octets; ils font des comparaisons 32 bits (ou 64 bits) à la place. Il prend tous les 4 octets comme un entier non signé et le compare aux 4 octets correspondants de l'autre chaîne. De cette façon, la comparaison ne prend que 8 comparaisons au maximum. Il garantit un ordre strict-faible, bien que l'ordre n'ait rien à voir avec les données en tant que caractères.

Stocker des chaînes de plus de 31 octets (nécessite le caractère NULL) tronque la chaîne (mais à partir du milieu plutôt que des extrémités. Je trouve que l'entropie a tendance à être la plus élevée au début et à la fin). Et les chaînes plus courtes que celles-ci remplissent les caractères restants avec \0.

Maintenant, vous pouvez abandonner mapentièrement et utiliser une table de hachage. Si vous avez vraiment plus de 100 000 types d'événements différents, cela pourrait être une bonne idée. Mais je ne connais pas de jeu où ce serait une chose à distance raisonnable.

Nicol Bolas
la source
Vous devez donc utiliser une chaîne de longueur fixe de 32 octets au lieu de std :: string. Fantastique! Je n'avais pas pensé à changer le type de chaîne.
ultifinitus
0

Pour répondre à la question générale:

Meilleure façon de configurer un système d'événements

Il n'y en a pas. Tout ce que vous pouvez faire est d'identifier les besoins spécifiques que vous aurez pour vos systèmes d'événements (il peut y en avoir plusieurs différents), puis d'utiliser le bon outil pour le bon travail.

J'ai implémenté et essayé de généraliser des tonnes de systèmes d'événements et je suis arrivé à cette conclusion. Parce que les compromis sont vraiment différents si vous le faites à la compilation ou à l'exécution, si vous utilisez des hiérarchies d'objets ou des tableaux noirs, etc.

La meilleure façon est d'étudier les différents types de systèmes d'événements, puis vous aurez des indices sur lequel utiliser dans quel cas.

Maintenant, si vous voulez le système vraiment le plus flexible, implémentez un système de tableau noir (runtime) avec les propriétés dynamiques des événements et vous aurez quelque chose de très flexible mais peut-être très lent.

Klaim
la source