You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
10 KiB
264 lines
10 KiB
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; |
|
} |
|
} |
|
} |