reduce AsyncTrigger allocation

master
neuecc 2020-05-16 23:31:49 +09:00
parent 79f770e687
commit 859eaa2278
8 changed files with 704 additions and 538 deletions

View File

@ -15,6 +15,55 @@ using System.Reactive.Concurrency;
namespace NetCoreSandbox namespace NetCoreSandbox
{ {
public class Text
{
public string text { get; set; }
}
public static partial class UnityUIComponentExtensions
{
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, Text text)
{
AAAACORECORE(source, text).Forget();
async UniTaskVoid AAAACORECORE(IUniTaskAsyncEnumerable<string> source2, Text text2)
{
var e = source2.GetAsyncEnumerator();
try
{
while (await e.MoveNextAsync())
{
text2.text = e.Current;
// action(e.Current);
}
}
finally
{
if (e != null)
{
await e.DisposeAsync();
}
}
}
}
//public static IDisposable SubscribeToText<T>(this IObservable<T> source, Text text)
//{
// return source.SubscribeWithState(text, (x, t) => t.text = x.ToString());
//}
//public static IDisposable SubscribeToText<T>(this IObservable<T> source, Text text, Func<T, string> selector)
//{
// return source.SubscribeWithState2(text, selector, (x, t, s) => t.text = s(x));
//}
//public static IDisposable SubscribeToInteractable(this IObservable<bool> source, Selectable selectable)
//{
// return source.SubscribeWithState(selectable, (x, s) => s.interactable = x);
//}
}
class Program class Program
{ {
static string FlattenGenArgs(Type type) static string FlattenGenArgs(Type type)

View File

@ -19,56 +19,13 @@ namespace Cysharp.Threading.Tasks.Triggers
} }
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class AsyncAwakeTrigger : MonoBehaviour public sealed class AsyncAwakeTrigger : AsyncTriggerBase<AsyncUnit>
{ {
bool called = false;
TriggerEvent<AsyncUnit> triggerEvent;
void Awake()
{
called = true;
triggerEvent?.TrySetResult(AsyncUnit.Default);
triggerEvent = null;
}
public UniTask AwakeAsync() public UniTask AwakeAsync()
{ {
if (called) return UniTask.CompletedTask; if (calledAwake) return UniTask.CompletedTask;
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this)); return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(this, true)).OneShotAsync();
if (triggerEvent == null)
{
triggerEvent = new TriggerEvent<AsyncUnit>();
}
return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(triggerEvent, true)).OneShotAsync();
}
private void OnDestroy()
{
triggerEvent?.TrySetCanceled(CancellationToken.None);
}
class AwakeMonitor : IPlayerLoopItem
{
readonly AsyncAwakeTrigger trigger;
public AwakeMonitor(AsyncAwakeTrigger trigger)
{
this.trigger = trigger;
}
public bool MoveNext()
{
if (trigger.called) return false;
if (trigger == null)
{
trigger.OnDestroy();
return false;
}
return true;
}
} }
} }
} }

View File

@ -19,11 +19,10 @@ namespace Cysharp.Threading.Tasks.Triggers
} }
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class AsyncDestroyTrigger : MonoBehaviour public sealed class AsyncDestroyTrigger : MonoBehaviour
{ {
bool awakeCalled = false; bool awakeCalled = false;
bool called = false; bool called = false;
TriggerEvent<AsyncUnit> triggerEvent;
CancellationTokenSource cancellationTokenSource; CancellationTokenSource cancellationTokenSource;
public CancellationToken CancellationToken public CancellationToken CancellationToken
@ -34,6 +33,12 @@ namespace Cysharp.Threading.Tasks.Triggers
{ {
cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource = new CancellationTokenSource();
} }
if (!awakeCalled)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
return cancellationTokenSource.Token; return cancellationTokenSource.Token;
} }
} }
@ -47,28 +52,24 @@ namespace Cysharp.Threading.Tasks.Triggers
{ {
called = true; called = true;
triggerEvent?.TrySetResult(AsyncUnit.Default);
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose(); cancellationTokenSource?.Dispose();
triggerEvent = null;
} }
public UniTask OnDestroyAsync() public UniTask OnDestroyAsync()
{ {
if (called) return UniTask.CompletedTask; if (called) return UniTask.CompletedTask;
if (!awakeCalled) var tcs = new UniTaskCompletionSource();
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
if (triggerEvent == null) // OnDestroy = Called Cancel.
CancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{ {
triggerEvent = new TriggerEvent<AsyncUnit>(); var tcs2 = (UniTaskCompletionSource)state;
} tcs2.TrySetResult();
}, tcs);
return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(triggerEvent, true)).OneShotAsync(); return tcs.Task;
} }
class AwakeMonitor : IPlayerLoopItem class AwakeMonitor : IPlayerLoopItem

View File

@ -1,6 +1,5 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System.Threading;
using UnityEngine; using UnityEngine;
namespace Cysharp.Threading.Tasks.Triggers namespace Cysharp.Threading.Tasks.Triggers
@ -19,67 +18,21 @@ namespace Cysharp.Threading.Tasks.Triggers
} }
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class AsyncStartTrigger : MonoBehaviour public sealed class AsyncStartTrigger : AsyncTriggerBase<AsyncUnit>
{ {
bool awakeCalled = false; bool called;
bool called = false;
TriggerEvent<AsyncUnit> triggerEvent;
void Awake()
{
awakeCalled = true;
}
void Start() void Start()
{ {
called = true; called = true;
triggerEvent?.TrySetResult(AsyncUnit.Default); RaiseEvent(AsyncUnit.Default);
triggerEvent = null;
} }
public UniTask StartAsync() public UniTask StartAsync()
{ {
if (called) return UniTask.CompletedTask; if (called) return UniTask.CompletedTask;
if (!awakeCalled) return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(this, true)).OneShotAsync();
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
if (triggerEvent == null)
{
triggerEvent = new TriggerEvent<AsyncUnit>();
}
return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(triggerEvent, true)).OneShotAsync();
}
private void OnDestroy()
{
triggerEvent?.TrySetCanceled(CancellationToken.None);
}
class AwakeMonitor : IPlayerLoopItem
{
readonly AsyncStartTrigger trigger;
public AwakeMonitor(AsyncStartTrigger trigger)
{
this.trigger = trigger;
}
public bool MoveNext()
{
if (trigger.called) return false;
if (trigger == null)
{
trigger.OnDestroy();
return false;
}
return true;
}
} }
} }
}
}

View File

@ -10,67 +10,67 @@ namespace Cysharp.Threading.Tasks.Triggers
{ {
public abstract class AsyncTriggerBase<T> : MonoBehaviour, IUniTaskAsyncEnumerable<T> public abstract class AsyncTriggerBase<T> : MonoBehaviour, IUniTaskAsyncEnumerable<T>
{ {
protected TriggerEvent<T> triggerEvent; TriggerEvent<T> triggerEvent;
bool calledAwake; internal protected bool calledAwake;
bool calledDestroy; internal protected bool calledDestroy;
ICancelPromise triggerSlot;
void Awake() void Awake()
{ {
calledAwake = true; calledAwake = true;
} }
protected TriggerEvent<T> GetTriggerEvent()
{
if (triggerEvent == null)
{
triggerEvent = new TriggerEvent<T>();
if (triggerSlot == null)
{
triggerSlot = triggerEvent;
}
else
{
throw new InvalidOperationException("triggerSlot is already filled.");
}
}
if (!calledAwake)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
return triggerEvent;
}
void OnDestroy() void OnDestroy()
{ {
if (calledDestroy) return; if (calledDestroy) return;
calledDestroy = true; calledDestroy = true;
triggerSlot?.TrySetCanceled(); triggerEvent.TrySetCanceled(CancellationToken.None);
triggerSlot = null; }
internal void AddHandler(IResolveCancelPromise<T> handler)
{
if (!calledAwake)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
triggerEvent.Add(handler);
}
internal void RemoveHandler(IResolveCancelPromise<T> handler)
{
if (!calledAwake)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, new AwakeMonitor(this));
}
triggerEvent.Remove(handler);
}
protected void RaiseEvent(T value)
{
triggerEvent.TrySetResult(value);
} }
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new AsyncTriggerEnumerator(GetTriggerEvent(), cancellationToken); return new AsyncTriggerEnumerator(this, cancellationToken);
} }
sealed class AsyncTriggerEnumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, IResolveCancelPromise<T> sealed class AsyncTriggerEnumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, IResolveCancelPromise<T>
{ {
static Action<object> cancellationCallback = CancellationCallback; static Action<object> cancellationCallback = CancellationCallback;
readonly TriggerEvent<T> triggerEvent; readonly AsyncTriggerBase<T> parent;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration registration; CancellationTokenRegistration registration;
bool called; bool called;
bool isDisposed; bool isDisposed;
public AsyncTriggerEnumerator(TriggerEvent<T> triggerEvent, CancellationToken cancellationToken) public AsyncTriggerEnumerator(AsyncTriggerBase<T> parent, CancellationToken cancellationToken)
{ {
this.triggerEvent = triggerEvent; this.parent = parent;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
} }
@ -105,13 +105,13 @@ namespace Cysharp.Threading.Tasks.Triggers
called = true; called = true;
TaskTracker.TrackActiveTask(this, 3); TaskTracker.TrackActiveTask(this, 3);
triggerEvent.Add(this); parent.AddHandler(this);
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
} }
} }
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -122,7 +122,7 @@ namespace Cysharp.Threading.Tasks.Triggers
isDisposed = true; isDisposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
registration.Dispose(); registration.Dispose();
triggerEvent.Remove(this); parent.RemoveHandler(this);
} }
return default; return default;
@ -169,7 +169,7 @@ namespace Cysharp.Threading.Tasks.Triggers
{ {
static Action<object> cancellationCallback = CancellationCallback; static Action<object> cancellationCallback = CancellationCallback;
readonly TriggerEvent<T> trigger; readonly AsyncTriggerBase<T> trigger;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration registration; CancellationTokenRegistration registration;
@ -180,7 +180,7 @@ namespace Cysharp.Threading.Tasks.Triggers
internal CancellationToken CancellationToken => cancellationToken; internal CancellationToken CancellationToken => cancellationToken;
public AsyncTriggerHandler(TriggerEvent<T> trigger, bool callOnce) internal AsyncTriggerHandler(AsyncTriggerBase<T> trigger, bool callOnce)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -193,12 +193,12 @@ namespace Cysharp.Threading.Tasks.Triggers
this.registration = default; this.registration = default;
this.callOnce = callOnce; this.callOnce = callOnce;
trigger.Add(this); trigger.AddHandler(this);
TaskTracker.TrackActiveTask(this, 3); TaskTracker.TrackActiveTask(this, 3);
} }
public AsyncTriggerHandler(TriggerEvent<T> trigger, CancellationToken cancellationToken, bool callOnce) internal AsyncTriggerHandler(AsyncTriggerBase<T> trigger, CancellationToken cancellationToken, bool callOnce)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -210,7 +210,7 @@ namespace Cysharp.Threading.Tasks.Triggers
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.callOnce = callOnce; this.callOnce = callOnce;
trigger.Add(this); trigger.AddHandler(this);
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
@ -235,7 +235,7 @@ namespace Cysharp.Threading.Tasks.Triggers
isDisposed = true; isDisposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
registration.Dispose(); registration.Dispose();
trigger.Remove(this); trigger.RemoveHandler(this);
} }
} }
@ -285,7 +285,8 @@ namespace Cysharp.Threading.Tasks.Triggers
} }
} }
public sealed class TriggerEvent<T> : IResolveCancelPromise<T> // be careful to use, itself is struct.
public struct TriggerEvent<T>
{ {
// optimize: many cases, handler is single. // optimize: many cases, handler is single.
IResolveCancelPromise<T> singleHandler; IResolveCancelPromise<T> singleHandler;

View File

@ -151,27 +151,27 @@ namespace Cysharp.Threading.Tasks.Triggers
{ {
void <#= (t.handlerInterface == null) ? "" : $"{t.handlerInterface}." #><#= t.methodName #>(<#= BuildMethodArgument(t.arguments) #>) void <#= (t.handlerInterface == null) ? "" : $"{t.handlerInterface}." #><#= t.methodName #>(<#= BuildMethodArgument(t.arguments) #>)
{ {
triggerEvent?.TrySetResult(<#= BuildResultParameter(t.arguments) #>); RaiseEvent(<#= BuildResultParameter(t.arguments) #>);
} }
public <#= ToInterfaceName(t.methodName) #> Get<#= t.methodName #>AsyncHandler() public <#= ToInterfaceName(t.methodName) #> Get<#= t.methodName #>AsyncHandler()
{ {
return new AsyncTriggerHandler<<#= t.returnType #>>(GetTriggerEvent(), false); return new AsyncTriggerHandler<<#= t.returnType #>>(this, false);
} }
public <#= ToInterfaceName(t.methodName) #> Get<#= t.methodName #>AsyncHandler(CancellationToken cancellationToken) public <#= ToInterfaceName(t.methodName) #> Get<#= t.methodName #>AsyncHandler(CancellationToken cancellationToken)
{ {
return new AsyncTriggerHandler<<#= t.returnType #>>(GetTriggerEvent(), cancellationToken, false); return new AsyncTriggerHandler<<#= t.returnType #>>(this, cancellationToken, false);
} }
public <#= ToUniTaskName(t.returnType) #> <#= t.methodName #>Async() public <#= ToUniTaskName(t.returnType) #> <#= t.methodName #>Async()
{ {
return ((<#= ToInterfaceName(t.methodName) #>)new AsyncTriggerHandler<<#= t.returnType #>>(GetTriggerEvent(), true)).<#= t.methodName #>Async(); return ((<#= ToInterfaceName(t.methodName) #>)new AsyncTriggerHandler<<#= t.returnType #>>(this, true)).<#= t.methodName #>Async();
} }
public <#= ToUniTaskName(t.returnType) #> <#= t.methodName #>Async(CancellationToken cancellationToken) public <#= ToUniTaskName(t.returnType) #> <#= t.methodName #>Async(CancellationToken cancellationToken)
{ {
return ((<#= ToInterfaceName(t.methodName) #>)new AsyncTriggerHandler<<#= t.returnType #>>(GetTriggerEvent(), cancellationToken, true)).<#= t.methodName #>Async(); return ((<#= ToInterfaceName(t.methodName) #>)new AsyncTriggerHandler<<#= t.returnType #>>(this, cancellationToken, true)).<#= t.methodName #>Async();
} }
} }
<# if(Is2019_3(t.triggerName)) { #> <# if(Is2019_3(t.triggerName)) { #>

View File

@ -37,6 +37,200 @@ public enum MyEnum
A, B, C A, B, C
} }
public interface IAsyncReadOnlyReactiveProperty<T> : IUniTaskAsyncEnumerable<T>
{
T Value { get; }
}
public interface IAsyncReactiveProperty<T> : IAsyncReadOnlyReactiveProperty<T>
{
new T Value { get; set; }
}
[Serializable]
public struct AsyncValueReactiveProperty<T> : IUniTaskAsyncEnumerable<T>
{
TriggerEvent<T> triggerEvent;
[SerializeField]
T latestValue;
public T Value
{
get
{
return latestValue;
}
set
{
this.latestValue = value;
triggerEvent.TrySetResult(value);
}
}
public AsyncValueReactiveProperty(T value)
{
this.latestValue = value;
this.triggerEvent = default;
}
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken)
{
return new Enumerator(triggerEvent, cancellationToken);
}
public class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, IResolveCancelPromise<T>
{
static Action<object> cancellationCallback = CancellationCallback;
readonly TriggerEvent<T> triggerEvent;
readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
T value;
public Enumerator(TriggerEvent<T> triggerEvent, CancellationToken cancellationToken)
{
this.triggerEvent = triggerEvent;
this.cancellationToken = cancellationToken;
triggerEvent.Add(this);
if (cancellationToken.CanBeCanceled)
{
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
}
}
public T Current => value;
public UniTask<bool> MoveNextAsync()
{
completionSource.Reset();
return new UniTask<bool>(this, completionSource.Version);
}
public UniTask DisposeAsync()
{
triggerEvent.TrySetCanceled(cancellationToken);
triggerEvent.Remove(this);
return default;
}
public bool TrySetResult(T value)
{
this.value = value;
return triggerEvent.TrySetResult(value);
}
public bool TrySetCanceled(CancellationToken cancellationToken = default)
{
DisposeAsync().Forget();
return true;
}
static void CancellationCallback(object state)
{
var self = (Enumerator)state;
self.DisposeAsync().Forget();
}
}
}
public static partial class UnityUIComponentExtensions
{
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, Text text, bool rebindOnError = true)
{
BindToCore(source, text, text.GetCancellationTokenOnDestroy(), rebindOnError).Forget();
}
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, Text text, CancellationToken cancellationToken, bool rebindOnError = true)
{
BindToCore(source, text, cancellationToken, rebindOnError).Forget();
}
static async UniTaskVoid BindToCore(IUniTaskAsyncEnumerable<string> source, Text text, CancellationToken cancellationToken, bool rebindOnError)
{
var repeat = false;
BIND_AGAIN:
var e = source.GetAsyncEnumerator(cancellationToken);
try
{
while (true)
{
bool moveNext;
try
{
moveNext = await e.MoveNextAsync();
repeat = false;
}
catch (Exception ex)
{
if (ex is OperationCanceledException) return;
if (rebindOnError && !repeat)
{
repeat = true;
if (e != null)
{
await e.DisposeAsync();
}
goto BIND_AGAIN;
}
else
{
throw;
}
}
if (!moveNext) return;
text.text = e.Current;
}
}
finally
{
if (e != null)
{
await e.DisposeAsync();
}
}
}
//public static IDisposable SubscribeToText<T>(this IObservable<T> source, Text text)
//{
// return source.SubscribeWithState(text, (x, t) => t.text = x.ToString());
//}
//public static IDisposable SubscribeToText<T>(this IObservable<T> source, Text text, Func<T, string> selector)
//{
// return source.SubscribeWithState2(text, selector, (x, t, s) => t.text = s(x));
//}
//public static IDisposable SubscribeToInteractable(this IObservable<bool> source, Selectable selectable)
//{
// return source.SubscribeWithState(selectable, (x, s) => s.interactable = x);
//}
}
public static class MyClass
{
}
public class SandboxMain : MonoBehaviour public class SandboxMain : MonoBehaviour
{ {
public Button okButton; public Button okButton;
@ -148,16 +342,27 @@ public class SandboxMain : MonoBehaviour
// UniTaskAsyncEnumerable.EveryUpdate(PlayerLoopTiming.FixedUpdate) // UniTaskAsyncEnumerable.EveryUpdate(PlayerLoopTiming.FixedUpdate)
await UniTask.Yield(PlayerLoopTiming.Update); this.GetAsyncUpdateTrigger().ForEachAsync(_ =>
Debug.Log("Start:" + Time.frameCount); {
UnityEngine.Debug.Log("Update Trigger 1");
}).Forget();
await UniTaskAsyncEnumerable.TimerFrame(3, 5, PlayerLoopTiming.PostLateUpdate)
.Select(x => x) this.GetAsyncUpdateTrigger().ForEachAsync(_ =>
.Do(x => Debug.Log("DODODO")) {
.ForEachAsync(_ => UnityEngine.Debug.Log("Update Trigger 2");
{ }).Forget();
Debug.Log("Call:" + Time.frameCount);
}, cancellationToken: this.GetCancellationTokenOnDestroy()); //await UniTask.Yield(PlayerLoopTiming.Update);
//Debug.Log("Start:" + Time.frameCount);
//await UniTaskAsyncEnumerable.TimerFrame(3, 5, PlayerLoopTiming.PostLateUpdate)
// .Select(x => x)
// .Do(x => Debug.Log("DODODO"))
// .ForEachAsync(_ =>
// {
// Debug.Log("Call:" + Time.frameCount);
// }, cancellationToken: this.GetCancellationTokenOnDestroy());
//try //try
//{ //{