UniRx 에셋 추가
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
#if !UniRxLibrary
|
||||
using ObservableUnity = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public static partial class AsyncOperationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// If you needs return value, use AsAsyncOperationObservable instead.
|
||||
/// </summary>
|
||||
public static IObservable<AsyncOperation> AsObservable(this AsyncOperation asyncOperation, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<AsyncOperation>((observer, cancellation) => AsObservableCore(asyncOperation, observer, progress, cancellation));
|
||||
}
|
||||
|
||||
// T: where T : AsyncOperation is ambigious with IObservable<T>.AsObservable
|
||||
public static IObservable<T> AsAsyncOperationObservable<T>(this T asyncOperation, IProgress<float> progress = null)
|
||||
where T : AsyncOperation
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<T>((observer, cancellation) => AsObservableCore(asyncOperation, observer, progress, cancellation));
|
||||
}
|
||||
|
||||
static IEnumerator AsObservableCore<T>(T asyncOperation, IObserver<T> observer, IProgress<float> reportProgress, CancellationToken cancel)
|
||||
where T : AsyncOperation
|
||||
{
|
||||
if (reportProgress != null)
|
||||
{
|
||||
while (!asyncOperation.isDone && !cancel.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(asyncOperation.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!asyncOperation.isDone)
|
||||
{
|
||||
yield return asyncOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested) yield break;
|
||||
|
||||
if (reportProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(asyncOperation.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
observer.OnNext(asyncOperation);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 245d77a29b1ece34e96bfc80f8c825d8
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
#if !(NETFX_CORE || NET_4_6 || NET_STANDARD_2_0 || UNITY_WSA_10_0)
|
||||
|
||||
using System;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public struct CancellationToken
|
||||
{
|
||||
readonly ICancelable source;
|
||||
|
||||
public static readonly CancellationToken Empty = new CancellationToken(null);
|
||||
|
||||
/// <summary>Same as Empty.</summary>
|
||||
public static readonly CancellationToken None = new CancellationToken(null);
|
||||
|
||||
public CancellationToken(ICancelable source)
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public bool IsCancellationRequested
|
||||
{
|
||||
get
|
||||
{
|
||||
return (source == null) ? false : source.IsDisposed;
|
||||
}
|
||||
}
|
||||
|
||||
public void ThrowIfCancellationRequested()
|
||||
{
|
||||
if (IsCancellationRequested)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e02a1bf45f8861048a6014cf7eab1825
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
#if (NET_4_6 || NET_STANDARD_2_0)
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public class CoroutineAsyncBridge : INotifyCompletion
|
||||
{
|
||||
Action continuation;
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
CoroutineAsyncBridge()
|
||||
{
|
||||
IsCompleted = false;
|
||||
}
|
||||
|
||||
public static CoroutineAsyncBridge Start<T>(T awaitTarget)
|
||||
{
|
||||
var bridge = new CoroutineAsyncBridge();
|
||||
MainThreadDispatcher.StartCoroutine(bridge.Run(awaitTarget));
|
||||
return bridge;
|
||||
}
|
||||
|
||||
IEnumerator Run<T>(T target)
|
||||
{
|
||||
yield return target;
|
||||
IsCompleted = true;
|
||||
continuation();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
if (!IsCompleted) throw new InvalidOperationException("coroutine not yet completed");
|
||||
}
|
||||
}
|
||||
|
||||
public class CoroutineAsyncBridge<T> : INotifyCompletion
|
||||
{
|
||||
readonly T result;
|
||||
Action continuation;
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
CoroutineAsyncBridge(T result)
|
||||
{
|
||||
IsCompleted = false;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public static CoroutineAsyncBridge<T> Start(T awaitTarget)
|
||||
{
|
||||
var bridge = new CoroutineAsyncBridge<T>(awaitTarget);
|
||||
MainThreadDispatcher.StartCoroutine(bridge.Run(awaitTarget));
|
||||
return bridge;
|
||||
}
|
||||
|
||||
IEnumerator Run(T target)
|
||||
{
|
||||
yield return target;
|
||||
IsCompleted = true;
|
||||
continuation();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
if (!IsCompleted) throw new InvalidOperationException("coroutine not yet completed");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CoroutineAsyncExtensions
|
||||
{
|
||||
public static CoroutineAsyncBridge GetAwaiter(this Coroutine coroutine)
|
||||
{
|
||||
return CoroutineAsyncBridge.Start(coroutine);
|
||||
}
|
||||
|
||||
#if !(CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)))
|
||||
|
||||
// should use UniRx.Async in C# 7.0
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning disable CS0618
|
||||
#endif
|
||||
public static CoroutineAsyncBridge<WWW> GetAwaiter(this WWW www)
|
||||
{
|
||||
return CoroutineAsyncBridge<WWW>.Start(www);
|
||||
}
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning restore CS0618
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
public static CoroutineAsyncBridge<AsyncOperation> GetAwaiter(this AsyncOperation asyncOperation)
|
||||
{
|
||||
return CoroutineAsyncBridge<AsyncOperation>.Start(asyncOperation);
|
||||
}
|
||||
|
||||
public static CoroutineAsyncBridge GetAwaiter(this IEnumerator coroutine)
|
||||
{
|
||||
return CoroutineAsyncBridge.Start(coroutine);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93ca3de3810199947871ab4a77014fa3
|
||||
timeCreated: 1475193276
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 125ca82be137b8544a2b65f7150ee2d4
|
||||
folderAsset: yes
|
||||
timeCreated: 1455373896
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public struct LogEntry
|
||||
{
|
||||
// requires
|
||||
public string LoggerName { get; private set; }
|
||||
public LogType LogType { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
public DateTime Timestamp { get; private set; }
|
||||
|
||||
// options
|
||||
|
||||
/// <summary>[Optional]</summary>
|
||||
public UnityEngine.Object Context { get; private set; }
|
||||
/// <summary>[Optional]</summary>
|
||||
public Exception Exception { get; private set; }
|
||||
/// <summary>[Optional]</summary>
|
||||
public string StackTrace { get; private set; }
|
||||
/// <summary>[Optional]</summary>
|
||||
public object State { get; private set; }
|
||||
|
||||
public LogEntry(string loggerName, LogType logType, DateTime timestamp, string message, UnityEngine.Object context = null, Exception exception = null, string stackTrace = null, object state = null)
|
||||
: this()
|
||||
{
|
||||
this.LoggerName = loggerName;
|
||||
this.LogType = logType;
|
||||
this.Timestamp = timestamp;
|
||||
this.Message = message;
|
||||
this.Context = context;
|
||||
this.Exception = exception;
|
||||
this.StackTrace = stackTrace;
|
||||
this.State = state;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var plusEx = (Exception != null) ? (Environment.NewLine + Exception.ToString()) : "";
|
||||
return "[" + Timestamp.ToString() + "]"
|
||||
+ "[" + LoggerName + "]"
|
||||
+ "[" + LogType.ToString() + "]"
|
||||
+ Message
|
||||
+ plusEx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53917e87e91c0e4449402e5d85a04765
|
||||
timeCreated: 1455373899
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public static partial class LogEntryExtensions
|
||||
{
|
||||
public static IDisposable LogToUnityDebug(this IObservable<LogEntry> source)
|
||||
{
|
||||
return source.Subscribe(new UnityDebugSink());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8706ef5a13e53ec46b4848a7eec5e826
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public partial class Logger
|
||||
{
|
||||
static bool isInitialized = false;
|
||||
static bool isDebugBuild = false;
|
||||
|
||||
public string Name { get; private set; }
|
||||
protected readonly Action<LogEntry> logPublisher;
|
||||
|
||||
public Logger(string loggerName)
|
||||
{
|
||||
this.Name = loggerName;
|
||||
this.logPublisher = ObservableLogger.RegisterLogger(this);
|
||||
}
|
||||
|
||||
/// <summary>Output LogType.Log but only enables isDebugBuild</summary>
|
||||
public virtual void Debug(object message, UnityEngine.Object context = null)
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
isInitialized = true;
|
||||
isDebugBuild = UnityEngine.Debug.isDebugBuild;
|
||||
}
|
||||
|
||||
if (isDebugBuild)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (message != null) ? message.ToString() : "",
|
||||
logType: LogType.Log,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: context));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Output LogType.Log but only enables isDebugBuild</summary>
|
||||
public virtual void DebugFormat(string format, params object[] args)
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
isInitialized = true;
|
||||
isDebugBuild = UnityEngine.Debug.isDebugBuild;
|
||||
}
|
||||
|
||||
if (isDebugBuild)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (format != null) ? string.Format(format, args) : "",
|
||||
logType: LogType.Log,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: null));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Log(object message, UnityEngine.Object context = null)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (message != null) ? message.ToString() : "",
|
||||
logType: LogType.Log,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: context));
|
||||
}
|
||||
|
||||
public virtual void LogFormat(string format, params object[] args)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (format != null) ? string.Format(format, args) : "",
|
||||
logType: LogType.Log,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: null));
|
||||
}
|
||||
|
||||
public virtual void Warning(object message, UnityEngine.Object context = null)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (message != null) ? message.ToString() : "",
|
||||
logType: LogType.Warning,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: context));
|
||||
}
|
||||
|
||||
public virtual void WarningFormat(string format, params object[] args)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (format != null) ? string.Format(format, args) : "",
|
||||
logType: LogType.Warning,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: null));
|
||||
}
|
||||
|
||||
public virtual void Error(object message, UnityEngine.Object context = null)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (message != null) ? message.ToString() : "",
|
||||
logType: LogType.Error,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: context));
|
||||
}
|
||||
|
||||
public virtual void ErrorFormat(string format, params object[] args)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (format != null) ? string.Format(format, args) : "",
|
||||
logType: LogType.Error,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: null));
|
||||
}
|
||||
|
||||
public virtual void Exception(Exception exception, UnityEngine.Object context = null)
|
||||
{
|
||||
logPublisher(new LogEntry(
|
||||
message: (exception != null) ? exception.ToString() : "",
|
||||
exception: exception,
|
||||
logType: LogType.Exception,
|
||||
timestamp: DateTime.Now,
|
||||
loggerName: Name,
|
||||
context: context));
|
||||
}
|
||||
|
||||
/// <summary>Publish raw LogEntry.</summary>
|
||||
public virtual void Raw(LogEntry logEntry)
|
||||
{
|
||||
logPublisher(logEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0ecf366503cb0644bdd90934d24da62
|
||||
timeCreated: 1455373902
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public static class ObservableDebugExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug helper of observbale stream. Works for only DEBUG symbol.
|
||||
/// </summary>
|
||||
public static IObservable<T> Debug<T>(this IObservable<T> source, string label = null)
|
||||
{
|
||||
#if DEBUG
|
||||
var l = (label == null) ? "" : "[" + label + "]";
|
||||
return source.Materialize()
|
||||
.Do(x => UnityEngine.Debug.Log(l + x.ToString()))
|
||||
.Dematerialize()
|
||||
.DoOnCancel(() => UnityEngine.Debug.Log(l + "OnCancel"))
|
||||
.DoOnSubscribe(() => UnityEngine.Debug.Log(l + "OnSubscribe"));
|
||||
|
||||
#else
|
||||
return source;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug helper of observbale stream. Works for only DEBUG symbol.
|
||||
/// </summary>
|
||||
public static IObservable<T> Debug<T>(this IObservable<T> source, UniRx.Diagnostics.Logger logger)
|
||||
{
|
||||
#if DEBUG
|
||||
return source.Materialize()
|
||||
.Do(x => logger.Debug(x.ToString()))
|
||||
.Dematerialize()
|
||||
.DoOnCancel(() => logger.Debug("OnCancel"))
|
||||
.DoOnSubscribe(() => logger.Debug("OnSubscribe"));
|
||||
|
||||
#else
|
||||
return source;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b43f948e095c3e749a0506709be90d68
|
||||
timeCreated: 1468662620
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public class ObservableLogger : IObservable<LogEntry>
|
||||
{
|
||||
static readonly Subject<LogEntry> logPublisher = new Subject<LogEntry>();
|
||||
|
||||
public static readonly ObservableLogger Listener = new ObservableLogger();
|
||||
|
||||
private ObservableLogger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static Action<LogEntry> RegisterLogger(Logger logger)
|
||||
{
|
||||
if (logger.Name == null) throw new ArgumentNullException("logger.Name is null");
|
||||
|
||||
return logPublisher.OnNext;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<LogEntry> observer)
|
||||
{
|
||||
return logPublisher.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 063f79dc45f902c459f0955d27b445d7
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Diagnostics
|
||||
{
|
||||
public class UnityDebugSink : IObserver<LogEntry>
|
||||
{
|
||||
public void OnCompleted()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void OnNext(LogEntry value)
|
||||
{
|
||||
// avoid multithread exception.
|
||||
// (value.Context == null) can only be called from the main thread.
|
||||
var ctx = (System.Object)value.Context;
|
||||
|
||||
switch (value.LogType)
|
||||
{
|
||||
case LogType.Error:
|
||||
if (ctx == null)
|
||||
{
|
||||
Debug.LogError(value.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(value.Message, value.Context);
|
||||
}
|
||||
break;
|
||||
case LogType.Exception:
|
||||
if (ctx == null)
|
||||
{
|
||||
Debug.LogException(value.Exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogException(value.Exception, value.Context);
|
||||
}
|
||||
break;
|
||||
case LogType.Log:
|
||||
if (ctx == null)
|
||||
{
|
||||
Debug.Log(value.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(value.Message, value.Context);
|
||||
}
|
||||
break;
|
||||
case LogType.Warning:
|
||||
if (ctx == null)
|
||||
{
|
||||
Debug.LogWarning(value.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(value.Message, value.Context);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 882166c30c3bff841b1e12d62c392e02
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a value associated with time interval information.
|
||||
/// The time interval can represent the time it took to produce the value, the interval relative to a previous value, the value's delivery time relative to a base, etc.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value being annotated with time interval information.</typeparam>
|
||||
[Serializable]
|
||||
public struct FrameInterval<T> : IEquatable<FrameInterval<T>>
|
||||
{
|
||||
private readonly int _interval;
|
||||
private readonly T _value;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a time interval value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to be annotated with a time interval.</param>
|
||||
/// <param name="interval">Time interval associated with the value.</param>
|
||||
public FrameInterval(T value, int interval)
|
||||
{
|
||||
_interval = interval;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get { return _value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interval.
|
||||
/// </summary>
|
||||
public int Interval
|
||||
{
|
||||
get { return _interval; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current FrameInterval<T> value has the same Value and Interval as a specified FrameInterval<T> value.
|
||||
/// </summary>
|
||||
/// <param name="other">An object to compare to the current FrameInterval<T> value.</param>
|
||||
/// <returns>true if both FrameInterval<T> values have the same Value and Interval; otherwise, false.</returns>
|
||||
public bool Equals(FrameInterval<T> other)
|
||||
{
|
||||
return other.Interval.Equals(Interval) && EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the two specified FrameInterval<T> values have the same Value and Interval.
|
||||
/// </summary>
|
||||
/// <param name="first">The first FrameInterval<T> value to compare.</param>
|
||||
/// <param name="second">The second FrameInterval<T> value to compare.</param>
|
||||
/// <returns>true if the first FrameInterval<T> value has the same Value and Interval as the second FrameInterval<T> value; otherwise, false.</returns>
|
||||
public static bool operator ==(FrameInterval<T> first, FrameInterval<T> second)
|
||||
{
|
||||
return first.Equals(second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the two specified FrameInterval<T> values don't have the same Value and Interval.
|
||||
/// </summary>
|
||||
/// <param name="first">The first FrameInterval<T> value to compare.</param>
|
||||
/// <param name="second">The second FrameInterval<T> value to compare.</param>
|
||||
/// <returns>true if the first FrameInterval<T> value has a different Value or Interval as the second FrameInterval<T> value; otherwise, false.</returns>
|
||||
public static bool operator !=(FrameInterval<T> first, FrameInterval<T> second)
|
||||
{
|
||||
return !first.Equals(second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified System.Object is equal to the current FrameInterval<T>.
|
||||
/// </summary>
|
||||
/// <param name="obj">The System.Object to compare with the current FrameInterval<T>.</param>
|
||||
/// <returns>true if the specified System.Object is equal to the current FrameInterval<T>; otherwise, false.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is FrameInterval<T>))
|
||||
return false;
|
||||
|
||||
var other = (FrameInterval<T>)obj;
|
||||
return this.Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for the current FrameInterval<T> value.
|
||||
/// </summary>
|
||||
/// <returns>A hash code for the current FrameInterval<T> value.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var valueHashCode = Value == null ? 1963 : Value.GetHashCode();
|
||||
|
||||
return Interval.GetHashCode() ^ valueHashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the current FrameInterval<T> value.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the current FrameInterval<T> value.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format(CultureInfo.CurrentCulture, "{0}@{1}", Value, Interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 266d1e44d71e7774c9abc5b23773e3f1
|
||||
timeCreated: 1467771656
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UniRx.InternalUtil;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class IntReactiveProperty : ReactiveProperty<int>
|
||||
{
|
||||
public IntReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IntReactiveProperty(int initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class LongReactiveProperty : ReactiveProperty<long>
|
||||
{
|
||||
public LongReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LongReactiveProperty(long initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ByteReactiveProperty : ReactiveProperty<byte>
|
||||
{
|
||||
public ByteReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ByteReactiveProperty(byte initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class FloatReactiveProperty : ReactiveProperty<float>
|
||||
{
|
||||
public FloatReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public FloatReactiveProperty(float initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class DoubleReactiveProperty : ReactiveProperty<double>
|
||||
{
|
||||
public DoubleReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DoubleReactiveProperty(double initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class StringReactiveProperty : ReactiveProperty<string>
|
||||
{
|
||||
public StringReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public StringReactiveProperty(string initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspectable ReactiveProperty.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BoolReactiveProperty : ReactiveProperty<bool>
|
||||
{
|
||||
public BoolReactiveProperty()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BoolReactiveProperty(bool initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class Vector2ReactiveProperty : ReactiveProperty<Vector2>
|
||||
{
|
||||
public Vector2ReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Vector2ReactiveProperty(Vector2 initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Vector2> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Vector2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class Vector3ReactiveProperty : ReactiveProperty<Vector3>
|
||||
{
|
||||
public Vector3ReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Vector3ReactiveProperty(Vector3 initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Vector3> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Vector3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class Vector4ReactiveProperty : ReactiveProperty<Vector4>
|
||||
{
|
||||
public Vector4ReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Vector4ReactiveProperty(Vector4 initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Vector4> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Vector4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class ColorReactiveProperty : ReactiveProperty<Color>
|
||||
{
|
||||
public ColorReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ColorReactiveProperty(Color initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Color> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class RectReactiveProperty : ReactiveProperty<Rect>
|
||||
{
|
||||
public RectReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RectReactiveProperty(Rect initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Rect> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class AnimationCurveReactiveProperty : ReactiveProperty<AnimationCurve>
|
||||
{
|
||||
public AnimationCurveReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AnimationCurveReactiveProperty(AnimationCurve initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class BoundsReactiveProperty : ReactiveProperty<Bounds>
|
||||
{
|
||||
public BoundsReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BoundsReactiveProperty(Bounds initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Bounds> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inspectable ReactiveProperty.</summary>
|
||||
[Serializable]
|
||||
public class QuaternionReactiveProperty : ReactiveProperty<Quaternion>
|
||||
{
|
||||
public QuaternionReactiveProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public QuaternionReactiveProperty(Quaternion initialValue)
|
||||
: base(initialValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEqualityComparer<Quaternion> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEqualityComparer.Quaternion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13c690f353ea23141aca4090d28aaa9c
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,306 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||||
public class InspectorDisplayAttribute : PropertyAttribute
|
||||
{
|
||||
public string FieldName { get; private set; }
|
||||
public bool NotifyPropertyChanged { get; private set; }
|
||||
|
||||
public InspectorDisplayAttribute(string fieldName = "value", bool notifyPropertyChanged = true)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
NotifyPropertyChanged = notifyPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables multiline input field for StringReactiveProperty. Default line is 3.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||||
public class MultilineReactivePropertyAttribute : PropertyAttribute
|
||||
{
|
||||
public int Lines { get; private set; }
|
||||
|
||||
public MultilineReactivePropertyAttribute()
|
||||
{
|
||||
Lines = 3;
|
||||
}
|
||||
|
||||
public MultilineReactivePropertyAttribute(int lines)
|
||||
{
|
||||
this.Lines = lines;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables range input field for Int/FloatReactiveProperty.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||||
public class RangeReactivePropertyAttribute : PropertyAttribute
|
||||
{
|
||||
public float Min { get; private set; }
|
||||
public float Max { get; private set; }
|
||||
|
||||
public RangeReactivePropertyAttribute(float min, float max)
|
||||
{
|
||||
this.Min = min;
|
||||
this.Max = max;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
|
||||
// InspectorDisplay and for Specialized ReactiveProperty
|
||||
// If you want to customize other specialized ReactiveProperty
|
||||
// [UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty))]
|
||||
// public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer { }
|
||||
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(InspectorDisplayAttribute))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(IntReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(LongReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(ByteReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(FloatReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(DoubleReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(StringReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(BoolReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(Vector2ReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(Vector3ReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(Vector4ReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(ColorReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(RectReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(AnimationCurveReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(BoundsReactiveProperty))]
|
||||
[UnityEditor.CustomPropertyDrawer(typeof(QuaternionReactiveProperty))]
|
||||
public class InspectorDisplayDrawer : UnityEditor.PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
|
||||
{
|
||||
string fieldName;
|
||||
bool notifyPropertyChanged;
|
||||
{
|
||||
var attr = this.attribute as InspectorDisplayAttribute;
|
||||
fieldName = (attr == null) ? "value" : attr.FieldName;
|
||||
notifyPropertyChanged = (attr == null) ? true : attr.NotifyPropertyChanged;
|
||||
}
|
||||
|
||||
if (notifyPropertyChanged)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
}
|
||||
var targetSerializedProperty = property.FindPropertyRelative(fieldName);
|
||||
if (targetSerializedProperty == null)
|
||||
{
|
||||
UnityEditor.EditorGUI.LabelField(position, label, new GUIContent() { text = "InspectorDisplay can't find target:" + fieldName });
|
||||
if (notifyPropertyChanged)
|
||||
{
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitPropertyField(position, targetSerializedProperty, label);
|
||||
}
|
||||
|
||||
if (notifyPropertyChanged)
|
||||
{
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties(); // deserialize to field
|
||||
|
||||
var paths = property.propertyPath.Split('.'); // X.Y.Z...
|
||||
var attachedComponent = property.serializedObject.targetObject;
|
||||
|
||||
var targetProp = (paths.Length == 1)
|
||||
? fieldInfo.GetValue(attachedComponent)
|
||||
: GetValueRecursive(attachedComponent, 0, paths);
|
||||
if (targetProp == null) return;
|
||||
var propInfo = targetProp.GetType().GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var modifiedValue = propInfo.GetValue(targetProp, null); // retrieve new value
|
||||
|
||||
var methodInfo = targetProp.GetType().GetMethod("SetValueAndForceNotify", BindingFlags.IgnoreCase | BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (methodInfo != null)
|
||||
{
|
||||
methodInfo.Invoke(targetProp, new object[] { modifiedValue });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GetValueRecursive(object obj, int index, string[] paths)
|
||||
{
|
||||
var path = paths[index];
|
||||
|
||||
FieldInfo fldInfo = null;
|
||||
var type = obj.GetType();
|
||||
while (fldInfo == null)
|
||||
{
|
||||
// attempt to get information about the field
|
||||
fldInfo = type.GetField(path, BindingFlags.IgnoreCase | BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (fldInfo != null ||
|
||||
type.BaseType == null ||
|
||||
type.BaseType.IsSubclassOf(typeof(ReactiveProperty<>))) break;
|
||||
|
||||
// if the field information is missing, it may be in the base class
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
// If array, path = Array.data[index]
|
||||
if (fldInfo == null && path == "Array")
|
||||
{
|
||||
try
|
||||
{
|
||||
path = paths[++index];
|
||||
var m = Regex.Match(path, @"(.+)\[([0-9]+)*\]");
|
||||
var arrayIndex = int.Parse(m.Groups[2].Value);
|
||||
var arrayValue = (obj as System.Collections.IList)[arrayIndex];
|
||||
if (index < paths.Length - 1)
|
||||
{
|
||||
return GetValueRecursive(arrayValue, ++index, paths);
|
||||
}
|
||||
else
|
||||
{
|
||||
return arrayValue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.Log("InspectorDisplayDrawer Exception, objType:" + obj.GetType().Name + " path:" + string.Join(", ", paths));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else if (fldInfo == null)
|
||||
{
|
||||
throw new Exception("Can't decode path, please report to UniRx's GitHub issues:" + string.Join(", ", paths));
|
||||
}
|
||||
|
||||
var v = fldInfo.GetValue(obj);
|
||||
if (index < paths.Length - 1)
|
||||
{
|
||||
return GetValueRecursive(v, ++index, paths);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var attr = this.attribute as InspectorDisplayAttribute;
|
||||
var fieldName = (attr == null) ? "value" : attr.FieldName;
|
||||
|
||||
var height = base.GetPropertyHeight(property, label);
|
||||
var valueProperty = property.FindPropertyRelative(fieldName);
|
||||
if (valueProperty == null)
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
if (valueProperty.propertyType == SerializedPropertyType.Rect)
|
||||
{
|
||||
return height * 2;
|
||||
}
|
||||
if (valueProperty.propertyType == SerializedPropertyType.Bounds)
|
||||
{
|
||||
return height * 3;
|
||||
}
|
||||
if (valueProperty.propertyType == SerializedPropertyType.String)
|
||||
{
|
||||
var multilineAttr = GetMultilineAttribute();
|
||||
if (multilineAttr != null)
|
||||
{
|
||||
return ((!EditorGUIUtility.wideMode) ? 16f : 0f) + 16f + (float)((multilineAttr.Lines - 1) * 13);
|
||||
};
|
||||
}
|
||||
|
||||
if (valueProperty.isExpanded)
|
||||
{
|
||||
var count = 0;
|
||||
var e = valueProperty.GetEnumerator();
|
||||
while (e.MoveNext()) count++;
|
||||
return ((height + 4) * count) + 6; // (Line = 20 + Padding) ?
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
protected virtual void EmitPropertyField(Rect position, UnityEditor.SerializedProperty targetSerializedProperty, GUIContent label)
|
||||
{
|
||||
var multiline = GetMultilineAttribute();
|
||||
if (multiline == null)
|
||||
{
|
||||
var range = GetRangeAttribute();
|
||||
if (range == null)
|
||||
{
|
||||
UnityEditor.EditorGUI.PropertyField(position, targetSerializedProperty, label, includeChildren: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetSerializedProperty.propertyType == SerializedPropertyType.Float)
|
||||
{
|
||||
EditorGUI.Slider(position, targetSerializedProperty, range.Min, range.Max, label);
|
||||
}
|
||||
else if (targetSerializedProperty.propertyType == SerializedPropertyType.Integer)
|
||||
{
|
||||
EditorGUI.IntSlider(position, targetSerializedProperty, (int)range.Min, (int)range.Max, label);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var property = targetSerializedProperty;
|
||||
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
var method = typeof(EditorGUI).GetMethod("MultiFieldPrefixLabel", BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
position = (Rect)method.Invoke(null, new object[] { position, 0, label, 1 });
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
var stringValue = EditorGUI.TextArea(position, property.stringValue);
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.stringValue = stringValue;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
|
||||
MultilineReactivePropertyAttribute GetMultilineAttribute()
|
||||
{
|
||||
var fi = this.fieldInfo;
|
||||
if (fi == null) return null;
|
||||
return fi.GetCustomAttributes(false).OfType<MultilineReactivePropertyAttribute>().FirstOrDefault();
|
||||
}
|
||||
|
||||
RangeReactivePropertyAttribute GetRangeAttribute()
|
||||
{
|
||||
var fi = this.fieldInfo;
|
||||
if (fi == null) return null;
|
||||
return fi.GetCustomAttributes(false).OfType<RangeReactivePropertyAttribute>().FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6180f9fd2198dee44ae7f4a617529ffa
|
||||
timeCreated: 1455373899
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UniRx.Triggers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public static partial class DisposableExtensions
|
||||
{
|
||||
/// <summary>Dispose self on target gameObject has been destroyed. Return value is self disposable.</summary>
|
||||
public static T AddTo<T>(this T disposable, GameObject gameObject)
|
||||
where T : IDisposable
|
||||
{
|
||||
if (gameObject == null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
return disposable;
|
||||
}
|
||||
|
||||
var trigger = gameObject.GetComponent<ObservableDestroyTrigger>();
|
||||
if (trigger == null)
|
||||
{
|
||||
trigger = gameObject.AddComponent<ObservableDestroyTrigger>();
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
|
||||
// If gameObject is deactive, does not raise OnDestroy, watch and invoke trigger.
|
||||
if (!trigger.IsActivated && !trigger.IsMonitoredActivate && !trigger.gameObject.activeInHierarchy)
|
||||
{
|
||||
trigger.IsMonitoredActivate = true;
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(MonitorTriggerHealth(trigger, gameObject));
|
||||
}
|
||||
|
||||
#pragma warning restore 618
|
||||
|
||||
trigger.AddDisposableOnDestroy(disposable);
|
||||
return disposable;
|
||||
}
|
||||
|
||||
static IEnumerator MonitorTriggerHealth(ObservableDestroyTrigger trigger, GameObject targetGameObject)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (trigger.IsActivated) yield break;
|
||||
|
||||
if (targetGameObject == null) // isDestroy
|
||||
{
|
||||
trigger.ForceRaiseOnDestroy(); // Force publish OnDestroy
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Dispose self on target gameObject has been destroyed. Return value is self disposable.</summary>
|
||||
public static T AddTo<T>(this T disposable, Component gameObjectComponent)
|
||||
where T : IDisposable
|
||||
{
|
||||
if (gameObjectComponent == null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
return disposable;
|
||||
}
|
||||
|
||||
return AddTo(disposable, gameObjectComponent.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Add disposable(self) to CompositeDisposable(or other ICollection) and Dispose self on target gameObject has been destroyed.</para>
|
||||
/// <para>Return value is self disposable.</para>
|
||||
/// </summary>
|
||||
public static T AddTo<T>(this T disposable, ICollection<IDisposable> container, GameObject gameObject)
|
||||
where T : IDisposable
|
||||
{
|
||||
return disposable.AddTo(container).AddTo(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Add disposable(self) to CompositeDisposable(or other ICollection) and Dispose self on target gameObject has been destroyed.</para>
|
||||
/// <para>Return value is self disposable.</para>
|
||||
/// </summary>
|
||||
public static T AddTo<T>(this T disposable, ICollection<IDisposable> container, Component gameObjectComponent)
|
||||
where T : IDisposable
|
||||
{
|
||||
return disposable.AddTo(container).AddTo(gameObjectComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7474e4acdc541340a1f566b2df46355
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,683 @@
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
|
||||
#define SupportCustomYieldInstruction
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using UniRx.InternalUtil;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public sealed class MainThreadDispatcher : MonoBehaviour
|
||||
{
|
||||
public enum CullingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Won't remove any MainThreadDispatchers.
|
||||
/// </summary>
|
||||
Disabled,
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there is an existing MainThreadDispatcher on Awake(). If so, the new dispatcher removes itself.
|
||||
/// </summary>
|
||||
Self,
|
||||
|
||||
/// <summary>
|
||||
/// Search for excess MainThreadDispatchers and removes them all on Awake().
|
||||
/// </summary>
|
||||
All
|
||||
}
|
||||
|
||||
public static CullingMode cullingMode = CullingMode.Self;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
// In UnityEditor's EditorMode can't instantiate and work MonoBehaviour.Update.
|
||||
// EditorThreadDispatcher use EditorApplication.update instead of MonoBehaviour.Update.
|
||||
class EditorThreadDispatcher
|
||||
{
|
||||
static object gate = new object();
|
||||
static EditorThreadDispatcher instance;
|
||||
|
||||
public static EditorThreadDispatcher Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
// Activate EditorThreadDispatcher is dangerous, completely Lazy.
|
||||
lock (gate)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new EditorThreadDispatcher();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadSafeQueueWorker editorQueueWorker = new ThreadSafeQueueWorker();
|
||||
|
||||
EditorThreadDispatcher()
|
||||
{
|
||||
UnityEditor.EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
public void Enqueue(Action<object> action, object state)
|
||||
{
|
||||
editorQueueWorker.Enqueue(action, state);
|
||||
}
|
||||
|
||||
public void UnsafeInvoke(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsafeInvoke<T>(Action<T> action, T state)
|
||||
{
|
||||
try
|
||||
{
|
||||
action(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void PseudoStartCoroutine(IEnumerator routine)
|
||||
{
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(routine), null);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
editorQueueWorker.ExecuteAll(x => Debug.LogException(x));
|
||||
}
|
||||
|
||||
void ConsumeEnumerator(IEnumerator routine)
|
||||
{
|
||||
if (routine.MoveNext())
|
||||
{
|
||||
var current = routine.Current;
|
||||
if (current == null)
|
||||
{
|
||||
goto ENQUEUE;
|
||||
}
|
||||
|
||||
var type = current.GetType();
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning disable CS0618
|
||||
#endif
|
||||
if (type == typeof(WWW))
|
||||
{
|
||||
var www = (WWW)current;
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitWWW(www, routine)), null);
|
||||
return;
|
||||
}
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning restore CS0618
|
||||
#endif
|
||||
else if (type == typeof(AsyncOperation))
|
||||
{
|
||||
var asyncOperation = (AsyncOperation)current;
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitAsyncOperation(asyncOperation, routine)), null);
|
||||
return;
|
||||
}
|
||||
else if (type == typeof(WaitForSeconds))
|
||||
{
|
||||
var waitForSeconds = (WaitForSeconds)current;
|
||||
var accessor = typeof(WaitForSeconds).GetField("m_Seconds", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic);
|
||||
var second = (float)accessor.GetValue(waitForSeconds);
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitForSeconds(second, routine)), null);
|
||||
return;
|
||||
}
|
||||
else if (type == typeof(Coroutine))
|
||||
{
|
||||
Debug.Log("Can't wait coroutine on UnityEditor");
|
||||
goto ENQUEUE;
|
||||
}
|
||||
#if SupportCustomYieldInstruction
|
||||
else if (current is IEnumerator)
|
||||
{
|
||||
var enumerator = (IEnumerator)current;
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapEnumerator(enumerator, routine)), null);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ENQUEUE:
|
||||
editorQueueWorker.Enqueue(_ => ConsumeEnumerator(routine), null); // next update
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning disable CS0618
|
||||
#endif
|
||||
IEnumerator UnwrapWaitWWW(WWW www, IEnumerator continuation)
|
||||
{
|
||||
while (!www.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
ConsumeEnumerator(continuation);
|
||||
}
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning restore CS0618
|
||||
#endif
|
||||
|
||||
IEnumerator UnwrapWaitAsyncOperation(AsyncOperation asyncOperation, IEnumerator continuation)
|
||||
{
|
||||
while (!asyncOperation.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
ConsumeEnumerator(continuation);
|
||||
}
|
||||
|
||||
IEnumerator UnwrapWaitForSeconds(float second, IEnumerator continuation)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
|
||||
var elapsed = (DateTimeOffset.UtcNow - startTime).TotalSeconds;
|
||||
if (elapsed >= second)
|
||||
{
|
||||
break;
|
||||
}
|
||||
};
|
||||
ConsumeEnumerator(continuation);
|
||||
}
|
||||
|
||||
IEnumerator UnwrapEnumerator(IEnumerator enumerator, IEnumerator continuation)
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
ConsumeEnumerator(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>Dispatch Asyncrhonous action.</summary>
|
||||
public static void Post(Action<object> action, object state)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.Enqueue(action, state); return; }
|
||||
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (!isQuitting && !object.ReferenceEquals(dispatcher, null))
|
||||
{
|
||||
dispatcher.queueWorker.Enqueue(action, state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Dispatch Synchronous action if possible.</summary>
|
||||
public static void Send(Action<object> action, object state)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.Enqueue(action, state); return; }
|
||||
#endif
|
||||
|
||||
if (mainThreadToken != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
action(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var dispatcher = MainThreadDispatcher.Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.unhandledExceptionCallback(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Post(action, state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Run Synchronous action.</summary>
|
||||
public static void UnsafeSend(Action action)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.UnsafeInvoke(action); return; }
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var dispatcher = MainThreadDispatcher.Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.unhandledExceptionCallback(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Run Synchronous action.</summary>
|
||||
public static void UnsafeSend<T>(Action<T> action, T state)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.UnsafeInvoke(action, state); return; }
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
action(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var dispatcher = MainThreadDispatcher.Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.unhandledExceptionCallback(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>ThreadSafe StartCoroutine.</summary>
|
||||
public static void SendStartCoroutine(IEnumerator routine)
|
||||
{
|
||||
if (mainThreadToken != null)
|
||||
{
|
||||
StartCoroutine(routine);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// call from other thread
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (!isQuitting && !object.ReferenceEquals(dispatcher, null))
|
||||
{
|
||||
dispatcher.queueWorker.Enqueue(_ =>
|
||||
{
|
||||
var dispacher2 = Instance;
|
||||
if (dispacher2 != null)
|
||||
{
|
||||
(dispacher2 as MonoBehaviour).StartCoroutine(routine);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartUpdateMicroCoroutine(IEnumerator routine)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.updateMicroCoroutine.AddCoroutine(routine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartFixedUpdateMicroCoroutine(IEnumerator routine)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.fixedUpdateMicroCoroutine.AddCoroutine(routine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartEndOfFrameMicroCoroutine(IEnumerator routine)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.endOfFrameMicroCoroutine.AddCoroutine(routine);
|
||||
}
|
||||
}
|
||||
|
||||
new public static Coroutine StartCoroutine(IEnumerator routine)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return null; }
|
||||
#endif
|
||||
|
||||
var dispatcher = Instance;
|
||||
if (dispatcher != null)
|
||||
{
|
||||
return (dispatcher as MonoBehaviour).StartCoroutine(routine);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterUnhandledExceptionCallback(Action<Exception> exceptionCallback)
|
||||
{
|
||||
if (exceptionCallback == null)
|
||||
{
|
||||
// do nothing
|
||||
Instance.unhandledExceptionCallback = Stubs<Exception>.Ignore;
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.unhandledExceptionCallback = exceptionCallback;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadSafeQueueWorker queueWorker = new ThreadSafeQueueWorker();
|
||||
Action<Exception> unhandledExceptionCallback = ex => Debug.LogException(ex); // default
|
||||
|
||||
MicroCoroutine updateMicroCoroutine = null;
|
||||
MicroCoroutine fixedUpdateMicroCoroutine = null;
|
||||
MicroCoroutine endOfFrameMicroCoroutine = null;
|
||||
|
||||
static MainThreadDispatcher instance;
|
||||
static bool initialized;
|
||||
static bool isQuitting = false;
|
||||
|
||||
public static string InstanceName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
throw new NullReferenceException("MainThreadDispatcher is not initialized.");
|
||||
}
|
||||
return instance.name;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInitialized
|
||||
{
|
||||
get { return initialized && instance != null; }
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
static object mainThreadToken;
|
||||
|
||||
static MainThreadDispatcher Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
Initialize();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Don't try to add a GameObject when the scene is not playing. Only valid in the Editor, EditorView.
|
||||
if (!ScenePlaybackDetector.IsPlaying) return;
|
||||
#endif
|
||||
MainThreadDispatcher dispatcher = null;
|
||||
|
||||
try
|
||||
{
|
||||
dispatcher = GameObject.FindObjectOfType<MainThreadDispatcher>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Throw exception when calling from a worker thread.
|
||||
var ex = new Exception("UniRx requires a MainThreadDispatcher component created on the main thread. Make sure it is added to the scene before calling UniRx from a worker thread.");
|
||||
UnityEngine.Debug.LogException(ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (isQuitting)
|
||||
{
|
||||
// don't create new instance after quitting
|
||||
// avoid "Some objects were not cleaned up when closing the scene find target" error.
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispatcher == null)
|
||||
{
|
||||
// awake call immediately from UnityEngine
|
||||
new GameObject("MainThreadDispatcher").AddComponent<MainThreadDispatcher>();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcher.Awake(); // force awake
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInMainThread
|
||||
{
|
||||
get
|
||||
{
|
||||
return (mainThreadToken != null);
|
||||
}
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
mainThreadToken = new object();
|
||||
initialized = true;
|
||||
|
||||
updateMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
|
||||
fixedUpdateMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
|
||||
endOfFrameMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
|
||||
|
||||
StartCoroutine(RunUpdateMicroCoroutine());
|
||||
StartCoroutine(RunFixedUpdateMicroCoroutine());
|
||||
StartCoroutine(RunEndOfFrameMicroCoroutine());
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this != instance)
|
||||
{
|
||||
if (cullingMode == CullingMode.Self)
|
||||
{
|
||||
// Try to destroy this dispatcher if there's already one in the scene.
|
||||
Debug.LogWarning("There is already a MainThreadDispatcher in the scene. Removing myself...");
|
||||
DestroyDispatcher(this);
|
||||
}
|
||||
else if (cullingMode == CullingMode.All)
|
||||
{
|
||||
Debug.LogWarning("There is already a MainThreadDispatcher in the scene. Cleaning up all excess dispatchers...");
|
||||
CullAllExcessDispatchers();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("There is already a MainThreadDispatcher in the scene.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RunUpdateMicroCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
updateMicroCoroutine.Run();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RunFixedUpdateMicroCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return YieldInstructionCache.WaitForFixedUpdate;
|
||||
fixedUpdateMicroCoroutine.Run();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RunEndOfFrameMicroCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return YieldInstructionCache.WaitForEndOfFrame;
|
||||
endOfFrameMicroCoroutine.Run();
|
||||
}
|
||||
}
|
||||
|
||||
static void DestroyDispatcher(MainThreadDispatcher aDispatcher)
|
||||
{
|
||||
if (aDispatcher != instance)
|
||||
{
|
||||
// Try to remove game object if it's empty
|
||||
var components = aDispatcher.gameObject.GetComponents<Component>();
|
||||
if (aDispatcher.gameObject.transform.childCount == 0 && components.Length == 2)
|
||||
{
|
||||
if (components[0] is Transform && components[1] is MainThreadDispatcher)
|
||||
{
|
||||
Destroy(aDispatcher.gameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove component
|
||||
MonoBehaviour.Destroy(aDispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CullAllExcessDispatchers()
|
||||
{
|
||||
var dispatchers = GameObject.FindObjectsOfType<MainThreadDispatcher>();
|
||||
for (int i = 0; i < dispatchers.Length; i++)
|
||||
{
|
||||
DestroyDispatcher(dispatchers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (instance == this)
|
||||
{
|
||||
instance = GameObject.FindObjectOfType<MainThreadDispatcher>();
|
||||
initialized = instance != null;
|
||||
|
||||
/*
|
||||
// Although `this` still refers to a gameObject, it won't be found.
|
||||
var foundDispatcher = GameObject.FindObjectOfType<MainThreadDispatcher>();
|
||||
|
||||
if (foundDispatcher != null)
|
||||
{
|
||||
// select another game object
|
||||
Debug.Log("new instance: " + foundDispatcher.name);
|
||||
instance = foundDispatcher;
|
||||
initialized = true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (update != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
update.OnNext(Unit.Default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
unhandledExceptionCallback(ex);
|
||||
}
|
||||
}
|
||||
queueWorker.ExecuteAll(unhandledExceptionCallback);
|
||||
}
|
||||
|
||||
// for Lifecycle Management
|
||||
|
||||
Subject<Unit> update;
|
||||
|
||||
public static IObservable<Unit> UpdateAsObservable()
|
||||
{
|
||||
return Instance.update ?? (Instance.update = new Subject<Unit>());
|
||||
}
|
||||
|
||||
Subject<Unit> lateUpdate;
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (lateUpdate != null) lateUpdate.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
public static IObservable<Unit> LateUpdateAsObservable()
|
||||
{
|
||||
return Instance.lateUpdate ?? (Instance.lateUpdate = new Subject<Unit>());
|
||||
}
|
||||
|
||||
Subject<bool> onApplicationFocus;
|
||||
|
||||
void OnApplicationFocus(bool focus)
|
||||
{
|
||||
if (onApplicationFocus != null) onApplicationFocus.OnNext(focus);
|
||||
}
|
||||
|
||||
public static IObservable<bool> OnApplicationFocusAsObservable()
|
||||
{
|
||||
return Instance.onApplicationFocus ?? (Instance.onApplicationFocus = new Subject<bool>());
|
||||
}
|
||||
|
||||
Subject<bool> onApplicationPause;
|
||||
|
||||
void OnApplicationPause(bool pause)
|
||||
{
|
||||
if (onApplicationPause != null) onApplicationPause.OnNext(pause);
|
||||
}
|
||||
|
||||
public static IObservable<bool> OnApplicationPauseAsObservable()
|
||||
{
|
||||
return Instance.onApplicationPause ?? (Instance.onApplicationPause = new Subject<bool>());
|
||||
}
|
||||
|
||||
Subject<Unit> onApplicationQuit;
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
isQuitting = true;
|
||||
if (onApplicationQuit != null) onApplicationQuit.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
public static IObservable<Unit> OnApplicationQuitAsObservable()
|
||||
{
|
||||
return Instance.onApplicationQuit ?? (Instance.onApplicationQuit = new Subject<Unit>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3cd207057515c4438a31a6a7b548fe7
|
||||
timeCreated: 1465903910
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -16000
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
#if UniRxLibrary
|
||||
public static partial class SchedulerUnity
|
||||
{
|
||||
#else
|
||||
public static partial class Scheduler
|
||||
{
|
||||
public static void SetDefaultForUnity()
|
||||
{
|
||||
Scheduler.DefaultSchedulers.ConstantTimeOperations = Scheduler.Immediate;
|
||||
Scheduler.DefaultSchedulers.TailRecursion = Scheduler.Immediate;
|
||||
Scheduler.DefaultSchedulers.Iteration = Scheduler.CurrentThread;
|
||||
Scheduler.DefaultSchedulers.TimeBasedOperations = MainThread;
|
||||
Scheduler.DefaultSchedulers.AsyncConversions = Scheduler.ThreadPool;
|
||||
}
|
||||
#endif
|
||||
static IScheduler mainThread;
|
||||
|
||||
/// <summary>
|
||||
/// Unity native MainThread Queue Scheduler. Run on mainthread and delayed on coroutine update loop, elapsed time is calculated based on Time.time.
|
||||
/// </summary>
|
||||
public static IScheduler MainThread
|
||||
{
|
||||
get
|
||||
{
|
||||
return mainThread ?? (mainThread = new MainThreadScheduler());
|
||||
}
|
||||
}
|
||||
|
||||
static IScheduler mainThreadIgnoreTimeScale;
|
||||
|
||||
/// <summary>
|
||||
/// Another MainThread scheduler, delay elapsed time is calculated based on Time.unscaledDeltaTime.
|
||||
/// </summary>
|
||||
public static IScheduler MainThreadIgnoreTimeScale
|
||||
{
|
||||
get
|
||||
{
|
||||
return mainThreadIgnoreTimeScale ?? (mainThreadIgnoreTimeScale = new IgnoreTimeScaleMainThreadScheduler());
|
||||
}
|
||||
}
|
||||
|
||||
static IScheduler mainThreadFixedUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Run on fixed update mainthread, delay elapsed time is calculated based on Time.fixedTime.
|
||||
/// </summary>
|
||||
public static IScheduler MainThreadFixedUpdate
|
||||
{
|
||||
get
|
||||
{
|
||||
return mainThreadFixedUpdate ?? (mainThreadFixedUpdate = new FixedUpdateMainThreadScheduler());
|
||||
}
|
||||
}
|
||||
|
||||
static IScheduler mainThreadEndOfFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Run on end of frame mainthread, delay elapsed time is calculated based on Time.deltaTime.
|
||||
/// </summary>
|
||||
public static IScheduler MainThreadEndOfFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
return mainThreadEndOfFrame ?? (mainThreadEndOfFrame = new EndOfFrameMainThreadScheduler());
|
||||
}
|
||||
}
|
||||
|
||||
class MainThreadScheduler : IScheduler, ISchedulerPeriodic, ISchedulerQueueing
|
||||
{
|
||||
readonly Action<object> scheduleAction;
|
||||
|
||||
public MainThreadScheduler()
|
||||
{
|
||||
MainThreadDispatcher.Initialize();
|
||||
scheduleAction = new Action<object>(Schedule);
|
||||
}
|
||||
|
||||
// delay action is run in StartCoroutine
|
||||
// Okay to action run synchronous and guaranteed run on MainThread
|
||||
IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
|
||||
{
|
||||
// zero == every frame
|
||||
if (dueTime == TimeSpan.Zero)
|
||||
{
|
||||
yield return null; // not immediately, run next frame
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new WaitForSeconds((float)dueTime.TotalSeconds);
|
||||
}
|
||||
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
|
||||
IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
|
||||
{
|
||||
// zero == every frame
|
||||
if (period == TimeSpan.Zero)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null; // not immediately, run next frame
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var seconds = (float)(period.TotalMilliseconds / 1000.0);
|
||||
var yieldInstruction = new WaitForSeconds(seconds); // cache single instruction object
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return yieldInstruction;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset Now
|
||||
{
|
||||
get { return Scheduler.Now; }
|
||||
}
|
||||
|
||||
void Schedule(object state)
|
||||
{
|
||||
var t = (Tuple<BooleanDisposable, Action>)state;
|
||||
if (!t.Item1.IsDisposed)
|
||||
{
|
||||
t.Item2();
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Schedule(Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
MainThreadDispatcher.Post(scheduleAction, Tuple.Create(d, action));
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable Schedule(DateTimeOffset dueTime, Action action)
|
||||
{
|
||||
return Schedule(dueTime - Now, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(TimeSpan dueTime, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(dueTime);
|
||||
|
||||
MainThreadDispatcher.SendStartCoroutine(DelayAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable SchedulePeriodic(TimeSpan period, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(period);
|
||||
|
||||
MainThreadDispatcher.SendStartCoroutine(PeriodicAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void ScheduleQueueing<T>(object state)
|
||||
{
|
||||
var t = (Tuple<ICancelable, T, Action<T>>)state;
|
||||
if (!t.Item1.IsDisposed)
|
||||
{
|
||||
t.Item3(t.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public void ScheduleQueueing<T>(ICancelable cancel, T state, Action<T> action)
|
||||
{
|
||||
MainThreadDispatcher.Post(QueuedAction<T>.Instance, Tuple.Create(cancel, state, action));
|
||||
}
|
||||
|
||||
static class QueuedAction<T>
|
||||
{
|
||||
public static readonly Action<object> Instance = new Action<object>(Invoke);
|
||||
|
||||
public static void Invoke(object state)
|
||||
{
|
||||
var t = (Tuple<ICancelable, T, Action<T>>)state;
|
||||
|
||||
if (!t.Item1.IsDisposed)
|
||||
{
|
||||
t.Item3(t.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoreTimeScaleMainThreadScheduler : IScheduler, ISchedulerPeriodic, ISchedulerQueueing
|
||||
{
|
||||
readonly Action<object> scheduleAction;
|
||||
|
||||
public IgnoreTimeScaleMainThreadScheduler()
|
||||
{
|
||||
MainThreadDispatcher.Initialize();
|
||||
scheduleAction = new Action<object>(Schedule);
|
||||
}
|
||||
|
||||
IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
|
||||
{
|
||||
if (dueTime == TimeSpan.Zero)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var dt = (float)dueTime.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
|
||||
{
|
||||
// zero == every frame
|
||||
if (period == TimeSpan.Zero)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null; // not immediately, run next frame
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var dt = (float)period.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
elapsed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset Now
|
||||
{
|
||||
get { return Scheduler.Now; }
|
||||
}
|
||||
|
||||
void Schedule(object state)
|
||||
{
|
||||
var t = (Tuple<BooleanDisposable, Action>)state;
|
||||
if (!t.Item1.IsDisposed)
|
||||
{
|
||||
t.Item2();
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Schedule(Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
MainThreadDispatcher.Post(scheduleAction, Tuple.Create(d, action));
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable Schedule(DateTimeOffset dueTime, Action action)
|
||||
{
|
||||
return Schedule(dueTime - Now, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(TimeSpan dueTime, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(dueTime);
|
||||
|
||||
MainThreadDispatcher.SendStartCoroutine(DelayAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable SchedulePeriodic(TimeSpan period, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(period);
|
||||
|
||||
MainThreadDispatcher.SendStartCoroutine(PeriodicAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public void ScheduleQueueing<T>(ICancelable cancel, T state, Action<T> action)
|
||||
{
|
||||
MainThreadDispatcher.Post(QueuedAction<T>.Instance, Tuple.Create(cancel, state, action));
|
||||
}
|
||||
|
||||
static class QueuedAction<T>
|
||||
{
|
||||
public static readonly Action<object> Instance = new Action<object>(Invoke);
|
||||
|
||||
public static void Invoke(object state)
|
||||
{
|
||||
var t = (Tuple<ICancelable, T, Action<T>>)state;
|
||||
|
||||
if (!t.Item1.IsDisposed)
|
||||
{
|
||||
t.Item3(t.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FixedUpdateMainThreadScheduler : IScheduler, ISchedulerPeriodic, ISchedulerQueueing
|
||||
{
|
||||
public FixedUpdateMainThreadScheduler()
|
||||
{
|
||||
MainThreadDispatcher.Initialize();
|
||||
}
|
||||
|
||||
IEnumerator ImmediateAction<T>(T state, Action<T> action, ICancelable cancellation)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action, state);
|
||||
}
|
||||
|
||||
IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
|
||||
{
|
||||
if (dueTime == TimeSpan.Zero)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
var startTime = Time.fixedTime;
|
||||
var dt = (float)dueTime.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
var elapsed = Time.fixedTime - startTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
|
||||
{
|
||||
// zero == every frame
|
||||
if (period == TimeSpan.Zero)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var startTime = Time.fixedTime;
|
||||
var dt = (float)period.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
var ft = Time.fixedTime;
|
||||
var elapsed = ft - startTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
startTime = ft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset Now
|
||||
{
|
||||
get { return Scheduler.Now; }
|
||||
}
|
||||
|
||||
public IDisposable Schedule(Action action)
|
||||
{
|
||||
return Schedule(TimeSpan.Zero, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(DateTimeOffset dueTime, Action action)
|
||||
{
|
||||
return Schedule(dueTime - Now, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(TimeSpan dueTime, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(dueTime);
|
||||
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(DelayAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable SchedulePeriodic(TimeSpan period, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(period);
|
||||
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(PeriodicAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public void ScheduleQueueing<T>(ICancelable cancel, T state, Action<T> action)
|
||||
{
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(ImmediateAction(state, action, cancel));
|
||||
}
|
||||
}
|
||||
|
||||
class EndOfFrameMainThreadScheduler : IScheduler, ISchedulerPeriodic, ISchedulerQueueing
|
||||
{
|
||||
public EndOfFrameMainThreadScheduler()
|
||||
{
|
||||
MainThreadDispatcher.Initialize();
|
||||
}
|
||||
|
||||
IEnumerator ImmediateAction<T>(T state, Action<T> action, ICancelable cancellation)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action, state);
|
||||
}
|
||||
|
||||
IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
|
||||
{
|
||||
if (dueTime == TimeSpan.Zero)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var dt = (float)dueTime.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
|
||||
{
|
||||
// zero == every frame
|
||||
if (period == TimeSpan.Zero)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) yield break;
|
||||
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var elapsed = 0f;
|
||||
var dt = (float)period.TotalSeconds;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
if (cancellation.IsDisposed) break;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
if (elapsed >= dt)
|
||||
{
|
||||
MainThreadDispatcher.UnsafeSend(action);
|
||||
elapsed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset Now
|
||||
{
|
||||
get { return Scheduler.Now; }
|
||||
}
|
||||
|
||||
public IDisposable Schedule(Action action)
|
||||
{
|
||||
return Schedule(TimeSpan.Zero, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(DateTimeOffset dueTime, Action action)
|
||||
{
|
||||
return Schedule(dueTime - Now, action);
|
||||
}
|
||||
|
||||
public IDisposable Schedule(TimeSpan dueTime, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(dueTime);
|
||||
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(DelayAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public IDisposable SchedulePeriodic(TimeSpan period, Action action)
|
||||
{
|
||||
var d = new BooleanDisposable();
|
||||
var time = Scheduler.Normalize(period);
|
||||
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(PeriodicAction(time, action, d));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public void ScheduleQueueing<T>(ICancelable cancel, T state, Action<T> action)
|
||||
{
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(ImmediateAction(state, action, cancel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f141c5dc72b97084a85631367a946ee8
|
||||
timeCreated: 1455373902
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6ef0a186b9ceaf41af7f2a9f4006216
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,442 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
#if !UniRxLibrary
|
||||
using ObservableUnity = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning disable CS0618
|
||||
#endif
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
using System.Threading;
|
||||
#if !(UNITY_METRO || UNITY_WP8) && (UNITY_4_4 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5 || UNITY_3_4 || UNITY_3_3 || UNITY_3_2 || UNITY_3_1 || UNITY_3_0_0 || UNITY_3_0 || UNITY_2_6_1 || UNITY_2_6)
|
||||
// Fallback for Unity versions below 4.5
|
||||
using Hash = System.Collections.Hashtable;
|
||||
using HashEntry = System.Collections.DictionaryEntry;
|
||||
#else
|
||||
// Unity 4.5 release notes:
|
||||
// WWW: deprecated 'WWW(string url, byte[] postData, Hashtable headers)',
|
||||
// use 'public WWW(string url, byte[] postData, Dictionary<string, string> headers)' instead.
|
||||
using Hash = System.Collections.Generic.Dictionary<string, string>;
|
||||
using HashEntry = System.Collections.Generic.KeyValuePair<string, string>;
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
[Obsolete("Use UnityWebRequest, a fully featured replacement which is more efficient and has additional features")]
|
||||
#endif
|
||||
public static partial class ObservableWWW
|
||||
{
|
||||
public static IObservable<string> Get(string url, Hash headers = null, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(new WWW(url, null, (headers ?? new Hash())), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<byte[]> GetAndGetBytes(string url, Hash headers = null, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(new WWW(url, null, (headers ?? new Hash())), observer, progress, cancellation));
|
||||
}
|
||||
public static IObservable<WWW> GetWWW(string url, Hash headers = null, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<WWW>((observer, cancellation) => Fetch(new WWW(url, null, (headers ?? new Hash())), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<string> Post(string url, byte[] postData, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(new WWW(url, postData), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<string> Post(string url, byte[] postData, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(new WWW(url, postData, headers), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<string> Post(string url, WWWForm content, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(new WWW(url, content), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<string> Post(string url, WWWForm content, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
var contentHeaders = content.headers;
|
||||
return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(new WWW(url, content.data, MergeHash(contentHeaders, headers)), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<byte[]> PostAndGetBytes(string url, byte[] postData, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(new WWW(url, postData), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<byte[]> PostAndGetBytes(string url, byte[] postData, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(new WWW(url, postData, headers), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<byte[]> PostAndGetBytes(string url, WWWForm content, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(new WWW(url, content), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<byte[]> PostAndGetBytes(string url, WWWForm content, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
var contentHeaders = content.headers;
|
||||
return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(new WWW(url, content.data, MergeHash(contentHeaders, headers)), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<WWW> PostWWW(string url, byte[] postData, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<WWW>((observer, cancellation) => Fetch(new WWW(url, postData), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<WWW> PostWWW(string url, byte[] postData, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<WWW>((observer, cancellation) => Fetch(new WWW(url, postData, headers), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<WWW> PostWWW(string url, WWWForm content, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<WWW>((observer, cancellation) => Fetch(new WWW(url, content), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<WWW> PostWWW(string url, WWWForm content, Hash headers, IProgress<float> progress = null)
|
||||
{
|
||||
var contentHeaders = content.headers;
|
||||
return ObservableUnity.FromCoroutine<WWW>((observer, cancellation) => Fetch(new WWW(url, content.data, MergeHash(contentHeaders, headers)), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<AssetBundle> LoadFromCacheOrDownload(string url, int version, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<AssetBundle>((observer, cancellation) => FetchAssetBundle(WWW.LoadFromCacheOrDownload(url, version), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<AssetBundle> LoadFromCacheOrDownload(string url, int version, uint crc, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<AssetBundle>((observer, cancellation) => FetchAssetBundle(WWW.LoadFromCacheOrDownload(url, version, crc), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
// over Unity5 supports Hash128
|
||||
#if !(UNITY_4_7 || UNITY_4_6 || UNITY_4_5 || UNITY_4_4 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5 || UNITY_3_4 || UNITY_3_3 || UNITY_3_2 || UNITY_3_1 || UNITY_3_0_0 || UNITY_3_0 || UNITY_2_6_1 || UNITY_2_6)
|
||||
public static IObservable<AssetBundle> LoadFromCacheOrDownload(string url, Hash128 hash128, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<AssetBundle>((observer, cancellation) => FetchAssetBundle(WWW.LoadFromCacheOrDownload(url, hash128), observer, progress, cancellation));
|
||||
}
|
||||
|
||||
public static IObservable<AssetBundle> LoadFromCacheOrDownload(string url, Hash128 hash128, uint crc, IProgress<float> progress = null)
|
||||
{
|
||||
return ObservableUnity.FromCoroutine<AssetBundle>((observer, cancellation) => FetchAssetBundle(WWW.LoadFromCacheOrDownload(url, hash128, crc), observer, progress, cancellation));
|
||||
}
|
||||
#endif
|
||||
|
||||
// over 4.5, Hash define is Dictionary.
|
||||
// below Unity 4.5, WWW only supports Hashtable.
|
||||
// Unity 4.5, 4.6 WWW supports Dictionary and [Obsolete]Hashtable but WWWForm.content is Hashtable.
|
||||
// Unity 5.0 WWW only supports Dictionary and WWWForm.content is also Dictionary.
|
||||
#if !(UNITY_METRO || UNITY_WP8) && (UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
|
||||
static Hash MergeHash(Hashtable wwwFormHeaders, Hash externalHeaders)
|
||||
{
|
||||
var newHeaders = new Hash();
|
||||
foreach (DictionaryEntry item in wwwFormHeaders)
|
||||
{
|
||||
newHeaders[item.Key.ToString()] = item.Value.ToString();
|
||||
}
|
||||
foreach (HashEntry item in externalHeaders)
|
||||
{
|
||||
newHeaders[item.Key] = item.Value;
|
||||
}
|
||||
return newHeaders;
|
||||
}
|
||||
#else
|
||||
static Hash MergeHash(Hash wwwFormHeaders, Hash externalHeaders)
|
||||
{
|
||||
foreach (HashEntry item in externalHeaders)
|
||||
{
|
||||
wwwFormHeaders[item.Key] = item.Value;
|
||||
}
|
||||
return wwwFormHeaders;
|
||||
}
|
||||
#endif
|
||||
|
||||
static IEnumerator Fetch(WWW www, IObserver<WWW> observer, IProgress<float> reportProgress, CancellationToken cancel)
|
||||
{
|
||||
using (www)
|
||||
{
|
||||
if (reportProgress != null)
|
||||
{
|
||||
while (!www.isDone && !cancel.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!www.isDone)
|
||||
{
|
||||
yield return www;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (reportProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(www.error))
|
||||
{
|
||||
observer.OnError(new WWWErrorException(www, www.text));
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnNext(www);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator FetchText(WWW www, IObserver<string> observer, IProgress<float> reportProgress, CancellationToken cancel)
|
||||
{
|
||||
using (www)
|
||||
{
|
||||
if (reportProgress != null)
|
||||
{
|
||||
while (!www.isDone && !cancel.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!www.isDone)
|
||||
{
|
||||
yield return www;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (reportProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(www.error))
|
||||
{
|
||||
observer.OnError(new WWWErrorException(www, www.text));
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnNext(www.text);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator FetchBytes(WWW www, IObserver<byte[]> observer, IProgress<float> reportProgress, CancellationToken cancel)
|
||||
{
|
||||
using (www)
|
||||
{
|
||||
if (reportProgress != null)
|
||||
{
|
||||
while (!www.isDone && !cancel.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!www.isDone)
|
||||
{
|
||||
yield return www;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (reportProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(www.error))
|
||||
{
|
||||
observer.OnError(new WWWErrorException(www, www.text));
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnNext(www.bytes);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator FetchAssetBundle(WWW www, IObserver<AssetBundle> observer, IProgress<float> reportProgress, CancellationToken cancel)
|
||||
{
|
||||
using (www)
|
||||
{
|
||||
if (reportProgress != null)
|
||||
{
|
||||
while (!www.isDone && !cancel.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!www.isDone)
|
||||
{
|
||||
yield return www;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (reportProgress != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reportProgress.Report(www.progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(www.error))
|
||||
{
|
||||
observer.OnError(new WWWErrorException(www, ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnNext(www.assetBundle);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WWWErrorException : Exception
|
||||
{
|
||||
public string RawErrorMessage { get; private set; }
|
||||
public bool HasResponse { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
public System.Net.HttpStatusCode StatusCode { get; private set; }
|
||||
public System.Collections.Generic.Dictionary<string, string> ResponseHeaders { get; private set; }
|
||||
public WWW WWW { get; private set; }
|
||||
|
||||
// cache the text because if www was disposed, can't access it.
|
||||
public WWWErrorException(WWW www, string text)
|
||||
{
|
||||
this.WWW = www;
|
||||
this.RawErrorMessage = www.error;
|
||||
this.ResponseHeaders = www.responseHeaders;
|
||||
this.HasResponse = false;
|
||||
this.Text = text;
|
||||
|
||||
var splitted = RawErrorMessage.Split(' ', ':');
|
||||
if (splitted.Length != 0)
|
||||
{
|
||||
int statusCode;
|
||||
if (int.TryParse(splitted[0], out statusCode))
|
||||
{
|
||||
this.HasResponse = true;
|
||||
this.StatusCode = (System.Net.HttpStatusCode)statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var text = this.Text;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return RawErrorMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RawErrorMessage + " " + text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
#pragma warning restore CS0618
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba71e5544e233dd4b83d4c5a6c696d05
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,264 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UniRx.InternalUtil;
|
||||
using UniRx.Triggers;
|
||||
|
||||
#if !UniRxLibrary
|
||||
using ObservableUnity = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public static partial class ObserveExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
|
||||
/// </summary>
|
||||
/// <param name="fastDestroyCheck">If true and target is UnityObject, use destroyed check by additional component. It is faster check for lifecycle but needs initial cost.</param>
|
||||
public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType = FrameCountType.Update, bool fastDestroyCheck = false)
|
||||
where TSource : class
|
||||
{
|
||||
return ObserveEveryValueChanged(source, propertySelector, frameCountType, UnityEqualityComparer.GetDefault<TProperty>(), fastDestroyCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
|
||||
/// </summary>
|
||||
public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType, IEqualityComparer<TProperty> comparer)
|
||||
where TSource : class
|
||||
{
|
||||
return ObserveEveryValueChanged(source, propertySelector, frameCountType, comparer, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
|
||||
/// </summary>
|
||||
/// <param name="fastDestroyCheck">If true and target is UnityObject, use destroyed check by additional component. It is faster check for lifecycle but needs initial cost.</param>
|
||||
public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType, IEqualityComparer<TProperty> comparer, bool fastDestroyCheck)
|
||||
where TSource : class
|
||||
{
|
||||
if (source == null) return Observable.Empty<TProperty>();
|
||||
if (comparer == null) comparer = UnityEqualityComparer.GetDefault<TProperty>();
|
||||
|
||||
var unityObject = source as UnityEngine.Object;
|
||||
var isUnityObject = source is UnityEngine.Object;
|
||||
if (isUnityObject && unityObject == null) return Observable.Empty<TProperty>();
|
||||
|
||||
// MicroCoroutine does not publish value immediately, so publish value on subscribe.
|
||||
if (isUnityObject)
|
||||
{
|
||||
return ObservableUnity.FromMicroCoroutine<TProperty>((observer, cancellationToken) =>
|
||||
{
|
||||
if (unityObject != null)
|
||||
{
|
||||
var firstValue = default(TProperty);
|
||||
try
|
||||
{
|
||||
firstValue = propertySelector((TSource)(object)unityObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
return EmptyEnumerator();
|
||||
}
|
||||
|
||||
observer.OnNext(firstValue);
|
||||
return PublishUnityObjectValueChanged(unityObject, firstValue, propertySelector, comparer, observer, cancellationToken, fastDestroyCheck);
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
return EmptyEnumerator();
|
||||
}
|
||||
}, frameCountType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reference = new WeakReference(source);
|
||||
source = null;
|
||||
|
||||
return ObservableUnity.FromMicroCoroutine<TProperty>((observer, cancellationToken) =>
|
||||
{
|
||||
var target = reference.Target;
|
||||
if (target != null)
|
||||
{
|
||||
var firstValue = default(TProperty);
|
||||
try
|
||||
{
|
||||
firstValue = propertySelector((TSource)target);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
return EmptyEnumerator();
|
||||
}
|
||||
finally
|
||||
{
|
||||
target = null;
|
||||
}
|
||||
|
||||
observer.OnNext(firstValue);
|
||||
return PublishPocoValueChanged(reference, firstValue, propertySelector, comparer, observer, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
return EmptyEnumerator();
|
||||
}
|
||||
}, frameCountType);
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator EmptyEnumerator()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
static IEnumerator PublishPocoValueChanged<TSource, TProperty>(WeakReference sourceReference, TProperty firstValue, Func<TSource, TProperty> propertySelector, IEqualityComparer<TProperty> comparer, IObserver<TProperty> observer, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentValue = default(TProperty);
|
||||
var prevValue = firstValue;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var target = sourceReference.Target;
|
||||
if (target != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentValue = propertySelector((TSource)target);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
target = null; // remove reference(must need!)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!comparer.Equals(currentValue, prevValue))
|
||||
{
|
||||
observer.OnNext(currentValue);
|
||||
prevValue = currentValue;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator PublishUnityObjectValueChanged<TSource, TProperty>(UnityEngine.Object unityObject, TProperty firstValue, Func<TSource, TProperty> propertySelector, IEqualityComparer<TProperty> comparer, IObserver<TProperty> observer, CancellationToken cancellationToken, bool fastDestroyCheck)
|
||||
{
|
||||
var currentValue = default(TProperty);
|
||||
var prevValue = firstValue;
|
||||
|
||||
var source = (TSource)(object)unityObject;
|
||||
|
||||
if (fastDestroyCheck)
|
||||
{
|
||||
ObservableDestroyTrigger destroyTrigger = null;
|
||||
{
|
||||
var gameObject = unityObject as UnityEngine.GameObject;
|
||||
if (gameObject == null)
|
||||
{
|
||||
var comp = unityObject as UnityEngine.Component;
|
||||
if (comp != null)
|
||||
{
|
||||
gameObject = comp.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// can't use faster path
|
||||
if (gameObject == null) goto STANDARD_LOOP;
|
||||
|
||||
destroyTrigger = GetOrAddDestroyTrigger(gameObject);
|
||||
}
|
||||
|
||||
// fast compare path
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var isDestroyed = destroyTrigger.IsActivated
|
||||
? !destroyTrigger.IsCalledOnDestroy
|
||||
: (unityObject != null);
|
||||
|
||||
if (isDestroyed)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentValue = propertySelector(source);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!comparer.Equals(currentValue, prevValue))
|
||||
{
|
||||
observer.OnNext(currentValue);
|
||||
prevValue = currentValue;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
STANDARD_LOOP:
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (unityObject != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentValue = propertySelector(source);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!comparer.Equals(currentValue, prevValue))
|
||||
{
|
||||
observer.OnNext(currentValue);
|
||||
prevValue = currentValue;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
static ObservableDestroyTrigger GetOrAddDestroyTrigger(UnityEngine.GameObject go)
|
||||
{
|
||||
var dt = go.GetComponent<ObservableDestroyTrigger>();
|
||||
if (dt == null)
|
||||
{
|
||||
dt = go.AddComponent<ObservableDestroyTrigger>();
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8741793924a6c2f4ea22ba27031d531f
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d126dc4a05228e418759d57f7661329
|
||||
folderAsset: yes
|
||||
timeCreated: 1455373896
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class BatchFrameObservable<T> : OperatorObservableBase<IList<T>>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public BatchFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<IList<T>> observer, IDisposable cancel)
|
||||
{
|
||||
return new BatchFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class BatchFrame : OperatorObserverBase<T, IList<T>>
|
||||
{
|
||||
readonly BatchFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
readonly BooleanDisposable cancellationToken = new BooleanDisposable();
|
||||
readonly System.Collections.IEnumerator timer;
|
||||
bool isRunning;
|
||||
bool isCompleted;
|
||||
List<T> list;
|
||||
|
||||
public BatchFrame(BatchFrameObservable<T> parent, IObserver<IList<T>> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.timer = new ReusableEnumerator(this);
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
list = new List<T>();
|
||||
var sourceSubscription = parent.source.Subscribe(this);
|
||||
return StableCompositeDisposable.Create(sourceSubscription, cancellationToken);
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (isCompleted) return;
|
||||
list.Add(value);
|
||||
if (!isRunning)
|
||||
{
|
||||
isRunning = true;
|
||||
timer.Reset(); // reuse
|
||||
|
||||
switch (parent.frameCountType)
|
||||
{
|
||||
case FrameCountType.Update:
|
||||
MainThreadDispatcher.StartUpdateMicroCoroutine(timer);
|
||||
break;
|
||||
case FrameCountType.FixedUpdate:
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(timer);
|
||||
break;
|
||||
case FrameCountType.EndOfFrame:
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(timer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
List<T> currentList;
|
||||
lock (gate)
|
||||
{
|
||||
isCompleted = true;
|
||||
currentList = list;
|
||||
}
|
||||
if (currentList.Count != 0)
|
||||
{
|
||||
observer.OnNext(currentList);
|
||||
}
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
// reuse, no gc allocate
|
||||
class ReusableEnumerator : System.Collections.IEnumerator
|
||||
{
|
||||
readonly BatchFrame parent;
|
||||
int currentFrame;
|
||||
|
||||
public ReusableEnumerator(BatchFrame parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public object Current
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (parent.cancellationToken.IsDisposed) return false;
|
||||
|
||||
List<T> currentList;
|
||||
lock (parent.gate)
|
||||
{
|
||||
if (currentFrame++ == parent.parent.frameCount)
|
||||
{
|
||||
if (parent.isCompleted) return false;
|
||||
|
||||
currentList = parent.list;
|
||||
parent.list = new List<T>();
|
||||
parent.isRunning = false;
|
||||
|
||||
// exit lock
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
parent.observer.OnNext(currentList);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
currentFrame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class BatchFrameObservable : OperatorObservableBase<Unit>
|
||||
{
|
||||
readonly IObservable<Unit> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public BatchFrameObservable(IObservable<Unit> source, int frameCount, FrameCountType frameCountType)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<Unit> observer, IDisposable cancel)
|
||||
{
|
||||
return new BatchFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class BatchFrame : OperatorObserverBase<Unit, Unit>
|
||||
{
|
||||
readonly BatchFrameObservable parent;
|
||||
readonly object gate = new object();
|
||||
readonly BooleanDisposable cancellationToken = new BooleanDisposable();
|
||||
readonly System.Collections.IEnumerator timer;
|
||||
|
||||
bool isRunning;
|
||||
bool isCompleted;
|
||||
|
||||
public BatchFrame(BatchFrameObservable parent, IObserver<Unit> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.timer = new ReusableEnumerator(this);
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
var sourceSubscription = parent.source.Subscribe(this);
|
||||
return StableCompositeDisposable.Create(sourceSubscription, cancellationToken);
|
||||
}
|
||||
|
||||
public override void OnNext(Unit value)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (!isRunning)
|
||||
{
|
||||
isRunning = true;
|
||||
timer.Reset(); // reuse
|
||||
|
||||
switch (parent.frameCountType)
|
||||
{
|
||||
case FrameCountType.Update:
|
||||
MainThreadDispatcher.StartUpdateMicroCoroutine(timer);
|
||||
break;
|
||||
case FrameCountType.FixedUpdate:
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(timer);
|
||||
break;
|
||||
case FrameCountType.EndOfFrame:
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(timer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
bool running;
|
||||
lock (gate)
|
||||
{
|
||||
running = isRunning;
|
||||
isCompleted = true;
|
||||
}
|
||||
if (running)
|
||||
{
|
||||
observer.OnNext(Unit.Default);
|
||||
}
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
// reuse, no gc allocate
|
||||
class ReusableEnumerator : System.Collections.IEnumerator
|
||||
{
|
||||
readonly BatchFrame parent;
|
||||
int currentFrame;
|
||||
|
||||
public ReusableEnumerator(BatchFrame parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public object Current
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (parent.cancellationToken.IsDisposed) return false;
|
||||
|
||||
lock (parent.gate)
|
||||
{
|
||||
if (currentFrame++ == parent.parent.frameCount)
|
||||
{
|
||||
if (parent.isCompleted) return false;
|
||||
parent.isRunning = false;
|
||||
|
||||
// exit lock
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
parent.observer.OnNext(Unit.Default);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
currentFrame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74a2b6b8c63d1144f914c7f0d6719a36
|
||||
timeCreated: 1467771656
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class DelayFrameObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public DelayFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new DelayFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class DelayFrame : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly DelayFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
readonly QueuePool pool = new QueuePool();
|
||||
int runningEnumeratorCount;
|
||||
bool readyDrainEnumerator;
|
||||
bool running;
|
||||
IDisposable sourceSubscription;
|
||||
Queue<T> currentQueueReference;
|
||||
bool calledCompleted;
|
||||
bool hasError;
|
||||
Exception error;
|
||||
BooleanDisposable cancelationToken;
|
||||
|
||||
public DelayFrame(DelayFrameObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
cancelationToken = new BooleanDisposable();
|
||||
|
||||
var _sourceSubscription = new SingleAssignmentDisposable();
|
||||
sourceSubscription = _sourceSubscription;
|
||||
_sourceSubscription.Disposable = parent.source.Subscribe(this);
|
||||
|
||||
return StableCompositeDisposable.Create(cancelationToken, sourceSubscription);
|
||||
}
|
||||
|
||||
IEnumerator DrainQueue(Queue<T> q, int frameCount)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
readyDrainEnumerator = false; // use next queue.
|
||||
running = false;
|
||||
}
|
||||
|
||||
while (!cancelationToken.IsDisposed && frameCount-- != 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (q != null)
|
||||
{
|
||||
while (q.Count > 0 && !hasError)
|
||||
{
|
||||
if (cancelationToken.IsDisposed) break;
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
running = true;
|
||||
}
|
||||
|
||||
var value = q.Dequeue();
|
||||
observer.OnNext(value);
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (q.Count == 0)
|
||||
{
|
||||
pool.Return(q);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError)
|
||||
{
|
||||
if (!cancelationToken.IsDisposed)
|
||||
{
|
||||
cancelationToken.Dispose();
|
||||
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
else if (calledCompleted)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
// not self only
|
||||
if (runningEnumeratorCount != 1) yield break;
|
||||
}
|
||||
|
||||
if (!cancelationToken.IsDisposed)
|
||||
{
|
||||
cancelationToken.Dispose();
|
||||
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
runningEnumeratorCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
if (cancelationToken.IsDisposed) return;
|
||||
|
||||
Queue<T> targetQueue = null;
|
||||
lock (gate)
|
||||
{
|
||||
if (!readyDrainEnumerator)
|
||||
{
|
||||
readyDrainEnumerator = true;
|
||||
runningEnumeratorCount++;
|
||||
targetQueue = currentQueueReference = pool.Get();
|
||||
targetQueue.Enqueue(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentQueueReference != null) // null - if doesn't start OnNext and start OnCompleted
|
||||
{
|
||||
currentQueueReference.Enqueue(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (parent.frameCountType)
|
||||
{
|
||||
case FrameCountType.Update:
|
||||
MainThreadDispatcher.StartUpdateMicroCoroutine(DrainQueue(targetQueue, parent.frameCount));
|
||||
break;
|
||||
case FrameCountType.FixedUpdate:
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(DrainQueue(targetQueue, parent.frameCount));
|
||||
break;
|
||||
case FrameCountType.EndOfFrame:
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(DrainQueue(targetQueue, parent.frameCount));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Invalid FrameCountType:" + parent.frameCountType);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
sourceSubscription.Dispose(); // stop subscription
|
||||
|
||||
if (cancelationToken.IsDisposed) return;
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
hasError = true;
|
||||
this.error = error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cancelationToken.Dispose();
|
||||
try { base.observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
sourceSubscription.Dispose(); // stop subscription
|
||||
|
||||
if (cancelationToken.IsDisposed) return;
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
calledCompleted = true;
|
||||
|
||||
if (!readyDrainEnumerator)
|
||||
{
|
||||
readyDrainEnumerator = true;
|
||||
runningEnumeratorCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (parent.frameCountType)
|
||||
{
|
||||
case FrameCountType.Update:
|
||||
MainThreadDispatcher.StartUpdateMicroCoroutine(DrainQueue(null, parent.frameCount));
|
||||
break;
|
||||
case FrameCountType.FixedUpdate:
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(DrainQueue(null, parent.frameCount));
|
||||
break;
|
||||
case FrameCountType.EndOfFrame:
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(DrainQueue(null, parent.frameCount));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Invalid FrameCountType:" + parent.frameCountType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QueuePool
|
||||
{
|
||||
readonly object gate = new object();
|
||||
readonly Queue<Queue<T>> pool = new Queue<Queue<T>>(2);
|
||||
|
||||
public Queue<T> Get()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (pool.Count == 0)
|
||||
{
|
||||
return new Queue<T>(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return pool.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Return(Queue<T> q)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
pool.Enqueue(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 868f75a703f1a944a801ab9c9b4512aa
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
#if UniRxLibrary
|
||||
using UnityObservable = UniRx.ObservableUnity;
|
||||
#else
|
||||
using UnityObservable = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class DelayFrameSubscriptionObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public DelayFrameSubscriptionObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
var d = new MultipleAssignmentDisposable();
|
||||
d.Disposable = UnityObservable.TimerFrame(frameCount, frameCountType)
|
||||
.SubscribeWithState3(observer, d, source, (_, o, disp, s) =>
|
||||
{
|
||||
disp.Disposable = s.Subscribe(o);
|
||||
});
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecfef95eedf36c2448944fb8932f682c
|
||||
timeCreated: 1455373902
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class FrameIntervalObservable<T> : OperatorObservableBase<UniRx.FrameInterval<T>>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
|
||||
public FrameIntervalObservable(IObservable<T> source)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<UniRx.FrameInterval<T>> observer, IDisposable cancel)
|
||||
{
|
||||
return source.Subscribe(new FrameInterval(observer, cancel));
|
||||
}
|
||||
|
||||
class FrameInterval : OperatorObserverBase<T, UniRx.FrameInterval<T>>
|
||||
{
|
||||
int lastFrame;
|
||||
|
||||
public FrameInterval(IObserver<UniRx.FrameInterval<T>> observer, IDisposable cancel)
|
||||
: base(observer, cancel)
|
||||
{
|
||||
this.lastFrame = UnityEngine.Time.frameCount;
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
var now = UnityEngine.Time.frameCount;
|
||||
var span = now - lastFrame;
|
||||
lastFrame = now;
|
||||
|
||||
base.observer.OnNext(new UniRx.FrameInterval<T>(value, span));
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a731a1a74be20a04a9d7dedc5ceefab2
|
||||
timeCreated: 1467771656
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class FrameTimeIntervalObservable<T> : OperatorObservableBase<UniRx.TimeInterval<T>>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly bool ignoreTimeScale;
|
||||
|
||||
public FrameTimeIntervalObservable(IObservable<T> source, bool ignoreTimeScale)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.ignoreTimeScale = ignoreTimeScale;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<UniRx.TimeInterval<T>> observer, IDisposable cancel)
|
||||
{
|
||||
return source.Subscribe(new FrameTimeInterval(this, observer, cancel));
|
||||
}
|
||||
|
||||
class FrameTimeInterval : OperatorObserverBase<T, UniRx.TimeInterval<T>>
|
||||
{
|
||||
readonly FrameTimeIntervalObservable<T> parent;
|
||||
float lastTime;
|
||||
|
||||
public FrameTimeInterval(FrameTimeIntervalObservable<T> parent, IObserver<UniRx.TimeInterval<T>> observer, IDisposable cancel)
|
||||
: base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.lastTime = (parent.ignoreTimeScale)
|
||||
? UnityEngine.Time.unscaledTime
|
||||
: UnityEngine.Time.time;
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
var now = (parent.ignoreTimeScale)
|
||||
? UnityEngine.Time.unscaledTime
|
||||
: UnityEngine.Time.time;
|
||||
var span = now - lastTime;
|
||||
lastTime = now;
|
||||
|
||||
base.observer.OnNext(new UniRx.TimeInterval<T>(value, TimeSpan.FromSeconds(span)));
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a55cce9ef638364409d1227a25a32421
|
||||
timeCreated: 1467771656
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class FromCoroutineObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly Func<IObserver<T>, CancellationToken, IEnumerator> coroutine;
|
||||
|
||||
public FromCoroutineObservable(Func<IObserver<T>, CancellationToken, IEnumerator> coroutine)
|
||||
: base(false)
|
||||
{
|
||||
this.coroutine = coroutine;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
var fromCoroutineObserver = new FromCoroutine(observer, cancel);
|
||||
|
||||
#if (NETFX_CORE || NET_4_6 || NET_STANDARD_2_0 || UNITY_WSA_10_0)
|
||||
var moreCancel = new CancellationDisposable();
|
||||
var token = moreCancel.Token;
|
||||
#else
|
||||
var moreCancel = new BooleanDisposable();
|
||||
var token = new CancellationToken(moreCancel);
|
||||
#endif
|
||||
|
||||
MainThreadDispatcher.SendStartCoroutine(coroutine(fromCoroutineObserver, token));
|
||||
|
||||
return moreCancel;
|
||||
}
|
||||
|
||||
class FromCoroutine : OperatorObserverBase<T, T>
|
||||
{
|
||||
public FromCoroutine(IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.observer.OnNext(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class FromMicroCoroutineObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly Func<IObserver<T>, CancellationToken, IEnumerator> coroutine;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public FromMicroCoroutineObservable(Func<IObserver<T>, CancellationToken, IEnumerator> coroutine, FrameCountType frameCountType)
|
||||
: base(false)
|
||||
{
|
||||
this.coroutine = coroutine;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
var microCoroutineObserver = new FromMicroCoroutine(observer, cancel);
|
||||
|
||||
#if (NETFX_CORE || NET_4_6 || NET_STANDARD_2_0 || UNITY_WSA_10_0)
|
||||
var moreCancel = new CancellationDisposable();
|
||||
var token = moreCancel.Token;
|
||||
#else
|
||||
var moreCancel = new BooleanDisposable();
|
||||
var token = new CancellationToken(moreCancel);
|
||||
#endif
|
||||
|
||||
switch (frameCountType)
|
||||
{
|
||||
case FrameCountType.Update:
|
||||
MainThreadDispatcher.StartUpdateMicroCoroutine(coroutine(microCoroutineObserver, token));
|
||||
break;
|
||||
case FrameCountType.FixedUpdate:
|
||||
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(coroutine(microCoroutineObserver, token));
|
||||
break;
|
||||
case FrameCountType.EndOfFrame:
|
||||
MainThreadDispatcher.StartEndOfFrameMicroCoroutine(coroutine(microCoroutineObserver, token));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Invalid FrameCountType:" + frameCountType);
|
||||
}
|
||||
|
||||
return moreCancel;
|
||||
}
|
||||
|
||||
class FromMicroCoroutine : OperatorObserverBase<T, T>
|
||||
{
|
||||
public FromMicroCoroutine(IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.observer.OnNext(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e83ddad992535fb4f8a68a1e7ef8be60
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class RepeatUntilObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IEnumerable<IObservable<T>> sources;
|
||||
readonly IObservable<Unit> trigger;
|
||||
readonly GameObject lifeTimeChecker;
|
||||
|
||||
public RepeatUntilObservable(IEnumerable<IObservable<T>> sources, IObservable<Unit> trigger, GameObject lifeTimeChecker)
|
||||
: base(true)
|
||||
{
|
||||
this.sources = sources;
|
||||
this.trigger = trigger;
|
||||
this.lifeTimeChecker = lifeTimeChecker;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new RepeatUntil(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class RepeatUntil : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly RepeatUntilObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
|
||||
IEnumerator<IObservable<T>> e;
|
||||
SerialDisposable subscription;
|
||||
SingleAssignmentDisposable schedule;
|
||||
Action nextSelf;
|
||||
bool isStopped;
|
||||
bool isDisposed;
|
||||
bool isFirstSubscribe;
|
||||
IDisposable stopper;
|
||||
|
||||
public RepeatUntil(RepeatUntilObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
isFirstSubscribe = true;
|
||||
isDisposed = false;
|
||||
isStopped = false;
|
||||
e = parent.sources.GetEnumerator();
|
||||
subscription = new SerialDisposable();
|
||||
schedule = new SingleAssignmentDisposable();
|
||||
|
||||
stopper = parent.trigger.Subscribe(_ =>
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
isStopped = true;
|
||||
e.Dispose();
|
||||
subscription.Dispose();
|
||||
schedule.Dispose();
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}, observer.OnError);
|
||||
|
||||
schedule.Disposable = Scheduler.CurrentThread.Schedule(RecursiveRun);
|
||||
|
||||
return new CompositeDisposable(schedule, subscription, stopper, Disposable.Create(() =>
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
isDisposed = true;
|
||||
e.Dispose();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void RecursiveRun(Action self)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
this.nextSelf = self;
|
||||
if (isDisposed) return;
|
||||
if (isStopped) return;
|
||||
|
||||
var current = default(IObservable<T>);
|
||||
var hasNext = false;
|
||||
var ex = default(Exception);
|
||||
|
||||
try
|
||||
{
|
||||
hasNext = e.MoveNext();
|
||||
if (hasNext)
|
||||
{
|
||||
current = e.Current;
|
||||
if (current == null) throw new InvalidOperationException("sequence is null.");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ex = exception;
|
||||
e.Dispose();
|
||||
}
|
||||
|
||||
if (ex != null)
|
||||
{
|
||||
stopper.Dispose();
|
||||
observer.OnError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasNext)
|
||||
{
|
||||
stopper.Dispose();
|
||||
observer.OnCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
var source = e.Current;
|
||||
var d = new SingleAssignmentDisposable();
|
||||
subscription.Disposable = d;
|
||||
|
||||
if (isFirstSubscribe)
|
||||
{
|
||||
isFirstSubscribe = false;
|
||||
d.Disposable = source.Subscribe(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
MainThreadDispatcher.SendStartCoroutine(SubscribeAfterEndOfFrame(d, source, this, parent.lifeTimeChecker));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerator SubscribeAfterEndOfFrame(SingleAssignmentDisposable d, IObservable<T> source, IObserver<T> observer, GameObject lifeTimeChecker)
|
||||
{
|
||||
yield return YieldInstructionCache.WaitForEndOfFrame;
|
||||
if (!d.IsDisposed && lifeTimeChecker != null)
|
||||
{
|
||||
d.Disposable = source.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
base.observer.OnNext(value);
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
try { observer.OnError(error); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
this.nextSelf();
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Dispose();
|
||||
if (!isDisposed)
|
||||
{
|
||||
try { observer.OnCompleted(); }
|
||||
finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93507e8a72a71094f870c8dbe1e5bed8
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
|
||||
#if UniRxLibrary
|
||||
using UnityObservable = UniRx.ObservableUnity;
|
||||
#else
|
||||
using UnityObservable = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class SampleFrameObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public SampleFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType) : base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new SampleFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class SampleFrame : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly SampleFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
T latestValue = default(T);
|
||||
bool isUpdated = false;
|
||||
bool isCompleted = false;
|
||||
SingleAssignmentDisposable sourceSubscription;
|
||||
|
||||
public SampleFrame(SampleFrameObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
sourceSubscription = new SingleAssignmentDisposable();
|
||||
sourceSubscription.Disposable = parent.source.Subscribe(this);
|
||||
|
||||
var scheduling = UnityObservable.IntervalFrame(parent.frameCount, parent.frameCountType)
|
||||
.Subscribe(new SampleFrameTick(this));
|
||||
|
||||
return StableCompositeDisposable.Create(sourceSubscription, scheduling);
|
||||
}
|
||||
|
||||
void OnNextTick(long _)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (isUpdated)
|
||||
{
|
||||
var value = latestValue;
|
||||
isUpdated = false;
|
||||
observer.OnNext(value);
|
||||
}
|
||||
if (isCompleted)
|
||||
{
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
latestValue = value;
|
||||
isUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
try { base.observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
isCompleted = true;
|
||||
sourceSubscription.Dispose();
|
||||
}
|
||||
}
|
||||
class SampleFrameTick : IObserver<long>
|
||||
{
|
||||
readonly SampleFrame parent;
|
||||
|
||||
public SampleFrameTick(SampleFrame parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNext(long _)
|
||||
{
|
||||
lock (parent.gate)
|
||||
{
|
||||
if (parent.isUpdated)
|
||||
{
|
||||
var value = parent.latestValue;
|
||||
parent.isUpdated = false;
|
||||
parent.observer.OnNext(value);
|
||||
}
|
||||
if (parent.isCompleted)
|
||||
{
|
||||
try { parent.observer.OnCompleted(); } finally { parent.Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e04c7fc1929a3db458bf7ae31bcd9e55
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class SubscribeOnMainThreadObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly IObservable<long> subscribeTrigger;
|
||||
|
||||
public SubscribeOnMainThreadObservable(IObservable<T> source, IObservable<long> subscribeTrigger)
|
||||
: base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.subscribeTrigger = subscribeTrigger;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
var m = new SingleAssignmentDisposable();
|
||||
var d = new SerialDisposable();
|
||||
d.Disposable = m;
|
||||
|
||||
m.Disposable = subscribeTrigger.SubscribeWithState3(observer, d, source, (_, o, disp, s) =>
|
||||
{
|
||||
disp.Disposable = s.Subscribe(o);
|
||||
});
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da3fd97518766ab43827991b7b5d4270
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
|
||||
#if UniRxLibrary
|
||||
using UnityObservable = UniRx.ObservableUnity;
|
||||
#else
|
||||
using UnityObservable = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class ThrottleFirstFrameObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public ThrottleFirstFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType) : base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new ThrottleFirstFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class ThrottleFirstFrame : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly ThrottleFirstFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
bool open = true;
|
||||
SerialDisposable cancelable;
|
||||
|
||||
ThrottleFirstFrameTick tick;
|
||||
|
||||
public ThrottleFirstFrame(ThrottleFirstFrameObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
tick = new ThrottleFirstFrameTick(this);
|
||||
cancelable = new SerialDisposable();
|
||||
|
||||
var subscription = parent.source.Subscribe(this);
|
||||
return StableCompositeDisposable.Create(cancelable, subscription);
|
||||
}
|
||||
|
||||
void OnNext()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
open = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (!open) return;
|
||||
observer.OnNext(value);
|
||||
open = false;
|
||||
}
|
||||
|
||||
var d = new SingleAssignmentDisposable();
|
||||
cancelable.Disposable = d;
|
||||
d.Disposable = UnityObservable.TimerFrame(parent.frameCount, parent.frameCountType)
|
||||
.Subscribe(tick);
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
cancelable.Dispose();
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
cancelable.Dispose();
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
// immutable, can share.
|
||||
class ThrottleFirstFrameTick : IObserver<long>
|
||||
{
|
||||
readonly ThrottleFirstFrame parent;
|
||||
|
||||
public ThrottleFirstFrameTick(ThrottleFirstFrame parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNext(long _)
|
||||
{
|
||||
lock (parent.gate)
|
||||
{
|
||||
parent.open = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ec92e777b0b4d949967b0663ce8bee8
|
||||
timeCreated: 1455373898
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
|
||||
#if UniRxLibrary
|
||||
using UnityObservable = UniRx.ObservableUnity;
|
||||
#else
|
||||
using UnityObservable = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class ThrottleFrameObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public ThrottleFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType) : base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new ThrottleFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class ThrottleFrame : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly ThrottleFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
T latestValue = default(T);
|
||||
bool hasValue = false;
|
||||
SerialDisposable cancelable;
|
||||
ulong id = 0;
|
||||
|
||||
public ThrottleFrame(ThrottleFrameObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
cancelable = new SerialDisposable();
|
||||
var subscription = parent.source.Subscribe(this);
|
||||
|
||||
return StableCompositeDisposable.Create(cancelable, subscription);
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
ulong currentid;
|
||||
lock (gate)
|
||||
{
|
||||
hasValue = true;
|
||||
latestValue = value;
|
||||
id = unchecked(id + 1);
|
||||
currentid = id;
|
||||
}
|
||||
|
||||
var d = new SingleAssignmentDisposable();
|
||||
cancelable.Disposable = d;
|
||||
d.Disposable = UnityObservable.TimerFrame(parent.frameCount, parent.frameCountType)
|
||||
.Subscribe(new ThrottleFrameTick(this, currentid));
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
cancelable.Dispose();
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
hasValue = false;
|
||||
id = unchecked(id + 1);
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
cancelable.Dispose();
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
if (hasValue)
|
||||
{
|
||||
observer.OnNext(latestValue);
|
||||
}
|
||||
hasValue = false;
|
||||
id = unchecked(id + 1);
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
class ThrottleFrameTick : IObserver<long>
|
||||
{
|
||||
readonly ThrottleFrame parent;
|
||||
readonly ulong currentid;
|
||||
|
||||
public ThrottleFrameTick(ThrottleFrame parent, ulong currentid)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.currentid = currentid;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNext(long _)
|
||||
{
|
||||
lock (parent.gate)
|
||||
{
|
||||
if (parent.hasValue && parent.id == currentid)
|
||||
{
|
||||
parent.observer.OnNext(parent.latestValue);
|
||||
}
|
||||
parent.hasValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c4ef0bfcfe787543999c7a6cda03c07
|
||||
timeCreated: 1455373898
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
|
||||
#if UniRxLibrary
|
||||
using UnityObservable = UniRx.ObservableUnity;
|
||||
#else
|
||||
using UnityObservable = UniRx.Observable;
|
||||
#endif
|
||||
|
||||
namespace UniRx.Operators
|
||||
{
|
||||
internal class TimeoutFrameObservable<T> : OperatorObservableBase<T>
|
||||
{
|
||||
readonly IObservable<T> source;
|
||||
readonly int frameCount;
|
||||
readonly FrameCountType frameCountType;
|
||||
|
||||
public TimeoutFrameObservable(IObservable<T> source, int frameCount, FrameCountType frameCountType) : base(source.IsRequiredSubscribeOnCurrentThread())
|
||||
{
|
||||
this.source = source;
|
||||
this.frameCount = frameCount;
|
||||
this.frameCountType = frameCountType;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
|
||||
{
|
||||
return new TimeoutFrame(this, observer, cancel).Run();
|
||||
}
|
||||
|
||||
class TimeoutFrame : OperatorObserverBase<T, T>
|
||||
{
|
||||
readonly TimeoutFrameObservable<T> parent;
|
||||
readonly object gate = new object();
|
||||
ulong objectId = 0ul;
|
||||
bool isTimeout = false;
|
||||
SingleAssignmentDisposable sourceSubscription;
|
||||
SerialDisposable timerSubscription;
|
||||
|
||||
public TimeoutFrame(TimeoutFrameObservable<T> parent, IObserver<T> observer, IDisposable cancel) : base(observer, cancel)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Run()
|
||||
{
|
||||
sourceSubscription = new SingleAssignmentDisposable();
|
||||
timerSubscription = new SerialDisposable();
|
||||
timerSubscription.Disposable = RunTimer(objectId);
|
||||
sourceSubscription.Disposable = parent.source.Subscribe(this);
|
||||
|
||||
return StableCompositeDisposable.Create(timerSubscription, sourceSubscription);
|
||||
}
|
||||
|
||||
IDisposable RunTimer(ulong timerId)
|
||||
{
|
||||
return UnityObservable.TimerFrame(parent.frameCount, parent.frameCountType)
|
||||
.Subscribe(new TimeoutFrameTick(this, timerId));
|
||||
}
|
||||
|
||||
public override void OnNext(T value)
|
||||
{
|
||||
ulong useObjectId;
|
||||
bool timeout;
|
||||
lock (gate)
|
||||
{
|
||||
timeout = isTimeout;
|
||||
objectId++;
|
||||
useObjectId = objectId;
|
||||
}
|
||||
if (timeout) return;
|
||||
|
||||
timerSubscription.Disposable = Disposable.Empty; // cancel old timer
|
||||
observer.OnNext(value);
|
||||
timerSubscription.Disposable = RunTimer(useObjectId);
|
||||
}
|
||||
|
||||
public override void OnError(Exception error)
|
||||
{
|
||||
bool timeout;
|
||||
lock (gate)
|
||||
{
|
||||
timeout = isTimeout;
|
||||
objectId++;
|
||||
}
|
||||
if (timeout) return;
|
||||
|
||||
timerSubscription.Dispose();
|
||||
try { observer.OnError(error); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
public override void OnCompleted()
|
||||
{
|
||||
bool timeout;
|
||||
lock (gate)
|
||||
{
|
||||
timeout = isTimeout;
|
||||
objectId++;
|
||||
}
|
||||
if (timeout) return;
|
||||
|
||||
timerSubscription.Dispose();
|
||||
try { observer.OnCompleted(); } finally { Dispose(); }
|
||||
}
|
||||
|
||||
class TimeoutFrameTick : IObserver<long>
|
||||
{
|
||||
readonly TimeoutFrame parent;
|
||||
readonly ulong timerId;
|
||||
|
||||
public TimeoutFrameTick(TimeoutFrame parent, ulong timerId)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.timerId = timerId;
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNext(long _)
|
||||
{
|
||||
|
||||
|
||||
lock (parent.gate)
|
||||
{
|
||||
if (parent.objectId == timerId)
|
||||
{
|
||||
parent.isTimeout = true;
|
||||
}
|
||||
}
|
||||
if (parent.isTimeout)
|
||||
{
|
||||
try { parent.observer.OnError(new TimeoutException()); } finally { parent.Dispose(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c27be0a585d78a944bccd31b86ee6722
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public struct CollectionAddEvent<T> : IEquatable<CollectionAddEvent<T>>
|
||||
{
|
||||
public int Index { get; private set; }
|
||||
public T Value { get; private set; }
|
||||
|
||||
public CollectionAddEvent(int index, T value)
|
||||
:this()
|
||||
{
|
||||
Index = index;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Index:{0} Value:{1}", Index, Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2;
|
||||
}
|
||||
|
||||
public bool Equals(CollectionAddEvent<T> other)
|
||||
{
|
||||
return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public struct CollectionRemoveEvent<T> : IEquatable<CollectionRemoveEvent<T>>
|
||||
{
|
||||
public int Index { get; private set; }
|
||||
public T Value { get; private set; }
|
||||
|
||||
public CollectionRemoveEvent(int index, T value)
|
||||
: this()
|
||||
{
|
||||
Index = index;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Index:{0} Value:{1}", Index, Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2;
|
||||
}
|
||||
|
||||
public bool Equals(CollectionRemoveEvent<T> other)
|
||||
{
|
||||
return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public struct CollectionMoveEvent<T> : IEquatable<CollectionMoveEvent<T>>
|
||||
{
|
||||
public int OldIndex { get; private set; }
|
||||
public int NewIndex { get; private set; }
|
||||
public T Value { get; private set; }
|
||||
|
||||
public CollectionMoveEvent(int oldIndex, int newIndex, T value)
|
||||
: this()
|
||||
{
|
||||
OldIndex = oldIndex;
|
||||
NewIndex = newIndex;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("OldIndex:{0} NewIndex:{1} Value:{2}", OldIndex, NewIndex, Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return OldIndex.GetHashCode() ^ NewIndex.GetHashCode() << 2 ^ EqualityComparer<T>.Default.GetHashCode(Value) >> 2;
|
||||
}
|
||||
|
||||
public bool Equals(CollectionMoveEvent<T> other)
|
||||
{
|
||||
return OldIndex.Equals(other.OldIndex) && NewIndex.Equals(other.NewIndex) && EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public struct CollectionReplaceEvent<T> : IEquatable<CollectionReplaceEvent<T>>
|
||||
{
|
||||
public int Index { get; private set; }
|
||||
public T OldValue { get; private set; }
|
||||
public T NewValue { get; private set; }
|
||||
|
||||
public CollectionReplaceEvent(int index, T oldValue, T newValue)
|
||||
: this()
|
||||
{
|
||||
Index = index;
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Index:{0} OldValue:{1} NewValue:{2}", Index, OldValue, NewValue);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(OldValue) << 2 ^ EqualityComparer<T>.Default.GetHashCode(NewValue) >> 2;
|
||||
}
|
||||
|
||||
public bool Equals(CollectionReplaceEvent<T> other)
|
||||
{
|
||||
return Index.Equals(other.Index)
|
||||
&& EqualityComparer<T>.Default.Equals(OldValue, other.OldValue)
|
||||
&& EqualityComparer<T>.Default.Equals(NewValue, other.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyList<out T> is from .NET 4.5
|
||||
public interface IReadOnlyReactiveCollection<T> : IEnumerable<T>
|
||||
{
|
||||
int Count { get; }
|
||||
T this[int index] { get; }
|
||||
IObservable<CollectionAddEvent<T>> ObserveAdd();
|
||||
IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false);
|
||||
IObservable<CollectionMoveEvent<T>> ObserveMove();
|
||||
IObservable<CollectionRemoveEvent<T>> ObserveRemove();
|
||||
IObservable<CollectionReplaceEvent<T>> ObserveReplace();
|
||||
IObservable<Unit> ObserveReset();
|
||||
}
|
||||
|
||||
public interface IReactiveCollection<T> : IList<T>, IReadOnlyReactiveCollection<T>
|
||||
{
|
||||
new int Count { get; }
|
||||
new T this[int index] { get; set; }
|
||||
void Move(int oldIndex, int newIndex);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ReactiveCollection<T> : Collection<T>, IReactiveCollection<T>, IDisposable
|
||||
{
|
||||
[NonSerialized]
|
||||
bool isDisposed = false;
|
||||
|
||||
public ReactiveCollection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ReactiveCollection(IEnumerable<T> collection)
|
||||
{
|
||||
if (collection == null) throw new ArgumentNullException("collection");
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public ReactiveCollection(List<T> list)
|
||||
: base(list != null ? new List<T>(list) : null)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
var beforeCount = Count;
|
||||
base.ClearItems();
|
||||
|
||||
if (collectionReset != null) collectionReset.OnNext(Unit.Default);
|
||||
if (beforeCount > 0)
|
||||
{
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
|
||||
if (collectionAdd != null) collectionAdd.OnNext(new CollectionAddEvent<T>(index, item));
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
|
||||
public void Move(int oldIndex, int newIndex)
|
||||
{
|
||||
MoveItem(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
protected virtual void MoveItem(int oldIndex, int newIndex)
|
||||
{
|
||||
T item = this[oldIndex];
|
||||
base.RemoveItem(oldIndex);
|
||||
base.InsertItem(newIndex, item);
|
||||
|
||||
if (collectionMove != null) collectionMove.OnNext(new CollectionMoveEvent<T>(oldIndex, newIndex, item));
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
T item = this[index];
|
||||
base.RemoveItem(index);
|
||||
|
||||
if (collectionRemove != null) collectionRemove.OnNext(new CollectionRemoveEvent<T>(index, item));
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, T item)
|
||||
{
|
||||
T oldItem = this[index];
|
||||
base.SetItem(index, item);
|
||||
|
||||
if (collectionReplace != null) collectionReplace.OnNext(new CollectionReplaceEvent<T>(index, oldItem, item));
|
||||
}
|
||||
|
||||
|
||||
[NonSerialized]
|
||||
Subject<int> countChanged = null;
|
||||
public IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false)
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<int>();
|
||||
|
||||
var subject = countChanged ?? (countChanged = new Subject<int>());
|
||||
if (notifyCurrentCount)
|
||||
{
|
||||
return subject.StartWith(() => this.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<Unit> collectionReset = null;
|
||||
public IObservable<Unit> ObserveReset()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<Unit>();
|
||||
return collectionReset ?? (collectionReset = new Subject<Unit>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<CollectionAddEvent<T>> collectionAdd = null;
|
||||
public IObservable<CollectionAddEvent<T>> ObserveAdd()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<CollectionAddEvent<T>>();
|
||||
return collectionAdd ?? (collectionAdd = new Subject<CollectionAddEvent<T>>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<CollectionMoveEvent<T>> collectionMove = null;
|
||||
public IObservable<CollectionMoveEvent<T>> ObserveMove()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<CollectionMoveEvent<T>>();
|
||||
return collectionMove ?? (collectionMove = new Subject<CollectionMoveEvent<T>>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<CollectionRemoveEvent<T>> collectionRemove = null;
|
||||
public IObservable<CollectionRemoveEvent<T>> ObserveRemove()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<CollectionRemoveEvent<T>>();
|
||||
return collectionRemove ?? (collectionRemove = new Subject<CollectionRemoveEvent<T>>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<CollectionReplaceEvent<T>> collectionReplace = null;
|
||||
public IObservable<CollectionReplaceEvent<T>> ObserveReplace()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<CollectionReplaceEvent<T>>();
|
||||
return collectionReplace ?? (collectionReplace = new Subject<CollectionReplaceEvent<T>>());
|
||||
}
|
||||
|
||||
void DisposeSubject<TSubject>(ref Subject<TSubject> subject)
|
||||
{
|
||||
if (subject != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
subject.OnCompleted();
|
||||
}
|
||||
finally
|
||||
{
|
||||
subject.Dispose();
|
||||
subject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
DisposeSubject(ref collectionReset);
|
||||
DisposeSubject(ref collectionAdd);
|
||||
DisposeSubject(ref collectionMove);
|
||||
DisposeSubject(ref collectionRemove);
|
||||
DisposeSubject(ref collectionReplace);
|
||||
DisposeSubject(ref countChanged);
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static partial class ReactiveCollectionExtensions
|
||||
{
|
||||
public static ReactiveCollection<T> ToReactiveCollection<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return new ReactiveCollection<T>(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e22185fb1dbcef42bc613efd4769011
|
||||
timeCreated: 1455373898
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,487 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 939b249fde5252f45a4404e7648931ed
|
||||
timeCreated: 1462927720
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,520 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public struct DictionaryAddEvent<TKey, TValue> : IEquatable<DictionaryAddEvent<TKey, TValue>>
|
||||
{
|
||||
public TKey Key { get; private set; }
|
||||
public TValue Value { get; private set; }
|
||||
|
||||
public DictionaryAddEvent(TKey key, TValue value)
|
||||
: this()
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Key:{0} Value:{1}", Key, Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.GetHashCode(Key) ^ EqualityComparer<TValue>.Default.GetHashCode(Value) << 2;
|
||||
}
|
||||
|
||||
public bool Equals(DictionaryAddEvent<TKey, TValue> other)
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.Equals(Key, other.Key) && EqualityComparer<TValue>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public struct DictionaryRemoveEvent<TKey, TValue> : IEquatable<DictionaryRemoveEvent<TKey, TValue>>
|
||||
{
|
||||
public TKey Key { get; private set; }
|
||||
public TValue Value { get; private set; }
|
||||
|
||||
public DictionaryRemoveEvent(TKey key, TValue value)
|
||||
: this()
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Key:{0} Value:{1}", Key, Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.GetHashCode(Key) ^ EqualityComparer<TValue>.Default.GetHashCode(Value) << 2;
|
||||
}
|
||||
|
||||
public bool Equals(DictionaryRemoveEvent<TKey, TValue> other)
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.Equals(Key, other.Key) && EqualityComparer<TValue>.Default.Equals(Value, other.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public struct DictionaryReplaceEvent<TKey, TValue> : IEquatable<DictionaryReplaceEvent<TKey, TValue>>
|
||||
{
|
||||
public TKey Key { get; private set; }
|
||||
public TValue OldValue { get; private set; }
|
||||
public TValue NewValue { get; private set; }
|
||||
|
||||
public DictionaryReplaceEvent(TKey key, TValue oldValue, TValue newValue)
|
||||
: this()
|
||||
{
|
||||
Key = key;
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Key:{0} OldValue:{1} NewValue:{2}", Key, OldValue, NewValue);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.GetHashCode(Key) ^ EqualityComparer<TValue>.Default.GetHashCode(OldValue) << 2 ^ EqualityComparer<TValue>.Default.GetHashCode(NewValue) >> 2;
|
||||
}
|
||||
|
||||
public bool Equals(DictionaryReplaceEvent<TKey, TValue> other)
|
||||
{
|
||||
return EqualityComparer<TKey>.Default.Equals(Key, other.Key) && EqualityComparer<TValue>.Default.Equals(OldValue, other.OldValue) && EqualityComparer<TValue>.Default.Equals(NewValue, other.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyDictionary is from .NET 4.5
|
||||
public interface IReadOnlyReactiveDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
int Count { get; }
|
||||
TValue this[TKey index] { get; }
|
||||
bool ContainsKey(TKey key);
|
||||
bool TryGetValue(TKey key, out TValue value);
|
||||
|
||||
IObservable<DictionaryAddEvent<TKey, TValue>> ObserveAdd();
|
||||
IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false);
|
||||
IObservable<DictionaryRemoveEvent<TKey, TValue>> ObserveRemove();
|
||||
IObservable<DictionaryReplaceEvent<TKey, TValue>> ObserveReplace();
|
||||
IObservable<Unit> ObserveReset();
|
||||
}
|
||||
|
||||
public interface IReactiveDictionary<TKey, TValue> : IReadOnlyReactiveDictionary<TKey, TValue>, IDictionary<TKey, TValue>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ReactiveDictionary<TKey, TValue> : IReactiveDictionary<TKey, TValue>, IDictionary<TKey, TValue>, IEnumerable, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, IDisposable
|
||||
#if !UNITY_METRO
|
||||
, ISerializable, IDeserializationCallback
|
||||
#endif
|
||||
{
|
||||
[NonSerialized]
|
||||
bool isDisposed = false;
|
||||
|
||||
#if !UniRxLibrary
|
||||
[UnityEngine.SerializeField]
|
||||
#endif
|
||||
readonly Dictionary<TKey, TValue> inner;
|
||||
|
||||
public ReactiveDictionary()
|
||||
{
|
||||
inner = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
public ReactiveDictionary(IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
inner = new Dictionary<TKey, TValue>(comparer);
|
||||
}
|
||||
|
||||
public ReactiveDictionary(Dictionary<TKey, TValue> innerDictionary)
|
||||
{
|
||||
inner = innerDictionary;
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner[key];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
TValue oldValue;
|
||||
if (TryGetValue(key, out oldValue))
|
||||
{
|
||||
inner[key] = value;
|
||||
if (dictionaryReplace != null) dictionaryReplace.OnNext(new DictionaryReplaceEvent<TKey, TValue>(key, oldValue, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
inner[key] = value;
|
||||
if (dictionaryAdd != null) dictionaryAdd.OnNext(new DictionaryAddEvent<TKey, TValue>(key, value));
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.KeyCollection Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.ValueCollection Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
inner.Add(key, value);
|
||||
|
||||
if (dictionaryAdd != null) dictionaryAdd.OnNext(new DictionaryAddEvent<TKey, TValue>(key, value));
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var beforeCount = Count;
|
||||
inner.Clear();
|
||||
|
||||
if (collectionReset != null) collectionReset.OnNext(Unit.Default);
|
||||
if (beforeCount > 0)
|
||||
{
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
TValue oldValue;
|
||||
if (inner.TryGetValue(key, out oldValue))
|
||||
{
|
||||
var isSuccessRemove = inner.Remove(key);
|
||||
if (isSuccessRemove)
|
||||
{
|
||||
if (dictionaryRemove != null) dictionaryRemove.OnNext(new DictionaryRemoveEvent<TKey, TValue>(key, oldValue));
|
||||
if (countChanged != null) countChanged.OnNext(Count);
|
||||
}
|
||||
return isSuccessRemove;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return inner.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return inner.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
|
||||
{
|
||||
return inner.GetEnumerator();
|
||||
}
|
||||
|
||||
void DisposeSubject<TSubject>(ref Subject<TSubject> subject)
|
||||
{
|
||||
if (subject != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
subject.OnCompleted();
|
||||
}
|
||||
finally
|
||||
{
|
||||
subject.Dispose();
|
||||
subject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
DisposeSubject(ref countChanged);
|
||||
DisposeSubject(ref collectionReset);
|
||||
DisposeSubject(ref dictionaryAdd);
|
||||
DisposeSubject(ref dictionaryRemove);
|
||||
DisposeSubject(ref dictionaryReplace);
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Observe
|
||||
|
||||
[NonSerialized]
|
||||
Subject<int> countChanged = null;
|
||||
public IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false)
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<int>();
|
||||
|
||||
var subject = countChanged ?? (countChanged = new Subject<int>());
|
||||
if (notifyCurrentCount)
|
||||
{
|
||||
return subject.StartWith(() => this.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<Unit> collectionReset = null;
|
||||
public IObservable<Unit> ObserveReset()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<Unit>();
|
||||
return collectionReset ?? (collectionReset = new Subject<Unit>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<DictionaryAddEvent<TKey, TValue>> dictionaryAdd = null;
|
||||
public IObservable<DictionaryAddEvent<TKey, TValue>> ObserveAdd()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<DictionaryAddEvent<TKey, TValue>>();
|
||||
return dictionaryAdd ?? (dictionaryAdd = new Subject<DictionaryAddEvent<TKey, TValue>>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<DictionaryRemoveEvent<TKey, TValue>> dictionaryRemove = null;
|
||||
public IObservable<DictionaryRemoveEvent<TKey, TValue>> ObserveRemove()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<DictionaryRemoveEvent<TKey, TValue>>();
|
||||
return dictionaryRemove ?? (dictionaryRemove = new Subject<DictionaryRemoveEvent<TKey, TValue>>());
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Subject<DictionaryReplaceEvent<TKey, TValue>> dictionaryReplace = null;
|
||||
public IObservable<DictionaryReplaceEvent<TKey, TValue>> ObserveReplace()
|
||||
{
|
||||
if (isDisposed) return Observable.Empty<DictionaryReplaceEvent<TKey, TValue>>();
|
||||
return dictionaryReplace ?? (dictionaryReplace = new Subject<DictionaryReplaceEvent<TKey, TValue>>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implement explicit
|
||||
|
||||
object IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this[(TKey)key];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this[(TKey)key] = (TValue)value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool IDictionary.IsFixedSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).IsFixedSize;
|
||||
}
|
||||
}
|
||||
|
||||
bool IDictionary.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection.IsSynchronized
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).IsSynchronized;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection IDictionary.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).Keys;
|
||||
}
|
||||
}
|
||||
|
||||
object ICollection.SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).SyncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection IDictionary.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)inner).Values;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)inner).IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return inner.Values;
|
||||
}
|
||||
}
|
||||
|
||||
void IDictionary.Add(object key, object value)
|
||||
{
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
return ((IDictionary)inner).Contains(key);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
((IDictionary)inner).CopyTo(array, index);
|
||||
}
|
||||
|
||||
#if !UNITY_METRO
|
||||
|
||||
public void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
((ISerializable)inner).GetObjectData(info, context);
|
||||
}
|
||||
|
||||
public void OnDeserialization(object sender)
|
||||
{
|
||||
((IDeserializationCallback)inner).OnDeserialization(sender);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
Remove((TKey)key);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
Add((TKey)item.Key, (TValue)item.Value);
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)inner).Contains(item);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)inner).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)inner).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return inner.GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
TValue v;
|
||||
if (TryGetValue(item.Key, out v))
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(v, item.Value))
|
||||
{
|
||||
Remove(item.Key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return ((IDictionary)inner).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static partial class ReactiveDictionaryExtensions
|
||||
{
|
||||
public static ReactiveDictionary<TKey, TValue> ToReactiveDictionary<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
return new ReactiveDictionary<TKey, TValue>(dictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12cd1079b0fe33f429f9f174c1f849af
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,610 @@
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UniRx.InternalUtil;
|
||||
#if !UniRxLibrary
|
||||
using UnityEngine;
|
||||
#endif
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
public interface IReadOnlyReactiveProperty<T> : IObservable<T>
|
||||
{
|
||||
T Value { get; }
|
||||
bool HasValue { get; }
|
||||
}
|
||||
|
||||
public interface IReactiveProperty<T> : IReadOnlyReactiveProperty<T>
|
||||
{
|
||||
new T Value { get; set; }
|
||||
}
|
||||
|
||||
internal interface IObserverLinkedList<T>
|
||||
{
|
||||
void UnsubscribeNode(ObserverNode<T> node);
|
||||
}
|
||||
|
||||
internal sealed class ObserverNode<T> : IObserver<T>, IDisposable
|
||||
{
|
||||
readonly IObserver<T> observer;
|
||||
IObserverLinkedList<T> list;
|
||||
|
||||
public ObserverNode<T> Previous { get; internal set; }
|
||||
public ObserverNode<T> Next { get; internal set; }
|
||||
|
||||
public ObserverNode(IObserverLinkedList<T> list, IObserver<T> observer)
|
||||
{
|
||||
this.list = list;
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
observer.OnNext(value);
|
||||
}
|
||||
|
||||
public void OnError(Exception error)
|
||||
{
|
||||
observer.OnError(error);
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
observer.OnCompleted();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var sourceList = Interlocked.Exchange(ref list, null);
|
||||
if (sourceList != null)
|
||||
{
|
||||
sourceList.UnsubscribeNode(this);
|
||||
sourceList = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight property broker.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ReactiveProperty<T> : IReactiveProperty<T>, IDisposable, IOptimizedObservable<T>, IObserverLinkedList<T>
|
||||
{
|
||||
#if !UniRxLibrary
|
||||
static readonly IEqualityComparer<T> defaultEqualityComparer = UnityEqualityComparer.GetDefault<T>();
|
||||
#else
|
||||
static readonly IEqualityComparer<T> defaultEqualityComparer = EqualityComparer<T>.Default;
|
||||
#endif
|
||||
|
||||
#if !UniRxLibrary
|
||||
[SerializeField]
|
||||
#endif
|
||||
T value = default(T);
|
||||
|
||||
[NonSerialized]
|
||||
ObserverNode<T> root;
|
||||
|
||||
[NonSerialized]
|
||||
ObserverNode<T> last;
|
||||
|
||||
[NonSerialized]
|
||||
bool isDisposed = false;
|
||||
|
||||
protected virtual IEqualityComparer<T> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultEqualityComparer;
|
||||
}
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!EqualityComparer.Equals(this.value, value))
|
||||
{
|
||||
SetValue(value);
|
||||
if (isDisposed)
|
||||
return;
|
||||
|
||||
RaiseOnNext(ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always true, allows empty constructor 'can' publish value on subscribe.
|
||||
// because sometimes value is deserialized from UnityEngine.
|
||||
public bool HasValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ReactiveProperty()
|
||||
: this(default(T))
|
||||
{
|
||||
}
|
||||
|
||||
public ReactiveProperty(T initialValue)
|
||||
{
|
||||
SetValue(initialValue);
|
||||
}
|
||||
|
||||
void RaiseOnNext(ref T value)
|
||||
{
|
||||
var node = root;
|
||||
while (node != null)
|
||||
{
|
||||
node.OnNext(value);
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SetValue(T value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void SetValueAndForceNotify(T value)
|
||||
{
|
||||
SetValue(value);
|
||||
if (isDisposed)
|
||||
return;
|
||||
|
||||
RaiseOnNext(ref value);
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<T> observer)
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
observer.OnCompleted();
|
||||
return Disposable.Empty;
|
||||
}
|
||||
|
||||
// raise latest value on subscribe
|
||||
observer.OnNext(value);
|
||||
|
||||
// subscribe node, node as subscription.
|
||||
var next = new ObserverNode<T>(this, observer);
|
||||
if (root == null)
|
||||
{
|
||||
root = last = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.Next = next;
|
||||
next.Previous = last;
|
||||
last = next;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void IObserverLinkedList<T>.UnsubscribeNode(ObserverNode<T> node)
|
||||
{
|
||||
if (node == root)
|
||||
{
|
||||
root = node.Next;
|
||||
}
|
||||
if (node == last)
|
||||
{
|
||||
last = node.Previous;
|
||||
}
|
||||
|
||||
if (node.Previous != null)
|
||||
{
|
||||
node.Previous.Next = node.Next;
|
||||
}
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Previous = node.Previous;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
var node = root;
|
||||
root = last = null;
|
||||
isDisposed = true;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
node.OnCompleted();
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (value == null) ? "(null)" : value.ToString();
|
||||
}
|
||||
|
||||
public bool IsRequiredSubscribeOnCurrentThread()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight property broker.
|
||||
/// </summary>
|
||||
public class ReadOnlyReactiveProperty<T> : IReadOnlyReactiveProperty<T>, IDisposable, IOptimizedObservable<T>, IObserverLinkedList<T>, IObserver<T>
|
||||
{
|
||||
#if !UniRxLibrary
|
||||
static readonly IEqualityComparer<T> defaultEqualityComparer = UnityEqualityComparer.GetDefault<T>();
|
||||
#else
|
||||
static readonly IEqualityComparer<T> defaultEqualityComparer = EqualityComparer<T>.Default;
|
||||
#endif
|
||||
|
||||
readonly bool distinctUntilChanged = true;
|
||||
bool canPublishValueOnSubscribe = false;
|
||||
bool isDisposed = false;
|
||||
bool isSourceCompleted = false;
|
||||
|
||||
T latestValue = default(T);
|
||||
Exception lastException = null;
|
||||
IDisposable sourceConnection = null;
|
||||
|
||||
ObserverNode<T> root;
|
||||
ObserverNode<T> last;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return latestValue;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return canPublishValueOnSubscribe;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEqualityComparer<T> EqualityComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultEqualityComparer;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyReactiveProperty(IObservable<T> source)
|
||||
{
|
||||
this.sourceConnection = source.Subscribe(this);
|
||||
}
|
||||
|
||||
public ReadOnlyReactiveProperty(IObservable<T> source, bool distinctUntilChanged)
|
||||
{
|
||||
this.distinctUntilChanged = distinctUntilChanged;
|
||||
this.sourceConnection = source.Subscribe(this);
|
||||
}
|
||||
|
||||
public ReadOnlyReactiveProperty(IObservable<T> source, T initialValue)
|
||||
{
|
||||
this.latestValue = initialValue;
|
||||
this.canPublishValueOnSubscribe = true;
|
||||
this.sourceConnection = source.Subscribe(this);
|
||||
}
|
||||
|
||||
public ReadOnlyReactiveProperty(IObservable<T> source, T initialValue, bool distinctUntilChanged)
|
||||
{
|
||||
this.distinctUntilChanged = distinctUntilChanged;
|
||||
this.latestValue = initialValue;
|
||||
this.canPublishValueOnSubscribe = true;
|
||||
this.sourceConnection = source.Subscribe(this);
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<T> observer)
|
||||
{
|
||||
if (lastException != null)
|
||||
{
|
||||
observer.OnError(lastException);
|
||||
return Disposable.Empty;
|
||||
}
|
||||
|
||||
if (isSourceCompleted)
|
||||
{
|
||||
if (canPublishValueOnSubscribe)
|
||||
{
|
||||
observer.OnNext(latestValue);
|
||||
observer.OnCompleted();
|
||||
return Disposable.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
observer.OnCompleted();
|
||||
return Disposable.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDisposed)
|
||||
{
|
||||
observer.OnCompleted();
|
||||
return Disposable.Empty;
|
||||
}
|
||||
|
||||
if (canPublishValueOnSubscribe)
|
||||
{
|
||||
observer.OnNext(latestValue);
|
||||
}
|
||||
|
||||
// subscribe node, node as subscription.
|
||||
var next = new ObserverNode<T>(this, observer);
|
||||
if (root == null)
|
||||
{
|
||||
root = last = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.Next = next;
|
||||
next.Previous = last;
|
||||
last = next;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
sourceConnection.Dispose();
|
||||
|
||||
var node = root;
|
||||
root = last = null;
|
||||
isDisposed = true;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
node.OnCompleted();
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
void IObserverLinkedList<T>.UnsubscribeNode(ObserverNode<T> node)
|
||||
{
|
||||
if (node == root)
|
||||
{
|
||||
root = node.Next;
|
||||
}
|
||||
if (node == last)
|
||||
{
|
||||
last = node.Previous;
|
||||
}
|
||||
|
||||
if (node.Previous != null)
|
||||
{
|
||||
node.Previous.Next = node.Next;
|
||||
}
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Previous = node.Previous;
|
||||
}
|
||||
}
|
||||
|
||||
void IObserver<T>.OnNext(T value)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
if (canPublishValueOnSubscribe)
|
||||
{
|
||||
if (distinctUntilChanged && EqualityComparer.Equals(this.latestValue, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
canPublishValueOnSubscribe = true;
|
||||
|
||||
// SetValue
|
||||
this.latestValue = value;
|
||||
|
||||
// call source.OnNext
|
||||
var node = root;
|
||||
while (node != null)
|
||||
{
|
||||
node.OnNext(value);
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
void IObserver<T>.OnError(Exception error)
|
||||
{
|
||||
lastException = error;
|
||||
|
||||
// call source.OnError
|
||||
var node = root;
|
||||
while (node != null)
|
||||
{
|
||||
node.OnError(error);
|
||||
node = node.Next;
|
||||
}
|
||||
|
||||
root = last = null;
|
||||
}
|
||||
|
||||
void IObserver<T>.OnCompleted()
|
||||
{
|
||||
isSourceCompleted = true;
|
||||
root = last = null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (latestValue == null) ? "(null)" : latestValue.ToString();
|
||||
}
|
||||
|
||||
public bool IsRequiredSubscribeOnCurrentThread()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods of ReactiveProperty<T>
|
||||
/// </summary>
|
||||
public static class ReactivePropertyExtensions
|
||||
{
|
||||
public static IReadOnlyReactiveProperty<T> ToReactiveProperty<T>(this IObservable<T> source)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source);
|
||||
}
|
||||
|
||||
public static IReadOnlyReactiveProperty<T> ToReactiveProperty<T>(this IObservable<T> source, T initialValue)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source, initialValue);
|
||||
}
|
||||
|
||||
public static ReadOnlyReactiveProperty<T> ToReadOnlyReactiveProperty<T>(this IObservable<T> source)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source);
|
||||
}
|
||||
|
||||
#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> WaitUntilValueChangedAsync<T>(this IReadOnlyReactiveProperty<T> source, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var tcs = new CancellableTaskCompletionSource<T>();
|
||||
|
||||
var disposable = new SingleAssignmentDisposable();
|
||||
if (source.HasValue)
|
||||
{
|
||||
// Skip first value
|
||||
var isFirstValue = true;
|
||||
disposable.Disposable = source.Subscribe(x =>
|
||||
{
|
||||
if (isFirstValue)
|
||||
{
|
||||
isFirstValue = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
disposable.Dispose(); // finish subscription.
|
||||
tcs.TrySetResult(x);
|
||||
}
|
||||
}, ex => tcs.TrySetException(ex), () => tcs.TrySetCanceled());
|
||||
}
|
||||
else
|
||||
{
|
||||
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 IReadOnlyReactiveProperty<T> source)
|
||||
{
|
||||
return source.WaitUntilValueChangedAsync(CancellationToken.None).GetAwaiter();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Create ReadOnlyReactiveProperty with distinctUntilChanged: false.
|
||||
/// </summary>
|
||||
public static ReadOnlyReactiveProperty<T> ToSequentialReadOnlyReactiveProperty<T>(this IObservable<T> source)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source, distinctUntilChanged: false);
|
||||
}
|
||||
|
||||
public static ReadOnlyReactiveProperty<T> ToReadOnlyReactiveProperty<T>(this IObservable<T> source, T initialValue)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source, initialValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create ReadOnlyReactiveProperty with distinctUntilChanged: false.
|
||||
/// </summary>
|
||||
public static ReadOnlyReactiveProperty<T> ToSequentialReadOnlyReactiveProperty<T>(this IObservable<T> source, T initialValue)
|
||||
{
|
||||
return new ReadOnlyReactiveProperty<T>(source, initialValue, distinctUntilChanged: false);
|
||||
}
|
||||
|
||||
public static IObservable<T> SkipLatestValueOnSubscribe<T>(this IReadOnlyReactiveProperty<T> source)
|
||||
{
|
||||
return source.HasValue ? source.Skip(1) : source;
|
||||
}
|
||||
|
||||
// for multiple toggle or etc..
|
||||
|
||||
/// <summary>
|
||||
/// Lastest values of each sequence are all true.
|
||||
/// </summary>
|
||||
public static IObservable<bool> CombineLatestValuesAreAllTrue(this IEnumerable<IObservable<bool>> sources)
|
||||
{
|
||||
return sources.CombineLatest().Select(xs =>
|
||||
{
|
||||
foreach (var item in xs)
|
||||
{
|
||||
if (item == false)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Lastest values of each sequence are all false.
|
||||
/// </summary>
|
||||
public static IObservable<bool> CombineLatestValuesAreAllFalse(this IEnumerable<IObservable<bool>> sources)
|
||||
{
|
||||
return sources.CombineLatest().Select(xs =>
|
||||
{
|
||||
foreach (var item in xs)
|
||||
{
|
||||
if (item == true)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88e12aa895fef434fbe3ea0cc8f57301
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class ScenePlaybackDetector
|
||||
{
|
||||
private static bool _isPlaying = false;
|
||||
|
||||
private static bool AboutToStartScene
|
||||
{
|
||||
get
|
||||
{
|
||||
return EditorPrefs.GetBool("AboutToStartScene");
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetBool("AboutToStartScene", value);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_isPlaying != value)
|
||||
{
|
||||
_isPlaying = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is notified after scripts have been reloaded.
|
||||
[DidReloadScripts]
|
||||
public static void OnDidReloadScripts()
|
||||
{
|
||||
// Filter DidReloadScripts callbacks to the moment where playmodeState transitions into isPlaying.
|
||||
if (AboutToStartScene)
|
||||
{
|
||||
IsPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeOnLoad ensures that this constructor is called when the Unity Editor is started.
|
||||
static ScenePlaybackDetector()
|
||||
{
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
EditorApplication.playModeStateChanged += e =>
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged += () =>
|
||||
#endif
|
||||
{
|
||||
// Before scene start: isPlayingOrWillChangePlaymode = false; isPlaying = false
|
||||
// Pressed Playback button: isPlayingOrWillChangePlaymode = true; isPlaying = false
|
||||
// Playing: isPlayingOrWillChangePlaymode = false; isPlaying = true
|
||||
// Pressed stop button: isPlayingOrWillChangePlaymode = true; isPlaying = true
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying)
|
||||
{
|
||||
AboutToStartScene = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AboutToStartScene = false;
|
||||
}
|
||||
|
||||
// Detect when playback is stopped.
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
IsPlaying = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d380d86e2ef6674c83ca983a1604273
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 726595c7d6d85824887a77691e3ca50a
|
||||
folderAsset: yes
|
||||
timeCreated: 1468655394
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,474 @@
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Toolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Bass class of ObjectPool.
|
||||
/// </summary>
|
||||
public abstract class ObjectPool<T> : IDisposable
|
||||
where T : UnityEngine.Component
|
||||
{
|
||||
bool isDisposed = false;
|
||||
Queue<T> q;
|
||||
|
||||
/// <summary>
|
||||
/// Limit of instace count.
|
||||
/// </summary>
|
||||
protected int MaxPoolCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create instance when needed.
|
||||
/// </summary>
|
||||
protected abstract T CreateInstance();
|
||||
|
||||
/// <summary>
|
||||
/// Called before return to pool, useful for set active object(it is default behavior).
|
||||
/// </summary>
|
||||
protected virtual void OnBeforeRent(T instance)
|
||||
{
|
||||
instance.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before return to pool, useful for set inactive object(it is default behavior).
|
||||
/// </summary>
|
||||
protected virtual void OnBeforeReturn(T instance)
|
||||
{
|
||||
instance.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when clear or disposed, useful for destroy instance or other finalize method.
|
||||
/// </summary>
|
||||
protected virtual void OnClear(T instance)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
var go = instance.gameObject;
|
||||
if (go == null) return;
|
||||
UnityEngine.Object.Destroy(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current pooled object count.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (q == null) return 0;
|
||||
return q.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get instance from pool.
|
||||
/// </summary>
|
||||
public T Rent()
|
||||
{
|
||||
if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed.");
|
||||
if (q == null) q = new Queue<T>();
|
||||
|
||||
var instance = (q.Count > 0)
|
||||
? q.Dequeue()
|
||||
: CreateInstance();
|
||||
|
||||
OnBeforeRent(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return instance to pool.
|
||||
/// </summary>
|
||||
public void Return(T instance)
|
||||
{
|
||||
if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed.");
|
||||
if (instance == null) throw new ArgumentNullException("instance");
|
||||
|
||||
if (q == null) q = new Queue<T>();
|
||||
|
||||
if ((q.Count + 1) == MaxPoolCount)
|
||||
{
|
||||
throw new InvalidOperationException("Reached Max PoolSize");
|
||||
}
|
||||
|
||||
OnBeforeReturn(instance);
|
||||
q.Enqueue(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear pool.
|
||||
/// </summary>
|
||||
public void Clear(bool callOnBeforeRent = false)
|
||||
{
|
||||
if (q == null) return;
|
||||
while (q.Count != 0)
|
||||
{
|
||||
var instance = q.Dequeue();
|
||||
if (callOnBeforeRent)
|
||||
{
|
||||
OnBeforeRent(instance);
|
||||
}
|
||||
OnClear(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trim pool instances.
|
||||
/// </summary>
|
||||
/// <param name="instanceCountRatio">0.0f = clear all ~ 1.0f = live all.</param>
|
||||
/// <param name="minSize">Min pool count.</param>
|
||||
/// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param>
|
||||
public void Shrink(float instanceCountRatio, int minSize, bool callOnBeforeRent = false)
|
||||
{
|
||||
if (q == null) return;
|
||||
|
||||
if (instanceCountRatio <= 0) instanceCountRatio = 0;
|
||||
if (instanceCountRatio >= 1.0f) instanceCountRatio = 1.0f;
|
||||
|
||||
var size = (int)(q.Count * instanceCountRatio);
|
||||
size = Math.Max(minSize, size);
|
||||
|
||||
while (q.Count > size)
|
||||
{
|
||||
var instance = q.Dequeue();
|
||||
if (callOnBeforeRent)
|
||||
{
|
||||
OnBeforeRent(instance);
|
||||
}
|
||||
OnClear(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If needs shrink pool frequently, start check timer.
|
||||
/// </summary>
|
||||
/// <param name="checkInterval">Interval of call Shrink.</param>
|
||||
/// <param name="instanceCountRatio">0.0f = clearAll ~ 1.0f = live all.</param>
|
||||
/// <param name="minSize">Min pool count.</param>
|
||||
/// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param>
|
||||
public IDisposable StartShrinkTimer(TimeSpan checkInterval, float instanceCountRatio, int minSize, bool callOnBeforeRent = false)
|
||||
{
|
||||
return Observable.Interval(checkInterval)
|
||||
.TakeWhile(_ => !isDisposed)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
Shrink(instanceCountRatio, minSize, callOnBeforeRent);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill pool before rent operation.
|
||||
/// </summary>
|
||||
/// <param name="preloadCount">Pool instance count.</param>
|
||||
/// <param name="threshold">Create count per frame.</param>
|
||||
public IObservable<Unit> PreloadAsync(int preloadCount, int threshold)
|
||||
{
|
||||
if (q == null) q = new Queue<T>(preloadCount);
|
||||
|
||||
return Observable.FromMicroCoroutine<Unit>((observer, cancel) => PreloadCore(preloadCount, threshold, observer, cancel));
|
||||
}
|
||||
|
||||
IEnumerator PreloadCore(int preloadCount, int threshold, IObserver<Unit> observer, CancellationToken cancellationToken)
|
||||
{
|
||||
while (Count < preloadCount && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var requireCount = preloadCount - Count;
|
||||
if (requireCount <= 0) break;
|
||||
|
||||
var createCount = Math.Min(requireCount, threshold);
|
||||
|
||||
for (int i = 0; i < createCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = CreateInstance();
|
||||
Return(instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
observer.OnError(ex);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
yield return null; // next frame.
|
||||
}
|
||||
|
||||
observer.OnNext(Unit.Default);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Clear(false);
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bass class of ObjectPool. If needs asynchronous initialization, use this instead of standard ObjectPool.
|
||||
/// </summary>
|
||||
public abstract class AsyncObjectPool<T> : IDisposable
|
||||
where T : UnityEngine.Component
|
||||
{
|
||||
bool isDisposed = false;
|
||||
Queue<T> q;
|
||||
|
||||
/// <summary>
|
||||
/// Limit of instace count.
|
||||
/// </summary>
|
||||
protected int MaxPoolCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create instance when needed.
|
||||
/// </summary>
|
||||
protected abstract IObservable<T> CreateInstanceAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Called before return to pool, useful for set active object(it is default behavior).
|
||||
/// </summary>
|
||||
protected virtual void OnBeforeRent(T instance)
|
||||
{
|
||||
instance.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before return to pool, useful for set inactive object(it is default behavior).
|
||||
/// </summary>
|
||||
protected virtual void OnBeforeReturn(T instance)
|
||||
{
|
||||
instance.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when clear or disposed, useful for destroy instance or other finalize method.
|
||||
/// </summary>
|
||||
protected virtual void OnClear(T instance)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
var go = instance.gameObject;
|
||||
if (go == null) return;
|
||||
UnityEngine.Object.Destroy(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current pooled object count.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (q == null) return 0;
|
||||
return q.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get instance from pool.
|
||||
/// </summary>
|
||||
public IObservable<T> RentAsync()
|
||||
{
|
||||
if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed.");
|
||||
if (q == null) q = new Queue<T>();
|
||||
|
||||
if (q.Count > 0)
|
||||
{
|
||||
var instance = q.Dequeue();
|
||||
OnBeforeRent(instance);
|
||||
return Observable.Return(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = CreateInstanceAsync();
|
||||
return instance.Do(x => OnBeforeRent(x));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return instance to pool.
|
||||
/// </summary>
|
||||
public void Return(T instance)
|
||||
{
|
||||
if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed.");
|
||||
if (instance == null) throw new ArgumentNullException("instance");
|
||||
|
||||
if (q == null) q = new Queue<T>();
|
||||
|
||||
if ((q.Count + 1) == MaxPoolCount)
|
||||
{
|
||||
throw new InvalidOperationException("Reached Max PoolSize");
|
||||
}
|
||||
|
||||
OnBeforeReturn(instance);
|
||||
q.Enqueue(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trim pool instances.
|
||||
/// </summary>
|
||||
/// <param name="instanceCountRatio">0.0f = clear all ~ 1.0f = live all.</param>
|
||||
/// <param name="minSize">Min pool count.</param>
|
||||
/// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param>
|
||||
public void Shrink(float instanceCountRatio, int minSize, bool callOnBeforeRent = false)
|
||||
{
|
||||
if (q == null) return;
|
||||
|
||||
if (instanceCountRatio <= 0) instanceCountRatio = 0;
|
||||
if (instanceCountRatio >= 1.0f) instanceCountRatio = 1.0f;
|
||||
|
||||
var size = (int)(q.Count * instanceCountRatio);
|
||||
size = Math.Max(minSize, size);
|
||||
|
||||
while (q.Count > size)
|
||||
{
|
||||
var instance = q.Dequeue();
|
||||
if (callOnBeforeRent)
|
||||
{
|
||||
OnBeforeRent(instance);
|
||||
}
|
||||
OnClear(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If needs shrink pool frequently, start check timer.
|
||||
/// </summary>
|
||||
/// <param name="checkInterval">Interval of call Shrink.</param>
|
||||
/// <param name="instanceCountRatio">0.0f = clearAll ~ 1.0f = live all.</param>
|
||||
/// <param name="minSize">Min pool count.</param>
|
||||
/// <param name="callOnBeforeRent">If true, call OnBeforeRent before OnClear.</param>
|
||||
public IDisposable StartShrinkTimer(TimeSpan checkInterval, float instanceCountRatio, int minSize, bool callOnBeforeRent = false)
|
||||
{
|
||||
return Observable.Interval(checkInterval)
|
||||
.TakeWhile(_ => !isDisposed)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
Shrink(instanceCountRatio, minSize, callOnBeforeRent);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear pool.
|
||||
/// </summary>
|
||||
public void Clear(bool callOnBeforeRent = false)
|
||||
{
|
||||
if (q == null) return;
|
||||
while (q.Count != 0)
|
||||
{
|
||||
var instance = q.Dequeue();
|
||||
if (callOnBeforeRent)
|
||||
{
|
||||
OnBeforeRent(instance);
|
||||
}
|
||||
OnClear(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill pool before rent operation.
|
||||
/// </summary>
|
||||
/// <param name="preloadCount">Pool instance count.</param>
|
||||
/// <param name="threshold">Create count per frame.</param>
|
||||
public IObservable<Unit> PreloadAsync(int preloadCount, int threshold)
|
||||
{
|
||||
if (q == null) q = new Queue<T>(preloadCount);
|
||||
|
||||
return Observable.FromMicroCoroutine<Unit>((observer, cancel) => PreloadCore(preloadCount, threshold, observer, cancel));
|
||||
}
|
||||
|
||||
IEnumerator PreloadCore(int preloadCount, int threshold, IObserver<Unit> observer, CancellationToken cancellationToken)
|
||||
{
|
||||
while (Count < preloadCount && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var requireCount = preloadCount - Count;
|
||||
if (requireCount <= 0) break;
|
||||
|
||||
var createCount = Math.Min(requireCount, threshold);
|
||||
|
||||
var loaders = new IObservable<Unit>[createCount];
|
||||
for (int i = 0; i < createCount; i++)
|
||||
{
|
||||
var instanceFuture = CreateInstanceAsync();
|
||||
loaders[i] = instanceFuture.ForEachAsync(x => Return(x));
|
||||
}
|
||||
|
||||
var awaiter = Observable.WhenAll(loaders).ToYieldInstruction(false, cancellationToken);
|
||||
while (!(awaiter.HasResult || awaiter.IsCanceled || awaiter.HasError))
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (awaiter.HasError)
|
||||
{
|
||||
observer.OnError(awaiter.Error);
|
||||
yield break;
|
||||
}
|
||||
else if (awaiter.IsCanceled)
|
||||
{
|
||||
yield break; // end.
|
||||
}
|
||||
}
|
||||
|
||||
observer.OnNext(Unit.Default);
|
||||
observer.OnCompleted();
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Clear(false);
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4980e1e001c7e94fab3250ba284dc91
|
||||
timeCreated: 1468655394
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5380144628ecdc74ab6778f80d103d51
|
||||
folderAsset: yes
|
||||
timeCreated: 1455373896
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableAnimatorTrigger : ObservableTriggerBase
|
||||
{
|
||||
Subject<int> onAnimatorIK;
|
||||
|
||||
/// <summary>Callback for setting up animation IK (inverse kinematics).</summary>
|
||||
void OnAnimatorIK(int layerIndex)
|
||||
{
|
||||
if (onAnimatorIK != null) onAnimatorIK.OnNext(layerIndex);
|
||||
}
|
||||
|
||||
/// <summary>Callback for setting up animation IK (inverse kinematics).</summary>
|
||||
public IObservable<int> OnAnimatorIKAsObservable()
|
||||
{
|
||||
return onAnimatorIK ?? (onAnimatorIK = new Subject<int>());
|
||||
}
|
||||
|
||||
Subject<Unit> onAnimatorMove;
|
||||
|
||||
/// <summary>Callback for processing animation movements for modifying root motion.</summary>
|
||||
void OnAnimatorMove()
|
||||
{
|
||||
if (onAnimatorMove != null) onAnimatorMove.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
/// <summary>Callback for processing animation movements for modifying root motion.</summary>
|
||||
public IObservable<Unit> OnAnimatorMoveAsObservable()
|
||||
{
|
||||
return onAnimatorMove ?? (onAnimatorMove = new Subject<Unit>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onAnimatorIK != null)
|
||||
{
|
||||
onAnimatorIK.OnCompleted();
|
||||
}
|
||||
if (onAnimatorMove != null)
|
||||
{
|
||||
onAnimatorMove.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e03f9257cc6667f4082439aa77d6f01e
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableBeginDragTrigger : ObservableTriggerBase, IEventSystemHandler, IBeginDragHandler
|
||||
{
|
||||
Subject<PointerEventData> onBeginDrag;
|
||||
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (onBeginDrag != null) onBeginDrag.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<PointerEventData> OnBeginDragAsObservable()
|
||||
{
|
||||
return onBeginDrag ?? (onBeginDrag = new Subject<PointerEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onBeginDrag != null)
|
||||
{
|
||||
onBeginDrag.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a81a9b6bec6b4f4fba7e0047cd989f6
|
||||
timeCreated: 1455373898
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableCancelTrigger : ObservableTriggerBase, IEventSystemHandler, ICancelHandler
|
||||
{
|
||||
Subject<BaseEventData> onCancel;
|
||||
|
||||
void ICancelHandler.OnCancel(BaseEventData eventData)
|
||||
{
|
||||
if (onCancel != null) onCancel.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<BaseEventData> OnCancelAsObservable()
|
||||
{
|
||||
return onCancel ?? (onCancel = new Subject<BaseEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onCancel != null)
|
||||
{
|
||||
onCancel.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c0a9070b7cc23746b2c0e2db3ec16cd
|
||||
timeCreated: 1455373898
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// after uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableCanvasGroupChangedTrigger : ObservableTriggerBase
|
||||
{
|
||||
Subject<Unit> onCanvasGroupChanged;
|
||||
|
||||
// Callback that is sent if the canvas group is changed
|
||||
void OnCanvasGroupChanged()
|
||||
{
|
||||
if (onCanvasGroupChanged != null) onCanvasGroupChanged.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
/// <summary>Callback that is sent if the canvas group is changed.</summary>
|
||||
public IObservable<Unit> OnCanvasGroupChangedAsObservable()
|
||||
{
|
||||
return onCanvasGroupChanged ?? (onCanvasGroupChanged = new Subject<Unit>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onCanvasGroupChanged != null)
|
||||
{
|
||||
onCanvasGroupChanged.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54095d3e740f7714085d0568207cbfe0
|
||||
timeCreated: 1455373899
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableCollision2DTrigger : ObservableTriggerBase
|
||||
{
|
||||
Subject<Collision2D> onCollisionEnter2D;
|
||||
|
||||
/// <summary>Sent when an incoming collider makes contact with this object's collider (2D physics only).</summary>
|
||||
void OnCollisionEnter2D(Collision2D coll)
|
||||
{
|
||||
if (onCollisionEnter2D != null) onCollisionEnter2D.OnNext(coll);
|
||||
}
|
||||
|
||||
/// <summary>Sent when an incoming collider makes contact with this object's collider (2D physics only).</summary>
|
||||
public IObservable<Collision2D> OnCollisionEnter2DAsObservable()
|
||||
{
|
||||
return onCollisionEnter2D ?? (onCollisionEnter2D = new Subject<Collision2D>());
|
||||
}
|
||||
|
||||
Subject<Collision2D> onCollisionExit2D;
|
||||
|
||||
/// <summary>Sent when a collider on another object stops touching this object's collider (2D physics only).</summary>
|
||||
void OnCollisionExit2D(Collision2D coll)
|
||||
{
|
||||
if (onCollisionExit2D != null) onCollisionExit2D.OnNext(coll);
|
||||
}
|
||||
|
||||
/// <summary>Sent when a collider on another object stops touching this object's collider (2D physics only).</summary>
|
||||
public IObservable<Collision2D> OnCollisionExit2DAsObservable()
|
||||
{
|
||||
return onCollisionExit2D ?? (onCollisionExit2D = new Subject<Collision2D>());
|
||||
}
|
||||
|
||||
Subject<Collision2D> onCollisionStay2D;
|
||||
|
||||
/// <summary>Sent each frame where a collider on another object is touching this object's collider (2D physics only).</summary>
|
||||
void OnCollisionStay2D(Collision2D coll)
|
||||
{
|
||||
if (onCollisionStay2D != null) onCollisionStay2D.OnNext(coll);
|
||||
}
|
||||
|
||||
/// <summary>Sent each frame where a collider on another object is touching this object's collider (2D physics only).</summary>
|
||||
public IObservable<Collision2D> OnCollisionStay2DAsObservable()
|
||||
{
|
||||
return onCollisionStay2D ?? (onCollisionStay2D = new Subject<Collision2D>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onCollisionEnter2D != null)
|
||||
{
|
||||
onCollisionEnter2D.OnCompleted();
|
||||
}
|
||||
if (onCollisionExit2D != null)
|
||||
{
|
||||
onCollisionExit2D.OnCompleted();
|
||||
}
|
||||
if (onCollisionStay2D != null)
|
||||
{
|
||||
onCollisionStay2D.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1be7847b61f30f24daa5762db87a5b19
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableCollisionTrigger : ObservableTriggerBase
|
||||
{
|
||||
Subject<Collision> onCollisionEnter;
|
||||
|
||||
/// <summary>OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider.</summary>
|
||||
void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (onCollisionEnter != null) onCollisionEnter.OnNext(collision);
|
||||
}
|
||||
|
||||
/// <summary>OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider.</summary>
|
||||
public IObservable<Collision> OnCollisionEnterAsObservable()
|
||||
{
|
||||
return onCollisionEnter ?? (onCollisionEnter = new Subject<Collision>());
|
||||
}
|
||||
|
||||
Subject<Collision> onCollisionExit;
|
||||
|
||||
/// <summary>OnCollisionExit is called when this collider/rigidbody has stopped touching another rigidbody/collider.</summary>
|
||||
void OnCollisionExit(Collision collisionInfo)
|
||||
{
|
||||
if (onCollisionExit != null) onCollisionExit.OnNext(collisionInfo);
|
||||
}
|
||||
|
||||
/// <summary>OnCollisionExit is called when this collider/rigidbody has stopped touching another rigidbody/collider.</summary>
|
||||
public IObservable<Collision> OnCollisionExitAsObservable()
|
||||
{
|
||||
return onCollisionExit ?? (onCollisionExit = new Subject<Collision>());
|
||||
}
|
||||
|
||||
Subject<Collision> onCollisionStay;
|
||||
|
||||
/// <summary>OnCollisionStay is called once per frame for every collider/rigidbody that is touching rigidbody/collider.</summary>
|
||||
void OnCollisionStay(Collision collisionInfo)
|
||||
{
|
||||
if (onCollisionStay != null) onCollisionStay.OnNext(collisionInfo);
|
||||
}
|
||||
|
||||
/// <summary>OnCollisionStay is called once per frame for every collider/rigidbody that is touching rigidbody/collider.</summary>
|
||||
public IObservable<Collision> OnCollisionStayAsObservable()
|
||||
{
|
||||
return onCollisionStay ?? (onCollisionStay = new Subject<Collision>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onCollisionEnter != null)
|
||||
{
|
||||
onCollisionEnter.OnCompleted();
|
||||
}
|
||||
if (onCollisionExit != null)
|
||||
{
|
||||
onCollisionExit.OnCompleted();
|
||||
}
|
||||
if (onCollisionStay != null)
|
||||
{
|
||||
onCollisionStay.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10b917196cbfcf74898ce1686e205d04
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableDeselectTrigger : ObservableTriggerBase, IEventSystemHandler, IDeselectHandler
|
||||
{
|
||||
Subject<BaseEventData> onDeselect;
|
||||
|
||||
void IDeselectHandler.OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
if (onDeselect != null) onDeselect.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<BaseEventData> OnDeselectAsObservable()
|
||||
{
|
||||
return onDeselect ?? (onDeselect = new Subject<BaseEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onDeselect != null)
|
||||
{
|
||||
onDeselect.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fe6f69c4d869c04e8a1924aab1d3694
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableDestroyTrigger : MonoBehaviour
|
||||
{
|
||||
bool calledDestroy = false;
|
||||
Subject<Unit> onDestroy;
|
||||
CompositeDisposable disposablesOnDestroy;
|
||||
|
||||
[Obsolete("Internal Use.")]
|
||||
internal bool IsMonitoredActivate { get; set; }
|
||||
|
||||
public bool IsActivated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Check called OnDestroy.
|
||||
/// This property does not guarantees GameObject was destroyed,
|
||||
/// when gameObject is deactive, does not raise OnDestroy.
|
||||
/// </summary>
|
||||
public bool IsCalledOnDestroy { get { return calledDestroy; } }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
IsActivated = true;
|
||||
}
|
||||
|
||||
/// <summary>This function is called when the MonoBehaviour will be destroyed.</summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
if (!calledDestroy)
|
||||
{
|
||||
calledDestroy = true;
|
||||
if (disposablesOnDestroy != null) disposablesOnDestroy.Dispose();
|
||||
if (onDestroy != null) { onDestroy.OnNext(Unit.Default); onDestroy.OnCompleted(); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>This function is called when the MonoBehaviour will be destroyed.</summary>
|
||||
public IObservable<Unit> OnDestroyAsObservable()
|
||||
{
|
||||
if (this == null) return Observable.Return(Unit.Default);
|
||||
if (calledDestroy) return Observable.Return(Unit.Default);
|
||||
return onDestroy ?? (onDestroy = new Subject<Unit>());
|
||||
}
|
||||
|
||||
/// <summary>Invoke OnDestroy, this method is used on internal.</summary>
|
||||
public void ForceRaiseOnDestroy()
|
||||
{
|
||||
OnDestroy();
|
||||
}
|
||||
|
||||
public void AddDisposableOnDestroy(IDisposable disposable)
|
||||
{
|
||||
if (calledDestroy)
|
||||
{
|
||||
disposable.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposablesOnDestroy == null) disposablesOnDestroy = new CompositeDisposable();
|
||||
disposablesOnDestroy.Add(disposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb219b23cdf4b314f94a27bca3cc8012
|
||||
timeCreated: 1455373901
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableDragTrigger : ObservableTriggerBase, IEventSystemHandler, IDragHandler
|
||||
{
|
||||
Subject<PointerEventData> onDrag;
|
||||
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (onDrag != null) onDrag.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<PointerEventData> OnDragAsObservable()
|
||||
{
|
||||
return onDrag ?? (onDrag = new Subject<PointerEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onDrag != null)
|
||||
{
|
||||
onDrag.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79db090dc9e4db245821e8b89b0e208e
|
||||
timeCreated: 1455373899
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableDropTrigger : ObservableTriggerBase, IEventSystemHandler, IDropHandler
|
||||
{
|
||||
Subject<PointerEventData> onDrop;
|
||||
|
||||
void IDropHandler.OnDrop(PointerEventData eventData)
|
||||
{
|
||||
if (onDrop != null) onDrop.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<PointerEventData> OnDropAsObservable()
|
||||
{
|
||||
return onDrop ?? (onDrop = new Subject<PointerEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onDrop != null)
|
||||
{
|
||||
onDrop.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2ffa8b5af3474446a310bb6aa0b180a
|
||||
timeCreated: 1455373902
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableEnableTrigger : ObservableTriggerBase
|
||||
{
|
||||
Subject<Unit> onEnable;
|
||||
|
||||
/// <summary>This function is called when the object becomes enabled and active.</summary>
|
||||
void OnEnable()
|
||||
{
|
||||
if (onEnable != null) onEnable.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
/// <summary>This function is called when the object becomes enabled and active.</summary>
|
||||
public IObservable<Unit> OnEnableAsObservable()
|
||||
{
|
||||
return onEnable ?? (onEnable = new Subject<Unit>());
|
||||
}
|
||||
|
||||
Subject<Unit> onDisable;
|
||||
|
||||
/// <summary>This function is called when the behaviour becomes disabled () or inactive.</summary>
|
||||
void OnDisable()
|
||||
{
|
||||
if (onDisable != null) onDisable.OnNext(Unit.Default);
|
||||
}
|
||||
|
||||
/// <summary>This function is called when the behaviour becomes disabled () or inactive.</summary>
|
||||
public IObservable<Unit> OnDisableAsObservable()
|
||||
{
|
||||
return onDisable ?? (onDisable = new Subject<Unit>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onEnable != null)
|
||||
{
|
||||
onEnable.OnCompleted();
|
||||
}
|
||||
if (onDisable != null)
|
||||
{
|
||||
onDisable.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d9c7eb607af1fd4aa0e15f52cc0543b
|
||||
timeCreated: 1455373897
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// for uGUI(from 4.6)
|
||||
#if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5)
|
||||
|
||||
using System; // require keep for Windows Universal App
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UniRx.Triggers
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ObservableEndDragTrigger : ObservableTriggerBase, IEventSystemHandler, IEndDragHandler
|
||||
{
|
||||
Subject<PointerEventData> onEndDrag;
|
||||
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (onEndDrag != null) onEndDrag.OnNext(eventData);
|
||||
}
|
||||
|
||||
public IObservable<PointerEventData> OnEndDragAsObservable()
|
||||
{
|
||||
return onEndDrag ?? (onEndDrag = new Subject<PointerEventData>());
|
||||
}
|
||||
|
||||
protected override void RaiseOnCompletedOnDestroy()
|
||||
{
|
||||
if (onEndDrag != null)
|
||||
{
|
||||
onEndDrag.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8ce8424f238d6842bd8b09c0cca1ac4
|
||||
timeCreated: 1455373900
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user