dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.24k stars 4.73k forks source link

[API Proposal]: A native(ish) coroutine integration #93073

Open Khhs167 opened 1 year ago

Khhs167 commented 1 year ago

Background and motivation

Coroutines are a staple of many development pipelines, whether that'd be UI or games, as long as you've got real-time mixed in there coroutines are effectively essential.

Some of the issues can be fixed with state machines, but the most universal solution would most likely be the IEnumerable-generator approach. Whilst this works fine 80% of the time, the best option would simply be to add coroutines to the .NET runtime(and C#), since there are a number of things that would be weird to do with IEnumerables, and the syntax can get quite messy.

API Proposal

yield; - Used to pause execution of coroutine - New keyword usage await [coroutine]; - Used to "fork" into another coroutine - New keyword usage invoke [coroutine]; - Used to run a coroutine fully coroutine - Either used like the async modifier to declare a coroutine function, or used to declare a coroutine object.

coroutine may have one type argument, T. This specifies the return value of the coroutine.

coroutine specifies the function bool Proceed(), or for coroutines with return values, bool Proceed(out T? @return).

Proceed returns true if the coroutine is finished or just hit the end.

@return in Proceed is always null if the return value is false, otherwise it is only null if the coroutine returned null.

API Usage

coroutine string ReturningCoroutine() {
    // Do some magic...
    return "myString"
}

coroutine void AwaitingCoroutine() {
    // Any ".Proceed()" calls will be inside of ReturningCoroutine until that finishes.
    string reurnValue = await ReturningCoroutine();
}

coroutine void BasicCoroutine() {
    while(!stmt) {
        yield; // Pause execution
    }
}

void Main() {
    coroutine awaited = AwaitingCoroutine(); // Create a coroutine instance
    while(true) {
         // Run until next yield
         if(awaited.Proceed())
             break;
    }

    // Run the entire coroutine without stopping at "yield"
    invoke AwaitingCoroutine();

    // Create a coroutine object with return type
    coroutine<string> returning = ReturningCoroutine();

    // New fancy way of invoking.
    returning.Proceed(out string? retVal);
}

Alternative Designs

Similar effect could be done via a change to IEnumerable generators(in ways which I do not recommend), to Task or maybe to async.

I personally believe it would be best to keep separate.

Risks

No response

huoyaoyuan commented 1 year ago

Related to dotnet/runtimelab#2398

nike4613 commented 1 year ago

I believe this can already be done with async at the language level. It just needs a custom task type.

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/area-system-threading-tasks See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation Coroutines are a staple of many development pipelines, whether that'd be UI or games, as long as you've got real-time mixed in there coroutines are effectively essential. Some of the issues can be fixed with state machines, but the most universal solution would most likely be the IEnumerable-generator approach. Whilst this works fine 80% of the time, the best option would simply be to add coroutines to the .NET runtime(and C#), since there are a number of things that would be weird to do with IEnumerables, and the syntax can get quite messy. ### API Proposal `yield;` - Used to pause execution of coroutine - New keyword usage `await [coroutine];` - Used to "fork" into another coroutine - New keyword usage `invoke [coroutine];` - Used to run a coroutine fully `coroutine` - Either used like the `async` modifier to declare a coroutine function, or used to declare a coroutine object. `coroutine` may have one type argument, `T`. This specifies the return value of the coroutine. `coroutine` specifies the function `bool Proceed()`, or for coroutines with return values, `bool Proceed(out T? @return)`. `Proceed` returns true if the coroutine is finished or just hit the end. `@return` in `Proceed` is always null if the return value is false, otherwise it is only null if the coroutine returned null. ### API Usage ```csharp coroutine string ReturningCoroutine() { // Do some magic... return "myString" } coroutine void AwaitingCoroutine() { // Any ".Proceed()" calls will be inside of ReturningCoroutine until that finishes. string reurnValue = await ReturningCoroutine(); } coroutine void BasicCoroutine() { while(!stmt) { yield; // Pause execution } } void Main() { coroutine awaited = AwaitingCoroutine(); // Create a coroutine instance while(true) { // Run until next yield if(awaited.Proceed()) break; } // Run the entire coroutine without stopping at "yield" invoke AwaitingCoroutine(); // Create a coroutine object with return type coroutine returning = ReturningCoroutine(); // New fancy way of invoking. returning.Proceed(out string? retVal); } ``` ### Alternative Designs Similar effect could be done via a change to `IEnumerable` generators(in ways which I do not recommend), to `Task` or maybe to `async`. I personally believe it would be best to keep separate. ### Risks _No response_
Author: Khhs167
Assignees: -
Labels: `api-suggestion`, `area-System.Threading.Tasks`, `untriaged`, `needs-area-label`
Milestone: -
Khhs167 commented 1 year ago

I believe this can already be done with async at the language level. It just needs a custom task type.

I would be more than happy if you could help me out with implementing that, just some general guidance would be nice.

:)

acaly commented 1 year ago

I believe this can already be done with async at the language level. It just needs a custom task type.

I would be more than happy if you could help me out with implementing that, just some general guidance would be nice.

:)

You should find this blog series useful: https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/

Khhs167 commented 10 months ago

I more or less pushed this to the back of my mind, until I found a library that more or less seemed to implement what I wanted(with some minor drawbacks due to how C# seems to work)(https://github.com/seanofw/HalfMaid.Async), however it is not entirely stable, and a lot of that can be attributed to a lack of documentation for how async/await works, as well as general jank. I believe that this shouldn't have to be the case, and whilst it might not be entirely related to this issue, may I suggest that, if we don't get coroutines, we at least get some good documentation for how awaiting works, as well as some helper functions or a possible rework to allow people who don't want to spend months reading various sources implement their own async task types?

Edit:

I'm sorry about the above message.

I was angry at the fact that my entire game's development speed crawled to a halt because of a small issue, and let it out in a related issue(hence the rant-like nature of it all). It basically boiled down to me being mad about something as big as this lacking docs, and the nature of this becoming overly complex for a single person.

Sorry. It won't happen again.