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 { /// /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted. /// /// If true and target is UnityObject, use destroyed check by additional component. It is faster check for lifecycle but needs initial cost. public static IObservable ObserveEveryValueChanged(this TSource source, Func propertySelector, FrameCountType frameCountType = FrameCountType.Update, bool fastDestroyCheck = false) where TSource : class { return ObserveEveryValueChanged(source, propertySelector, frameCountType, UnityEqualityComparer.GetDefault(), fastDestroyCheck); } /// /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted. /// public static IObservable ObserveEveryValueChanged(this TSource source, Func propertySelector, FrameCountType frameCountType, IEqualityComparer comparer) where TSource : class { return ObserveEveryValueChanged(source, propertySelector, frameCountType, comparer, false); } /// /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted. /// /// If true and target is UnityObject, use destroyed check by additional component. It is faster check for lifecycle but needs initial cost. public static IObservable ObserveEveryValueChanged(this TSource source, Func propertySelector, FrameCountType frameCountType, IEqualityComparer comparer, bool fastDestroyCheck) where TSource : class { if (source == null) return Observable.Empty(); if (comparer == null) comparer = UnityEqualityComparer.GetDefault(); var unityObject = source as UnityEngine.Object; var isUnityObject = source is UnityEngine.Object; if (isUnityObject && unityObject == null) return Observable.Empty(); // MicroCoroutine does not publish value immediately, so publish value on subscribe. if (isUnityObject) { return ObservableUnity.FromMicroCoroutine((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((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(WeakReference sourceReference, TProperty firstValue, Func propertySelector, IEqualityComparer comparer, IObserver 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(UnityEngine.Object unityObject, TProperty firstValue, Func propertySelector, IEqualityComparer comparer, IObserver 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(); if (dt == null) { dt = go.AddComponent(); } return dt; } } }