Ein expression in Verse kann entweder unmittelbar oder asynchron sein. Dies beschreibt die Zeit, die ein Ausdruck für evaluate relativ zur simulation updates benötigen kann.
Stelle dir eine Simulationsaktualisierung als den Zeitpunkt vor, an dem ein neuer frame angezeigt wird.
Es gibt Fälle, in denen mehrere Simulationsaktualisierungen vor einem neuen Frame stattfinden können, beispielsweise wenn ein Online-Spiel nicht mehr mit dem Server synchronisiert wird.
| unmittelbar | asynchron |
|---|---|
Ein unmittelbarer Ausdruck wird ohne Verzögerung ausgewertet, was bedeutet, dass die Auswertung innerhalb der aktuellen Simulationsaktualisierung abgeschlossen wird. | Die Auswertung eines asynchronen Ausdrucks kann Zeit in Anspruch nehmen, muss es aber nicht. Ein asynchroner Ausdruck kann in der aktuellen Simulationsaktualisierung oder in einer späteren abgeschlossen werden oder auch nicht. |
Asynchrone Kontexte
Asynchrone Ausdrücke können in jedem Verse-Code verwendet werden, der einen async context hat.
Ein asynchroner Kontext ist der Körper einer Funktion mit dem Suspends-Effekt-Bezeichner. Der Effekt suspends zeigt an, dass asynchrone Funktionen die Kontrolle an verschiedenen Punkten über mehrere simulation-Aktualisierungen hinweg aussetzen und kooperativ an andere gleichzeitige Ausdrücke übertragen können, bevor sie abgeschlossen werden.
Die Funktion OnBegin() in einem Verse-Gerät ist eine gängige asynchrone Funktion, die als Ausgangspunkt für asynchronen Code verwendet wird.
Eine asynchrone Funktion aufzurufen, hat die gleiche syntax wie der Aufruf einer unmittelbaren Funktion:
OnBegin<override>()<suspends> : void =
HideAllPlatforms()
HideAllPlatforms()<suspends> : void =
for (Platform : Platforms):
Platform.Hide()
Sleep(Delay)Wie jeder andere Ausdruck kann ein asynchroner Ausdruck ein results haben. Das Ergebnis eines asynchronen Ausdrucks ist erst verfügbar, wenn er abgeschlossen ist.
# Npc is undefined until it is bound after MoveToNearestNPC() completes which may be several frames into the future
Npc := Player.MoveToNearestNPC()
#Only called after MoveToNearestNPC() completes
Print("Moved to {Npc}")Jeder Codeblock, der sich in einem asynchronen Kontext (innerhalb des Body einer asynchronen Funktion) befindet, kann eine beliebige Mischung aus unmittelbaren und asynchronen Ausdrücken enthalten.
Wenn irgendwelche Ausdrücke in einem Codeblock asynchron sind, wird der gesamte Codeblock als asynchron betrachtet.
Wenn alle Ausdrücke in einem Codeblock unmittelbar sind, wird der gesamte Codeblock als unmittelbar betrachtet.
Alle Ausdrücke im folgenden Beispiel sind asynchrone Ausdrücke, sodass der gesamte Codeblock asynchron ist:
Sleep(2.0) # waits 2 seconds
Boss.TauntEmote() # waits until TauntEmote() completes
Player.MoveToNearestNPC() # waits until MoveToNearestNPC() completesAusdrücke im folgenden Beispiel sind unmittelbare Ausdrücke, sodass der gesamte Codeblock unmittelbar ist:
Print("Reset after explosion")
Platform.Show()
set SecondsUntilExplosion = 12.0Die Ausdrücke im folgenden Beispiel sind eine Mischung aus asynchronen und sofortigen Ausdrücken, daher ist der gesamte Codeblock asynchron:
Print("Started")
var Seconds := 1.0
Sleep(Seconds)
Print("Waited {Second} seconds")
set Second += 1.0
Sleep(Seconds)
Print("Waited {Second} seconds")
set Second += 1.0
Unmittelbare Ausdrücke bleiben von alleine zusammen. Alle benachbarten unmittelbaren (nicht asynchronen) Ausdrücke gelten als atomic- ihr Code wird garantiert ohne Unterbrechung innerhalb derselben Aktualisierung und ohne preemption oder context switching ausgeführt. Es ist so, als hätten solche Codes einem automatischen mutual-exclusion primitive umwickelt wäre.
Im obigen Code-Beispiel werden diese unmittelbaren Ausdrücke also atomar behandelt:
# These two expressions are always kept together
Print("Started")
var Seconds := 1.0
Sleep(Seconds)
# These two expressions are always kept together
Print("Waited {Second} seconds")
set Second += 1.0
Wie jeder andere Codeblock wird der letzte Ausdruck in einem asynchronen Codeblock als result verwendet.
Gleichzeitigkeitsausdrücke
Verse verwendet Parallelität, um zu bestimmen, ob expressions exceute gleichzeitig (zur gleichen Zeit) oder in Sequenz nacheinander werden. Ein asynchroner Ausdruck wird im Laufe der Zeit ausgeführt oder invoked, daher können diese Gleichzeitigkeitsausdrücke besonders nützlich sein, wenn du asynchrone Ausdrücke verwendest.
Strukturierte Gleichzeitigkeit
Ein asynchroner Ausdruck blockiert die Ausführung anderer Ausdrücke, wenn er zu lange dauert, ihn auszuführen. Zum Beispiel mit Sleep(90.0) führt dazu, dass das Programm 90 Sekunden wartet und den nächsten Ausdruck blockiert, bis Sleep(90.0) vollständig ausgeführt ist.
Strukturierte Gleichzeitigkeitsausdrücke werden verwendet, um einen asynchronen logischen Zeitablauf zu spezifizieren und die blockierende Natur von asynchronen Ausdrücken mit einer Lebensdauer zu ändern, die logisch auf einen bestimmten asynchronen Kontextbereich beschränkt ist (wie z. B. ein asynchroner Funktionsrumpf).
Dies ähnelt einem strukturierten Kontrollfluss wie block, if, for, und loop, die sich auf den verbundenen scope beschränken.
Asynchrone Ausdrücke in Verse verwenden nicht die yield und await primitives, die von asynchronen Implementierungen in anderen Sprachen verwendet werden. Dieselben Mechanismen werden durch die Verwendung von Verse-Gleichzeitigkeitsausdrücken und internen Mechanismen erreicht.
Weitere Informationen zur strukturierten Gleichzeitigkeit findest du unter Synchronisation, Rennen, Rausch, und Verzweigung.
Unstrukturierte Gleichzeitigkeit
Es gibt nur einen unstrukturierten Gleichzeitigkeitsausdruck - spawn. Dieser Ausdruck hat eine Lebensdauer, die logisch nicht auf einen bestimmten Geltungsbereich des asynchronen Kontexts beschränkt ist, sich aber möglicherweise über den Bereich hinaus erstrecken kann, in dem er ausgeführt wurde.
Unstrukturierte Gleichzeitigkeit ist wie eine Notausstiegsluke – du solltest sie nicht regelmäßig benutzen, obwohl sie manchmal deine beste und einzige Option ist.
Strukturierte Gleichzeitigkeitsausdrücke (sync, race, rush und branch) sollten wann immer möglich vor unstrukturierten Gleichzeitigkeitsausdrücken (spawn) verwendet werden.
Weitere Informationen zur unstrukturierten Gleichzeitigkeit findest du unter Spawn.
Aufgaben zum Verfolgen von derzeit ausgeführten asynchronen Ausdrücken
Ein asynchroner Ausdruck ist mit einer Aufgabe assoziiert.
Eine Aufgabe ist ein object, das eine asynchrone Funktion darstellt, die mit der Ausführung begonnen hat, aber suspended wurde, um eine andere Aufgabe abschließen zu können.
Die Aufgabe kann verwendet werden, um den Status eines asynchronen Ausdrucks zu überprüfen und den asynchronen Ausdruck bei Bedarf abzubrechen.
Weitere Informationen über Aufgaben findest du unter Aufgabe.