Y a-t-il des cas où les globales / singletons sont utiles dans le développement de jeux? [fermé]

11

Je sais que le fait d'avoir des variables globales ou des classes singleton crée des cas qui peuvent être difficiles à tester / gérer et j'ai été obligé d'utiliser ces modèles dans le code, mais souvent vous devez expédier.

Y a-t-il donc des cas où des variables globales ou singletons sont réellement utiles dans le développement de jeux?

Ólafur Waage
la source

Réponses:

30

Ces choses peuvent toujours être utiles . Que ce soit la solution la plus jolie ou la plus sûre est une autre affaire, mais je pense que le développement de jeux implique un certain degré de pragmatisme.

JasonD
la source
Très proche de mon mantra.
Ólafur Waage du
15

Certainement une liste non exhaustive, mais voici:

Les inconvénients

  • Gestion à vie . Les singletons et les globaux peuvent démarrer avant que les systèmes clés (par exemple le tas) ne soient initialisés, selon la façon dont vous les avez configurés. Si vous souhaitez les détruire (utile si vous effectuez un suivi des fuites, par exemple), vous devez faire attention à l'ordre de démontage ou commencer à vous lancer dans des choses comme les singletons Phoenix . (Voir fiasco d'ordre d'initialisation statique .)
  • Contrôle d'accès . Le rendu ne devrait-il avoir qu'un accès const aux données du jeu, tandis que la mise à jour devient non-const? Cela peut être plus difficile à appliquer avec des singletons et des globaux.
  • État . Comme l'a souligné Kaj, il est plus difficile de décomposer et de transformer fonctionnellement les singletons pour qu'ils fonctionnent dans une architecture à mémoire non partagée. Il a également des implications potentielles sur les performances pour d'autres types de systèmes NUMA (accès à une mémoire qui n'est pas locale). Les singletons représentent généralement un état centralisé et sont donc l'antithèse des transformations facilitées par la pureté.

Soit pour ou contre

  • Accès simultané . Dans un environnement simultané, les singletons peuvent être une douleur (vous devez considérer les problèmes de course / réentrance des données) ou une bénédiction (il est plus facile de centraliser et de raisonner sur le verrouillage et la gestion des ressources). Une utilisation intelligente de choses comme le stockage local des threads peut atténuer quelque peu les problèmes potentiels, mais ce n'est généralement pas un problème facile.
  • Codegen (en fonction du compilateur, de l'architecture, etc.): pour une implémentation singulière de création à la première utilisation naïve, vous pouvez évaluer une branche conditionnelle supplémentaire pour chaque accès. (Cela peut s'additionner.) Plusieurs globaux utilisés dans une fonction peuvent gonfler le pool littéral . Une approche "struct de tous les globaux" peut économiser de l'espace dans le pool littéral: une seule entrée à la base de la structure, puis des décalages codés dans les instructions de chargement. Enfin, si vous évitez les globaux et les singletons à tout prix, vous devez généralement utiliser au moins un peu de mémoire supplémentaire (registres, pile ou tas) pour transmettre des pointeurs, des références ou même des copies.

Avantages

  • Simplicité . Si vous savez que les inconvénients énumérés ci-dessus ne vous affectent pas autant (par exemple, vous travaillez dans un environnement à thread unique pour une plate-forme portable à processeur unique), vous évitez une certaine architecture (comme le passage d'arguments susmentionné). Les singletons et les globaux peuvent être plus faciles à comprendre pour les codeurs moins expérimentés (bien qu'il puisse être plus facile de les utiliser incorrectement).
  • Gestion à vie (encore). Si vous utilisez une «structure de globaux» ou un mécanisme autre qu'une création à la première demande, vous pouvez avoir un contrôle facile à lire et précis sur l'ordre d'initialisation et de destruction. Vous automatisez cela dans une certaine mesure, ou vous le gérez manuellement (avoir à le gérer peut être un pro ou un con, selon le nombre de globaux / singletons et leurs interdépendances).

Nous utilisons beaucoup une «structure de singletons mondiaux» dans nos titres de poche. Les titres de PC et de console ont tendance à moins compter sur eux; nous passerons davantage à une architecture événementielle / messagerie. Cela étant dit, les titres de PC / console utilisent encore souvent un TextureManager central; car il enveloppe généralement une seule ressource partagée (la mémoire de texture), cela a du sens pour nous.

Si vous maintenez votre API relativement propre, il pourrait ne pas être trop difficile de refactoriser un (ou un!) Modèle singleton lorsque vous en avez besoin ...

maigre
la source
Grande liste. Personnellement, je ne trouve que de la valeur négative dans les singletons et les globaux en raison des problèmes qui proviennent d'un état partagé (probablement mutable). Je ne pense pas que le problème "codegen" soit un problème; vous devez soit passer les pointeurs à travers les registres ou vous devez faire une séquence d'opérations pour l'obtenir, je pense que cela change le code par rapport à la taille des données à vue d'oeil, mais la somme sera à peu près la même.
dash-tom-bang
Oui, en ce qui concerne le codegen, la seule chose que nous avons réellement vue faire une différence significative est passée de globaux individuels à une table globale: elle a réduit les pools littéraux, et donc la taille de la section .text, de plusieurs pour cent. Ce qui est très bien sur les petits systèmes. =) Les avantages en termes de performances de ne pas frapper autant le dcache pour les littéraux étaient juste un avantage secondaire agréable (quoique minuscule dans la plupart des cas). Un autre avantage du passage à une table globale était la possibilité de le déplacer très facilement vers une section qui résidait dans une mémoire plus rapide ...
leander
4

Ils peuvent être très utiles, en particulier lors du prototypage ou de la mise en œuvre expérimentale, mais en général, nous préférons transmettre des références pour les structures de type gestionnaire, de cette façon, vous avez au moins un certain contrôle sur leur accès. Le plus gros problème avec les globaux et singletons (à mon avis) est qu'ils ne sont pas très conviviaux pour le multithread, et le code qui les utilise est beaucoup plus difficile à porter sur la mémoire non partagée, comme les SPU.

Kaj
la source
2

Je dirais que le design singleton lui-même n'est pas utile du tout. Les variables globales peuvent certainement être utiles, mais je préfère les voir cachées derrière une interface bien écrite afin que vous ne soyez pas au courant de leur existence. Avec les singletons, vous êtes définitivement conscient de leur existence.

J'utilise souvent des variables globales pour les choses qui nécessitent un accès tout au long d'un moteur. Mon outil de performance est un bon exemple que j'utilise partout dans le moteur pour appeler. Les appels sont simples; ProbeRegister (), ProbeHit () et ProbeScoped (). Leur accès réel est un peu plus délicat et utilise des variables globales pour certaines de leurs choses.

Simon
la source
2

Le principal problème avec les globaux et les singletons mal implémentés est les bogues obscurs de construction et de déconstruction.

Donc, si vous travaillez avec des primitives qui n'ont pas ces problèmes ou qui sont très conscientes du problème avec un pointeur. Ensuite, ils peuvent être utilisés en toute sécurité.

Les globaux ont leur place, comme les gotos et ne doivent pas être rejetés d'emblée mais plutôt utilisés avec précaution.

Il y a une bonne explication à ce sujet dans le Google C ++ Style Guide

Kimau
la source
Je ne suis pas d'accord que c'est le principal problème; «N'utilisez pas de globaux» remonte plus loin que les constructeurs d'existence. Je dirais qu'il y a deux problèmes majeurs avec eux. Premièrement, ils compliquent le raisonnement sur un programme. Si j'ai une fonction qui accède à un global, le comportement de cette fonction dépend non seulement de ses propres arguments, mais aussi de toutes les autres fonctions qui accèdent au global. C'est beaucoup plus à penser. Deuxièmement, même si vous pouvez tout garder dans votre tête (vous ne pouvez pas), cela entraîne toujours des problèmes de concurrence plus compliqués.
@Joe, le mot clé est "dépendances". Une fonction qui accède aux globaux (ou singletons ou tout autre état partagé) a des dépendances implicites sur toutes ces choses. Il est beaucoup plus facile de raisonner sur un peu de code si toutes ses dépendances sont explicites, ce que vous obtenez lorsque la liste complète des dépendances est documentée dans la liste des arguments.
dash-tom-bang
1

Les globaux sont utiles tout en prototypant rapidement un système qui nécessite un certain état entre les appels de fonction. Une fois que vous avez établi que le système fonctionne, déplacez l'état dans une classe et transformez les fonctions en méthodes de cette classe.

Les singletons sont utiles pour vous causer des problèmes à vous-même et aux autres. Plus vous introduisez un état global, plus vous aurez de problèmes avec la correction, la maintenance, l'extensibilité, la simultanéité du code, etc. Ne le faites pas.

Kylotan
la source
1

J'ai tendance à recommander l'utilisation d'une sorte de conteneur DI / IoC avec gestion personnalisée de la durée de vie au lieu des singletons (même si vous utilisez un gestionnaire de durée de vie "à une seule instance"). Au moins, il est facile d'échanger l'implémentation pour faciliter les tests.

Mike Strobel
la source
Pour ceux d'entre vous qui ne le savent pas, DI est "Dependency Injection", et IoC est "Inversion of Control". Lien: martinfowler.com/articles/injection.html (J'avais déjà lu à ce sujet mais je n'ai jamais vu l'acronyme, donc cela m'a pris un peu de recherche.) +1 pour mentionner cette excellente transformation, cependant.
leander
0

Si vous voulez les fonctionnalités d'économie de mémoire d'un singleton, vous pouvez peut-être essayer le modèle de conception de poids mouche?

http://en.wikipedia.org/wiki/Flyweight_pattern

En ce qui concerne les problèmes multi-threads mentionnés ci-dessus, il devrait être assez simple avec une certaine prévoyance d'implémenter un mécanisme de verrouillage pour les ressources qui peuvent être partagées entre les threads. http://en.wikipedia.org/wiki/Read/write_lock_pattern

lathomas64
la source
0

Les singletons sont un excellent moyen de stocker un état partagé dans un premier prototype.

Ils ne sont pas une solution miracle et posent des problèmes, mais ils constituent un modèle très utile pour certains états de l'interface utilisateur / logique.

Par exemple, dans iOS, vous utilisez des singletons pour obtenir [UIApplication sharedApplication], dans cocos2d vous pouvez l'utiliser pour obtenir des références à certains objets comme [CCNotifications sharedManager] et personnellement, je commence généralement par un singleton [Game sharedGame] où je peux état de stockage partagé entre de nombreux composants différents.

DFectuoso
la source
0

Wow, c'est intéressant pour moi, car je n'ai jamais personnellement eu de problème avec le motif singleton. Mon projet actuel est un moteur de jeu C ++ pour la Nintendo DS, et j'implémente de nombreux utilitaires d'accès au matériel (c.-à-d. VRAM Banks, Wifi, les deux moteurs graphiques) en tant qu'instances singleton, car ils sont destinés à envelopper le C mondial fonctions dans la bibliothèque sous-jacente.


la source
Ma question serait alors, pourquoi utiliser des singletons? Je vois que les gens aiment emballer les API avec des singletons ou des classes qui ne sont constituées que de fonctions statiques, mais pourquoi s'embêter avec la classe? Il me semble encore plus facile de simplement avoir les appels de fonction (encapsulés comme vous le souhaitez), mais en interne, d'accéder à l'état "global". Par exemple Log :: GetInstance () -> LogError (...) pourrait tout aussi bien être LogError (...) qui accède en interne à n'importe quel état global sans que le code client ne le sache.
dash-tom-bang
0

Uniquement lorsque vous avez un élément qui n'a qu'un seul contrôleur mais qui est géré par plusieurs modules.

Tels que, par exemple, l'interface de la souris. Ou interface joystick. Ou un lecteur de musique. Ou un lecteur audio. Ou l'écran. Ou le gestionnaire de fichiers de sauvegarde.

zaratustra
la source
-1

Les globaux sont beaucoup plus rapides! Il conviendrait donc parfaitement à une application exigeante en performances, comme un jeu.

Les singletons sont un meilleur mondial, l'OMI, et donc le bon outil.

Utilisez-le avec parcimonie!

jacmoe
la source
Beaucoup plus rapide que quoi ? Les globaux se trouveront dans une page de mémoire aléatoire s'ils sont un pointeur, et dans une page de mémoire fixe mais probablement éloignée s'ils sont une valeur. Le compilateur ne peut utiliser aucune optimisation de judas utile sur eux. De toute façon à laquelle je peux penser, les globales sont lentes .
Plus rapide que les getters et setters et en passant des références autour. Mais pas beaucoup. Cela aide à réduire les tailles, donc cela aiderait dans certains systèmes. J'ai probablement exagéré quand j'ai dit «beaucoup», mais je suis très sceptique quant à quiconque dit que vous ne devriez pas utiliser quelque chose. Vous avez juste besoin d'utiliser votre bon sens. Après tout, les singletons ne sont rien de plus qu'un membre de classe statique, et ce sont des globaux.
jacmoe
Mais pour résoudre tout problème de synchronisation avec les globaux, vous avez besoin de getters / setters pour eux, donc ce n'est pas vraiment pertinent. Le problème avec (et les rares avantages de) l'état global est qu'il s'agit d'un état global, pas de soucis concernant la vitesse ou (effectivement) les aspects syntaxiques de l'interface.