Difference between revisions of "Coroutine"
m |
|||
(3 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
__NOTOC__ | __NOTOC__ | ||
− | |||
<!-- GIDEROSOBJ:coroutine --> | <!-- GIDEROSOBJ:coroutine --> | ||
− | ''' | + | '''Supported platforms:''' [[File:Platform android.png]][[File:Platform ios.png]][[File:Platform mac.png]][[File:Platform pc.png]][[File:Platform html5.png]][[File:Platform winrt.png]][[File:Platform win32.png]]<br/> |
− | ''' | + | '''Available since:''' Gideros 2011.6<br/> |
− | === < | + | |
− | < | + | === Description === |
− | === | + | A coroutine is used to perform multiple tasks at the same time from within the same script. Such tasks might include producing values from inputs or performing work on a subroutine when solving a larger problem. A task doesn't even need to have a defined ending point, but it does need to define particular times at which it yields (pause) to let other things be worked on. See Coroutine basics to learn more. |
− | '''Simple coroutine usage example''' | + | |
− | < | + | |
+ | '''Using Coroutines''' | ||
+ | |||
+ | A new coroutine can be created by providing a function to coroutine.create(). Once created, a coroutine doesn't begin running until the first call to coroutine.resume() which passes the arguments to the function. This call returns when the function either halts or calls coroutine.yield() and, when this happens, coroutine.resume() returns either the values returned by the function, the values sent to coroutine.yield(), or an error message. If it does error, the second return value is the thrown error. | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | local function task(...) | ||
+ | -- This function might do some work for a bit then yield some value | ||
+ | coroutine.yield("first") -- To be returned by coroutine.resume() | ||
+ | -- The function continues once it is resumed again | ||
+ | return "second" | ||
+ | end | ||
+ | |||
+ | local taskCoro = coroutine.create(task) | ||
+ | -- Call resume for the first time, which runs the function from the beginning | ||
+ | local success, result = coroutine.resume(taskCoro, ...) | ||
+ | print(success, result) --> true, first (task called coroutine.yield()) | ||
+ | -- Continue running the function until it yields or halts | ||
+ | success, result = coroutine.resume(taskCoro) | ||
+ | print(success, result) --> true, second (task halted because it returned "second") | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | During the lifetime of the coroutine, you can call coroutine.status() to inspect its status:<br/> | ||
+ | '''Status''' / '''Meaning''' | ||
+ | *'''suspended''': the coroutine is waiting to be resumed. Coroutines begin in this state and enter it when their function calls coroutine.yield() | ||
+ | *'''running''': the coroutine is running right now | ||
+ | *'''normal''': the coroutine is awaiting the yield of another coroutine; in other words, it has resumed another coroutine | ||
+ | *'''dead''': the function has halted (returned or thrown an error). The coroutine cannot be used further | ||
+ | |||
+ | |||
+ | '''Wrapping Coroutines''' | ||
+ | |||
+ | When working with coroutines, you can also forgo the use of the coroutine object and instead use a wrapper function. Such a wrapper function will resume a particular coroutine when it is called and will return only the yielded values. You can do this using coroutine.wrap(): | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | -- Create coroutine and return a wrapper function that resumes it | ||
+ | local f = coroutine.wrap(task) | ||
+ | -- Resume the coroutine as if we called coroutine.resume() | ||
+ | local result = f() | ||
+ | -- If an error occurs it will be raised here! | ||
+ | -- This differs from coroutine.resume() which acts similar to pcall() | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | The first value returned from coroutine.resume() describes whether a coroutine ran without errors. However, functions returned by coroutine.wrap() will not do this: instead they directly return the values returned or passed to coroutine.yield(), if any. Should an error have occurred while running the coroutine function, the error is raised on the call of the returned function. | ||
+ | |||
+ | |||
+ | '''Producer Pattern Example''' | ||
+ | |||
+ | Imagine a task that produces repetitions of a word: each time it produces a repetition, the next one will produce one more. For example, providing Hello will produce Hello, HelloHello, HelloHelloHello, etc. To do this, you can define repeatThis(): | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | -- This function repeats a word every time its coroutine is resumed | ||
+ | local function repeatThis(word) | ||
+ | local repetition = "" | ||
+ | while true do | ||
+ | -- Do one repetition then yield the result | ||
+ | repetition = repetition .. word | ||
+ | coroutine.yield(repetition) | ||
+ | end | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | To run this function as a coroutine, you can use coroutine.create() followed by multiple calls to coroutine.resume(): | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | local repetitionCoro = coroutine.create(repeatThis) | ||
+ | print(coroutine.resume(repetitionCoro, "Hello")) -- true, Hello | ||
+ | print(coroutine.resume(repetitionCoro)) -- true, HelloHello | ||
+ | print(coroutine.resume(repetitionCoro)) -- true, HelloHelloHello | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | For this producer function, you can also use coroutine.wrap() to get a function that produces values: | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | local f = coroutine.wrap(repeatThis) | ||
+ | print(f("Hello")) -- Hello | ||
+ | print(f()) -- HelloHello | ||
+ | print(f()) -- HelloHelloHello | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''The coroutine cannot be running a C function, a metamethod, or an iterator. Any arguments to yield are passed as extra results to resume''' | ||
+ | |||
+ | === Example === | ||
+ | '''Simple coroutine usage example''' | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | --To create a coroutine we must have function which represents it, e.g., | ||
function foo() | function foo() | ||
print("foo", 1) | print("foo", 1) | ||
Line 15: | Line 94: | ||
end | end | ||
− | + | -- We create a coroutine using the coroutine.create(fn) function. We pass it an entry point for the thread which is a Lua function. The object returned by Lua is a thread: | |
− | + | co = coroutine.create(foo) -- create a coroutine with foo as the entry | |
− | print(type(co)) | + | print(type(co)) -- display the type of object "co" = thread |
− | -- | + | -- We can find out what state the thread is in using the coroutine.status() function, e.g., |
print(coroutine.status(co)) --suspended | print(coroutine.status(co)) --suspended | ||
− | -- | + | -- The state suspended means that the thread is alive, and as you would expect, not doing anything. Note that when we created the thread it did not start executing. To start the thread we use the coroutine.resume() function. Lua will enter the thread and leave when the thread yields. |
coroutine.resume(co) -- prints foo 1 | coroutine.resume(co) -- prints foo 1 | ||
− | -- | + | -- The coroutine.resume() function returns the error status of the resume call. The output acknowledges that we entered the function foo and then exited with no errors. Now is the interesting bit. With a function we would not be able to carry on where we left off, but with coroutines we can resume again: |
coroutine.resume(co) -- prints foo 2 | coroutine.resume(co) -- prints foo 2 | ||
− | -- | + | -- We can see we executed the line after the yield in foo and again returned without error. However, if we look at the status we can see that we exited the function foo and the coroutine terminated. |
− | print(coroutine.status(co)) --prints dead | + | print(coroutine.status(co)) -- prints dead |
− | -- | + | -- If we try to resume again a pair of values is returned: an error flag and an error message: |
print(coroutine.resume(co)) -- prints false cannot resume dead coroutine | print(coroutine.resume(co)) -- prints false cannot resume dead coroutine | ||
− | -- Once a coroutine exits or returns like a function it cannot be resumed.</ | + | -- Once a coroutine exits or returns like a function it cannot be resumed. |
+ | </syntaxhighlight> | ||
+ | |||
{|- | {|- | ||
| style="width: 50%; vertical-align:top;"| | | style="width: 50%; vertical-align:top;"| | ||
− | === | + | |
− | [[ | + | === Methods === |
− | [[ | + | [[coroutine.close]] ''closes and puts the provided coroutine in a dead state''<br/><!--GIDEROSMTD:coroutine.close(co: thread): boolean, closes and puts the provided coroutine in a dead state--> |
− | [[ | + | [[coroutine.create]] ''creates a new coroutine, with body f. f must be a Lua function''<br/><!--GIDEROSMTD:coroutine.create(f: function): thread, creates a new coroutine, with body f. f must be a Lua function--> |
− | [[ | + | [[coroutine.isyieldable]] ''returns true if the coroutine this function is called within can safely yield''<br/><!--GIDEROSMTD:coroutine.isyieldable(): boolean, returns true if the coroutine this function is called within can safely yield--> |
− | [[ | + | [[coroutine.resume]] ''starts or continues the execution of coroutine co''<br/><!--GIDEROSMTD:coroutine.resume(co: thread, ...: Variant): boolean, starts or continues the execution of coroutine co--> |
+ | [[coroutine.running]] ''returns the running coroutine''<br/><!--GIDEROSMTD:coroutine.running(): thread, returns the running coroutine--> | ||
+ | [[coroutine.status]] ''returns co status: "running", "suspended" or "dead"''<br/><!--GIDEROSMTD:coroutine.status(co: thread): string, returns co status: "running", "suspended" or "dead"--> | ||
+ | [[coroutine.wrap]] ''creates a new coroutine and returns a function that, when called, resumes the coroutine''<br/><!--GIDEROSMTD:coroutine.wrap(f: function): function, creates a new coroutine and returns a function that, when called, resumes the coroutine--> | ||
+ | [[coroutine.yield]] ''suspends execution of the coroutine''<br/><!--GIDEROSMTD:coroutine.yield(...: Tuple): Tuple<Variant>, suspends execution of the coroutine--> | ||
+ | |||
| style="width: 50%; vertical-align:top;"| | | style="width: 50%; vertical-align:top;"| | ||
− | === | + | === Events === |
− | === | + | === Constants === |
|} | |} | ||
{{GIDEROS IMPORTANT LINKS}} | {{GIDEROS IMPORTANT LINKS}} |
Latest revision as of 06:48, 4 January 2024
Supported platforms:
Available since: Gideros 2011.6
Description
A coroutine is used to perform multiple tasks at the same time from within the same script. Such tasks might include producing values from inputs or performing work on a subroutine when solving a larger problem. A task doesn't even need to have a defined ending point, but it does need to define particular times at which it yields (pause) to let other things be worked on. See Coroutine basics to learn more.
Using Coroutines
A new coroutine can be created by providing a function to coroutine.create(). Once created, a coroutine doesn't begin running until the first call to coroutine.resume() which passes the arguments to the function. This call returns when the function either halts or calls coroutine.yield() and, when this happens, coroutine.resume() returns either the values returned by the function, the values sent to coroutine.yield(), or an error message. If it does error, the second return value is the thrown error.
local function task(...)
-- This function might do some work for a bit then yield some value
coroutine.yield("first") -- To be returned by coroutine.resume()
-- The function continues once it is resumed again
return "second"
end
local taskCoro = coroutine.create(task)
-- Call resume for the first time, which runs the function from the beginning
local success, result = coroutine.resume(taskCoro, ...)
print(success, result) --> true, first (task called coroutine.yield())
-- Continue running the function until it yields or halts
success, result = coroutine.resume(taskCoro)
print(success, result) --> true, second (task halted because it returned "second")
During the lifetime of the coroutine, you can call coroutine.status() to inspect its status:
Status / Meaning
- suspended: the coroutine is waiting to be resumed. Coroutines begin in this state and enter it when their function calls coroutine.yield()
- running: the coroutine is running right now
- normal: the coroutine is awaiting the yield of another coroutine; in other words, it has resumed another coroutine
- dead: the function has halted (returned or thrown an error). The coroutine cannot be used further
Wrapping Coroutines
When working with coroutines, you can also forgo the use of the coroutine object and instead use a wrapper function. Such a wrapper function will resume a particular coroutine when it is called and will return only the yielded values. You can do this using coroutine.wrap():
-- Create coroutine and return a wrapper function that resumes it
local f = coroutine.wrap(task)
-- Resume the coroutine as if we called coroutine.resume()
local result = f()
-- If an error occurs it will be raised here!
-- This differs from coroutine.resume() which acts similar to pcall()
The first value returned from coroutine.resume() describes whether a coroutine ran without errors. However, functions returned by coroutine.wrap() will not do this: instead they directly return the values returned or passed to coroutine.yield(), if any. Should an error have occurred while running the coroutine function, the error is raised on the call of the returned function.
Producer Pattern Example
Imagine a task that produces repetitions of a word: each time it produces a repetition, the next one will produce one more. For example, providing Hello will produce Hello, HelloHello, HelloHelloHello, etc. To do this, you can define repeatThis():
-- This function repeats a word every time its coroutine is resumed
local function repeatThis(word)
local repetition = ""
while true do
-- Do one repetition then yield the result
repetition = repetition .. word
coroutine.yield(repetition)
end
end
To run this function as a coroutine, you can use coroutine.create() followed by multiple calls to coroutine.resume():
local repetitionCoro = coroutine.create(repeatThis)
print(coroutine.resume(repetitionCoro, "Hello")) -- true, Hello
print(coroutine.resume(repetitionCoro)) -- true, HelloHello
print(coroutine.resume(repetitionCoro)) -- true, HelloHelloHello
For this producer function, you can also use coroutine.wrap() to get a function that produces values:
local f = coroutine.wrap(repeatThis)
print(f("Hello")) -- Hello
print(f()) -- HelloHello
print(f()) -- HelloHelloHello
The coroutine cannot be running a C function, a metamethod, or an iterator. Any arguments to yield are passed as extra results to resume
Example
Simple coroutine usage example
--To create a coroutine we must have function which represents it, e.g.,
function foo()
print("foo", 1)
coroutine.yield()
print("foo", 2)
end
-- We create a coroutine using the coroutine.create(fn) function. We pass it an entry point for the thread which is a Lua function. The object returned by Lua is a thread:
co = coroutine.create(foo) -- create a coroutine with foo as the entry
print(type(co)) -- display the type of object "co" = thread
-- We can find out what state the thread is in using the coroutine.status() function, e.g.,
print(coroutine.status(co)) --suspended
-- The state suspended means that the thread is alive, and as you would expect, not doing anything. Note that when we created the thread it did not start executing. To start the thread we use the coroutine.resume() function. Lua will enter the thread and leave when the thread yields.
coroutine.resume(co) -- prints foo 1
-- The coroutine.resume() function returns the error status of the resume call. The output acknowledges that we entered the function foo and then exited with no errors. Now is the interesting bit. With a function we would not be able to carry on where we left off, but with coroutines we can resume again:
coroutine.resume(co) -- prints foo 2
-- We can see we executed the line after the yield in foo and again returned without error. However, if we look at the status we can see that we exited the function foo and the coroutine terminated.
print(coroutine.status(co)) -- prints dead
-- If we try to resume again a pair of values is returned: an error flag and an error message:
print(coroutine.resume(co)) -- prints false cannot resume dead coroutine
-- Once a coroutine exits or returns like a function it cannot be resumed.
Methodscoroutine.close closes and puts the provided coroutine in a dead state |
EventsConstants |