2013-03-13 5 views
11

Ich arbeite an einem Entity-Komponentensystem für eine Game-Engine. Eines meiner Ziele ist die Verwendung eines datenorientierten Ansatzes für eine optimale Datenverarbeitung. Mit anderen Worten, ich möchte der Richtlinie folgen, die eher Structures von Arrays als Arrays von Structs verlangt. Mein Problem ist jedoch, dass es mir nicht gelungen ist, einen guten Weg für mich zu finden.Datenorientierter Zugriff auf mehrere indizierte Datenfelder

Meine Idee ist bis jetzt, dass jede Komponente im System für einen bestimmten Teil der Spiellogik verantwortlich ist, sagen wir, dass die Gravity Component sich um die Berechnung von Kräften kümmert, abhängig von Masse, Geschwindigkeit etc. und anderen Komponenten von anderen Sachen. Daher ist jede Komponente an unterschiedlichen Datensätzen interessiert. Die Gravitationskomponente könnte an Masse und Geschwindigkeit interessiert sein, während die Kollisionskomponente an Begrenzungsboxen und Position interessiert sein könnte.

Bisher dachte ich, ich könnte einen Datenmanager haben, der ein Array pro Attribut speichert. Sagen Sie also, dass Entitäten eines oder mehrere von Gewicht, Position, Geschwindigkeit usw. haben können und dass sie eine eindeutige ID haben. Die Daten in den Datenmanager würden wie folgt dargestellt, wo jede Zahl eine Einheit ID repräsentiert:

weightarray -> [0,1,2,3] 
positionarray -> [0,1,2,3] 
velocityarray -> [0,1,2,3] 

Dieser Ansatz funktioniert gut, wenn alle Einheiten jeweils eines der Attribute haben. Wenn jedoch nur Entität 0 und 2 haben alle Baum Attribute und die anderen sind Einheiten des Typs, der sich nicht bewegt, werden sie nicht Geschwindigkeit haben und die Daten aussehen würde:

weightarray -> [0,1,2,3] 
positionarray -> [0,1,2,3] 
velocityarray -> [0,2]  //either squash it like this 
velocityarray -> [0 ,2 ]  //or leave "empty gaps" to keep alignment 

Plötzlich ist es nicht so einfach um es zu durchlaufen. Eine Komponente, die nur daran interessiert ist, darüber zu iterieren und die Geschwindigkeit zu manipulieren, müsste entweder die leeren Lücken irgendwie überspringen, wenn ich mit der zweiten Annäherung fortfahren würde. Der erste Ansatz, das Array kurz zu halten, würde auch in komplizierteren Situationen nicht gut funktionieren. Angenommen, ich habe eine Entität 0 mit allen drei Attributen, eine andere Entität 1, die nur Gewicht und Position hat, und eine Entität 2, die nur Position und Geschwindigkeit hat. Schließlich gibt es eine letzte Einheit 3, die nur Gewicht hat. Die Arrays würden wie gestaucht aussehen:

weightarray -> [0,1,3] 
positionarray -> [0,1,2] 
velocityarray -> [0,2] 

Der andere Ansatz würde wie so Lücken hinterlassen:

weightarray -> [0,1, ,3] 
positionarray -> [0,1,2, ] 
velocityarray -> [0, ,2, ] 

Beide Situationen nicht trivial sind iterieren, wenn Sie in Iterieren über die Menge von Entitäten nur daran interessiert sind das hat nur ein paar der Attribute. Eine gegebene Komponente X wäre beispielsweise an der Verarbeitung von Entitäten mit Position und Geschwindigkeit interessiert. Wie kann ich iterierbare Array-Zeiger extrahieren, um diese Komponente zu berechnen? Ich würde ihm ein Array geben wollen, in dem die Elemente gerade nebeneinander stehen, aber das scheint unmöglich.

Ich habe über Lösungen nachgedacht, wie ein Bitfeld für jedes Array zu haben, welche Spots gültig sind und welche Lücken sind, oder ein System, das Daten in temporäre Arrays kopiert, die keine Löcher haben und dann an die Komponenten und andere Ideen, aber keine, von denen ich dachte, war elegant und hatte keinen zusätzlichen Aufwand für die Verarbeitung (wie zusätzliche Prüfungen, wenn Daten gültig sind, oder zusätzliches Kopieren von Daten).

Ich frage hier, weil ich hoffe, dass jemand von Ihnen Erfahrung mit etwas Ähnlichem haben oder Ideen oder Gedanken haben könnte, die bei der Verfolgung dieses Problems hilfreich sind. :) Auch wenn diese ganze Idee beschissen und unmöglich ist, richtig zu kommen und du stattdessen eine viel bessere Idee hast, bitte sag es mir. Hoffentlich ist die Frage nicht zu lang oder unübersichtlich.

Danke.

+0

Wählen Sie einen bestimmten Wert wie -1, um eine Lücke darzustellen. – tp1

+0

@ tp1 das funktioniert, wenn Sie einige Werte wie Ints für Gesundheitspunkte zum Beispiel speichern. Wenn Sie jedoch Geschwindigkeits- oder Positionswerte oder andere Werte mit Gleitkommazahlen oder Attributen speichern, die 0 oder negativ sein können, funktioniert diese Methode nicht. – Tobias

+0

@ tp1 - und deshalb haben wir Nullen – Duniyadnd

Antwort

2

Anstatt die Strukturierung Ihrer Daten adressieren, würde Ich mag nur Perspektive bieten, wie ich Sachen wie dies in der Vergangenheit getan haben.

Die Spiel-Engine hat eine Liste der Manager verantwortlich für verschiedene Systeme im Spiel (Inputmanager, PhysicsManager, RenderManager, etc ...).

Die meisten Dinge in der 3D-Welt sind durch eine Objektklasse repräsentiert, und jedes Objekt eine beliebige Anzahl von Komponenten aufweisen kann. Jede Komponente ist für verschiedene Aspekte des Verhaltens des Objekts verantwortlich (RenderComponent, PhysicsComponent, etc ...).

Die Physik Komponente war verantwortlich für die Physik Mesh-Laden, und sie alle notwendigen Eigenschaften wie Masse, Dichte, Massenmittelpunkt, Trägheit Antwortdaten und vieles mehr geben. Diese Komponente speicherte auch Informationen über das physikalische Modell, sobald es in der Welt war, wie eine Position, Rotation, lineare Geschwindigkeit, Winkelgeschwindigkeit und mehr. Der PhysicsManager verfügte über Kenntnisse über jedes physikalische Netz, das von Physikkomponenten geladen worden war. Dies ermöglichte es dem Manager, alle physikalischen Aufgaben zu bewältigen, wie zum Beispiel Kollisionserkennung, Kollisionsmeldungen, Physical Ray Casts. Wenn wir ein spezielles Verhalten wünschen, das nur ein paar Objekte benötigen würden, würden wir eine Komponente dafür erstellen, und diese Komponente würde Daten wie Geschwindigkeit oder Reibung manipulieren, und diese Änderungen würden vom PhysicsManager gesehen und in der Datenbank berücksichtigt werden Physik-Simulation.

Soweit die Datenstruktur geht, können Sie das System habe ich oben erwähnt und strukturieren sie in mehrfacher Hinsicht. Im Allgemeinen werden die Objekte entweder in einem Vektor oder einer Karte gespeichert, und Komponenten befinden sich in einem Vektor oder einer Liste auf dem Objekt. Was physikalische Informationen betrifft, so hat der PhysicsManager eine Liste aller Physikobjekte, die in einem Array/Vektor gespeichert werden können, und die PhysicsComponent hat eine Kopie ihrer Position, Geschwindigkeit und anderer Daten, so dass sie alles mögliche tun kann Diese Daten müssen vom Physiker manipuliert werden. Wenn Sie zum Beispiel die Geschwindigkeit eines Objekts ändern möchten, würden Sie der PhysicsComponent nur sagen, dass es seinen Geschwindigkeitswert ändern und dann den PhysicsManager benachrichtigen würde.

ich mehr über das Thema Objekt/Komponente Motorstruktur sprechen hier: https://gamedev.stackexchange.com/a/23578/12611

3

Die Lösung ist eigentlich die Annahme, dass es Grenzen, wie weit können Sie optimieren.

die Lücke Problem zu lösen, wird nur die folgende verursachen eingeführt werden:

  • If-Anweisungen (Zweige) die Daten Ausnahmen (Einheiten, die Komponente fehlen) zu handhaben.
  • Das Einführen von Löchern bedeutet, dass Sie auch Listen nach dem Zufallsprinzip wiederholen können. Die Stärke von DoD besteht darin, dass alle Daten in der Art und Weise, wie sie verarbeitet werden, dicht gepackt und geordnet sind.

Was Sie tun möchten, können:

verschiedene Listen erstellen für verschiedene Systeme/Fälle optimiert. Jeder Rahmen: kopiert die Eigenschaften von einem System in ein anderes System nur für die Entitäten, die es benötigen (die diese spezifische Komponente haben).

mit der folgenden vereinfachten Listen und deren Attribute:

  • Starrkörper (Kraft, Geschwindigkeit, Transformation)
  • Kollision (BoundingBox, Transformation)
  • drawable (texture_id, shader_id , transform)
  • rigidbody_to_collision (rigidbody_index, collision_index)
  • collision_to_rigidbody (collision_index, rigidbody_index)
  • rigidbody_to_drawable (rigidbody_index, drawable_index)

etc ...

Für die Prozesse/Jobs, die Sie möchten die folgenden:

  • RigidbodyApplyForces (...), Kräfte anwenden (z. Schwerkraft) zu Geschwindigkeiten
  • RigidbodyIntegrate (...), wenden Sie Geschwindigkeiten auf Transformationen an.
  • RigidbodyToCollision (...), Kopieren von Starrkörpertransformationen in Kollisionsumwandlungen nur für Entitäten mit der Kollisionskomponente. Die Liste "rigidbody_to_collision" enthält die Indizes, welche Starrkörper-ID zu welcher Kollisions-ID kopiert werden soll. Dies hält die Kollisionsliste dicht gepackt.
  • RigidbodyToDrawable (...), Kopieren von Starrkörpertransformationen in Zeichnungstransformationen für Entities, die über die Drawkomponente verfügen. Die Liste "rigidbody_to_drawable" enthält die Indizes, welche Starrkörper-ID auf welche ziehbare ID kopiert werden soll. Dies hält die Drawabkl-Liste dicht gepackt.
  • CollisionUpdateBoundingBoxes (...), Bounding-Boxen mit neuen Transformationen aktualisieren.
  • CollisionRecalculateHashgrid (...), aktualisieren Sie Hashgrid mit Bounding-Boxen. Sie können dies über mehrere Frames verteilt ausführen, um die Last zu verteilen.
  • CollisionBroadphaseResolve (...) berechnen mögliche Kollisionen hashgrid usw. verwenden ....
  • CollisionMidphaseResolve (...) berechnen Kollision mit Begrenzungskästen für broadphase etc ....
  • CollisionNarrowphaseResolve (...), berechnen Sie Kollision mit Polygonen aus Midphase etc ....
  • CollisionToRigidbody (...), fügen reaktive Kräfte kollidierender Objekte zu Starrkörperkräfte. Die Liste "collision_to_rigidbody" enthält die Indizes, aus denen die Kollisions-ID der Kraft zu der Starrkörper-ID hinzugefügt werden soll. Sie können auch eine andere Liste namens "reactive_forces_to_be_added" erstellen. Sie können damit die Addition der Kräfte verzögern.
  • RenderDrawable (...), render die Zeichen auf Bildschirm (Renderer ist nur vereinfacht).

Natürlich brauchen Sie viel mehr Prozesse/Jobs. Wahrscheinlich möchten Sie die Zeichnungsobjekte verschließen und sortieren, fügen Sie ein Transformationsdiagrammsystem zwischen der Physik und Zeichnungselementen hinzu (siehe Sony-Präsentation, wie Sie dies tun können) usw. Die Ausführung der Aufträge kann über mehrere Kerne verteilt ausgeführt werden. Dies ist sehr einfach, wenn alles nur eine Liste ist, da sie in mehrere Listen unterteilt werden können.

Wenn eine Entität erstellt wird, werden die Komponentendaten auch zusammen erstellt und in derselben Reihenfolge gespeichert. Das heißt, die Listen bleiben meist in der gleichen Reihenfolge.

Bei den Prozessen "Objekt in Objekt kopieren". Wenn das Überspringen von Löchern wirklich ein Problem wird, können Sie immer einen "Reorder Objects" -Prozess erstellen, der am Ende jedes Frames, verteilt über mehrere Frames, Objekte in der optimalsten Reihenfolge neu anordnen wird. Die Reihenfolge, die das geringste Überspringen von Löchern erfordert. Das Überspringen von Löchern ist der Preis, den man zahlen muss, um alle Listen so dicht wie möglich zu halten und es auch so zu bestellen, wie es verarbeitet wird.

10

Gute Frage. Soweit ich das beurteilen kann, gibt es keine einfache Lösung für dieses Problem. Es gibt mehrere Lösungen (von denen Sie einige bereits erwähnt haben), aber ich sehe keine sofortige Lösung.

Schauen wir uns zuerst das Ziel an. Das Ziel ist nicht, alle Daten in lineare Arrays zu bringen, sondern nur die Mittel, um das Ziel zu erreichen. Ziel ist die Optimierung der Performance durch Minimierung von Cache-Misses. Das ist alles. Wenn Sie OOP-Objekte verwenden, sind Ihre Entities-Daten von Daten umgeben, die Sie nicht unbedingt benötigen. Wenn Ihre Architektur eine Cache-Zeilengröße von 64 Bytes hat und Sie nur Gewicht (float), Position (vec3) und Geschwindigkeit (vec3) benötigen, verwenden Sie 28 Bytes, aber die restlichen 36 Bytes werden trotzdem geladen. Noch schlimmer ist, wenn diese 3 Werte nicht nebeneinander im Speicher liegen oder Ihre Datenstruktur eine Cache-Zeilengrenze überlappt, laden Sie mehrere Cache-Zeilen für nur 28 Bytes der tatsächlich verwendeten Daten.

Jetzt ist das nicht so schlimm, wenn Sie dies ein paar Mal tun. Selbst wenn Sie es hundert Mal tun, werden Sie es kaum bemerken. Wenn Sie dies jedoch tausend Mal pro Sekunde tun, kann dies zu einem Problem werden. Geben Sie also DOD ein, in dem Sie die Cache-Auslastung optimieren, indem Sie in der Regel lineare Arrays für jede Variable in Situationen erstellen, in denen lineare Zugriffsmuster vorhanden sind. In Ihrem Fall Arrays für Gewicht, Position, Geschwindigkeit. Wenn Sie die Position einer Entität laden, laden Sie erneut 64 Byte Daten. Da Ihre Positionsdaten jedoch in einem Array nebeneinander liegen, laden Sie nicht 1 Positionswert, sondern laden die Daten für 5 benachbarte Entitäten. Die nächste Iteration Ihrer Update-Schleife benötigt wahrscheinlich den nächsten Positionswert, der bereits in den Cache geladen wurde, usw., bis nur bei der 6. Entität neue Daten aus dem Hauptspeicher geladen werden müssen.

Das Ziel von DOD ist also nicht die Verwendung linearer Arrays, es maximiert die Cache-Auslastung, indem Daten platziert werden, auf die (ungefähr) gleichzeitig im Speicher zugegriffen wird. Wenn Sie fast immer auf 3 Variablen gleichzeitig zugreifen, müssen Sie nicht 3 Arrays für jede Variable erstellen, Sie können ebenso einfach eine Struktur erstellen, die nur diese 3 Werte enthält, und ein Array dieser Strukturen erstellen. Die beste Lösung hängt immer davon ab, wie Sie die Daten verwenden. Wenn Ihre Zugriffsmuster linear sind, Sie aber nicht immer alle Variablen verwenden, wählen Sie separate lineare Arrays. Wenn Ihre Zugriffsmuster unregelmäßiger sind, Sie aber immer alle Variablen gleichzeitig verwenden, setzen Sie sie zusammen in eine Struktur und erstellen Sie ein Array dieser Strukturen.

So gibt es Ihre Antwort in Kurzform: Es hängt alles von Ihrer Datennutzung ab. Aus diesem Grund kann ich Ihre Frage nicht direkt beantworten.Ich kann Ihnen einige Ideen geben, wie Sie mit Ihren Daten umgehen können, und Sie können selbst entscheiden, welches die nützlichsten (wenn es eines von ihnen gibt) in Ihrer Situation ist, oder vielleicht können Sie sie anpassen/mischen.

Sie können die am häufigsten verwendeten Daten in einem kontinuierlichen Array speichern. Zum Beispiel wird die Position oft von vielen verschiedenen Komponenten verwendet, daher ist sie ein idealer Kandidat für ein kontinuierliches Array. Gewicht wird dagegen nur von der Schwerkraftkomponente genutzt, so dass hier Lücken entstehen können. Sie haben den am häufigsten verwendeten Fall optimiert und erhalten weniger Leistung für weniger häufig verwendete Daten. Dennoch bin ich aus mehreren Gründen kein großer Fan dieser Lösung: Es ist immer noch ineffizient, Sie werden viel zu viele leere Daten laden, je niedriger das Verhältnis von # spezifischen Komponenten/# gesamten Einheiten ist, desto schlechter wird es. Wenn nur eine von acht Entitäten Schwerkraftkomponenten aufweist und diese Entitäten gleichmäßig über die Arrays verteilt sind, erhalten Sie für jede Aktualisierung immer noch einen Cache-Fehltreffer. Es nimmt auch an, dass alle Entitäten eine Position haben (oder was auch immer die allgemeine Variable ist), es ist schwer Entitäten hinzuzufügen oder zu entfernen, es ist unflexibel und einfach hässlich (imho sowieso). Es kann jedoch die einfachste Lösung sein.

Eine andere Möglichkeit, dies zu lösen, ist die Verwendung von Indizes. Jedes Array für eine Komponente wird gepackt, aber es gibt zwei zusätzliche Arrays, eins zum Abrufen der Entitäts-ID aus einem Komponenten-Array-Index und ein zweites zum Abrufen des Komponenten-Array-Index aus einer Entitäts-ID. Nehmen wir an, die Position wird von allen Entitäten geteilt, während Gewicht und Geschwindigkeit nur von Gravitation verwendet werden. Sie können jetzt über die gepackten Gewichtungs- und Geschwindigkeitsfelder iterieren und um die entsprechende Position zu erhalten/setzen, können Sie den gravityIndex -> entityID Wert erhalten, gehen Sie zur Position-Komponente, verwenden Sie die entityID -> positionIndex, um den korrekten Index zu erhalten Array positionieren. Der Vorteil ist, dass Ihre Zugriffe auf Gewicht und Geschwindigkeit nicht mehr zu Cache-Fehlern führen, aber Sie erhalten immer noch Cache-Fehler für die Positionen, wenn das Verhältnis zwischen # Schwerkraftkomponenten/# Positionskomponenten niedrig ist. Sie erhalten auch zusätzliche 2 Array-Lookups, aber ein 16-Bit unsigned int-Index sollte in den meisten Fällen ausreichen, damit diese Arrays gut in den Cache passen, was bedeutet, dass dies in den meisten Fällen keine sehr teure Operation ist. Trotzdem Profil Profil Profil, um sicher zu sein!

Eine dritte Option ist die Datenduplizierung. Nun, ich bin mir ziemlich sicher, dass das im Falle Ihrer Gravity-Komponente nicht die Mühe wert ist, ich denke, es ist in rechenintensiven Situationen interessanter, aber nehmen wir es als Beispiel. In diesem Fall verfügt die Gravity-Komponente über 3 gepackte Arrays für Gewicht, Geschwindigkeit und Position. Es hat auch eine ähnliche Indextabelle wie in der zweiten Option. Wenn Sie das Gravity-Komponentenupdate starten, aktualisieren Sie zuerst das Positionsarray vom ursprünglichen Positionsarray in der Positionskomponente, indem Sie die Indextabelle wie in Beispiel 2 verwenden. Jetzt haben Sie 3 gepackte Arrays, mit denen Sie linear mit maximalem Cache rechnen können Nutzung. Wenn Sie fertig sind, kopieren Sie die Position mithilfe der Indextabelle zurück in die ursprüngliche Positionskomponente. Nun, dies ist nicht schneller (in der Tat wahrscheinlich langsamer) als die zweite Option, wenn Sie es für etwas wie Schwerkraft verwenden, weil Sie Position nur einmal lesen und schreiben. Angenommen, Sie haben eine Komponente, in der Entitäten miteinander interagieren, wobei jeder Aktualisierungsdurchlauf mehrere Lese- und Schreibvorgänge erfordert, ist dies möglicherweise schneller. Alles hängt jedoch von Zugriffsmustern ab.

Die letzte Option, die ich erwähnen werde, ist ein auf Änderungen basierendes System. Sie können dies leicht in etwas wie ein Messaging-System anpassen. In diesem Fall aktualisieren Sie nur Daten, die geändert wurden. In Ihrer Schwerkraft-Komponente liegen die meisten Objekte auf dem Boden ohne Veränderung, aber einige fallen. Die Gravitätskomponente hat Arrays für Position, Geschwindigkeit und Gewicht gepackt. Wenn die Position während Ihrer Update-Schleife aktualisiert wird, fügen Sie die Entitäts-ID und die neue Position zu einer Änderungsliste hinzu. Wenn Sie fertig sind, senden Sie diese Änderungen an jede andere Komponente, die einen Positionswert enthält. Dasselbe Prinzip, wenn eine andere Komponente (zum Beispiel die Spielersteuerungskomponente) die Position ändert, sendet sie die neuen Positionen von geänderten Entitäten, die Schwerkraftkomponente kann dies hören und nur diese Positionen in ihrem Positionsarray aktualisieren. Sie werden viele Daten wie im vorherigen Beispiel duplizieren, aber anstatt alle Daten bei jedem Aktualisierungszyklus erneut zu lesen, aktualisieren Sie die Daten nur, wenn sie sich ändern. Sehr nützlich in Situationen, in denen kleine Datenmengen tatsächlich jeden Frame ändern, aber bei großen Datenmengen unwirksam werden.

So gibt es keine Wunderwaffe. Es gibt viele Möglichkeiten. Die beste Lösung hängt vollständig von Ihrer Situation, Ihren Daten und der Art ab, wie Sie diese Daten verarbeiten. Vielleicht ist keines der Beispiele, die ich gegeben habe, das Richtige für dich, vielleicht sind sie alle. Nicht jede Komponente muss auf die gleiche Weise funktionieren, manche verwenden das Change/Meldesystem, während andere die Option "Indizes" verwenden. Denken Sie daran, dass viele DOD-Leistungsrichtlinien zwar großartig sind, wenn Sie die Leistung benötigen, aber nur in bestimmten Situationen nützlich ist. Bei DOD geht es nicht darum, immer Arrays zu verwenden, es geht nicht immer darum, die Cache-Auslastung zu maximieren, sondern nur dort, wo es wirklich zählt. Profil Profil Profil. Kenne deine Daten. Kennen Sie Ihre Datenzugriffsmuster. Kennen Sie Ihre (Cache-) Architektur. Wenn Sie all das tun, werden Lösungen offensichtlich, wenn Sie darüber nachdenken :)

Hoffe, das hilft!

2

Ich verlasse mich auf zwei Strukturen für dieses Problem. Hoffentlich werden die Diagramme sind klar genug (ich eine weitere Erklärung hinzufügen können sonst):

enter image description here

Die spärliche Array ermöglicht es uns, Daten parallel zu einem anderen zu verbinden, ohne zu viel Speicher aus nicht verwendeten Indizes hogging und ohne Verschlechterung räumliche Lokalität viel (da jeder Block eine Reihe von Elementen zusammenhängend speichert).

Sie könnten eine kleinere Blockgröße als 512 verwenden, da dies für einen bestimmten Komponententyp ziemlich groß sein kann. Etwas wie 32 könnte sinnvoll sein, oder Sie können die Blockgröße im laufenden Betrieb basierend auf der sizeof(ComponentType) anpassen. Mit diesem können Sie Ihre Komponenten einfach parallel zu Ihren Entitäten zuordnen, ohne den Speicherverbrauch zu sehr aus nicht belegten Räumen zu erhöhen, obwohl ich es nicht so benutze (ich verwende die vertikale Art der Repräsentation, aber mein System hat viele Komponententypen - - Wenn Sie nur ein paar haben, können Sie einfach alles parallel speichern.

Wir benötigen jedoch eine andere Struktur beim Iterieren, um herauszufinden, welche Indizes besetzt sind. Es verwende ich eine hierarchische bitset (I Liebe und diese Datenstruktur eine Menge, aber ich weiß nicht, ob es für sie einen offiziellen Namen ist, da es Ich habe gerade etwas, ohne zu wissen, was es heißt):

enter image description here

Dies ermöglicht den Zugriff auf die Elemente, auf die zugegriffen wird, immer in sequentieller Reihenfolge (ähnlich wie bei sortierten Indizes). Diese Struktur ist extrem schnell für sequentielle Iterationen, da das Testen eines einzelnen Bits anzeigen kann, dass eine Million zusammenhängender Elemente verarbeitet werden kann, ohne eine Million Bits zu prüfen oder eine Million Indizes in dem Container speichern und darauf zugreifen zu müssen.

Als Bonus können Sie auch Schnittpunkte in einem Best-Case-Szenario von Log(N)/Log(64) setzen (z. B. den Schnittpunkt zwischen zwei dichten Indexsätzen finden, die jeweils eine Million Elemente in 3-4 Iterationen enthalten). wenn Sie schnelle Schnittpunkte benötigen, die für ein ECS oft sehr praktisch sind.

Diese beiden Strukturen sind die Backbones meiner ECS-Engine. Sie sind ziemlich schnell, da ich 2 Millionen Partikel-Entitäten verarbeiten kann (indem ich auf zwei verschiedene Komponenten zugreife), ohne die Abfrage für die Entitäten mit beiden Komponenten bei nur knapp 30 FPS zu cachen. Natürlich ist das eine miserable Bildrate für nur 2 Millionen Partikel, aber das ist, wenn sie als ganze Entitäten mit jeweils zwei Komponenten (Bewegung und Sprite) dargestellt werden, wobei das Partikelsystem die Abfrage jeden einzelnen Frame ohne Cache durchführt tun (besser, wie eine ParticleEmitter Komponente zu verwenden, die viele Partikel für eine gegebene Entität darstellt, anstatt einen Partikel zu einer separaten Einheit zu machen).

Hoffentlich sind die Diagramme klar genug, um Ihre eigene Version zu implementieren, wenn Sie interessiert sind.

Verwandte Themen