ObserveExtensions.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Threading;
  5. using UniRx.InternalUtil;
  6. using UniRx.Triggers;
  7. #if !UniRxLibrary
  8. using ObservableUnity = UniRx.Observable;
  9. #endif
  10. namespace UniRx
  11. {
  12. public static partial class ObserveExtensions
  13. {
  14. /// <summary>
  15. /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
  16. /// </summary>
  17. /// <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>
  18. public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType = FrameCountType.Update, bool fastDestroyCheck = false)
  19. where TSource : class
  20. {
  21. return ObserveEveryValueChanged(source, propertySelector, frameCountType, UnityEqualityComparer.GetDefault<TProperty>(), fastDestroyCheck);
  22. }
  23. /// <summary>
  24. /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
  25. /// </summary>
  26. public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType, IEqualityComparer<TProperty> comparer)
  27. where TSource : class
  28. {
  29. return ObserveEveryValueChanged(source, propertySelector, frameCountType, comparer, false);
  30. }
  31. /// <summary>
  32. /// Publish target property when value is changed. If source is destroyed/destructed, publish OnCompleted.
  33. /// </summary>
  34. /// <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>
  35. public static IObservable<TProperty> ObserveEveryValueChanged<TSource, TProperty>(this TSource source, Func<TSource, TProperty> propertySelector, FrameCountType frameCountType, IEqualityComparer<TProperty> comparer, bool fastDestroyCheck)
  36. where TSource : class
  37. {
  38. if (source == null) return Observable.Empty<TProperty>();
  39. if (comparer == null) comparer = UnityEqualityComparer.GetDefault<TProperty>();
  40. var unityObject = source as UnityEngine.Object;
  41. var isUnityObject = source is UnityEngine.Object;
  42. if (isUnityObject && unityObject == null) return Observable.Empty<TProperty>();
  43. // MicroCoroutine does not publish value immediately, so publish value on subscribe.
  44. if (isUnityObject)
  45. {
  46. return ObservableUnity.FromMicroCoroutine<TProperty>((observer, cancellationToken) =>
  47. {
  48. if (unityObject != null)
  49. {
  50. var firstValue = default(TProperty);
  51. try
  52. {
  53. firstValue = propertySelector((TSource)(object)unityObject);
  54. }
  55. catch (Exception ex)
  56. {
  57. observer.OnError(ex);
  58. return EmptyEnumerator();
  59. }
  60. observer.OnNext(firstValue);
  61. return PublishUnityObjectValueChanged(unityObject, firstValue, propertySelector, comparer, observer, cancellationToken, fastDestroyCheck);
  62. }
  63. else
  64. {
  65. observer.OnCompleted();
  66. return EmptyEnumerator();
  67. }
  68. }, frameCountType);
  69. }
  70. else
  71. {
  72. var reference = new WeakReference(source);
  73. source = null;
  74. return ObservableUnity.FromMicroCoroutine<TProperty>((observer, cancellationToken) =>
  75. {
  76. var target = reference.Target;
  77. if (target != null)
  78. {
  79. var firstValue = default(TProperty);
  80. try
  81. {
  82. firstValue = propertySelector((TSource)target);
  83. }
  84. catch (Exception ex)
  85. {
  86. observer.OnError(ex);
  87. return EmptyEnumerator();
  88. }
  89. finally
  90. {
  91. target = null;
  92. }
  93. observer.OnNext(firstValue);
  94. return PublishPocoValueChanged(reference, firstValue, propertySelector, comparer, observer, cancellationToken);
  95. }
  96. else
  97. {
  98. observer.OnCompleted();
  99. return EmptyEnumerator();
  100. }
  101. }, frameCountType);
  102. }
  103. }
  104. static IEnumerator EmptyEnumerator()
  105. {
  106. yield break;
  107. }
  108. static IEnumerator PublishPocoValueChanged<TSource, TProperty>(WeakReference sourceReference, TProperty firstValue, Func<TSource, TProperty> propertySelector, IEqualityComparer<TProperty> comparer, IObserver<TProperty> observer, CancellationToken cancellationToken)
  109. {
  110. var currentValue = default(TProperty);
  111. var prevValue = firstValue;
  112. while (!cancellationToken.IsCancellationRequested)
  113. {
  114. var target = sourceReference.Target;
  115. if (target != null)
  116. {
  117. try
  118. {
  119. currentValue = propertySelector((TSource)target);
  120. }
  121. catch (Exception ex)
  122. {
  123. observer.OnError(ex);
  124. yield break;
  125. }
  126. finally
  127. {
  128. target = null; // remove reference(must need!)
  129. }
  130. }
  131. else
  132. {
  133. observer.OnCompleted();
  134. yield break;
  135. }
  136. if (!comparer.Equals(currentValue, prevValue))
  137. {
  138. observer.OnNext(currentValue);
  139. prevValue = currentValue;
  140. }
  141. yield return null;
  142. }
  143. }
  144. static IEnumerator PublishUnityObjectValueChanged<TSource, TProperty>(UnityEngine.Object unityObject, TProperty firstValue, Func<TSource, TProperty> propertySelector, IEqualityComparer<TProperty> comparer, IObserver<TProperty> observer, CancellationToken cancellationToken, bool fastDestroyCheck)
  145. {
  146. var currentValue = default(TProperty);
  147. var prevValue = firstValue;
  148. var source = (TSource)(object)unityObject;
  149. if (fastDestroyCheck)
  150. {
  151. ObservableDestroyTrigger destroyTrigger = null;
  152. {
  153. var gameObject = unityObject as UnityEngine.GameObject;
  154. if (gameObject == null)
  155. {
  156. var comp = unityObject as UnityEngine.Component;
  157. if (comp != null)
  158. {
  159. gameObject = comp.gameObject;
  160. }
  161. }
  162. // can't use faster path
  163. if (gameObject == null) goto STANDARD_LOOP;
  164. destroyTrigger = GetOrAddDestroyTrigger(gameObject);
  165. }
  166. // fast compare path
  167. while (!cancellationToken.IsCancellationRequested)
  168. {
  169. var isDestroyed = destroyTrigger.IsActivated
  170. ? !destroyTrigger.IsCalledOnDestroy
  171. : (unityObject != null);
  172. if (isDestroyed)
  173. {
  174. try
  175. {
  176. currentValue = propertySelector(source);
  177. }
  178. catch (Exception ex)
  179. {
  180. observer.OnError(ex);
  181. yield break;
  182. }
  183. }
  184. else
  185. {
  186. observer.OnCompleted();
  187. yield break;
  188. }
  189. if (!comparer.Equals(currentValue, prevValue))
  190. {
  191. observer.OnNext(currentValue);
  192. prevValue = currentValue;
  193. }
  194. yield return null;
  195. }
  196. yield break;
  197. }
  198. STANDARD_LOOP:
  199. while (!cancellationToken.IsCancellationRequested)
  200. {
  201. if (unityObject != null)
  202. {
  203. try
  204. {
  205. currentValue = propertySelector(source);
  206. }
  207. catch (Exception ex)
  208. {
  209. observer.OnError(ex);
  210. yield break;
  211. }
  212. }
  213. else
  214. {
  215. observer.OnCompleted();
  216. yield break;
  217. }
  218. if (!comparer.Equals(currentValue, prevValue))
  219. {
  220. observer.OnNext(currentValue);
  221. prevValue = currentValue;
  222. }
  223. yield return null;
  224. }
  225. }
  226. static ObservableDestroyTrigger GetOrAddDestroyTrigger(UnityEngine.GameObject go)
  227. {
  228. var dt = go.GetComponent<ObservableDestroyTrigger>();
  229. if (dt == null)
  230. {
  231. dt = go.AddComponent<ObservableDestroyTrigger>();
  232. }
  233. return dt;
  234. }
  235. }
  236. }