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.
488 lines
17 KiB
488 lines
17 KiB
11 months ago
|
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
|
||
|
}
|
||
|
}
|