You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
20 KiB
644 lines
20 KiB
using System; |
|
using System.Threading; |
|
|
|
namespace Cysharp.Threading.Tasks |
|
{ |
|
public interface IReadOnlyAsyncReactiveProperty<T> : IUniTaskAsyncEnumerable<T> |
|
{ |
|
T Value { get; } |
|
IUniTaskAsyncEnumerable<T> WithoutCurrent(); |
|
UniTask<T> WaitAsync(CancellationToken cancellationToken = default); |
|
} |
|
|
|
public interface IAsyncReactiveProperty<T> : IReadOnlyAsyncReactiveProperty<T> |
|
{ |
|
new T Value { get; set; } |
|
} |
|
|
|
[Serializable] |
|
public class AsyncReactiveProperty<T> : IAsyncReactiveProperty<T>, IDisposable |
|
{ |
|
TriggerEvent<T> triggerEvent; |
|
|
|
#if UNITY_2018_3_OR_NEWER |
|
[UnityEngine.SerializeField] |
|
#endif |
|
T latestValue; |
|
|
|
public T Value |
|
{ |
|
get |
|
{ |
|
return latestValue; |
|
} |
|
set |
|
{ |
|
this.latestValue = value; |
|
triggerEvent.SetResult(value); |
|
} |
|
} |
|
|
|
public AsyncReactiveProperty(T value) |
|
{ |
|
this.latestValue = value; |
|
this.triggerEvent = default; |
|
} |
|
|
|
public IUniTaskAsyncEnumerable<T> WithoutCurrent() |
|
{ |
|
return new WithoutCurrentEnumerable(this); |
|
} |
|
|
|
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken) |
|
{ |
|
return new Enumerator(this, cancellationToken, true); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
triggerEvent.SetCompleted(); |
|
} |
|
|
|
public static implicit operator T(AsyncReactiveProperty<T> value) |
|
{ |
|
return value.Value; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
if (isValueType) return latestValue.ToString(); |
|
return latestValue?.ToString(); |
|
} |
|
|
|
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default) |
|
{ |
|
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token); |
|
} |
|
|
|
static bool isValueType; |
|
|
|
static AsyncReactiveProperty() |
|
{ |
|
isValueType = typeof(T).IsValueType; |
|
} |
|
|
|
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource> |
|
{ |
|
static Action<object> cancellationCallback = CancellationCallback; |
|
|
|
static TaskPool<WaitAsyncSource> pool; |
|
WaitAsyncSource nextNode; |
|
ref WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode => ref nextNode; |
|
|
|
static WaitAsyncSource() |
|
{ |
|
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size); |
|
} |
|
|
|
AsyncReactiveProperty<T> parent; |
|
CancellationToken cancellationToken; |
|
CancellationTokenRegistration cancellationTokenRegistration; |
|
UniTaskCompletionSourceCore<T> core; |
|
|
|
WaitAsyncSource() |
|
{ |
|
} |
|
|
|
public static IUniTaskSource<T> Create(AsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token) |
|
{ |
|
if (cancellationToken.IsCancellationRequested) |
|
{ |
|
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token); |
|
} |
|
|
|
if (!pool.TryPop(out var result)) |
|
{ |
|
result = new WaitAsyncSource(); |
|
} |
|
|
|
result.parent = parent; |
|
result.cancellationToken = cancellationToken; |
|
|
|
if (cancellationToken.CanBeCanceled) |
|
{ |
|
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result); |
|
} |
|
|
|
result.parent.triggerEvent.Add(result); |
|
|
|
TaskTracker.TrackActiveTask(result, 3); |
|
|
|
token = result.core.Version; |
|
return result; |
|
} |
|
|
|
bool TryReturn() |
|
{ |
|
TaskTracker.RemoveTracking(this); |
|
core.Reset(); |
|
cancellationTokenRegistration.Dispose(); |
|
cancellationTokenRegistration = default; |
|
parent.triggerEvent.Remove(this); |
|
parent = null; |
|
cancellationToken = default; |
|
return pool.TryPush(this); |
|
} |
|
|
|
static void CancellationCallback(object state) |
|
{ |
|
var self = (WaitAsyncSource)state; |
|
self.OnCanceled(self.cancellationToken); |
|
} |
|
|
|
// IUniTaskSource |
|
|
|
public T GetResult(short token) |
|
{ |
|
try |
|
{ |
|
return core.GetResult(token); |
|
} |
|
finally |
|
{ |
|
TryReturn(); |
|
} |
|
} |
|
|
|
void IUniTaskSource.GetResult(short token) |
|
{ |
|
GetResult(token); |
|
} |
|
|
|
public void OnCompleted(Action<object> continuation, object state, short token) |
|
{ |
|
core.OnCompleted(continuation, state, token); |
|
} |
|
|
|
public UniTaskStatus GetStatus(short token) |
|
{ |
|
return core.GetStatus(token); |
|
} |
|
|
|
public UniTaskStatus UnsafeGetStatus() |
|
{ |
|
return core.UnsafeGetStatus(); |
|
} |
|
|
|
// ITriggerHandler |
|
|
|
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; } |
|
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; } |
|
|
|
public void OnCanceled(CancellationToken cancellationToken) |
|
{ |
|
core.TrySetCanceled(cancellationToken); |
|
} |
|
|
|
public void OnCompleted() |
|
{ |
|
// Complete as Cancel. |
|
core.TrySetCanceled(CancellationToken.None); |
|
} |
|
|
|
public void OnError(Exception ex) |
|
{ |
|
core.TrySetException(ex); |
|
} |
|
|
|
public void OnNext(T value) |
|
{ |
|
core.TrySetResult(value); |
|
} |
|
} |
|
|
|
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T> |
|
{ |
|
readonly AsyncReactiveProperty<T> parent; |
|
|
|
public WithoutCurrentEnumerable(AsyncReactiveProperty<T> parent) |
|
{ |
|
this.parent = parent; |
|
} |
|
|
|
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) |
|
{ |
|
return new Enumerator(parent, cancellationToken, false); |
|
} |
|
} |
|
|
|
sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, ITriggerHandler<T> |
|
{ |
|
static Action<object> cancellationCallback = CancellationCallback; |
|
|
|
readonly AsyncReactiveProperty<T> parent; |
|
readonly CancellationToken cancellationToken; |
|
readonly CancellationTokenRegistration cancellationTokenRegistration; |
|
T value; |
|
bool isDisposed; |
|
bool firstCall; |
|
|
|
public Enumerator(AsyncReactiveProperty<T> parent, CancellationToken cancellationToken, bool publishCurrentValue) |
|
{ |
|
this.parent = parent; |
|
this.cancellationToken = cancellationToken; |
|
this.firstCall = publishCurrentValue; |
|
|
|
parent.triggerEvent.Add(this); |
|
TaskTracker.TrackActiveTask(this, 3); |
|
|
|
if (cancellationToken.CanBeCanceled) |
|
{ |
|
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); |
|
} |
|
} |
|
|
|
public T Current => value; |
|
|
|
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; } |
|
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; } |
|
|
|
public UniTask<bool> MoveNextAsync() |
|
{ |
|
// raise latest value on first call. |
|
if (firstCall) |
|
{ |
|
firstCall = false; |
|
value = parent.Value; |
|
return CompletedTasks.True; |
|
} |
|
|
|
completionSource.Reset(); |
|
return new UniTask<bool>(this, completionSource.Version); |
|
} |
|
|
|
public UniTask DisposeAsync() |
|
{ |
|
if (!isDisposed) |
|
{ |
|
isDisposed = true; |
|
TaskTracker.RemoveTracking(this); |
|
completionSource.TrySetCanceled(cancellationToken); |
|
parent.triggerEvent.Remove(this); |
|
} |
|
return default; |
|
} |
|
|
|
public void OnNext(T value) |
|
{ |
|
this.value = value; |
|
completionSource.TrySetResult(true); |
|
} |
|
|
|
public void OnCanceled(CancellationToken cancellationToken) |
|
{ |
|
DisposeAsync().Forget(); |
|
} |
|
|
|
public void OnCompleted() |
|
{ |
|
completionSource.TrySetResult(false); |
|
} |
|
|
|
public void OnError(Exception ex) |
|
{ |
|
completionSource.TrySetException(ex); |
|
} |
|
|
|
static void CancellationCallback(object state) |
|
{ |
|
var self = (Enumerator)state; |
|
self.DisposeAsync().Forget(); |
|
} |
|
} |
|
} |
|
|
|
public class ReadOnlyAsyncReactiveProperty<T> : IReadOnlyAsyncReactiveProperty<T>, IDisposable |
|
{ |
|
TriggerEvent<T> triggerEvent; |
|
|
|
T latestValue; |
|
IUniTaskAsyncEnumerator<T> enumerator; |
|
|
|
public T Value |
|
{ |
|
get |
|
{ |
|
return latestValue; |
|
} |
|
} |
|
|
|
public ReadOnlyAsyncReactiveProperty(T initialValue, IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken) |
|
{ |
|
latestValue = initialValue; |
|
ConsumeEnumerator(source, cancellationToken).Forget(); |
|
} |
|
|
|
public ReadOnlyAsyncReactiveProperty(IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken) |
|
{ |
|
ConsumeEnumerator(source, cancellationToken).Forget(); |
|
} |
|
|
|
async UniTaskVoid ConsumeEnumerator(IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken) |
|
{ |
|
enumerator = source.GetAsyncEnumerator(cancellationToken); |
|
try |
|
{ |
|
while (await enumerator.MoveNextAsync()) |
|
{ |
|
var value = enumerator.Current; |
|
this.latestValue = value; |
|
triggerEvent.SetResult(value); |
|
} |
|
} |
|
finally |
|
{ |
|
await enumerator.DisposeAsync(); |
|
enumerator = null; |
|
} |
|
} |
|
|
|
public IUniTaskAsyncEnumerable<T> WithoutCurrent() |
|
{ |
|
return new WithoutCurrentEnumerable(this); |
|
} |
|
|
|
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken) |
|
{ |
|
return new Enumerator(this, cancellationToken, true); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
if (enumerator != null) |
|
{ |
|
enumerator.DisposeAsync().Forget(); |
|
} |
|
|
|
triggerEvent.SetCompleted(); |
|
} |
|
|
|
public static implicit operator T(ReadOnlyAsyncReactiveProperty<T> value) |
|
{ |
|
return value.Value; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
if (isValueType) return latestValue.ToString(); |
|
return latestValue?.ToString(); |
|
} |
|
|
|
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default) |
|
{ |
|
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token); |
|
} |
|
|
|
static bool isValueType; |
|
|
|
static ReadOnlyAsyncReactiveProperty() |
|
{ |
|
isValueType = typeof(T).IsValueType; |
|
} |
|
|
|
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource> |
|
{ |
|
static Action<object> cancellationCallback = CancellationCallback; |
|
|
|
static TaskPool<WaitAsyncSource> pool; |
|
WaitAsyncSource nextNode; |
|
ref WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode => ref nextNode; |
|
|
|
static WaitAsyncSource() |
|
{ |
|
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size); |
|
} |
|
|
|
ReadOnlyAsyncReactiveProperty<T> parent; |
|
CancellationToken cancellationToken; |
|
CancellationTokenRegistration cancellationTokenRegistration; |
|
UniTaskCompletionSourceCore<T> core; |
|
|
|
WaitAsyncSource() |
|
{ |
|
} |
|
|
|
public static IUniTaskSource<T> Create(ReadOnlyAsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token) |
|
{ |
|
if (cancellationToken.IsCancellationRequested) |
|
{ |
|
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token); |
|
} |
|
|
|
if (!pool.TryPop(out var result)) |
|
{ |
|
result = new WaitAsyncSource(); |
|
} |
|
|
|
result.parent = parent; |
|
result.cancellationToken = cancellationToken; |
|
|
|
if (cancellationToken.CanBeCanceled) |
|
{ |
|
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result); |
|
} |
|
|
|
result.parent.triggerEvent.Add(result); |
|
|
|
TaskTracker.TrackActiveTask(result, 3); |
|
|
|
token = result.core.Version; |
|
return result; |
|
} |
|
|
|
bool TryReturn() |
|
{ |
|
TaskTracker.RemoveTracking(this); |
|
core.Reset(); |
|
cancellationTokenRegistration.Dispose(); |
|
cancellationTokenRegistration = default; |
|
parent.triggerEvent.Remove(this); |
|
parent = null; |
|
cancellationToken = default; |
|
return pool.TryPush(this); |
|
} |
|
|
|
static void CancellationCallback(object state) |
|
{ |
|
var self = (WaitAsyncSource)state; |
|
self.OnCanceled(self.cancellationToken); |
|
} |
|
|
|
// IUniTaskSource |
|
|
|
public T GetResult(short token) |
|
{ |
|
try |
|
{ |
|
return core.GetResult(token); |
|
} |
|
finally |
|
{ |
|
TryReturn(); |
|
} |
|
} |
|
|
|
void IUniTaskSource.GetResult(short token) |
|
{ |
|
GetResult(token); |
|
} |
|
|
|
public void OnCompleted(Action<object> continuation, object state, short token) |
|
{ |
|
core.OnCompleted(continuation, state, token); |
|
} |
|
|
|
public UniTaskStatus GetStatus(short token) |
|
{ |
|
return core.GetStatus(token); |
|
} |
|
|
|
public UniTaskStatus UnsafeGetStatus() |
|
{ |
|
return core.UnsafeGetStatus(); |
|
} |
|
|
|
// ITriggerHandler |
|
|
|
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; } |
|
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; } |
|
|
|
public void OnCanceled(CancellationToken cancellationToken) |
|
{ |
|
core.TrySetCanceled(cancellationToken); |
|
} |
|
|
|
public void OnCompleted() |
|
{ |
|
// Complete as Cancel. |
|
core.TrySetCanceled(CancellationToken.None); |
|
} |
|
|
|
public void OnError(Exception ex) |
|
{ |
|
core.TrySetException(ex); |
|
} |
|
|
|
public void OnNext(T value) |
|
{ |
|
core.TrySetResult(value); |
|
} |
|
} |
|
|
|
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T> |
|
{ |
|
readonly ReadOnlyAsyncReactiveProperty<T> parent; |
|
|
|
public WithoutCurrentEnumerable(ReadOnlyAsyncReactiveProperty<T> parent) |
|
{ |
|
this.parent = parent; |
|
} |
|
|
|
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) |
|
{ |
|
return new Enumerator(parent, cancellationToken, false); |
|
} |
|
} |
|
|
|
sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, ITriggerHandler<T> |
|
{ |
|
static Action<object> cancellationCallback = CancellationCallback; |
|
|
|
readonly ReadOnlyAsyncReactiveProperty<T> parent; |
|
readonly CancellationToken cancellationToken; |
|
readonly CancellationTokenRegistration cancellationTokenRegistration; |
|
T value; |
|
bool isDisposed; |
|
bool firstCall; |
|
|
|
public Enumerator(ReadOnlyAsyncReactiveProperty<T> parent, CancellationToken cancellationToken, bool publishCurrentValue) |
|
{ |
|
this.parent = parent; |
|
this.cancellationToken = cancellationToken; |
|
this.firstCall = publishCurrentValue; |
|
|
|
parent.triggerEvent.Add(this); |
|
TaskTracker.TrackActiveTask(this, 3); |
|
|
|
if (cancellationToken.CanBeCanceled) |
|
{ |
|
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); |
|
} |
|
} |
|
|
|
public T Current => value; |
|
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; } |
|
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; } |
|
|
|
public UniTask<bool> MoveNextAsync() |
|
{ |
|
// raise latest value on first call. |
|
if (firstCall) |
|
{ |
|
firstCall = false; |
|
value = parent.Value; |
|
return CompletedTasks.True; |
|
} |
|
|
|
completionSource.Reset(); |
|
return new UniTask<bool>(this, completionSource.Version); |
|
} |
|
|
|
public UniTask DisposeAsync() |
|
{ |
|
if (!isDisposed) |
|
{ |
|
isDisposed = true; |
|
TaskTracker.RemoveTracking(this); |
|
completionSource.TrySetCanceled(cancellationToken); |
|
parent.triggerEvent.Remove(this); |
|
} |
|
return default; |
|
} |
|
|
|
public void OnNext(T value) |
|
{ |
|
this.value = value; |
|
completionSource.TrySetResult(true); |
|
} |
|
|
|
public void OnCanceled(CancellationToken cancellationToken) |
|
{ |
|
DisposeAsync().Forget(); |
|
} |
|
|
|
public void OnCompleted() |
|
{ |
|
completionSource.TrySetResult(false); |
|
} |
|
|
|
public void OnError(Exception ex) |
|
{ |
|
completionSource.TrySetException(ex); |
|
} |
|
|
|
static void CancellationCallback(object state) |
|
{ |
|
var self = (Enumerator)state; |
|
self.DisposeAsync().Forget(); |
|
} |
|
} |
|
} |
|
|
|
public static class StateExtensions |
|
{ |
|
public static ReadOnlyAsyncReactiveProperty<T> ToReadOnlyAsyncReactiveProperty<T>(this IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken) |
|
{ |
|
return new ReadOnlyAsyncReactiveProperty<T>(source, cancellationToken); |
|
} |
|
|
|
public static ReadOnlyAsyncReactiveProperty<T> ToReadOnlyAsyncReactiveProperty<T>(this IUniTaskAsyncEnumerable<T> source, T initialValue, CancellationToken cancellationToken) |
|
{ |
|
return new ReadOnlyAsyncReactiveProperty<T>(initialValue, source, cancellationToken); |
|
} |
|
} |
|
} |