264 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			264 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
| 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;
 | |
|         }
 | |
|     }
 | |
| } |