123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- 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
- }
- }
|