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.
487 lines
17 KiB
487 lines
17 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Threading; |
|
|
|
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) |
|
using System.Threading.Tasks; |
|
using UniRx.InternalUtil; |
|
#endif |
|
namespace UniRx |
|
{ |
|
public interface IReactiveCommand<T> : IObservable<T> |
|
{ |
|
IReadOnlyReactiveProperty<bool> CanExecute { get; } |
|
bool Execute(T parameter); |
|
} |
|
|
|
public interface IAsyncReactiveCommand<T> |
|
{ |
|
IReadOnlyReactiveProperty<bool> CanExecute { get; } |
|
IDisposable Execute(T parameter); |
|
IDisposable Subscribe(Func<T, IObservable<Unit>> asyncAction); |
|
} |
|
|
|
/// <summary> |
|
/// Represents ReactiveCommand<Unit> |
|
/// </summary> |
|
public class ReactiveCommand : ReactiveCommand<Unit> |
|
{ |
|
/// <summary> |
|
/// CanExecute is always true. |
|
/// </summary> |
|
public ReactiveCommand() |
|
: base() |
|
{ } |
|
|
|
/// <summary> |
|
/// CanExecute is changed from canExecute sequence. |
|
/// </summary> |
|
public ReactiveCommand(IObservable<bool> canExecuteSource, bool initialValue = true) |
|
: base(canExecuteSource, initialValue) |
|
{ |
|
} |
|
|
|
/// <summary>Push null to subscribers.</summary> |
|
public bool Execute() |
|
{ |
|
return Execute(Unit.Default); |
|
} |
|
|
|
/// <summary>Force push parameter to subscribers.</summary> |
|
public void ForceExecute() |
|
{ |
|
ForceExecute(Unit.Default); |
|
} |
|
} |
|
|
|
public class ReactiveCommand<T> : IReactiveCommand<T>, IDisposable |
|
{ |
|
readonly Subject<T> trigger = new Subject<T>(); |
|
readonly IDisposable canExecuteSubscription; |
|
|
|
ReactiveProperty<bool> canExecute; |
|
public IReadOnlyReactiveProperty<bool> CanExecute |
|
{ |
|
get |
|
{ |
|
return canExecute; |
|
} |
|
} |
|
|
|
public bool IsDisposed { get; private set; } |
|
|
|
/// <summary> |
|
/// CanExecute is always true. |
|
/// </summary> |
|
public ReactiveCommand() |
|
{ |
|
this.canExecute = new ReactiveProperty<bool>(true); |
|
this.canExecuteSubscription = Disposable.Empty; |
|
} |
|
|
|
/// <summary> |
|
/// CanExecute is changed from canExecute sequence. |
|
/// </summary> |
|
public ReactiveCommand(IObservable<bool> canExecuteSource, bool initialValue = true) |
|
{ |
|
this.canExecute = new ReactiveProperty<bool>(initialValue); |
|
this.canExecuteSubscription = canExecuteSource |
|
.DistinctUntilChanged() |
|
.SubscribeWithState(canExecute, (b, c) => c.Value = b); |
|
} |
|
|
|
/// <summary>Push parameter to subscribers when CanExecute.</summary> |
|
public bool Execute(T parameter) |
|
{ |
|
if (canExecute.Value) |
|
{ |
|
trigger.OnNext(parameter); |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
/// <summary>Force push parameter to subscribers.</summary> |
|
public void ForceExecute(T parameter) |
|
{ |
|
trigger.OnNext(parameter); |
|
} |
|
|
|
/// <summary>Subscribe execute.</summary> |
|
public IDisposable Subscribe(IObserver<T> observer) |
|
{ |
|
return trigger.Subscribe(observer); |
|
} |
|
|
|
/// <summary> |
|
/// Stop all subscription and lock CanExecute is false. |
|
/// </summary> |
|
public void Dispose() |
|
{ |
|
if (IsDisposed) return; |
|
|
|
IsDisposed = true; |
|
canExecute.Dispose(); |
|
trigger.OnCompleted(); |
|
trigger.Dispose(); |
|
canExecuteSubscription.Dispose(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Variation of ReactiveCommand, when executing command then CanExecute = false after CanExecute = true. |
|
/// </summary> |
|
public class AsyncReactiveCommand : AsyncReactiveCommand<Unit> |
|
{ |
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// </summary> |
|
public AsyncReactiveCommand() |
|
: base() |
|
{ |
|
|
|
} |
|
|
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// </summary> |
|
public AsyncReactiveCommand(IObservable<bool> canExecuteSource) |
|
: base(canExecuteSource) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// The source is shared between other AsyncReactiveCommand. |
|
/// </summary> |
|
public AsyncReactiveCommand(IReactiveProperty<bool> sharedCanExecute) |
|
: base(sharedCanExecute) |
|
{ |
|
} |
|
|
|
public IDisposable Execute() |
|
{ |
|
return base.Execute(Unit.Default); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Variation of ReactiveCommand, canExecute is changed when executing command then CanExecute = false after CanExecute = true. |
|
/// </summary> |
|
public class AsyncReactiveCommand<T> : IAsyncReactiveCommand<T> |
|
{ |
|
UniRx.InternalUtil.ImmutableList<Func<T, IObservable<Unit>>> asyncActions = UniRx.InternalUtil.ImmutableList<Func<T, IObservable<Unit>>>.Empty; |
|
|
|
readonly object gate = new object(); |
|
readonly IReactiveProperty<bool> canExecuteSource; |
|
readonly IReadOnlyReactiveProperty<bool> canExecute; |
|
|
|
public IReadOnlyReactiveProperty<bool> CanExecute |
|
{ |
|
get |
|
{ |
|
return canExecute; |
|
} |
|
} |
|
|
|
public bool IsDisposed { get; private set; } |
|
|
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// </summary> |
|
public AsyncReactiveCommand() |
|
{ |
|
this.canExecuteSource = new ReactiveProperty<bool>(true); |
|
this.canExecute = canExecuteSource; |
|
} |
|
|
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// </summary> |
|
public AsyncReactiveCommand(IObservable<bool> canExecuteSource) |
|
{ |
|
this.canExecuteSource = new ReactiveProperty<bool>(true); |
|
this.canExecute = this.canExecuteSource.CombineLatest(canExecuteSource, (x, y) => x && y).ToReactiveProperty(); |
|
} |
|
|
|
/// <summary> |
|
/// CanExecute is automatically changed when executing to false and finished to true. |
|
/// The source is shared between other AsyncReactiveCommand. |
|
/// </summary> |
|
public AsyncReactiveCommand(IReactiveProperty<bool> sharedCanExecute) |
|
{ |
|
this.canExecuteSource = sharedCanExecute; |
|
this.canExecute = sharedCanExecute; |
|
} |
|
|
|
/// <summary>Push parameter to subscribers when CanExecute.</summary> |
|
public IDisposable Execute(T parameter) |
|
{ |
|
if (canExecute.Value) |
|
{ |
|
canExecuteSource.Value = false; |
|
var a = asyncActions.Data; |
|
if (a.Length == 1) |
|
{ |
|
try |
|
{ |
|
var asyncState = a[0].Invoke(parameter) ?? Observable.ReturnUnit(); |
|
return asyncState.Finally(() => canExecuteSource.Value = true).Subscribe(); |
|
} |
|
catch |
|
{ |
|
canExecuteSource.Value = true; |
|
throw; |
|
} |
|
} |
|
else |
|
{ |
|
var xs = new IObservable<Unit>[a.Length]; |
|
try |
|
{ |
|
for (int i = 0; i < a.Length; i++) |
|
{ |
|
xs[i] = a[i].Invoke(parameter) ?? Observable.ReturnUnit(); |
|
} |
|
} |
|
catch |
|
{ |
|
canExecuteSource.Value = true; |
|
throw; |
|
} |
|
|
|
return Observable.WhenAll(xs).Finally(() => canExecuteSource.Value = true).Subscribe(); |
|
} |
|
} |
|
else |
|
{ |
|
return Disposable.Empty; |
|
} |
|
} |
|
|
|
/// <summary>Subscribe execute.</summary> |
|
public IDisposable Subscribe(Func<T, IObservable<Unit>> asyncAction) |
|
{ |
|
lock (gate) |
|
{ |
|
asyncActions = asyncActions.Add(asyncAction); |
|
} |
|
|
|
return new Subscription(this, asyncAction); |
|
} |
|
|
|
/// <summary> |
|
/// Stop all subscription and lock CanExecute is false. |
|
/// </summary> |
|
public void Dispose() |
|
{ |
|
if (IsDisposed) return; |
|
|
|
IsDisposed = true; |
|
asyncActions = UniRx.InternalUtil.ImmutableList<Func<T, IObservable<Unit>>>.Empty; |
|
} |
|
class Subscription : IDisposable |
|
{ |
|
readonly AsyncReactiveCommand<T> parent; |
|
readonly Func<T, IObservable<Unit>> asyncAction; |
|
|
|
public Subscription(AsyncReactiveCommand<T> parent, Func<T, IObservable<Unit>> asyncAction) |
|
{ |
|
this.parent = parent; |
|
this.asyncAction = asyncAction; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
lock (parent.gate) |
|
{ |
|
parent.asyncActions = parent.asyncActions.Remove(asyncAction); |
|
} |
|
} |
|
} |
|
} |
|
|
|
public static class ReactiveCommandExtensions |
|
{ |
|
/// <summary> |
|
/// Create non parameter commands. CanExecute is changed from canExecute sequence. |
|
/// </summary> |
|
public static ReactiveCommand ToReactiveCommand(this IObservable<bool> canExecuteSource, bool initialValue = true) |
|
{ |
|
return new ReactiveCommand(canExecuteSource, initialValue); |
|
} |
|
|
|
/// <summary> |
|
/// Create parametered comamnds. CanExecute is changed from canExecute sequence. |
|
/// </summary> |
|
public static ReactiveCommand<T> ToReactiveCommand<T>(this IObservable<bool> canExecuteSource, bool initialValue = true) |
|
{ |
|
return new ReactiveCommand<T>(canExecuteSource, initialValue); |
|
} |
|
|
|
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) |
|
|
|
static readonly Action<object> Callback = CancelCallback; |
|
|
|
static void CancelCallback(object state) |
|
{ |
|
var tuple = (Tuple<ICancellableTaskCompletionSource, IDisposable>)state; |
|
tuple.Item2.Dispose(); |
|
tuple.Item1.TrySetCanceled(); |
|
} |
|
|
|
public static Task<T> WaitUntilExecuteAsync<T>(this IReactiveCommand<T> source, CancellationToken cancellationToken = default(CancellationToken)) |
|
{ |
|
var tcs = new CancellableTaskCompletionSource<T>(); |
|
|
|
var disposable = new SingleAssignmentDisposable(); |
|
disposable.Disposable = source.Subscribe(x => |
|
{ |
|
disposable.Dispose(); // finish subscription. |
|
tcs.TrySetResult(x); |
|
}, ex => tcs.TrySetException(ex), () => tcs.TrySetCanceled()); |
|
|
|
cancellationToken.Register(Callback, Tuple.Create(tcs, disposable.Disposable), false); |
|
|
|
return tcs.Task; |
|
} |
|
|
|
public static System.Runtime.CompilerServices.TaskAwaiter<T> GetAwaiter<T>(this IReactiveCommand<T> command) |
|
{ |
|
return command.WaitUntilExecuteAsync(CancellationToken.None).GetAwaiter(); |
|
} |
|
|
|
#endif |
|
|
|
#if !UniRxLibrary |
|
|
|
// for uGUI(from 4.6) |
|
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5) |
|
|
|
/// <summary> |
|
/// Bind ReactiveCommand to button's interactable and onClick. |
|
/// </summary> |
|
public static IDisposable BindTo(this IReactiveCommand<Unit> command, UnityEngine.UI.Button button) |
|
{ |
|
var d1 = command.CanExecute.SubscribeToInteractable(button); |
|
var d2 = button.OnClickAsObservable().SubscribeWithState(command, (x, c) => c.Execute(x)); |
|
return StableCompositeDisposable.Create(d1, d2); |
|
} |
|
|
|
/// <summary> |
|
/// Bind ReactiveCommand to button's interactable and onClick and register onClick action to command. |
|
/// </summary> |
|
public static IDisposable BindToOnClick(this IReactiveCommand<Unit> command, UnityEngine.UI.Button button, Action<Unit> onClick) |
|
{ |
|
var d1 = command.CanExecute.SubscribeToInteractable(button); |
|
var d2 = button.OnClickAsObservable().SubscribeWithState(command, (x, c) => c.Execute(x)); |
|
var d3 = command.Subscribe(onClick); |
|
|
|
return StableCompositeDisposable.Create(d1, d2, d3); |
|
} |
|
|
|
/// <summary> |
|
/// Bind canExecuteSource to button's interactable and onClick and register onClick action to command. |
|
/// </summary> |
|
public static IDisposable BindToButtonOnClick(this IObservable<bool> canExecuteSource, UnityEngine.UI.Button button, Action<Unit> onClick, bool initialValue = true) |
|
{ |
|
return ToReactiveCommand(canExecuteSource, initialValue).BindToOnClick(button, onClick); |
|
} |
|
|
|
#endif |
|
|
|
#endif |
|
} |
|
|
|
public static class AsyncReactiveCommandExtensions |
|
{ |
|
public static AsyncReactiveCommand ToAsyncReactiveCommand(this IReactiveProperty<bool> sharedCanExecuteSource) |
|
{ |
|
return new AsyncReactiveCommand(sharedCanExecuteSource); |
|
} |
|
|
|
public static AsyncReactiveCommand<T> ToAsyncReactiveCommand<T>(this IReactiveProperty<bool> sharedCanExecuteSource) |
|
{ |
|
return new AsyncReactiveCommand<T>(sharedCanExecuteSource); |
|
} |
|
|
|
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) |
|
|
|
static readonly Action<object> Callback = CancelCallback; |
|
|
|
static void CancelCallback(object state) |
|
{ |
|
var tuple = (Tuple<ICancellableTaskCompletionSource, IDisposable>)state; |
|
tuple.Item2.Dispose(); |
|
tuple.Item1.TrySetCanceled(); |
|
} |
|
|
|
public static Task<T> WaitUntilExecuteAsync<T>(this IAsyncReactiveCommand<T> source, CancellationToken cancellationToken = default(CancellationToken)) |
|
{ |
|
var tcs = new CancellableTaskCompletionSource<T>(); |
|
|
|
var subscription = source.Subscribe(x => { tcs.TrySetResult(x); return Observable.ReturnUnit(); }); |
|
cancellationToken.Register(Callback, Tuple.Create(tcs, subscription), false); |
|
|
|
return tcs.Task; |
|
} |
|
|
|
public static System.Runtime.CompilerServices.TaskAwaiter<T> GetAwaiter<T>(this IAsyncReactiveCommand<T> command) |
|
{ |
|
return command.WaitUntilExecuteAsync(CancellationToken.None).GetAwaiter(); |
|
} |
|
|
|
#endif |
|
|
|
|
|
#if !UniRxLibrary |
|
|
|
// for uGUI(from 4.6) |
|
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5) |
|
|
|
/// <summary> |
|
/// Bind AsyncRaectiveCommand to button's interactable and onClick. |
|
/// </summary> |
|
public static IDisposable BindTo(this IAsyncReactiveCommand<Unit> command, UnityEngine.UI.Button button) |
|
{ |
|
var d1 = command.CanExecute.SubscribeToInteractable(button); |
|
var d2 = button.OnClickAsObservable().SubscribeWithState(command, (x, c) => c.Execute(x)); |
|
|
|
return StableCompositeDisposable.Create(d1, d2); |
|
} |
|
|
|
/// <summary> |
|
/// Bind AsyncRaectiveCommand to button's interactable and onClick and register async action to command. |
|
/// </summary> |
|
public static IDisposable BindToOnClick(this IAsyncReactiveCommand<Unit> command, UnityEngine.UI.Button button, Func<Unit, IObservable<Unit>> asyncOnClick) |
|
{ |
|
var d1 = command.CanExecute.SubscribeToInteractable(button); |
|
var d2 = button.OnClickAsObservable().SubscribeWithState(command, (x, c) => c.Execute(x)); |
|
var d3 = command.Subscribe(asyncOnClick); |
|
|
|
return StableCompositeDisposable.Create(d1, d2, d3); |
|
} |
|
|
|
/// <summary> |
|
/// Create AsyncReactiveCommand and bind to button's interactable and onClick and register async action to command. |
|
/// </summary> |
|
public static IDisposable BindToOnClick(this UnityEngine.UI.Button button, Func<Unit, IObservable<Unit>> asyncOnClick) |
|
{ |
|
return new AsyncReactiveCommand().BindToOnClick(button, asyncOnClick); |
|
} |
|
|
|
/// <summary> |
|
/// Create AsyncReactiveCommand and bind sharedCanExecuteSource source to button's interactable and onClick and register async action to command. |
|
/// </summary> |
|
public static IDisposable BindToOnClick(this UnityEngine.UI.Button button, IReactiveProperty<bool> sharedCanExecuteSource, Func<Unit, IObservable<Unit>> asyncOnClick) |
|
{ |
|
return sharedCanExecuteSource.ToAsyncReactiveCommand().BindToOnClick(button, asyncOnClick); |
|
} |
|
#endif |
|
|
|
#endif |
|
} |
|
}
|
|
|