babylon.observable.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. module BABYLON {
  2. /**
  3. * A class serves as a medium between the observable and its observers
  4. */
  5. export class EventState {
  6. /**
  7. * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
  8. */
  9. constructor(mask: number, skipNextObservers = false, target?: any, currentTarget?: any) {
  10. this.initalize(mask, skipNextObservers, target, currentTarget);
  11. }
  12. public initalize(mask: number, skipNextObservers = false, target?: any, currentTarget?: any): EventState {
  13. this.mask = mask;
  14. this.skipNextObservers = skipNextObservers;
  15. this.target = target;
  16. this.currentTarget = currentTarget;
  17. return this;
  18. }
  19. /**
  20. * An Observer can set this property to true to prevent subsequent observers of being notified
  21. */
  22. public skipNextObservers: boolean;
  23. /**
  24. * Get the mask value that were used to trigger the event corresponding to this EventState object
  25. */
  26. public mask: number;
  27. public target?: any;
  28. public currentTarget?: any;
  29. }
  30. /**
  31. * Represent an Observer registered to a given Observable object.
  32. */
  33. export class Observer<T> {
  34. constructor(public callback: (eventData: T, eventState: EventState) => void, public mask: number, public scope: any = null) {
  35. }
  36. }
  37. /**
  38. * Represent a list of observers registered to multiple Observables object.
  39. */
  40. export class MultiObserver<T> {
  41. private _observers: Observer<T>[];
  42. private _observables: Observable<T>[];
  43. public dispose(): void {
  44. for (var index = 0; index < this._observers.length; index++) {
  45. this._observables[index].remove(this._observers[index]);
  46. }
  47. this._observers = null;
  48. this._observables = null;
  49. }
  50. public static Watch<T>(observables: Observable<T>[], callback: (eventData: T, eventState: EventState) => void, mask: number = -1, scope: any = null): MultiObserver<T> {
  51. let result = new MultiObserver<T>();
  52. result._observers = new Array<Observer<T>>();
  53. result._observables = observables;
  54. for (var observable of observables) {
  55. result._observers.push(observable.add(callback, mask, false, scope));
  56. }
  57. return result;
  58. }
  59. }
  60. /**
  61. * The Observable class is a simple implementation of the Observable pattern.
  62. * There's one slight particularity though: a given Observable can notify its observer using a particular mask value, only the Observers registered with this mask value will be notified.
  63. * This enable a more fine grained execution without having to rely on multiple different Observable objects.
  64. * For instance you may have a given Observable that have four different types of notifications: Move (mask = 0x01), Stop (mask = 0x02), Turn Right (mask = 0X04), Turn Left (mask = 0X08).
  65. * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
  66. */
  67. export class Observable<T> {
  68. _observers = new Array<Observer<T>>();
  69. private _eventState: EventState;
  70. private _onObserverAdded: (observer: Observer<T>) => void;
  71. constructor(onObserverAdded?: (observer: Observer<T>) => void) {
  72. this._eventState = new EventState(0);
  73. this._onObserverAdded = onObserverAdded;
  74. }
  75. /**
  76. * Create a new Observer with the specified callback
  77. * @param callback the callback that will be executed for that Observer
  78. * @param mask the mask used to filter observers
  79. * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
  80. * @param scope optional scope for the callback to be called from
  81. */
  82. public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null): Observer<T> {
  83. if (!callback) {
  84. return null;
  85. }
  86. var observer = new Observer(callback, mask, scope);
  87. if (insertFirst) {
  88. this._observers.unshift(observer);
  89. } else {
  90. this._observers.push(observer);
  91. }
  92. if (this._onObserverAdded) {
  93. this._onObserverAdded(observer);
  94. }
  95. return observer;
  96. }
  97. /**
  98. * Remove an Observer from the Observable object
  99. * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
  100. */
  101. public remove(observer: Observer<T>): boolean {
  102. var index = this._observers.indexOf(observer);
  103. if (index !== -1) {
  104. this._observers.splice(index, 1);
  105. return true;
  106. }
  107. return false;
  108. }
  109. /**
  110. * Remove a callback from the Observable object
  111. * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
  112. */
  113. public removeCallback(callback: (eventData: T, eventState: EventState) => void): boolean {
  114. for (var index = 0; index < this._observers.length; index++) {
  115. if (this._observers[index].callback === callback) {
  116. this._observers.splice(index, 1);
  117. return true;
  118. }
  119. }
  120. return false;
  121. }
  122. /**
  123. * Notify all Observers by calling their respective callback with the given data
  124. * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
  125. * @param eventData
  126. * @param mask
  127. */
  128. public notifyObservers(eventData: T, mask: number = -1, target?: any, currentTarget?: any): boolean {
  129. let state = this._eventState;
  130. state.mask = mask;
  131. state.target = target;
  132. state.currentTarget = currentTarget;
  133. state.skipNextObservers = false;
  134. for (var obs of this._observers) {
  135. if (obs.mask & mask) {
  136. if(obs.scope){
  137. obs.callback.apply(obs.scope, [eventData, state])
  138. }else{
  139. obs.callback(eventData, state);
  140. }
  141. }
  142. if (state.skipNextObservers) {
  143. return false;
  144. }
  145. }
  146. return true;
  147. }
  148. /**
  149. * Notify a specific observer
  150. * @param eventData
  151. * @param mask
  152. */
  153. public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
  154. let state = this._eventState;
  155. state.mask = mask;
  156. state.skipNextObservers = false;
  157. observer.callback(eventData, state);
  158. }
  159. /**
  160. * return true is the Observable has at least one Observer registered
  161. */
  162. public hasObservers(): boolean {
  163. return this._observers.length > 0;
  164. }
  165. /**
  166. * Clear the list of observers
  167. */
  168. public clear(): void {
  169. this._observers = new Array<Observer<T>>();
  170. this._onObserverAdded = null;
  171. }
  172. /**
  173. * Clone the current observable
  174. */
  175. public clone(): Observable<T> {
  176. var result = new Observable<T>();
  177. result._observers = this._observers.slice(0);
  178. return result;
  179. }
  180. /**
  181. * Does this observable handles observer registered with a given mask
  182. * @param {number} trigger - the mask to be tested
  183. * @return {boolean} whether or not one observer registered with the given mask is handeled
  184. **/
  185. public hasSpecificMask(mask: number = -1): boolean {
  186. for (var obs of this._observers) {
  187. if (obs.mask & mask || obs.mask === mask) {
  188. return true;
  189. }
  190. }
  191. return false;
  192. }
  193. }
  194. }