Hello everyone,
I want to present another experimental feature for upcoming LDMud releases
and invite you to test them: Coroutines.
Coroutines are special functions that can be suspended and resumed at a
later time. This is an example of a coroutine:
async void demo()
{
string msg;
msg = await(get_input("Choice: "));
await(sleep(2));
printf("Your choice: %Q\n", msg);
}
The example uses two other coroutines get_input() and sleep().
As you see coroutines are declared with the 'async' keyword. When such a
function is called it will return immediately a new coroutine object
representing the execution of the function. The function itself is not
executed at that time. So there is also a new LPC type named 'coroutine'.
A coroutine object represents the (usually suspended) state of a function
execution. Such a function can be resumed with the call_coroutine() efun.
Whenever a coroutine is suspended and resumed, data can be exchanged. The
coroutine can return data upon suspension, the caller can pass data when
resuming. So call_coroutine() will take in addition to the coroutine
object itself an optional parameter to pass into the coroutine, and it
will return a value that the coroutine yielded at the next suspension
point.
To suspend its execution a coroutine has four options:
1. yield( [value] )
This is a normal suspension to continue execution of the caller.
The value is passed to the caller. The yield() expression returns
the value that will be passed in at the next resumption. This
is an example of a counting coroutine:
async void range(int start, int stop, int step = 1)
{
for (int i = start; i < stop; i+= step)
yield(i);
}
2. yield( value, coroutine )
This is similar to the simple yield() expression, but instead of
returning to the caller it will resume another coroutine.
3. await( coroutine [, value] )
This is similar to the second yield() expression and will resume
another coroutine. But the current coroutine will only be resumed
when the other coroutine finishes (with a return statement or
end of function). If some caller tries to resume it before then,
the other coroutine will be resumed instead.
You may view this as a the equivalent of a function call for
coroutines. The new coroutine steps into place of the current
coroutine.
4. return [value]
Finishes the coroutine, destroys the coroutine object and
resumes execution of either an awaiting coroutine or a
regular caller.
You might wonder how a sleep() function as used in the first example then
might look like:
async void sleep(int sec)
{
call_out(#'call_coroutine, sec, this_coroutine());
yield();
}
This is a coroutine that calls call_out() with the task to resume
(call_coroutine() efun) itself. Then it suspends execution.
You can use this scheme to wait for any event (input_to, erq callbacks,
mudlib events).
Coroutines are implemented (together with lightweight objects) in my
experimental branch:
https://github.com/amotzkau/ldmud/tree/experimental
There is also another overview of coroutines:
https://github.com/amotzkau/ldmud/blob/experimental/doc/LPC/coroutines
Mudlibs need no adaptations to use the coroutines.
As this is an experimental feature it may be unstable and is not ready for
production use. Also implementation details may change until release.
I'm very interested in your thoughts about this feature, any further ideas,
and I'd like to hear about your own experiments with these coroutines.
Regards,
Gnomi