Eine Funktion anhalten
Willkommen zur Lektion über Koroutinen, dem letzten großen Teil von Lua. Viele Anfänger haben noch nie etwas Ähnliches gesehen, also beginnen wir mit einer Idee, nicht mit Code.
Jede Funktion, die du bisher geschrieben hast, läuft in einem Zug von Anfang bis Ende: Wenn du sie aufrufst, wartet der Rest des Programms, bis sie fertig ist. Stell dir nun vor, Emma erzählt dir von ihrem Tag, ein Stück nach dem anderen. Am Morgen sagt sie: „Ich habe die Hühner gefüttert.“ Dann geht sie zur Arbeit. Triffst du sie mittags, fängt sie nicht von vorn an; sie macht weiter: „Ich habe die Karotten gegossen.“ Sie weiß noch, wo sie aufgehört hat.
Eine Koroutine ist eine Funktion, die dasselbe kann: mittendrin anhalten, sich merken, wo sie war (und die Werte ihrer lokalen Variablen), und später genau an dieser Stelle weitermachen. Der Name bedeutet „Routinen, die zusammenarbeiten“: „Routine“ ist ein altes Wort für eine Funktion, und eine Koroutine wechselt sich mit dem Rest des Programms ab. Hier ist Emmas Tag als Koroutine:
-- Emmas Tag, Schritt für Schritt
local function emmasTag()
print("Emma füttert die Hühner.")
coroutine.yield()
print("Emma gießt die Karotten.")
coroutine.yield()
print("Emma verkauft Eier auf dem Markt.")
end
local tag = coroutine.create(emmasTag)
print(coroutine.status(tag))
coroutine.resume(tag)
print("(Die Sonne steigt höher.)")
coroutine.resume(tag)
print(coroutine.status(tag))
coroutine.resume(tag)
print(coroutine.status(tag))Das gibt Folgendes aus:
suspended
Emma füttert die Hühner.
(Die Sonne steigt höher.)
Emma gießt die Karotten.
suspended
Emma verkauft Eier auf dem Markt.
deadDrei Funktionen aus Luas Bibliothek coroutine erledigen die Arbeit:
coroutine.create(emmasTag)macht aus der FunktionemmasTageine Koroutine. Es führt noch nichts davon aus. Du übergibst die Funktion selbst, ohne Klammern.coroutine.resume(tag)führt die Koroutine aus, bis siecoroutine.yield()oder das Ende der Funktion erreicht. Das ersteresumebeginnt am Anfang der Funktion; jedes weitere macht direkt nach demyieldweiter, an dem die Koroutine angehalten hat.coroutine.yield()hält die Koroutine innerhalb der Funktion an, und das Programm geht nach demresumeweiter, das sie gestartet hat. („Yield“ heißt etwa „nachgeben“, wie ein Auto an einer Kreuzung die Vorfahrt gibt.)
coroutine.status(tag) sagt dir, wie es der Koroutine geht: suspended (angehalten: noch nicht gestartet oder wartend an einem yield), running (ihr Code läuft gerade, das kann also nur die Koroutine selbst je sehen) oder dead (sie hat das Ende ihrer Funktion erreicht und kann nicht weitermachen).
Wozu ist das nützlich? In Spielen dauern viele Dinge eine Weile: Hugo, der Händler, reist von Stadt zu Stadt, eine Tür öffnet sich langsam, Emma geht über das Feld. Ein Spiel zeichnet viele Male pro Sekunde ein neues Bild (einen Frame) oder gibt in einem rundenbasierten Spiel jeder Figur ihren Zug, und alle diese Dinge müssen in kleinen Schritten vorankommen. Mit einer Koroutine schreibst du so etwas als eine gewöhnliche Funktion, von Anfang bis Ende, mit coroutine.yield() überall dort, wo sie auf den nächsten Frame oder Zug warten soll. Solche Skripte schreibst du später in dieser Lektion.
Typische Fehler:
- Die Funktion selbst aufzurufen,
emmasTag(), oder mit Klammern incoroutine.create(emmasTag()). Beides führtemmasTagals gewöhnliche Funktion aus, nicht als Koroutine, sodass ihryieldnichts anzuhalten hat. Das Programm gibtEmma füttert die Hühner.aus und stoppt mit dem Fehlerattempt to yield from outside a coroutine. Diese Meldung hat keine Zeilennummer; die Zeile findest du imstack traceback:darunter. - Die Koroutine wie eine Funktion aufzurufen,
tag(). Eine Koroutine ist keine Funktion, sondern ein Wert eines eigenen Typs,thread, deshalb bekommst duattempt to call a thread value (local 'tag'). Eine Koroutine läuft nur übercoroutine.resume.
So schreibst du es:
Anweisungen
- 1
Emma gießt ihr Feld, will aber nach dem ersten Beet eine Pause machen. Mache aus
feldGiesseneine Koroutine: Setzecoroutine.yield()nach dem Gießen von Beet 1 und nach Beet 2 (nicht nach Beet 3). Ersetze den AufruffeldGiessen()durchlocal giessen = coroutine.create(feldGiessen), führegiesseneinmal mitresumefort und gib dannEmma macht eine Pause.aus. Das Programm gibt aus:Beet 1 gegossen Emma macht eine Pause. - 2
Gib nach der Pause den Status der Koroutine nach dem Text
Status:aus. Führe danngiessennoch zweimal mitresumefort und gib den Status noch einmal aus. Das Programm gibt aus:Beet 1 gegossen Emma macht eine Pause. Status: suspended Beet 2 gegossen Beet 3 gegossen Status: dead