Second Draft: Coroutines
Richard (Rikki) Andrew Cattermole
richard at cattermole.co.nz
Fri Jan 24 23:22:19 UTC 2025
On 25/01/2025 9:56 AM, Sebastiaan Koppe wrote:
> On Thursday, 23 January 2025 at 23:09:42 UTC, Richard (Rikki) Andrew
> Cattermole wrote:
>>
>> On 24/01/2025 10:17 AM, Sebastiaan Koppe wrote:
>>> On Thursday, 23 January 2025 at 20:37:59 UTC, Richard (Rikki) Andrew
>>> Cattermole wrote:
>>> You don't need to describe how scheduling works, just the mechanism
>>> by which a scheduler gets notified when a coroutine is ready for
>>> resumption.
>>>
>>> Rust has a Waker, C++ has the await_suspend function, etc.
>>
>> Are you wanting this snippet?
>
> No, not specifically. I am requesting the DIP to clarify the mechanism
> by which a scheduler is notified when a coroutine is ready for
> resumption, not the specific scheduling itself.
It should be scheduled:
If: tag >= 0
And: waitingOnCoroutine == None || (waitingOnCoroutine != None &&
waitingOnCoroutine.isCompleteOrHaveValue)
Where isCompleteOrHaveValue is: tag < 0 || haveValue
The DPI does not require you to do any of this (if things are written
correctly it should not segfault and hopefully won't corrupt anything),
but this would be good practice. And yes it is library code. The
compiler does not help you to do any of this. You the library author are
responsible for it.
If you want to do something different like a waker style where these
rules do not apply, you are free to. The language only requires the tag
to be >= 0 due to the branch table stuff.
> The snippet you posted raises more questions than it answers to be
> honest. First of all I still don't know what a GenericCoroutine or what
> a Future is.
I wrote it out for someone else here:
https://forum.dlang.org/post/vn14i8$1g46$1@digitalmars.com
```d
struct GenericCoroutine {
bool isComplete();
CoroutineCondition condition();
void unsafeResume();
void blockUntilCompleteOrHaveValue();
}
struct Future(ReturnType) : GenericCoroutine {
ReturnType result();
}
struct InstantiableCoroutine(ReturnType, Parameters...) {
Future!ReturnType makeInstance(Parameters);
InstantiableCoroutine!(ReturnType, ughhhhh) partial(Args...)(Args); //
removes N from start of Parameters
static InstantiableCoroutine opConstrucCo(CoroutineDescriptor :
__descriptorco)();
}
```
https://github.com/Project-Sidero/eventloop/tree/master/source/sidero/eventloop/coroutine
If it doesn't work for you, do it a different way. The language has no
inbuilt knowledge of any of these types. It determines everything that
it needs from the operator overload and the core.attributes attributes.
If it turns out those attributes are not enough (I am not expecting any
to be needed), we can add some to allow your library to communicate to
the compiler on how it needs to do the slicing and dicing of the
function into the state object that you can consume and call.
> It seems that in your design coroutines are only able to wait for other
> coroutines. This means you need to model async operations as coroutines
> in order to suspend on them.
It should be coroutines, but I left out the filtering for the type that
the ``await`` statement will accept. It'll chuck whatever you want into
the sumtype value. Its your job to filter it. If you want to support
other types and behaviors go for it!
Remember ``await`` statement does two things, assign to ``waitingOn`,
then yield (aka return) (and set tag appropriately).
> Why was this done? C++'s approach of having
> an awaiter seems simpler.
This is the C# approach.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1298-await-expressions
A significantly more mature solution where we have Adam who has
experience working with it since it was created in teams. He has dealt
with all the problems that come with that. I don't have a stake holder
who fits the bill for other styles.
In saying all that, I find the dependency approach to be very intuitive,
and I was able to implement it purely off of first principles. Whereas
the other approaches including C++ is still after much reading not in my
mental model.
> For one it allows the object you are awaiting
> on to control the continuation directly.
If you want that for your library to look at the ``waitingOn`` variable
for control over scheduling, go for it! Nothing in the DIP currently
should stop you from doing that.
You could even add support for it as part of instantiation of the
coroutine! Its your library code, you can do whatever you want on this
front.
You control execution of the coroutine itself, you can see that this
value was set. You can inspect it, you can call whatever you like.
That is what the last example with ``
void execute(COState)(GenericCoroutine us, COState* coState) {`` shows.
You are fully in control over the coroutines execution the language is
focused solely on the slicing and dicing of the function into something
that a library can then call.
The language defines none of this _on purpose_.
More information about the dip.development
mailing list