Skip to content

Commit

Permalink
feat!: renamed YieldReturn to YieldAssign and moved Yielders.Yield to…
Browse files Browse the repository at this point in the history
… Coroutine.Yield

BREAKING CHANGE: To better reflect that you "replace" the coroutine result of a yielder the name YieldReturn has been renamed to YieldAssign. Also moved Yielders.Yield to Coroutine.Yield to align with Task.Yield.
  • Loading branch information
teneko committed Oct 6, 2024
1 parent 8dee952 commit d19f885
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 89 deletions.
133 changes: 102 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dotnet add package Tenekon.Coroutines --version <type version here>

## Quick start guide

**Simple Coroutine**
**Simple Coroutine: Hello World**

```csharp
using static Tenekon.Coroutines.Yielders;
Expand All @@ -53,7 +53,31 @@ await Func<Coroutine>(async () => {
// Hello world
```

**Simple AsyncIterator**
**Advanced Coroutine: Parallelism**

```csharp
await Coroutine.Start(async () =>
{
var t1 = await Coroutine.Factory.StartNew(new Func<Coroutine>(async () =>
{
Thread.Sleep(3000);
Console.WriteLine(DateTime.UtcNow - Process.GetCurrentProcess().StartTime.ToUniversalTime() + ": Finished");
}));

var t2 = await Coroutine.Factory.StartNew(new Func<Coroutine>(async () =>
{
Thread.Sleep(3000);
Console.WriteLine(DateTime.UtcNow - Process.GetCurrentProcess().StartTime.ToUniversalTime() + ": Finished");
}));

await Task.WhenAll(t1.AsTask(), t2.AsTask());
});

// 00:00:03.0317613: Finished
// 00:00:03.0317757: Finished
```

**Simple AsyncIterator: Iterator**

```csharp
using static Tenekon.Coroutines.Yielders;
Expand All @@ -64,16 +88,15 @@ var iterator = AsyncIterator.Create(async () => {
});

while (await iterator.MoveNextAsync()) {
if (iterator.CurrentKey == CallKey) {
Console.WriteLine(((CallArgument<string>)iterator.Current).Closure);
}
Console.WriteLine(((CallArgument<string>)iterator.Current).Closure);
}

// Outputs:
// Hello world
// Hello world
```

**Advanced AsyncIterator**
**Advanced AsyncIterator: Iterator, Replace Current**

```csharp
using static Tenekon.Coroutines.Yielders;
Expand All @@ -84,18 +107,16 @@ var iterator = AsyncIterator.Create(async () => {
});

while (await iterator.MoveNextAsync()) {
if (iterator.CurrentKey == CallKey) {
Console.WriteLine(((CallArgument<string>)iterator.Current).Closure);
iterator.Current = new CallArgument<string>(Console.WriteLine, "Hello iterator")
}
Console.WriteLine(((CallArgument<string>)iterator.Current).Closure);
iterator.Current = new CallArgument<string>(Console.WriteLine, "Hello iterator")
}

// Outputs:
// Hello world
// Hello iterator
```

**Advanced AsyncIterator**
**Advanced AsyncIterator: Iterator, Yield Assign**

```csharp
using static Tenekon.Coroutines.Iterator.Yielders;
Expand All @@ -106,10 +127,8 @@ var iterator = AsyncIterator.Create(async () => {
});

while (await iterator.MoveNextAsync()) {
if (iterator.CurrentKey == ExchangeKey) {
Console.WriteLine(((ExchangeArgument<string>)iterator.Current).Value);
iterator.YieldReturn("Hello iterator");
}
Console.WriteLine(((ExchangeArgument<string>)iterator.Current).Value);
iterator.YieldAssign("Hello iterator");
}

// Outputs:
Expand All @@ -123,11 +142,61 @@ Yielders are the equivalent to effects in Redux-Saga.

In the following are all available `Yielders` classes and their contained yielders listed.

We recommend to make use of `using static`, e.g. `using static Tenekon.Coroutines.Yielders`.
### Tenekon.Coroutines.Coroutine

The yielding components of `Coroutine` are designed to align with the functionality of `Task`.

#### Tenekon.Coroutines.Coroutine.Yield

Use `Yield` to instruct the coroutine to suspend and resume immediatelly. In the underlying code, `Task.Yield()` is used.

| Yielder | Signature |
| ------- | ----------------------------- |
| `Yield` | `Coroutine Coroutine Yield()` |

#### Tenekon.Coroutines.Coroutine.Run

The `Coroutine.Run` yielder is the equivalent to `Task.Run`

| Yielder | Signature |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable> Run(Func<Coroutine> provider, CancellationToken cancellationToken)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable> Run(Func<Coroutine> provider)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable> Run<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure, CancellationToken cancellationToken)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable> Run<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable<TResult>> Run<TResult>(Func<Coroutine<TResult>> provider, CancellationToken cancellationToken)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable<TResult>> Run<TResult>(Func<Coroutine<TResult>> provider)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable<TResult>> Run<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure, CancellationToken cancellationToken)` |
| `Coroutine.Run` | `Coroutine<CoroutineAwaitable<TResult>> Run<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

#### Tenekon.Coroutines.Coroutine.Run

The `Coroutine.Factory.StartNew` yielder is the equivalent to `Task.Factory.StartNew`

| Yielder | Signature |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew(Func<Coroutine> provider, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew(Func<Coroutine> provider, CancellationToken cancellationToken)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew(Func<Coroutine> provider, TaskCreationOptions creationOptions)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew(Func<Coroutine> provider)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure, CancellationToken cancellationToken)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure, TaskCreationOptions creationOptions)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable> StartNew<TClosure>(Func<TClosure, Coroutine> provider, TClosure closure)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TResult>(Func<Coroutine<TResult>> provider, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TResult>(Func<Coroutine<TResult>> provider, CancellationToken cancellationToken)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TResult>(Func<Coroutine<TResult>> provider, TaskCreationOptions creationOptions)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TResult>(Func<Coroutine<TResult>> provider)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure, CancellationToken cancellationToken)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure, TaskCreationOptions creationOptions)` |
| `Coroutine.Factory.StartNew` | `Coroutine<CoroutineAwaitable<TResult>> StartNew<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

### Tenekon.Coroutines.Yielders

#### Call
We recommend to make use of `using static`, e.g. `using static Tenekon.Coroutines.Yielders`.

#### Tenekon.Coroutines.Yielders.Call

The `Call` yielder is the equivalent to the `call` effect in Redux-Saga.

Expand All @@ -140,7 +209,7 @@ Use it to instruct the coroutine to suspend and invoke `provider` with optional
| `Call` | `Coroutine<TResult> Call<TResult>(Func<Coroutine<TResult>> provider)` |
| `Call` | `Coroutine<TResult> Call<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

#### Launch
#### Tenekon.Coroutines.Yielders.Launch

The `Launch` yielder is the equivalent to the `fork` effect in Redux-Saga.

Expand All @@ -153,7 +222,7 @@ Use it to instruct the coroutine to suspend and invoke `provider` with optional
| `Launch` | `Coroutine<CoroutineAwaitable<TResult>> Launch<TResult>(Func<Coroutine<TResult>> provider)` |
| `Launch` | `Coroutine<CoroutineAwaitable<TResult>> Launch<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

#### Spawn
#### Tenekon.Coroutines.Yielders.Spawn

The `Spawn` yielder is the equivalent to the `spawn` effect in Redux-Saga.

Expand All @@ -166,15 +235,15 @@ Use it to instruct the coroutine to suspend and invoke `provider` with optional
| `Spawn` | `Coroutine<CoroutineAwaitable<TResult>> Spawn<TResult>(Func<Coroutine<TResult>> provider)` |
| `Spawn` | `Coroutine<CoroutineAwaitable<TResult>> Spawn<TClosure, TResult>(Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

#### Spawn
#### Tenekon.Coroutines.Yielders.Spawn

Use `Throw` to instruct the coroutine to suspend and let the coroutine throw `exception`. The coroutine resumes immediatelly and throws `excpetion`.

| Yielder | Signature |
| ------- | -------------------------------------- |
| `Throw` | `Coroutine Throw(Exception exception)` |

#### WithContext
#### Tenekon.Coroutines.Yielders.WithContext

Use `WithContext` to instruct the coroutine to suspend and invoke `provider` with `additiveContext` and optional `closure`.

Expand All @@ -185,23 +254,25 @@ Use `WithContext` to instruct the coroutine to suspend and invoke `provider` wit
| `WithContext` | `Coroutine<TResult> WithContext<TResult>(CoroutineContext additiveContext, Func<Coroutine<TResult>> provider)` |
| `WithContext` | `Coroutine<TResult> WithContext<TClosure, TResult>(CoroutineContext additiveContext, Func<TClosure, Coroutine<TResult>> provider, TClosure closure)` |

#### Yield

Use `Yield` to instruct the coroutine to suspend and resume immediatelly. In the underlying is `Task.Yield()` used.

| Yielder | Signature |
| ------- | ----------------------------- |
| `Yield` | `Coroutine Coroutine Yield()` |

#### YieldReturn
#### Tenekon.Coroutines.Yielders.YieldReturn

Use `YieldReturn` to instruct the coroutine to suspend and resume immediatelly. Allows the coroutine middleware to get the hands on `value`.

| Yielder | Signature |
| ------------- | ----------------------------------- |
| `YieldReturn` | `Coroutine YieldReturn<T>(T value)` |

## Tenekon.Coroutines.Iterators.Yielders
#### Tenekon.Coroutines.Yielders.YieldAssign

Use `YieldAssign` to instruct the coroutine to suspend and resume immediatelly.
Allows the coroutine middleware to get the hands on `value` and also to yield assign a custom value of type `TAssign`.
If you do not yield assign a custom value of type `TAssign`, then `default(TAssign)` is yield assigned.

| Yielder | Signature |
| ------------- | ------------------------------------------------------------------------------------------------ |
| `YieldAssign` | `YieldAssign<TYield> Yield<TYield>(TYield value) => new(value)` -> `Coroutine<TAssign> Assign<TAssign>()` |

## Tenekon.Coroutines.Yielders.Exchange

Use `Exchange` to instruct the coroutine to suspend and resume immediatelly. Allows the coroutine middleware to get the hands on `value` and exchange it.

Expand Down
3 changes: 2 additions & 1 deletion samples/Playground/Playground.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
Expand Down
19 changes: 8 additions & 11 deletions samples/Playground/Program.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
using Tenekon.Coroutines;
using System.Diagnostics;
using Tenekon.Coroutines;

await Coroutine.Start(async () =>
{
Console.WriteLine(Environment.CurrentManagedThreadId);

var t1 = await Coroutine.Factory.StartNew(async () =>
var t1 = await Coroutine.Factory.StartNew(new Func<Coroutine>(async () =>
{
Console.WriteLine("Hello World " + Environment.CurrentManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine("Finished");
});
Console.WriteLine(DateTime.UtcNow - Process.GetCurrentProcess().StartTime.ToUniversalTime() + ": Finished");
}));

var t2 = await Coroutine.Factory.StartNew(async () =>
var t2 = await Coroutine.Factory.StartNew(new Func<Coroutine>(async () =>
{
Console.WriteLine("Hello World " + Environment.CurrentManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine("Finished");
});
Console.WriteLine(DateTime.UtcNow - Process.GetCurrentProcess().StartTime.ToUniversalTime() + ": Finished");
}));

await Task.WhenAll(t1.AsTask(), t2.AsTask());
});
14 changes: 14 additions & 0 deletions src/Tenekon.Coroutines/Coroutines/Coroutine.Yield.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Tenekon.Coroutines.Sources;
using static Tenekon.Coroutines.Yielders.Arguments;

namespace Tenekon.Coroutines;

partial struct Coroutine
{
public static Coroutine Yield()
{
var completionSource = ManualResetCoroutineCompletionSource<VoidCoroutineResult>.RentFromCache();
var argument = new YieldArgument(completionSource);
return new(completionSource, argument);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ partial class AsyncIteratorImpl<TResult>

ValueTask<bool> IAsyncIterator<TResult>.MoveNextAsync() => MoveNextAsync();

void IAsyncIterator<TResult>.YieldReturn<TYieldResult>(TYieldResult result) => YieldReturn(result);
void IAsyncIterator<TResult>.YieldAssign<TYieldResult>(TYieldResult result) => YieldAssign(result);

void IAsyncIterator<TResult>.YieldReturn() => YieldReturn();
void IAsyncIterator<TResult>.YieldAssign() => YieldAssign();

void IAsyncIterator<TResult>.Return(TResult result) => Return(result);

Expand Down
4 changes: 2 additions & 2 deletions src/Tenekon.Coroutines/Coroutines/Iterators/AsyncIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ partial class AsyncIteratorImpl<TResult>

ValueTask<bool> IAsyncIterator.MoveNextAsync() => MoveNextAsync();

void IAsyncIterator.YieldReturn<TYieldResult>(TYieldResult result) => YieldReturn(result);
void IAsyncIterator.YieldAssign<TYieldResult>(TYieldResult result) => YieldAssign(result);

void IAsyncIterator.YieldReturn() => YieldReturn();
void IAsyncIterator.YieldAssign() => YieldAssign();

void IAsyncIterator.Return() => Return(default!);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public async ValueTask<bool> MoveNextAsync()
return false;
}

public void YieldReturn<TYieldResult>(TYieldResult result)
public void YieldAssign<TYieldResult>(TYieldResult result)
{
if (_nextSuspensionPoint._state != 0) {
try {
Expand All @@ -220,7 +220,7 @@ public void YieldReturn<TYieldResult>(TYieldResult result)
}
}

public void YieldReturn()
public void YieldAssign()
{
if (_nextSuspensionPoint._state != 0) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public interface IAsyncIterator<TResult>
/// <returns></returns>
ValueTask<bool> MoveNextAsync();

void YieldReturn<TYieldResult>(TYieldResult result);
void YieldAssign<TYieldResult>(TYieldResult result);

void YieldReturn();
void YieldAssign();

void Return(TResult result);

Expand Down
4 changes: 2 additions & 2 deletions src/Tenekon.Coroutines/Coroutines/Iterators/IAsyncIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public interface IAsyncIterator
/// <returns></returns>
ValueTask<bool> MoveNextAsync();

void YieldReturn<TYieldResult>(TYieldResult result);
void YieldAssign<TYieldResult>(TYieldResult result);

void YieldReturn();
void YieldAssign();

void Return();

Expand Down
8 changes: 0 additions & 8 deletions src/Tenekon.Coroutines/Coroutines/Yielders.Yield.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
using Tenekon.Coroutines.Sources;
using static Tenekon.Coroutines.Yielders.Arguments;

namespace Tenekon.Coroutines;

partial class Yielders
{
public static Coroutine Yield()
{
var completionSource = ManualResetCoroutineCompletionSource<VoidCoroutineResult>.RentFromCache();
var argument = new YieldArgument(completionSource);
return new(completionSource, argument);
}

partial class Arguments
{
public class YieldArgument : ICallableArgument<ManualResetCoroutineCompletionSource<VoidCoroutineResult>>, ISiblingCoroutine
Expand Down
Loading

0 comments on commit d19f885

Please sign in to comment.