Gibt es Fälle, in denen Globale / Singletons bei der Spieleentwicklung nützlich sind? [geschlossen]

11

Ich weiß, dass globale Variablen oder Singleton-Klassen Fälle verursachen, die schwierig zu testen / zu verwalten sind, und ich bin mit der Verwendung dieser Muster im Code überfordert, aber oft muss man sie versenden.

Gibt es also Fälle, in denen globale Variablen oder Singletons bei der Spieleentwicklung tatsächlich nützlich sind?

Ólafur Waage
quelle

Antworten:

30

Diese Dinge können immer nützlich sein . Ob es die schönste oder sicherste Lösung ist oder nicht, ist eine andere Frage, aber ich denke, dass die Spieleentwicklung ein gewisses Maß an Pragmatismus beinhaltet.

JasonD
quelle
Sehr nah an meinem Mantra.
Ólafur Waage
15

Auf jeden Fall eine nicht erschöpfende Liste, aber hier geht's:

Nachteile

  • Lebenszeitmanagement . Singletons und Globals möchten möglicherweise gestartet werden, bevor Schlüsselsysteme (z. B. Heap) initialisiert werden, je nachdem, wie Sie sie eingerichtet haben. Wenn Sie sie jemals abreißen möchten (nützlich, wenn Sie beispielsweise Leckverfolgung durchführen), müssen Sie vorsichtig mit der Abreißreihenfolge sein oder sich mit Dingen wie Phoenix-Singletons befassen . (Siehe Fiasko der statischen Initialisierungsreihenfolge .)
  • Zugangskontrolle . Sollte das Rendern nur konstanten Zugriff auf Spieldaten haben, während das Update nicht konstant ist? Dies kann bei Singletons und Globals schwieriger durchzusetzen sein.
  • Staat . Wie Kaj betonte, ist es schwieriger, Singletons funktional zu zerlegen und zu transformieren, um überhaupt in einer Architektur ohne gemeinsam genutzten Speicher zu funktionieren. Es hat auch potenzielle Auswirkungen auf die Leistung für andere Arten von NUMA- Systemen (Zugriff auf Speicher, der nicht lokal ist). Singletons repräsentieren normalerweise einen zentralisierten Zustand und sind somit das Gegenteil von Transformationen, die durch Reinheit erleichtert werden.

Entweder für oder gegen

  • Parallelität . In einer gleichzeitigen Umgebung können Singletons entweder ein Schmerz sein (Sie müssen Probleme mit Datenrennen / Wiedereintritten berücksichtigen) oder ein Segen (es ist einfacher zu zentralisieren und über Sperren und Ressourcenverwaltung nachzudenken). Die geschickte Verwendung von Dingen wie dem lokalen Thread-Speicher kann potenzielle Probleme etwas mindern, ist jedoch normalerweise kein einfaches Problem.
  • Codegen (abhängig von Compiler, Architektur usw.): Für eine naive Singleton-Implementierung bei erstmaliger Verwendung erstellen Sie möglicherweise einen zusätzlichen bedingten Zweig für jeden Zugriff. (Dies kann addieren.) Mehrere Globals in einer Funktion verwendet wird, kann das aufblasen wörtlichen Pools . Ein Ansatz "Struktur aller Globalen" kann Platz im Literalpool sparen: nur ein Eintrag in der Basis der Struktur und dann Offsets, die in den Ladeanweisungen codiert sind. Wenn Sie Globale und Singletons um jeden Preis vermeiden möchten, müssen Sie im Allgemeinen mindestens ein wenig zusätzlichen Speicher (Register, Stapel oder Heap) verwenden, um Zeiger, Referenzen oder sogar Kopien weiterzugeben.

Vorteile

  • Einfachheit . Wenn Sie wissen, dass die oben aufgeführten Nachteile Sie nicht so sehr betreffen (z. B. wenn Sie in einer Single-Thread-Umgebung für eine einzelne CPU-Handheld-Plattform arbeiten), vermeiden Sie einige Architekturen (z. B. die oben genannte Argumentübergabe). Singletons und Globals sind für weniger erfahrene Programmierer möglicherweise leichter zu verstehen (obwohl es möglicherweise einfacher ist, sie falsch zu verwenden).
  • Lebenszeitmanagement (wieder). Wenn Sie eine "Struktur von Globals" oder einen anderen Mechanismus als "Create-on-First-Request" verwenden, können Sie die Initialisierungs- und Zerstörungsreihenfolge leicht lesen und genau steuern. Sie automatisieren dies bis zu einem gewissen Grad oder verwalten es manuell (abhängig von der Anzahl der Globals / Singletons und ihren gegenseitigen Abhängigkeiten kann es ein Pro oder Contra sein, es verwalten zu müssen).

In unseren Handheld-Titeln verwenden wir häufig eine "Struktur globaler Singletons". PC- und Konsolentitel sind in der Regel weniger auf sie angewiesen. Wir werden mehr auf eine ereignisgesteuerte / Messaging-Architektur umsteigen. Trotzdem verwenden die PC- / Konsolentitel immer noch häufig einen zentralen TextureManager. Da es normalerweise eine einzelne gemeinsam genutzte Ressource (den Texturspeicher) umschließt, hat dies für uns Sinn gemacht.

Wenn Sie Ihre API relativ sauber halten, ist es möglicherweise nicht allzu schwierig, ein Singleton-Muster zu überarbeiten (oder in!), Wenn Sie ...

schlanker
quelle
Tolle Liste. Persönlich finde ich nur einen negativen Wert in Singletons und Globals aufgrund der Probleme, die aus dem gemeinsamen (wahrscheinlich veränderlichen) Zustand resultieren. Ich finde das "Codegen" -Problem jedoch kein Problem. Entweder müssen Sie die Zeiger durch die Register führen, oder Sie müssen eine Reihe von Operationen ausführen, um sie zu erhalten. Ich denke, sie ändern den Code im Vergleich zur Datengröße sichtbar, aber die Summe wird ungefähr gleich sein.
Dash-Tom-Bang
Ja, was den Codegen betrifft, das einzige, was wir tatsächlich gesehen haben, das einen signifikanten Unterschied macht, war der Übergang von einzelnen Globals zu einem Globaltable: Es hat die Literalpools und damit die Größe des Textabschnitts um mehrere Prozent verkleinert. Was auf kleinen Systemen sehr schön ist. =) Die Leistungsvorteile, wenn man den Dcache für Literale nicht so oft trifft, waren nur ein netter (wenn auch in den meisten Fällen winziger) Nebeneffekt. Ein weiterer Vorteil des Wechsels zu einer globalen Tabelle war die Möglichkeit, sie wirklich einfach in einen Abschnitt zu verschieben, der sich in einem schnelleren Speicher befand ...
Leander
4

Sie können sehr nützlich sein, insbesondere während des Prototyping oder der experimentellen Implementierung. Im Allgemeinen bevorzugen wir es jedoch, Referenzen für Manager-ähnliche Strukturen weiterzugeben, damit Sie zumindest eine gewisse Kontrolle darüber haben, von wo aus auf sie zugegriffen wird. Das größte Problem bei Globals und Singletons (meiner Meinung nach) ist, dass sie nicht sehr multithreadfreundlich sind und dass Code, der sie verwendet, viel schwieriger auf nicht gemeinsam genutzten Speicher wie SPUs zu portieren ist.

Kaj
quelle
2

Ich würde sagen, dass das Singleton-Design selbst überhaupt nicht nützlich ist. Globale Variablen können auf jeden Fall nützlich sein, aber ich würde sie lieber hinter einer gut geschriebenen Oberfläche verstecken, damit Sie sich ihrer Existenz nicht bewusst sind. Mit Singletons sind Sie sich ihrer Existenz definitiv bewusst.

Ich verwende oft globale Variablen für Dinge, auf die über eine Engine zugegriffen werden muss. Mein Performance-Tool ist ein gutes Beispiel, das ich überall in der Engine zum Aufrufen verwende. Die Anrufe sind einfach; ProbeRegister (), ProbeHit () und ProbeScoped (). Ihr wirklicher Zugriff ist etwas kniffliger und verwendet für einige ihrer Dinge globale Variablen.

Simon
quelle
2

Das Hauptproblem bei globalen und schlecht implementierten Singletons sind obskure Konstruktions- und Dekonstruktionsfehler.

Wenn Sie also mit Grundelementen arbeiten, die diese Probleme nicht haben oder sich des Problems mit einem Zeiger sehr bewusst sind. Dann können sie sicher verwendet werden.

Globals haben ihren Platz, genau wie gotos, und sollten nicht sofort entlassen werden, sondern mit Vorsicht verwendet werden.

Eine gute Erklärung dafür finden Sie im Google C ++ Style Guide

Kimau
quelle
Ich bin nicht der Meinung, dass dies das Hauptproblem ist. "Verwenden Sie keine Globalen" geht weiter zurück als die Existenzkonstruktoren. Ich würde sagen, es gibt zwei Hauptprobleme mit ihnen. Erstens erschweren sie das Denken über ein Programm. Wenn ich eine Funktion habe, die auf eine globale Funktion zugreift, hängt das Verhalten dieser Funktion nicht nur von ihren eigenen Argumenten ab, sondern auch von allen anderen Funktionen, die auf die globale Funktion zugreifen. Das ist viel mehr zu überlegen. Zweitens führen sie zu komplizierteren Parallelitätsproblemen, selbst wenn Sie alles in Ihrem Kopf halten können (Sie können es nicht).
@ Joe das Schlüsselwort ist "Abhängigkeiten". Eine Funktion, die auf Globals (oder Singletons oder einen anderen gemeinsam genutzten Status) zugreift, hat implizite Abhängigkeiten von all diesen Dingen. Es ist viel einfacher, über ein bisschen Code nachzudenken, wenn alle Abhängigkeiten explizit sind. Dies erhalten Sie, wenn die vollständige Liste der Abhängigkeiten in der Argumentliste dokumentiert ist.
Dash-Tom-Bang
1

Globals sind nützlich, um schnell ein System zu prototypisieren, das einen gewissen Status zwischen Funktionsaufrufen erfordert. Wenn Sie festgestellt haben, dass das System funktioniert, verschieben Sie den Status in eine Klasse und machen Sie die Funktionen zu Methoden dieser Klasse.

Singletons sind nützlich, um sich selbst und anderen Probleme zu bereiten. Je mehr globaler Status Sie einführen, desto mehr Probleme treten bei der Codekorrektheit, Wartung, Erweiterbarkeit, Parallelität usw. auf. Tun Sie es einfach nicht.

Kylotan
quelle
1

Ich empfehle eher die Verwendung eines DI / IoC-Containers mit benutzerdefinierter Lebensdauerverwaltung anstelle von Singletons (selbst wenn Sie einen Lebenszeitmanager mit einer einzelnen Instanz verwenden). Zumindest ist es dann einfach, die Implementierung auszutauschen, um das Testen zu erleichtern.

Mike Strobel
quelle
Für diejenigen unter Ihnen, die es nicht wissen, ist DI "Dependency Injection" und IoC "Inversion of Control". Link: martinfowler.com/articles/injection.html (Ich hatte zuvor darüber gelesen, aber das Akronym noch nie gesehen, daher musste ich ein wenig suchen.) +1, um diese hervorragende Transformation zu erwähnen.
Leander
0

Wenn Sie die speichersparenden Funktionen eines Singletons möchten, können Sie vielleicht das Flyweight-Entwurfsmuster ausprobieren?

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

In Bezug auf die oben genannten Multithread-Probleme sollte es ziemlich einfach sein, einen Sperrmechanismus für Ressourcen zu implementieren, die von Threads gemeinsam genutzt werden können. http://en.wikipedia.org/wiki/Read/write_lock_pattern

lathomas64
quelle
0

Singletons sind eine großartige Möglichkeit, einen gemeinsamen Zustand in einem frühen Prototyp zu speichern.

Sie sind keine Wunderwaffe und haben einige Probleme, sind jedoch ein sehr nützliches Muster für bestimmte UI / Logic-Zustände.

In iOS verwenden Sie beispielsweise Singletons, um die [UIApplication sharedApplication] abzurufen. In cocos2d können Sie damit Verweise auf bestimmte Objekte wie [CCNotifications sharedManager] abrufen, und ich persönlich beginne normalerweise mit einem Singleton [Game sharedGame], wo ich kann Speicherstatus, der von vielen verschiedenen Komponenten gemeinsam genutzt wird.

DFectuoso
quelle
0

Wow, das ist interessant für mich, da ich persönlich nie ein Problem mit dem Singleton-Muster hatte. Mein aktuelles Projekt ist eine C ++ - Spiel-Engine für den Nintendo DS, und ich implementiere viele der Hardware-Zugriffsdienstprogramme (z. B. VRAM Banks, Wifi, die beiden Grafik-Engines) als Singleton-Instanzen, da sie das globale C umschließen sollen Funktionen in der zugrunde liegenden Bibliothek.


quelle
Meine Frage wäre dann, warum überhaupt Singletons verwenden? Ich sehe, dass Leute APIs gerne mit Singletons oder Klassen umschließen, die nur aus statischen Funktionen bestehen, aber warum sich überhaupt mit der Klasse beschäftigen? Es scheint mir noch einfacher zu sein, nur die Funktionsaufrufe (wie gewünscht verpackt) zu haben, aber dann intern auf den "globalen" Status zuzugreifen. Zum Beispiel könnte Log :: GetInstance () -> LogError (...) genauso gut LogError (...) sein, der intern auf jeden globalen Status zugreift, ohne dass der Client-Code davon erfahren muss.
Dash-Tom-Bang
0

Nur wenn Sie ein Element haben, das nur einen Controller hat, aber von mehreren Modulen verarbeitet wird.

Wie zum Beispiel die Mausschnittstelle. Oder Joystick-Oberfläche. Oder Musik-Player. Oder Soundplayer. Oder der Bildschirm. Oder der Manager zum Speichern von Dateien.

Zaratustra
quelle
-1

Globals sind viel schneller! Es würde also perfekt für eine leistungsintensive Anwendung wie ein Spiel passen.

Singletons sind eine bessere globale IMO und somit das richtige Werkzeug.

Gehen Sie sparsam damit um!

Jacmoe
quelle
Viel schneller als was ? Globals befinden sich auf einer zufälligen Speicherseite, wenn sie ein Zeiger sind, und auf einer festen, aber wahrscheinlich entfernten Speicherseite, wenn sie ein Wert sind. Der Compiler kann keine nützlichen Gucklochoptimierungen verwenden. In jeder Hinsicht sind Globals langsam .
Schneller als Getter und Setter und Weitergabe von Referenzen. Aber nicht viel. Es hilft, die Größen niedrig zu halten, daher würde es in einigen Systemen helfen. Ich habe wahrscheinlich übertrieben, als ich "viel" sagte, aber ich bin sehr skeptisch gegenüber jemandem, der sagt, dass Sie etwas nicht verwenden sollten. Sie müssen nur Ihren gesunden Menschenverstand verwenden. Schließlich sind Singletons nichts anderes als ein statisches Klassenmitglied, und das sind Globale.
Jacmoe
Aber um Synchronisationsprobleme mit Globals zu lösen, benötigen Sie Getter / Setter für diese, das ist also nicht wirklich relevant. Das Problem mit (und dem seltenen Vorteil) des globalen Status besteht darin, dass es sich um einen globalen Status handelt, nicht um Bedenken hinsichtlich der Geschwindigkeit oder (effektiv) der Syntax der Schnittstelle.