Coroutines in Lua are supported out of the box. Really, as long as you don’t exclude the coroutines module intentionally (see sandboxing), they are supported for free.
There are a lot of caveats (which incidentally apply also to the original Lua implementation) and are discussed in the “Caveats” section below.
Use any Lua coroutine tutorial to work with them.
Coroutines from CLR code
Coroutines can be created with the script CreateCoroutine method, which accepts a DynValue which must be a function.
Coroutines from CLR code as CLR iterator
It’s possible to invoke coroutines as if they were an iterator:
Caveats
And here we come to the most important section of all the coroutines stuff.
Just like in the original Lua, it’s not possible to yield out of nested calls.
In particular, in MoonSharp if you Call a script inside a C# function called from Lua, you can’t use yield to resume to a coroutine external to the C# call.
There is a way out: returning a TailCallRequest DynValue:
It’s also possible to specify a continuation - a piece of function which will be called after the execution of the tail call completes.
99% of the time this is probably overkill - not even the Lua standard library handles callbacks+yield correctly in the vast majority of the cases. But
if you plan to implement APIs like load, pcall or coroutine.resume by yourself, this is needed.
As an aside, in some corner cases MoonSharp handles yielding in a different way (better in every case I tried so far but who knows) than standard Lua. For example, tostring() supports yielding __tostring metamethods without raising an error.
Preemptive coroutines
In MoonSharp it’s possible to have a coroutine suspended even if it does not call coroutine.yield.
This can be useful, for example, if one wants to non-destructively limit the amount of CPU time dedicated to a script, but there are some caveats which are important to maintain script consistency.
Let’s start with an example:
The steps are simple:
Create a coroutine
Set the AutoYieldCounter to some number greater than 0. 1000 is a good starting point, tune from there. This is the number of instructions which will be executed before yielding to the caller.
Call coroutine.Coroutine.Resume(...) with the proper args
If the result of the above call is of type DataType.YieldRequest, call coroutine.Coroutine.Resume() with no args.
Repeat the previous until we have a real return type.
Done.
Now on to the caveats:
If the code re-enters (for example, the callback of string.gsub), it will not be preempted until it returns to a “yieldable state”
Mixing standard coroutine operations with preempted ones is possible but dangerous and difficult. Remember that the coroutine can preempt-yield in any place, thus, for example, there are no guarantee of operation atomicity etc. If you mix them, be ware.