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:
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 ()
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é.)
la source
Réponses:
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.
la source
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.Eh bien, débarrassons-nous d'abord des choses simples. Vous avez cela
map
entre 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
map
est 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::string
etoperator<
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 (carmap
cela 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
map
entiè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.la source
Pour répondre à la question générale:
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.
la source