Ich entwerfe gerade mein Entity System für C ++ neu und habe viele Manager. In meinem Design habe ich diese Klassen, um meine Bibliothek zusammenzubinden. Ich habe viele schlechte Dinge gehört, wenn es um "Manager" -Klassen geht. Vielleicht benenne ich meine Klassen nicht richtig. Ich habe jedoch keine Ahnung, wie ich sie sonst benennen soll.
Die meisten Manager in meiner Bibliothek setzen sich aus den folgenden Klassen zusammen (die allerdings ein wenig variieren):
- Container - Ein Container für Objekte im Manager
- Attribute - Attribute für die Objekte im Manager
In meinem neuen Design für meine Bibliothek habe ich diese spezifischen Klassen, um meine Bibliothek zusammenzubinden.
ComponentManager - verwaltet Komponenten im Entity System
- ComponentContainer
- ComponentAttributes
- Szene * - ein Verweis auf eine Szene (siehe unten)
SystemManager - verwaltet Systeme im Entitätssystem
- SystemContainer
- Szene * - ein Verweis auf eine Szene (siehe unten)
EntityManager - verwaltet Entitäten im Entitätssystem
- EntityPool - ein Pool von Entitäten
- EntityAttributes - Attribute einer Entität (nur für ComponentContainer- und System-Klassen verfügbar)
- Szene * - ein Verweis auf eine Szene (siehe unten)
Szene - bindet alle Manager zusammen
- ComponentManager
- Systemmanager
- EntityManager
Ich dachte nur daran, alle Container / Pools in die Scene-Klasse selbst zu setzen.
dh
An Stelle von:
Scene scene; // create a Scene
// NOTE:
// I technically could wrap this line in a createEntity() call in the Scene class
Entity entity = scene.getEntityManager().getPool().create();
Es wäre dies:
Scene scene; // create a Scene
Entity entity = scene.getEntityPool().create();
Aber ich bin mir nicht sicher. Wenn ich Letzteres machen würde, würde das bedeuten, dass ich viele Objekte und Methoden in meiner Scene-Klasse deklariert hätte.
ANMERKUNGEN:
- Ein Entitätssystem ist einfach ein Design, das für Spiele verwendet wird. Es besteht aus drei Hauptteilen: Komponenten, Entitäten und Systemen. Die Komponenten sind einfach Daten, die den Entitäten "hinzugefügt" werden können, damit die Entitäten unterscheidbar sind. Eine Entität wird durch eine ganze Zahl dargestellt. Systeme enthalten die Logik für eine Entität mit bestimmten Komponenten.
- Der Grund, warum ich mein Design für meine Bibliothek ändere, ist, dass ich denke, dass es ziemlich viel geändert werden kann. Ich mag das Gefühl / Fließen momentan nicht.
quelle
Antworten:
DeadMG ist genau auf die Besonderheiten Ihres Codes abgestimmt, aber ich habe das Gefühl, es fehlt eine Klärung. Ich bin auch nicht mit einigen seiner Empfehlungen einverstanden, die in bestimmten Kontexten, wie zum Beispiel der Entwicklung der meisten Hochleistungs-Videospiele, nicht zutreffen. Aber er hat global Recht, dass der Großteil Ihres Codes momentan nicht nützlich ist.
Wie Dunk sagt, werden Manager-Klassen so genannt, weil sie Dinge "verwalten". "manage" ist wie "data" oder "do", es ist ein abstraktes Wort, das fast alles enthalten kann.
Ich befand mich vor ungefähr 7 Jahren größtenteils am selben Ort wie Sie, und ich begann zu glauben, dass etwas in meiner Denkweise nicht stimmte, weil es so viel Mühe gab, Code zu schreiben, um noch nichts zu tun.
Was ich geändert habe, um dies zu beheben, ist das Ändern des Vokabulars, das ich in Code verwende. Ich vermeide generische Wörter vollständig (es sei denn, es handelt sich um generischen Code, aber es ist selten, wenn Sie keine generischen Bibliotheken erstellen). Ich vermeide es, einen Typ "Manager" oder "Objekt" zu nennen.
Die Auswirkung ist direkt im Code: Sie werden gezwungen, das richtige Wort zu finden, das der tatsächlichen Verantwortung Ihres Typs entspricht. Wenn Sie der Meinung sind, dass der Typ mehrere Aufgaben erfüllt (einen Index der Bücher führen, sie am Leben erhalten, Bücher erstellen / zerstören), benötigen Sie verschiedene Typen, für die jeweils eine Verantwortung besteht, und kombinieren Sie sie in dem Code, der sie verwendet. Manchmal brauche ich eine Fabrik, manchmal nicht. Manchmal brauche ich eine Registrierung, also richte ich eine ein (unter Verwendung von Standardcontainern und intelligenten Zeigern). Manchmal brauche ich ein System, das aus mehreren unterschiedlichen Untersystemen besteht, damit ich alles so weit wie möglich voneinander trenne, damit jeder Teil etwas Nützliches bewirkt.
Nennen Sie niemals einen Typ "Manager" und stellen Sie sicher, dass alle Ihre Typen eine eindeutige Rolle haben. Dies ist meine Empfehlung. Es mag schwierig sein, irgendwann Namen zu finden, aber es ist eine der schwierigsten Aufgaben bei der Programmierung im Allgemeinen
quelle
dynamic_cast
Ihre Leistung beeinträchtigen, verwenden Sie das statische Typsystem - dafür ist es gedacht. Es ist wirklich ein spezieller Kontext, kein allgemeiner, um eine eigene RTTI zu erstellen, wie es LLVM getan hat. Sogar dann tun sie das nicht.template <typename T> class Class { ... };
dient eigentlich zum Zuweisen von IDs für verschiedene Klassen (nämlich benutzerdefinierte Komponenten und benutzerdefinierte Systeme). Die ID-Zuweisung wird zum Speichern von Komponenten / Systemen in einem Vektor verwendet, da eine Karte möglicherweise nicht die Leistung erbringt, die ich für ein Spiel benötige.Nun, ich habe einige der Codes, mit denen Sie verlinkt sind, und Ihren Beitrag durchgelesen, und meine ehrliche Zusammenfassung lautet, dass der Großteil davon im Grunde genommen völlig wertlos ist. Es tut uns leid. Ich meine, Sie haben all diesen Code, aber Sie haben nichts erreicht . Überhaupt. Ich werde hier etwas tiefer gehen müssen, also nimm mich in Acht.
Beginnen wir mit ObjectFactory . Erstens Funktionszeiger. Diese sind zustandslos. Der einzige nützliche zustandslose Weg, ein Objekt zu erstellen, ist,
new
und Sie brauchen dafür keine Factory. Das zustandsbehaftete Erstellen eines Objekts ist nützlich, und Funktionszeiger sind für diese Aufgabe nicht nützlich. Und zweitens muss jedes Objekt wissen, wie es sich selbst zerstört , ohne dass es genau die Instanz finden muss, die es erschafft, um es zu zerstören. Außerdem müssen Sie einen intelligenten Zeiger und keinen unformatierten Zeiger zurückgeben, um die Ausnahmebedingung und die Ressourcensicherheit Ihres Benutzers zu gewährleisten. Ihre gesamte ClassRegistryData-Klasse ist nurstd::function<std::unique_ptr<Base, std::function<void(Base*)>>()>
, aber mit den oben genannten Löchern. Es muss überhaupt nicht existieren.Dann haben wir AllocatorFunctor- gibt es bereits Standard - Allocator Schnittstellen provided- nicht zu erwähnen , dass sie nur Wrapper auf
delete obj;
undreturn new T;
- was wiederum, ist bereits vorgesehen, und sie werden nicht interact mit irgendwelchen Stateful oder benutzerdefinierte Speicherverteilern , die vorhanden sind .Und ClassRegistry ist einfach,
std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>
aber Sie haben eine Klasse dafür geschrieben, anstatt die vorhandene Funktionalität zu verwenden. Es ist der gleiche, aber völlig unnötig globale, wandelbare Zustand mit einer Menge beschissener Makros.Was ich hier sage, ist, dass Sie einige Klassen erstellt haben, in denen Sie das gesamte Ding durch Funktionen ersetzen konnten, die aus Standardklassen erstellt wurden, und diese dann noch schlimmer machten, indem Sie sie zu einem global wandelbaren Zustand machten und eine Reihe von Makros einschlossen, was das ist das Schlimmste, was du tun könntest.
Dann haben wir Identifizierbar . Es ist hier eine Menge der gleichen Geschichte - es übernimmt
std::type_info
bereits die Aufgabe, jeden Typ als einen Laufzeitwert zu identifizieren, und es erfordert keine übermäßige Heizplatte seitens des Benutzers.Problem gelöst, aber ohne die vorhergehenden zweihundert Zeilen von Makros und so weiter. Dies kennzeichnet auch Ihr Identifizierbares Array als nichts anderes als,
std::vector
aber Sie müssen die Referenzzählung verwenden, auch wenn eindeutiges Eigentum oder Nicht-Eigentum besser wäre.Oh, und habe ich erwähnt, dass Types eine Menge absolut nutzloser Typedefs enthält? Sie gewinnen buchstäblich keinen Vorteil gegenüber der direkten Verwendung der Rohtypen und verlieren einen guten Teil der Lesbarkeit.
Als nächstes ist ComponentContainer . Sie können den Eigentümer nicht auswählen, Sie können keine separaten Container für abgeleitete Klassen verschiedener Komponenten haben, Sie können keine eigene Lebensdauersemantik verwenden. Löschen Sie ohne Reue.
Jetzt ist der ComponentFilter möglicherweise ein wenig wert, wenn Sie ihn häufig überarbeitet haben. So etwas wie
Dies bezieht sich nicht auf Klassen, die von denjenigen abgeleitet sind, mit denen Sie sich befassen möchten, aber auch nicht auf Ihre.
Der Rest ist so ziemlich die gleiche Geschichte. Es gibt hier nichts, was ohne die Nutzung Ihrer Bibliothek nicht besser gemacht werden könnte.
Wie würden Sie Manager in diesem Code vermeiden? Löschen Sie im Grunde alles.
quelle
typeid
oder nicht benutzedynamic_cast
. Vielen Dank für das Feedback, ich weiß es zu schätzen.Das Problem mit Manager-Klassen ist, dass ein Manager alles tun kann. Sie können den Klassennamen nicht lesen und wissen nicht, was die Klasse tut. EntityManager ... Ich weiß, dass es etwas mit Entities zu tun hat, aber wer weiß was. SystemManager ... ja, es verwaltet das System. Das ist sehr hilfreich.
Meine Vermutung ist, dass Ihre Manager-Klassen wahrscheinlich eine Menge Dinge tun. Ich wette, wenn Sie diese Dinge in eng verwandte funktionale Teile unterteilen würden, könnten Sie wahrscheinlich Klassennamen finden, die mit den funktionalen Teilen zusammenhängen, die jeder anhand des Klassennamens erkennen kann, was sie tun. Dies erleichtert die Wartung und das Verständnis des Designs erheblich.
quelle
Eigentlich finde ich
Manager
das in diesem Zusammenhang gar nicht so schlecht, Achselzucken. Wenn es einen Ort gibt, den ich fürManager
verzeihlich halte , dann für ein Entity-Component-System, da es wirklich die Lebensdauer von Komponenten, Entitäten und Systemen " verwaltet ". Zumindest ist das hier ein aussagekräftigerer Name als zum Beispiel,Factory
was in diesem Zusammenhang nicht so viel Sinn macht, da ein ECS tatsächlich ein bisschen mehr tut als Dinge zu erschaffen.Ich nehme an, Sie könnten verwenden
Database
, wieComponentDatabase
. Oder Sie könnten nur verwendenComponents
,Systems
undEntities
(das ist , was ich persönlich verwenden und vor allem , nur weil ich wie terse Identifikatoren obwohl es zugegebenermaßen vermitteln noch weniger Informationen, vielleicht als „Manager“).Der eine Teil, den ich etwas ungeschickt finde, ist der folgende:
Das ist eine Menge Arbeit,
get
bevor Sie eine Entität erstellen können, und es scheint, als ob beim Entwurf Implementierungsdetails verloren gehen, wenn Sie sich mit Entitätspools und "Managern" auskennen müssen, um sie zu erstellen. Ich denke, Dinge wie "Pools" und "Manager" (wenn Sie sich an diesen Namen halten) sollten Implementierungsdetails des ECS sein, nicht etwas, das dem Kunden angezeigt wird.Aber weiß nich, habe ich einige sehr einfallslos und allgemeine Verwendungen gesehen
Manager
,Handler
,Adapter
und die Namen dieser Art, aber ich denke , das ist ein ziemlich verzeihlich Anwendungsfall ist , wenn das Ding namens „Manager“ ist eigentlich verantwortlich für die „Verwaltung“ ( "Controlling? "," Umgang mit? ") die Lebensdauer von Objekten, die für die Erstellung und Zerstörung von Objekten verantwortlich sind und den Zugriff auf diese Objekte individuell ermöglichen. Das ist zumindest für mich die einfachste und intuitivste Verwendung von "Manager".Das heißt, eine allgemeine Strategie, um "Manager" in Ihrem Namen zu vermeiden, besteht darin, den "Manager" -Teil herauszunehmen und
-s
am Ende einen hinzuzufügen . :-D Nehmen wir zum Beispiel an, Sie haben einThreadManager
und es ist für das Erstellen von Threads und das Bereitstellen von Informationen über Threads und den Zugriff auf Threads usw. verantwortlich. Es werden jedoch keine Threads zusammengefasst, sodass wir es nicht aufrufen könnenThreadPool
. In diesem Fall nennst du es einfachThreads
. Das mache ich sowieso, wenn ich einfallslos bin.Ich erinnere mich irgendwie an damals, als das analoge Äquivalent von "Windows Explorer" "Dateimanager" hieß:
Und ich denke tatsächlich, dass "Datei-Manager" in diesem Fall aussagekräftiger ist als "Windows Explorer", selbst wenn es einen besseren Namen gibt, der nicht "Manager" beinhaltet. Bitte seien Sie also zumindest nicht zu schick mit Ihren Namen und beginnen Sie, Dinge wie "ComponentExplorer" zu benennen. "ComponentManager" gibt mir zumindest eine vernünftige Vorstellung davon, was das Ding macht, als jemand, der mit ECS-Motoren arbeitet.
quelle
Manager
angemessen. Die Klasse hat möglicherweise Entwurfsprobleme , aber den Namen ist genau das.