In der neuen Version von
Unity im Jahr 2018 haben sie schließlich offiziell das neue
Entity-Komponentensystem, kurz
ECS , hinzugefügt, mit dem Sie nur mit ihren Daten arbeiten können, anstatt wie üblich mit den Komponenten des Objekts.
Ein zusätzliches Task-System bietet Ihnen die Möglichkeit, parallele Rechenleistung zu verwenden, um die Leistung Ihres Codes zu verbessern.
Zusammen bieten diese beiden neuen Systeme (
ECS und
Job System ) eine neue Ebene der Datenverarbeitung.
Insbesondere werde ich in diesem Artikel nicht das gesamte
ECS- System analysieren, das derzeit als separat herunterladbarer Satz von Tools in
Einheit verfügbar ist, sondern nur das Task-System und dessen Verwendung außerhalb des
ECS- Pakets berücksichtigen.
Neues System
Ursprünglich hätte
Unity zuvor Multithread-Computing verwenden können, aber all dies musste vom Entwickler selbst erstellt werden, um die Probleme selbst zu lösen und die Fallstricke zu umgehen. Und wenn es früher notwendig war, direkt mit Dingen wie dem Erstellen von Threads, dem Schließen von Threads, Pools und der Synchronisierung zu arbeiten, fiel diese ganze Arbeit jetzt auf die Schultern der Engine, und der Entwickler selbst musste nur noch Aufgaben erstellen und ausführen.
Die Aufgaben
Um Berechnungen im neuen System durchzuführen, müssen Aufgaben verwendet werden, bei denen es sich um Objekte handelt, die aus Methoden und Daten für die Berechnung bestehen.
Wie alle anderen Daten im
ECS- System werden auch Aufgaben im
Jobsystem als Strukturen dargestellt, die eine von drei Schnittstellen erben.
Ijob
Die einfachste Task-Schnittstelle mit einer
Execute- Methode, die nichts in Form von Parametern akzeptiert und nichts zurückgibt.
Die Aufgabe selbst sieht folgendermaßen aus:
Ijobpublic struct JobStruct : IJob { public void Execute() {} }
In der
Execute- Methode können Sie die erforderlichen Berechnungen durchführen.
IJobParallelFor
Eine weitere Schnittstelle mit derselben
Execute- Methode, die wiederum bereits den numerischen Parameterindex akzeptiert.
IJobParallelFor public struct JobStruct : IJobParallelFor { public void Execute(int index) {} }
Diese
IJobParallelFor- Schnittstelle bietet im Gegensatz zur
IJob- Schnittstelle die Möglichkeit, eine Aufgabe mehrmals auszuführen und diese Ausführung nicht nur auszuführen, sondern in Blöcke
aufzuteilen , die zwischen Threads verteilt werden.
Unverständlich? Mach dir darüber keine Sorgen, ich werde dir mehr erzählen.IJobParallelForTransform
Und die letzte spezielle Schnittstelle, die, wie der Name schon sagt, für diese Transformationen des Objekts ausgelegt ist. Es enthält auch die
Execute- Methode mit dem numerischen Parameterindex und dem
TransformAccess- Parameter, in der sich Position, Größe und Drehung der Transformation befinden.
IJobParallelForTransform public struct JobStruct : IJobParallelForTransform { public void Execute(int index, TransformAccess transform) {} }
Aufgrund der Tatsache, dass Sie nicht direkt in der Aufgabe mit
Unity- Objekten arbeiten können, kann diese Schnittstelle Transformationsdaten nur als separate
TransformAccess- Struktur verarbeiten.
Wenn Sie fertig sind und wissen, wie Aufgabenstrukturen erstellt werden, können Sie mit dem Üben fortfahren.
Aufgabenerfüllung
Lassen Sie uns eine einfache Aufgabe erstellen, die von der
IJob- Oberfläche
geerbt wurde , und sie abschließen. Dazu benötigen wir ein einfaches
MonoBehaviour- Skript und die Struktur der Aufgabe selbst.
Testjob public class TestJob : MonoBehaviour { void Start() {} }
Legen Sie dieses Skript nun auf einem Objekt in der Szene ab. Im selben Skript (
TestJob ) schreiben wir die Struktur der Aufgabe und vergessen nicht, die erforderlichen Bibliotheken zu importieren.
Simplejob using Unity.Jobs; public struct SimpleJob : IJob { public void Execute() { Debug.Log("Hello parallel world!"); } }
Drucken Sie in der
Execute- Methode beispielsweise eine einfache Zeile an die Konsole.
Fahren wir nun mit der
Start- Methode des
TestJob- Skripts fort, in der wir eine Instanz der Aufgabe erstellen und dann ausführen.
Testjob public class TestJob : MonoBehaviour { void Start() { SimpleJob job = new SimpleJob(); job.Schedule().Complete(); } }
Wenn Sie alles wie im Beispiel gemacht haben, erhalten Sie nach dem Start des Spiels eine einfache Nachricht an die Konsole wie auf dem Bild.

Was hier passiert: Nach dem Aufrufen der
Schedule- Methode platziert der Scheduler die Aufgabe im Handle und kann nun durch Aufrufen der
Complete- Methode
abgeschlossen werden.
Dies war ein Beispiel für eine Aufgabe, bei der einfach Text auf die Konsole gedruckt wurde. Damit eine Aufgabe parallele Berechnungen durchführen kann, muss sie mit Daten gefüllt werden.
Daten in der Aufgabe
Wie im
ECS- System gibt es bei Aufgaben keinen Zugriff auf
Unity- Objekte. Sie können das
GameObject nicht in die Aufgabe aufnehmen und dort seinen Namen ändern. Sie können lediglich einige separate Objektparameter auf die Aufgabe übertragen, diese Parameter ändern und diese Änderungen nach Abschluss der Aufgabe wieder auf das Objekt anwenden.
Die Daten in der Aufgabe selbst unterliegen mehreren Einschränkungen: Erstens müssen es Strukturen sein, und zweitens dürfen es
keine konvertierbaren Datentypen sein,
dh Sie können nicht denselben
Booleschen Wert oder dieselbe
Zeichenfolge an die Aufgabe übergeben.
Simplejob public struct SimpleJob : IJob { public float a, b; public void Execute() { float result = a + b; Debug.Log(result); } }
Und die Hauptbedingung: Auf Daten, die nicht in einem Container enthalten sind, kann nur innerhalb der Aufgabe zugegriffen werden!
Container
Bei der Arbeit mit Multithread-Computing müssen Daten zwischen Threads ausgetauscht werden. Um Daten in sie übertragen und im Task-System zurücklesen zu können, gibt es für diese Zwecke Container. Diese Container werden in Form gewöhnlicher Strukturen dargestellt, und ich arbeite nach dem Prinzip einer Brücke, über die Elementardaten zwischen Flüssen synchronisiert werden.
Es gibt verschiedene Arten von Behältern:
NativeArray . Der einfachste und am häufigsten verwendete Containertyp wird als einfaches Array mit fester Größe dargestellt.
NativeSlice . Ein weiterer Container - ein Array, wie aus der Übersetzung hervorgeht, dient dazu, das NativeArray in Stücke zu schneiden.
Dies sind die beiden Hauptcontainer, die ohne Anschluss eines
ECS- Systems verfügbar sind. In einer fortgeschritteneren Version gibt es mehrere weitere Arten von Containern.
NativeList . Es ist eine regelmäßige Liste von Daten.
NativeHashMap . Ein Analogon eines Wörterbuchs mit einem Schlüssel und einem Wert.
NativeMultiHashMap .
Dieselbe NativeHashMap mit nur wenigen Werten unter einem Schlüssel.
NativeQueue Liste der Datenwarteschlangen.
Da wir ohne Anschluss eines
ECS- Systems arbeiten, stehen
uns nur
NativeArray und
NativeSlice zur Verfügung.
Bevor Sie mit dem praktischen Teil fortfahren, müssen Sie den wichtigsten Punkt analysieren - die Erstellung von Instanzen.
Erstellen Sie Container
Wie bereits erwähnt, stellen diese Container eine Brücke dar, über die Daten zwischen Threads synchronisiert werden. Das Task-System öffnet diese Brücke vor Arbeitsbeginn und schließt sie nach Abschluss. Der Öffnungsprozess wird als "
Zuweisung " (
Zuweisung ) oder
"Zuweisung von Speicher" bezeichnet , der Schließvorgang als "
Freigabe von Ressourcen " (
Entsorgen ).
Die Zuordnung bestimmt, wie lange die Aufgabe die Daten im Container verwenden kann - mit anderen Worten, wie lange die Brücke geöffnet sein wird.
Um diese beiden Prozesse besser zu verstehen, schauen wir uns das folgende Bild an.

Der untere Teil zeigt den Lebenszyklus des Hauptthreads (
Hauptthread ), der in der Anzahl der Frames berechnet wird. Im ersten Frame erstellen wir einen weiteren parallelen Thread (
Neuer Thread), der für eine bestimmte Anzahl von Frames vorhanden ist, und schließen dann sicher.
Im selben
neuen Thread kommt die Aufgabe mit dem Container an.
Schauen Sie sich jetzt den oberen Rand des Bildes an.

Der weiße Balken
Allocation zeigt die Lebensdauer des Containers an. Im ersten Frame wird der Container
zugewiesen - die Brücke wird geöffnet, bis zu diesem Zeitpunkt der Container nicht vorhanden war. Nachdem alle Berechnungen in der Aufgabe abgeschlossen wurden, wird der Container aus dem Speicher freigegeben und im 9. Frame wird die Brücke geschlossen.
Auch auf diesem Streifen (
Zuordnung ) befinden sich
Zeitsegmente (
Temp ,
TempJob und
Presistent ). Jedes dieser Segmente zeigt die geschätzte Lebensdauer des Containers.
Warum werden diese Segmente benötigt? Tatsache ist, dass die Ausführung einer Aufgabe nach Dauer unterschiedlich sein kann, wir können sie direkt in derselben Methode ausführen, in der wir sie erstellt haben, oder wir können die Ausführungszeit der Aufgabe verlängern, wenn sie recht kompliziert ist, und diese Segmente zeigen, wie dringend und wie lange die Aufgabe die Daten verwenden kann im Behälter.
Wenn es immer noch nicht klar ist, werde ich jede Art der Zuordnung anhand eines Beispiels analysieren.Jetzt können wir mit dem praktischen Teil des Erstellens von Containern
fortfahren. Dazu kehren wir zur
Start- Methode des
TestJob- Skripts zurück und erstellen eine neue Instanz des
NativeArray- Containers. Vergessen Sie nicht, die erforderlichen Bibliotheken zu verbinden.
Temp
Testjob using Unity.Jobs; using Unity.Collections; public class TestJob : MonoBehaviour { void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); } }
Um eine neue Containerinstanz zu erstellen, müssen Sie die Größe und den Typ der Zuordnung in ihrem Konstruktor angeben. In diesem Beispiel wird der
Temp- Typ verwendet, da die Aufgabe nur in der
Start- Methode ausgeführt wird.
Initialisieren Sie nun genau dieselbe Array-Variable in der Struktur der
SimpleJob- Task.
Simplejob public struct SimpleJob : IJob { public NativeArray<int> array; public void Execute() {} }
Fertig. Jetzt können Sie die Aufgabe selbst erstellen und eine Array-Instanz an sie übergeben.
Starten Sie void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); SimpleJob job = new SimpleJob(); job.array = array; }
Um die Aufgabe dieses Mal
auszuführen , verwenden wir das
JobHandle- Handle, um sie durch Aufrufen derselben
Schedule- Methode
abzurufen .
Starten Sie void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); SimpleJob job = new SimpleJob(); job.array = array; JobHandle handle = job.Schedule(); }
Jetzt können Sie die
Complete- Methode an ihrem Handle aufrufen und prüfen, ob die Aufgabe abgeschlossen ist, um den Text in der Konsole anzuzeigen.
Starten Sie void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); SimpleJob job = new SimpleJob(); job.array = array; JobHandle handle = job.Schedule(); handle.Complete(); if (handle.IsCompleted) print(" "); }
Wenn Sie die Aufgabe in dieser Form ausführen, wird nach dem Start des Spiels ein fetter roter Fehler angezeigt, der besagt, dass Sie den Array-Container nach Abschluss der Aufgabe nicht aus den Ressourcen freigegeben haben.
So etwas in der Art.

Um dies zu vermeiden, rufen Sie nach Abschluss der Aufgabe die
Dispose- Methode für den Container auf.
Starten Sie void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); SimpleJob job = new SimpleJob(); job.array = array; JobHandle handle = job.Schedule(); handle.Complete(); if (handle.IsCompleted) print("Complete"); array.Dispose(); }
Dann können Sie es sicher neu starten.
Aber die Aufgabe macht nichts! - Fügen Sie dann einige Aktionen hinzu.
Simplejob public struct SimpleJob : IJob { public NativeArray<int> array; public void Execute() { for(int i = 0; i < array.Length; i++) { array[i] = i * i; } } }
Bei der
Execute- Methode multipliziere ich den Index jedes Elements des Arrays mit mir selbst und schreibe ihn zurück in das Array-
Array , um das Ergebnis in der
Start- Methode an die Konsole zu drucken.
Starten Sie void Start() { NativeArray<int> array = new NativeArray<int>(10, Allocator.Temp); SimpleJob job = new SimpleJob(); job.array = array; JobHandle handle = job.Schedule(); handle.Complete(); if (handle.IsCompleted) print(job.array[job.array.Length - 1]); array.Dispose(); }
Was ist das Ergebnis in der Konsole, wenn wir das letzte Element des Arrays im Quadrat drucken?
Auf diese Weise können Sie Container erstellen, in Aufgaben einfügen und Aktionen für sie ausführen.
Dies war ein Beispiel für die Verwendung des
Temp- Zuordnungstyps, bei dem eine Aufgabe innerhalb eines Frames ausgeführt wird. Dieser Typ wird am besten verwendet, wenn Sie schnell Berechnungen durchführen müssen, ohne den Hauptthread zu laden. Sie müssen jedoch vorsichtig sein, wenn die Aufgabe zu kompliziert ist oder wenn viele davon auftreten, kann es zu einem Durchhängen kommen. In diesem Fall ist es besser, den
TempJob- Typ zu verwenden
, den ich später analysieren werde.
Tempjob
In diesem Beispiel werde ich
die Struktur der
SimpleJob- Task leicht
ändern und von einer anderen
IJobParallelFor- Schnittstelle erben.
Simplejob public struct SimpleJob : IJobParallelFor { public NativeArray<Vector2> array; public void Execute(int index) {} }
Da die Aufgabe länger als ein Frame ausgeführt wird, werden die Ergebnisse der Aufgabe in verschiedenen
Awake- und
Start- Methoden ausgeführt und
gesammelt , die in Form einer Coroutine dargestellt werden. Ändern Sie dazu das Erscheinungsbild der
TestJob- Klasse ein
wenig .
Testjob public class TestJob : MonoBehaviour { private NativeArray<Vector2> array; private JobHandle handle; void Awake() {} IEnumerator Start() {} }
In der
Awake- Methode erstellen wir eine Aufgabe und einen Container mit Vektoren und geben in der
Start- Methode die empfangenen Daten aus und geben Ressourcen frei.
Wach auf void Awake() { this.array = new NativeArray<Vector2>(100, Allocator.TempJob); SimpleJob job = new SimpleJob(); job.array = this.array; }
Auch hier wird ein
Array- Container mit der Art der Zuordnung
TempJob erstellt.
Anschließend erstellen wir eine Aufgabe und erhalten ihr Handle, indem wir die
Schedule- Methode mit geringfügigen Änderungen aufrufen.
Wach auf void Awake() { this.array = new NativeArray<Vector2>(100, Allocator.TempJob); SimpleJob job = new SimpleJob(); job.array = this.array; this.handle = job.Schedule(100, 5) }
Der erste Parameter in der
Schedule- Methode gibt an, wie oft die Aufgabe ausgeführt wird. Hier ist dieselbe Zahl wie die Größe des Array-
Arrays .
Der zweite Parameter gibt an, wie viele Blöcke die Aufgabe gemeinsam nutzen sollen.
Welche anderen Blöcke?Früher, um eine Aufgabe abzuschließen, hat ein Thread die
Execute- Methode nur einmal aufgerufen. Jetzt muss diese Methode 100 Mal aufgerufen werden, sodass der Scheduler diese 100-maligen Wiederholungen in Blöcke aufteilt, die er zwischen den Threads verteilt, um keinen separaten Thread zu laden. In diesem Beispiel werden hundert Wiederholungen in 5 Blöcke mit jeweils 20 Wiederholungen unterteilt, dh der Scheduler verteilt diese 5 Blöcke vermutlich auf 5 Threads, wobei jeder Thread die
Execute- Methode 20 Mal
aufruft . In der Praxis ist es natürlich keine Tatsache, dass der Scheduler genau das tut, es hängt alles von der Arbeitslast des Systems ab, sodass möglicherweise alle 100 Wiederholungen in einem Thread stattfinden.
Jetzt können Sie die
Complete- Methode im Task-Handle aufrufen.
Wach auf void Awake() { this.array = new NativeArray<Vector2>(100, Allocator.TempJob); SimpleJob job = new SimpleJob(); job.array = this.array; this.handle = job.Schedule(100, 5); this.handle.Complete(); }
In der
Start- Coroutine überprüfen wir die Ausführung der Aufgabe und bereinigen dann den Container.
Starten Sie IEnumerator Start() { while(this.handle.isCompleted == false){ yield return new WaitForEndOfFrame(); } this.array.Dispose(); }
Fahren wir nun mit den Aktionen in der Aufgabe selbst fort.
Simplejob public struct SimpleJob : IJobParallelFor { public NativeArray<Vector2> array; public void Execute(int index) { float x = index; float y = index; Vector2 vector = new Vector2(x * x, y * y / (y * 2)); this.array[index] = vector; } }
Zeigen Sie nach Abschluss der Aufgabe in der
Start- Methode alle Elemente des Arrays in der Konsole an.
Starten Sie IEnumerator Start() { while(this.handle.IsCompleted == false){ yield return new WaitForEndOfFrame(); } foreach(Vector2 vector in this.array) { print(vector); } this.array.Dispose(); }
Wenn Sie fertig sind, können Sie das Ergebnis anzeigen.
Schauen Sie sich die folgenden Bilder an
, um den Unterschied zwischen
IJob und
IJobParallelFor zu verstehen.
In
IJob können Sie beispielsweise eine einfache
for- Schleife verwenden, um Berechnungen mehrmals durchzuführen. In jedem Fall kann ein Thread die
Execute- Methode jedoch nur einmal für die gesamte Dauer der Aufgabe aufrufen. Auf diese Weise kann eine Person Hunderte derselben Aktionen hintereinander ausführen.
IJobParallelFor bietet nicht nur die
Möglichkeit , eine Aufgabe in einem Thread mehrmals auszuführen, sondern diese Wiederholungen auch auf andere Threads zu verteilen.

Im Allgemeinen ist die Art der Zuordnung
TempJob perfekt für die meisten Aufgaben, die über mehrere Frames ausgeführt werden.
Was aber, wenn Sie Daten auch nach Abschluss einer Aufgabe speichern müssen, was ist, wenn Sie sie nach Erhalt des Ergebnisses nicht sofort zerstören müssen? Hierzu ist es erforderlich, die Art der Zuordnung
Persistent zu verwenden , was die Freigabe von Ressourcen dann „
bei Bedarf!“ Impliziert. .
Hartnäckig
Kehren wir zur
TestJob- Klasse zurück und ändern sie. Jetzt erstellen wir Aufgaben in der
OnEnable- Methode, überprüfen deren Ausführung in der
Update- Methode und bereinigen Ressourcen in der
OnDisable- Methode.
In diesem Beispiel verschieben wir das Objekt in der
Update- Methode. Zur Berechnung der Trajektorie verwenden wir zwei Vektorcontainer -
inputArray, in die wir die aktuelle Position und
outputArray einfügen, von wo aus wir die Ergebnisse erhalten.
Testjob public class TestJob : MonoBehaviour { private NativeArray<Vector2> inputArray; private NativeArray<Vector2> outputArray; private JobHandle handle; void OnEnable() {} void Update() {} void OnDisable() {} }
Wir werden auch
die Struktur der
SimpleJob- Task leicht
ändern, indem
wir sie von der
IJob- Schnittstelle erben, um sie einmal auszuführen.
Simplejob public struct SimpleJob : IJob { public void Execute() {} }
In der Aufgabe selbst werden wir auch zwei Vektorcontainer verraten, einen Positionsvektor und ein numerisches Delta, die das Objekt zum Ziel bewegen.
Simplejob public struct SimpleJob : IJob { [ReadOnly] public NativeArray<Vector2> inputArray; [WriteOnly] public NativeArray<Vector2> outputArray; public Vector2 position; public float delta; public void Execute() {} }
Die Attribute
ReadOnly und
WriteOnly zeigen die
Flussbeschränkungen für die Aktionen an, die den Daten in den Containern zugeordnet sind.
ReadOnly bietet den Stream nur zum Lesen von Daten aus dem Container an. Das
WriteOnly- Attribut hingegen ermöglicht es dem Stream, nur Daten in den Container zu schreiben. Wenn Sie diese beiden Aktionen gleichzeitig mit einem Container ausführen müssen, müssen Sie ihn überhaupt nicht mit einem Attribut markieren.
Fahren wir mit der
OnEnable- Methode der
TestJob- Klasse fort, in der die Container initialisiert werden.
Onenable void OnEnable() { this.inputArray = new NativeArray<Vector2>(1, Allocator.Persistent); this.outputArray = new NativeArray<Vector2>(1, Allocator.Persistent); }
Die Abmessungen der Container sind einfach, da Parameter nur einmal gesendet und empfangen werden müssen. Die Art der Zuordnung ist
dauerhaft .
Bei der
OnDisable- Methode geben
wir die Ressourcen der Container frei.
Ondisable void OnDisable() { this.inputArray.Dispose(); this.outputArray.Dispose(); }
Erstellen wir eine separate
CreateJob- Methode, in der wir eine Aufgabe mit ihrem Handle erstellen und dort mit Daten füllen.
CreateJob void CreateJob() { SimpleJob job = new SimpleJob(); job.delta = Time.deltaTime; Vector2 position = this.transform.position; job.position = position; Vector2 newPosition = position + Vector2.right; this.inputArray[0] = newPosition; job.inputArray = this.inputArray; job.outputArray = this.outputArray; this.handle = job.Schedule(); this.handle.Complete(); }
Eigentlich wird inputArray hier nicht wirklich benötigt, da es möglich ist, einen Richtungsvektor nur auf die Aufgabe zu übertragen, aber ich denke, es ist besser zu verstehen, warum diese ReadOnly- und WriteOnly- Attribute überhaupt benötigt werden.In der
Update- Methode prüfen wir, ob die Aufgabe abgeschlossen ist. Anschließend wenden wir das erhaltene Ergebnis auf die Objekttransformation an und führen es erneut aus.
Update void Update() { if (this.handle.IsCompleted) { Vector2 newPosition = this.outputArray[0]; this.transform.position = newPosition; CreateJob(); } }
Bevor wir beginnen, werden wir die
OnEnable- Methode leicht
anpassen , sodass die Aufgabe unmittelbar nach der Initialisierung der Container erstellt wird.
Onenable void OnEnable() { this.inputArray = new NativeArray<Vector2>(1, Allocator.Persistent); this.outputArray = new NativeArray<Vector2>(1, Allocator.Persistent); CreateJob(); }
Fertig, jetzt können Sie zur Aufgabe selbst gehen und die erforderlichen Berechnungen in der
Execute- Methode durchführen.
Ausführen public void Execute() { Vector2 newPosition = this.inputArray[0]; newPosition = Vector2.Lerp(this.position, newPosition, this.delta); this.outputArray[0] = newPosition; }
Um das Ergebnis der Arbeit zu sehen, können Sie das
TestJob- Skript auf ein Objekt werfen und das Spiel ausführen.
Zum Beispiel verschiebt sich mein Sprite nur allmählich nach rechts.
Im Allgemeinen
eignet sich die Art der Zuordnung
Persistent hervorragend für wiederverwendbare Container, die nicht jedes Mal zerstört und neu erstellt werden müssen.
Also, welche Art zu verwenden !?Der
Temp- Typ eignet sich am besten für die schnelle Durchführung von Berechnungen. Wenn die Aufgabe jedoch zu komplex und zu groß ist, kann es zu einem Durchhang kommen.
Der
TempJob- Typ
eignet sich hervorragend für die Arbeit mit
Unity- Objekten, sodass Sie die Parameter von Objekten ändern und sie beispielsweise im nächsten Frame anwenden können.
Der Typ
Persistent kann verwendet werden, wenn die Geschwindigkeit für Sie nicht wichtig ist, Sie jedoch nur ständig Daten nebenbei berechnen müssen, z. B. Daten über ein Netzwerk verarbeiten oder die Arbeit einer KI.
Ungültig und keineEs gibt zwei weitere Arten der Zuweisung: Ungültig und Keine . Sie werden jedoch häufiger zum Debuggen benötigt und nehmen nicht an der Arbeit teil.
Jobhandle
Unabhängig davon lohnt es sich, die Funktionen des Aufgabenhandles zu analysieren, da dieses kleine Handle nicht nur den Prozess der Aufgabenausführung überprüft, sondern auch ganze Netzwerke von Aufgaben durch Abhängigkeiten erstellen kann (obwohl ich sie lieber eher als Warteschlangen bezeichne).
Wenn Sie beispielsweise zwei Aufgaben in einer bestimmten Reihenfolge ausführen müssen, müssen Sie dazu nur das Handle einer Aufgabe an das Handle einer anderen anhängen.
Es sieht ungefähr so aus.

Jedes einzelne Handle enthält zunächst eine eigene Aufgabe. In Kombination erhalten wir jedoch ein neues Handle mit zwei Aufgaben.
Starten Sie void Start() { Job jobA = new Job(); JobHandle handleA = jobA.Schedule(); Job jobB = new Job(); JobHandle handleB = jobB.Schedule(); JobHandle result = JobHandle.CombineDependecies(handleA, handleB); result.Complete(); }
Oder so.
Starten Sie void Start() { JobHandle handle; for(int i = 0; i < 10; i++) { Job job = new Job(); handle = job.Schedule(handle); } handle.Complete(); }
Die Ausführungssequenz wird gespeichert und der Scheduler startet die nächste Aufgabe erst, wenn er von der vorherigen überzeugt ist. Beachten Sie jedoch, dass die
Handle- Eigenschaft IsCompleted auf den Abschluss aller darin enthaltenen Aufgaben wartet.
Fazit
Container
- Vergessen Sie beim Arbeiten mit Daten in Containern nicht, dass es sich um Strukturen handelt. Wenn Sie also Daten im Container überschreiben, werden diese nicht geändert, sondern erneut erstellt.
- Was passiert, wenn Sie die Art der Zuordnung Temp festlegen und die Ressourcen nach Abschluss der Aufgabe nicht löschen? Der Fehler.
- Kann ich meine eigenen Container erstellen? Es ist möglich, dass die Unites den Prozess der Erstellung benutzerdefinierter Container hier ausführlich beschrieben haben, aber es ist besser, ein paar Mal darüber nachzudenken: Lohnt es sich, vielleicht gibt es genug normale Container!
Sicherheit!
Statische Daten.Versuchen Sie nicht, statische Daten in einer Aufgabe zu verwenden ( zufällig und andere). Jeder Zugriff auf statische Daten verletzt die Sicherheit des Systems. Momentan können Sie auf statische Daten zugreifen, aber nur, wenn Sie sicher sind, dass sie sich während der Arbeit nicht ändern - das heißt, sie sind vollständig statisch und schreibgeschützt.Wann soll das Task-System verwendet werden?Alle diese Beispiele, die hier im Artikel aufgeführt sind, sind nur bedingt und zeigen, wie Sie mit diesem System arbeiten und nicht wann Sie es verwenden. Das Task-System kann ohne ECS verwendet werden .Sie müssen verstehen, dass das System auch bei der Arbeit Ressourcen verbraucht und dass es aus jedem Grund einfach sinnlos ist, sofort Aufgaben zu schreiben und Containerhaufen zu erstellen - alles wird noch schlimmer. Zum Beispiel ist die Neuberechnung eines Arrays mit einer Größe von 10 Tausend Elementen nicht korrekt. Es dauert länger, bis der Scheduler funktioniert. Wenn Sie jedoch alle Polygone eines riesigen Terrans neu berechnen oder sogar generieren, ist dies die richtige Lösung. Sie können den Terran in Aufgaben aufteilen und jedes in einem separaten Stream verarbeiten.Wenn Sie ständig in komplexe Berechnungen in Projekten involviert sind und ständig nach neuen Möglichkeiten suchen, um diesen Prozess weniger ressourcenintensiv zu gestalten, dann ist Job System genau das Richtige für Sie . ,
ECS .
WebGL ,
Job System , , .