Skip to content

Commit

Permalink
Merge pull request #80 from hadashiA/remove-unitask
Browse files Browse the repository at this point in the history
Make UniTask as optional
  • Loading branch information
hadashiA authored Sep 22, 2024
2 parents 695283d + 91214bf commit 5f549a0
Show file tree
Hide file tree
Showing 34 changed files with 539 additions and 357 deletions.
33 changes: 16 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ public partial class ExamplePresenter
}
```

| Feature | Description |
|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Declarative routing | The event delivery destination and interceptor stack are self-explanatory in the type definition. |
| Async/non-Async handlers | Integrate with async/await (with UniTask), and providing optimized fast pass for non-async way |
| With DI and without DI | Auto-wiring the publisher/subscriber reference by DI (Dependency Injection). But can be used without DI for any project |
| Fast, less allocations | The SourceGenerator eliminates meta-programming overhead and is more attentive to performance. See [Performance](#performance) section for details. |
| Async sequence controlling (Parallel, FIFO, Drop, Switch) | The behavior when async processes are executed in parallel can be specified in detail. See [Command ordering](#command-ordering) section for details. |
| Feature | Description |
|-------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Declarative routing | The event delivery destination and interceptor stack are self-explanatory in the type definition. |
| Async/non-Async handlers | Integrate with async/await, and providing optimized fast pass for non-async way |
| With DI and without DI | Auto-wiring the publisher/subscriber reference by DI (Dependency Injection). But can be used without DI for any project |
| Fast, less allocations | The SourceGenerator eliminates meta-programming overhead and is more attentive to performance. See [Performance](#performance) section for details. |
| Async sequence controlling (Parallel, FIFO, Drop, Switch) | The behavior when async processes are executed in parallel can be specified in detail. See [Command ordering](#command-ordering) section for details. |
| Scripting with [mruby](https://github.com/mruby/mruby) (Optional) | You can write ruby scripts to publish messages to the C# async handlers. This is very useful for adding a layer to your game that allows scripted hot loading. See [VitalRouter.MRuby](#mruby-scripting) section for details. |

## Table of Contents
Expand Down Expand Up @@ -85,9 +85,8 @@ Prerequirements:

- Unity 2022.2+
- This limitation is due to the use of the Incremental Source Generator.
- Install [UniTask](https://github.com/Cysharp/UniTask)
- Currently, VitalRouter uses UniTask instead of `UnityEngine.Awaitable`. UniTask is a fully featured and fast awaitable implementation.
- In a future, if `UnityEngine.Awaitable` is enhanced in a future version of Unity, it may be migrated.
- (optional )Install [UniTask](https://github.com/Cysharp/UniTask) >= 2.5.5
- If UniTask is installed in your project, the `VITALROUTER_UNITASK_INTEGRATION` flag is turned on and the optimized GC-free code is executed.
- (optional) Install [VContainer](https://github.com/hadashiA/VContainer) >= 1.15.1
- For bringing in DI style, VitalRouter supports Integration with VContainer, a fast and lightweight DI container for Unity.

Expand Down Expand Up @@ -467,7 +466,7 @@ router.SubscribeAwait(new AsyncSubscriber());

class AsyncSubscriber : IAsyncCommandSubscriber
{
pubilc UniTask Receive<T>(T cmd, PublishContext ctx) where T : ICommand { /* ... */ }
pubilc ValueTask Receive<T>(T cmd, PublishContext ctx) where T : ICommand { /* ... */ }
}

// Add interceptor via lambda expresion
Expand Down Expand Up @@ -517,10 +516,10 @@ Example 1: Some kind of processing is interspersed before and after the command
```cs
class Logging : ICommandInterceptor
{
public async UniTask InvokeAsync<T>(
public async ValueTask InvokeAsync<T>(
T command,
CancellationToken cancellation,
Func<T, CancellationToken, UniTask> next)
Func<T, CancellationToken, ValueTask> next)
where T : ICommand
{
UnityEngine.Debug.Log($"Start {typeof(T)}");
Expand All @@ -536,10 +535,10 @@ Example 2: try/catch all subscribers exceptions.
```cs
class ExceptionHandling : ICommandInterceptor
{
public async UniTask InvokeAsync<T>(
public async ValueTask InvokeAsync<T>(
T command,
CancellationToken cancellation,
Func<T, CancellationToken, UniTask> next)
Func<T, CancellationToken, ValueTask> next)
where T : ICommand
{
try
Expand All @@ -560,10 +559,10 @@ Example 3: Filtering command.
```cs
class MyFilter : ICommandInterceptor
{
public async UniTask InvokeAsync<T>(
public async ValueTask InvokeAsync<T>(
T command,
CancellationToken cancellation,
Func<T, CancellationToken, UniTask> next)
Func<T, CancellationToken, ValueTask> next)
where T : ICommand
{
if (command is FooCommand { X: > 100 } cmd)
Expand Down
1 change: 1 addition & 0 deletions src/VitalRouter.SourceGenerator/RouteMethodMeta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class RouteMethodMeta
public bool TakePublishContext { get; }

public bool IsAsync => Symbol.IsAsync || !Symbol.ReturnsVoid;
public bool IsValueTask() => SymbolEqualityComparer.Default.Equals(Symbol.ReturnType, references.ValueTaskType);
public bool IsUniTask() => SymbolEqualityComparer.Default.Equals(Symbol.ReturnType, references.UniTaskType);

readonly ReferenceSymbols references;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ static bool TryEmit(TypeMeta typeMeta, ReferenceSymbols references, StringBuilde
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using VitalRouter;
using VitalRouter.Internal;

Expand Down Expand Up @@ -431,7 +431,7 @@ class VitalRouterGeneratedAsyncSubscriber : IAsyncCommandSubscriber
{
static class MethodTable<T> where T : ICommand
{
public static Func<{{typeMeta.FullTypeName}}, T, PublishContext, UniTask>? Value;
public static Func<{{typeMeta.FullTypeName}}, T, PublishContext, ValueTask>? Value;
public static Func<VitalRouterGeneratedAsyncSubscriber, ICommandInterceptor[]>? InterceptorFinder;
}

Expand Down Expand Up @@ -518,7 +518,7 @@ public VitalRouterGeneratedAsyncSubscriber({{string.Join(", ", constructorParams
builder.AppendLine($$"""
}

public UniTask ReceiveAsync<T>(T command, PublishContext context) where T : ICommand
public ValueTask ReceiveAsync<T>(T command, PublishContext context) where T : ICommand
{
if (MethodTable<T>.Value is { } method)
{
Expand All @@ -529,7 +529,7 @@ public UniTask ReceiveAsync<T>(T command, PublishContext context) where T : ICom
var interceptorStack = finder.Invoke(this);
return ReceiveContext<T>.InvokeAsync(command, interceptorStack, context);
}
return UniTask.CompletedTask;
return default;
}
}
""");
Expand All @@ -552,7 +552,7 @@ class VitalRouterGeneratedAsyncSubscriberCore : ICommandInterceptor
{
static class MethodTable<T> where T : ICommand
{
public static Func<{{typeMeta.TypeName}}, T, PublishContext, UniTask>? Value;
public static Func<{{typeMeta.TypeName}}, T, PublishContext, ValueTask>? Value;
}

static VitalRouterGeneratedAsyncSubscriberCore()
Expand All @@ -575,9 +575,9 @@ public VitalRouterGeneratedAsyncSubscriberCore({{typeMeta.TypeName}} source)
this.source = source;
}

public UniTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> _) where T : ICommand
public ValueTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> _) where T : ICommand
{
return MethodTable<T>.Value?.Invoke(source, command, context) ?? UniTask.CompletedTask;
return MethodTable<T>.Value?.Invoke(source, command, context) ?? default;
}
}
""");
Expand All @@ -590,36 +590,36 @@ static string GetMethodTableEntry(RouteMethodMeta method)
{
if (method.TakePublishContext)
{
if (method.IsUniTask())
if (method.IsValueTask())
{
return $"static (source, command, context) => source.{method.Symbol.Name}(command, context)";
return $"static (source, command, context) => (ValueTask)source.{method.Symbol.Name}(command, context)";
}
return $"static async (source, command, context) => await source.{method.Symbol.Name}(command, context)";
}
if (method.TakeCancellationToken)
{
if (method.IsUniTask())
if (method.IsValueTask() || method.IsUniTask())
{
return $"static (source, command, context) => source.{method.Symbol.Name}(command, context.CancellationToken)";
return $"static (source, command, context) => (ValueTask)source.{method.Symbol.Name}(command, context.CancellationToken)";
}
return $"static async (source, command, context) => await source.{method.Symbol.Name}(command, context.CancellationToken)";
}
if (method.IsUniTask())
if (method.IsValueTask() || method.IsUniTask())
{
return $"static (source, command, context) => source.{method.Symbol.Name}(command)";
return $"static (source, command, context) => (ValueTask)source.{method.Symbol.Name}(command)";
}
return $"static async (source, command, context) => await source.{method.Symbol.Name}(command)";
}

if (method.TakePublishContext)
{
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command, context); return UniTask.CompletedTask; }}";
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command, context); return default; }}";
}

if (method.TakeCancellationToken)
{
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command, context.CancellationToken); return UniTask.CompletedTask; }}";
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command, context.CancellationToken); return default; }}";
}
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command); return UniTask.CompletedTask; }}";
return $"static (source, command, context) => {{ source.{method.Symbol.Name}(command); return default; }}";
}
}
9 changes: 4 additions & 5 deletions src/VitalRouter.Unity/Assets/Sandbox/Controllers.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Sandbox;
using UnityEngine;
using VContainer.Unity;
using VitalRouter;


public class LoggingInterceptor : ICommandInterceptor
{
public async UniTask InvokeAsync<T>(T command, PublishContext ctx, PublishContinuation<T> next) where T : ICommand
public async ValueTask InvokeAsync<T>(T command, PublishContext ctx, PublishContinuation<T> next) where T : ICommand
{
var path = ctx.CallerFilePath.Replace(Application.dataPath, "Assets");
UnityEngine.Debug.Log($"publish {ctx.CallerMemberName} at (<a href=\"{path}\" line=\"{ctx.CallerLineNumber}\">{path}:{ctx.CallerLineNumber}</a>) {command.GetType()}");
Expand All @@ -17,7 +16,7 @@ public async UniTask InvokeAsync<T>(T command, PublishContext ctx, PublishContin

public class AInterceptor : ICommandInterceptor
{
public UniTask InvokeAsync<T>(T command, PublishContext cancellation, PublishContinuation<T> next)
public ValueTask InvokeAsync<T>(T command, PublishContext cancellation, PublishContinuation<T> next)
where T : ICommand
{
return next(command, cancellation);
Expand All @@ -26,7 +25,7 @@ public UniTask InvokeAsync<T>(T command, PublishContext cancellation, PublishCon

public class BInterceptor : ICommandInterceptor
{
public UniTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> next)
public ValueTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> next)
where T : ICommand
{
return next(command, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public SampleEntryPoint(Router router)
public async UniTask StartAsync(CancellationToken cancellation)
{
Profiler.BeginSample("Publish!");
router.PublishAsync(new CharacterEnterCommand(), cancellation).Forget();
_ = router.PublishAsync(new CharacterEnterCommand(), cancellation);
Profiler.EndSample();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;

namespace VitalRouter.MRuby
{
Expand Down Expand Up @@ -30,7 +30,7 @@ public MRubyContextInterceptor(MRubyContext mrubyContext)
this.mrubyContext = mrubyContext;
}

public UniTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> next) where T : ICommand
public ValueTask InvokeAsync<T>(T command, PublishContext context, PublishContinuation<T> next) where T : ICommand
{
context.Extensions[MRubyContextKey] = mrubyContext;
return next(command, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Threading;
using System.Threading.Tasks;
using AOT;
#if VITALROUTER_UNITASK_INTEGRATION
using Cysharp.Threading.Tasks;
#endif

namespace VitalRouter.MRuby
{
Expand Down Expand Up @@ -46,24 +48,30 @@ public static bool TryRun(MRubyScript script, FixedUtf8String commandName, MrbVa

if (commandName.Equals(Names.WaitSecs))
{
throw new NotSupportedException($"{Names.WaitSecs} is only supported with UniTask. Prelase install UniTask");
#if VITALROUTER_UNITASK_INTEGRATION
var duration = MrbValueSerializer.Deserialize<double>(payload, script.Context);
UniTask.Create(async () =>
{
await UniTask.Delay(TimeSpan.FromSeconds(duration));
script.Resume();
});
return true;
#endif
}

if (commandName.Equals(Names.WaitFrames))
{
throw new NotSupportedException($"{Names.WaitFrames} is only supported with UniTask. Prelase install UniTask");
#if VITALROUTER_UNITASK_INTEGRATION
var duration = MrbValueSerializer.Deserialize<int>(payload, script.Context);
UniTask.Create(async () =>
{
await UniTask.DelayFrame(duration);
script.Resume();
});
return true;
#endif
}
return false;
}
Expand Down
Binary file not shown.
Loading

0 comments on commit 5f549a0

Please sign in to comment.