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
{
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
{
static string FlattenGenArgs(Type type)

View File

@ -19,56 +19,13 @@ namespace Cysharp.Threading.Tasks.Triggers
}
[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()
{
if (called) return UniTask.CompletedTask;
if (calledAwake) return UniTask.CompletedTask;
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 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;
}
return ((IAsyncOneShotTrigger)new AsyncTriggerHandler<AsyncUnit>(this, true)).OneShotAsync();
}
}
}

View File

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

View File

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

View File

@ -10,67 +10,67 @@ namespace Cysharp.Threading.Tasks.Triggers
{
public abstract class AsyncTriggerBase<T> : MonoBehaviour, IUniTaskAsyncEnumerable<T>
{
protected TriggerEvent<T> triggerEvent;
TriggerEvent<T> triggerEvent;
bool calledAwake;
bool calledDestroy;
ICancelPromise triggerSlot;
internal protected bool calledAwake;
internal protected bool calledDestroy;
void Awake()
{
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()
{
if (calledDestroy) return;
calledDestroy = true;
triggerSlot?.TrySetCanceled();
triggerSlot = null;
triggerEvent.TrySetCanceled(CancellationToken.None);
}
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)
{
return new AsyncTriggerEnumerator(GetTriggerEvent(), cancellationToken);
return new AsyncTriggerEnumerator(this, cancellationToken);
}
sealed class AsyncTriggerEnumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, IResolveCancelPromise<T>
{
static Action<object> cancellationCallback = CancellationCallback;
readonly TriggerEvent<T> triggerEvent;
readonly AsyncTriggerBase<T> parent;
CancellationToken cancellationToken;
CancellationTokenRegistration registration;
bool called;
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;
}
@ -105,13 +105,13 @@ namespace Cysharp.Threading.Tasks.Triggers
called = true;
TaskTracker.TrackActiveTask(this, 3);
triggerEvent.Add(this);
parent.AddHandler(this);
if (cancellationToken.CanBeCanceled)
{
registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
}
}
return new UniTask<bool>(this, completionSource.Version);
}
@ -122,7 +122,7 @@ namespace Cysharp.Threading.Tasks.Triggers
isDisposed = true;
TaskTracker.RemoveTracking(this);
registration.Dispose();
triggerEvent.Remove(this);
parent.RemoveHandler(this);
}
return default;
@ -169,7 +169,7 @@ namespace Cysharp.Threading.Tasks.Triggers
{
static Action<object> cancellationCallback = CancellationCallback;
readonly TriggerEvent<T> trigger;
readonly AsyncTriggerBase<T> trigger;
CancellationToken cancellationToken;
CancellationTokenRegistration registration;
@ -180,7 +180,7 @@ namespace Cysharp.Threading.Tasks.Triggers
internal CancellationToken CancellationToken => cancellationToken;
public AsyncTriggerHandler(TriggerEvent<T> trigger, bool callOnce)
internal AsyncTriggerHandler(AsyncTriggerBase<T> trigger, bool callOnce)
{
if (cancellationToken.IsCancellationRequested)
{
@ -193,12 +193,12 @@ namespace Cysharp.Threading.Tasks.Triggers
this.registration = default;
this.callOnce = callOnce;
trigger.Add(this);
trigger.AddHandler(this);
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)
{
@ -210,7 +210,7 @@ namespace Cysharp.Threading.Tasks.Triggers
this.cancellationToken = cancellationToken;
this.callOnce = callOnce;
trigger.Add(this);
trigger.AddHandler(this);
if (cancellationToken.CanBeCanceled)
{
@ -235,7 +235,7 @@ namespace Cysharp.Threading.Tasks.Triggers
isDisposed = true;
TaskTracker.RemoveTracking(this);
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.
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) #>)
{
triggerEvent?.TrySetResult(<#= BuildResultParameter(t.arguments) #>);
RaiseEvent(<#= BuildResultParameter(t.arguments) #>);
}
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)
{
return new AsyncTriggerHandler<<#= t.returnType #>>(GetTriggerEvent(), cancellationToken, false);
return new AsyncTriggerHandler<<#= t.returnType #>>(this, cancellationToken, false);
}
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)
{
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)) { #>

View File

@ -37,6 +37,200 @@ public enum MyEnum
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 Button okButton;
@ -148,16 +342,27 @@ public class SandboxMain : MonoBehaviour
// UniTaskAsyncEnumerable.EveryUpdate(PlayerLoopTiming.FixedUpdate)
await UniTask.Yield(PlayerLoopTiming.Update);
Debug.Log("Start:" + Time.frameCount);
this.GetAsyncUpdateTrigger().ForEachAsync(_ =>
{
UnityEngine.Debug.Log("Update Trigger 1");
}).Forget();
await UniTaskAsyncEnumerable.TimerFrame(3, 5, PlayerLoopTiming.PostLateUpdate)
.Select(x => x)
.Do(x => Debug.Log("DODODO"))
.ForEachAsync(_ =>
{
Debug.Log("Call:" + Time.frameCount);
}, cancellationToken: this.GetCancellationTokenOnDestroy());
this.GetAsyncUpdateTrigger().ForEachAsync(_ =>
{
UnityEngine.Debug.Log("Update Trigger 2");
}).Forget();
//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
//{