babylon.observable.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. module BABYLON {
  2. /**
  3. * A class serves as a medium between the observable and its observers
  4. */
  5. export class EventState {
  6. /**
  7. * Create a new EventState
  8. * @param mask defines the mask associated with this state
  9. * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
  10. * @param target defines the original target of the state
  11. * @param currentTarget defines the current target of the state
  12. */
  13. constructor(mask: number, skipNextObservers = false, target?: any, currentTarget?: any) {
  14. this.initalize(mask, skipNextObservers, target, currentTarget);
  15. }
  16. /**
  17. * Initialize the current event state
  18. * @param mask defines the mask associated with this state
  19. * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
  20. * @param target defines the original target of the state
  21. * @param currentTarget defines the current target of the state
  22. * @returns the current event state
  23. */
  24. public initalize(mask: number, skipNextObservers = false, target?: any, currentTarget?: any): EventState {
  25. this.mask = mask;
  26. this.skipNextObservers = skipNextObservers;
  27. this.target = target;
  28. this.currentTarget = currentTarget;
  29. return this;
  30. }
  31. /**
  32. * An Observer can set this property to true to prevent subsequent observers of being notified
  33. */
  34. public skipNextObservers: boolean;
  35. /**
  36. * Get the mask value that were used to trigger the event corresponding to this EventState object
  37. */
  38. public mask: number;
  39. /**
  40. * The object that originally notified the event
  41. */
  42. public target?: any;
  43. /**
  44. * The current object in the bubbling phase
  45. */
  46. public currentTarget?: any;
  47. /**
  48. * This will be populated with the return value of the last function that was executed.
  49. * If it is the first function in the callback chain it will be the event data.
  50. */
  51. public lastReturnValue?: any;
  52. }
  53. /**
  54. * Represent an Observer registered to a given Observable object.
  55. */
  56. export class Observer<T> {
  57. /** @ignore */
  58. public _willBeUnregistered = false;
  59. /**
  60. * Gets or sets a property defining that the observer as to be unregistered after the next notification
  61. */
  62. public unregisterOnNextCall = false;
  63. /**
  64. * Creates a new observer
  65. * @param callback defines the callback to call when the observer is notified
  66. * @param mask defines the mask of the observer (used to filter notifications)
  67. * @param scope defines the current scope used to restore the JS context
  68. */
  69. constructor(
  70. /**
  71. * Defines the callback to call when the observer is notified
  72. */
  73. public callback: (eventData: T, eventState: EventState) => void,
  74. /**
  75. * Defines the mask of the observer (used to filter notifications)
  76. */
  77. public mask: number,
  78. /**
  79. * Defines the current scope used to restore the JS context
  80. */
  81. public scope: any = null) {
  82. }
  83. }
  84. /**
  85. * Represent a list of observers registered to multiple Observables object.
  86. */
  87. export class MultiObserver<T> {
  88. private _observers: Nullable<Observer<T>[]>;
  89. private _observables: Nullable<Observable<T>[]>;
  90. /**
  91. * Release associated resources
  92. */
  93. public dispose(): void {
  94. if (this._observers && this._observables) {
  95. for (var index = 0; index < this._observers.length; index++) {
  96. this._observables[index].remove(this._observers[index]);
  97. }
  98. }
  99. this._observers = null;
  100. this._observables = null;
  101. }
  102. /**
  103. * Raise a callback when one of the observable will notify
  104. * @param observables defines a list of observables to watch
  105. * @param callback defines the callback to call on notification
  106. * @param mask defines the mask used to filter notifications
  107. * @param scope defines the current scope used to restore the JS context
  108. * @returns the new MultiObserver
  109. */
  110. public static Watch<T>(observables: Observable<T>[], callback: (eventData: T, eventState: EventState) => void, mask: number = -1, scope: any = null): MultiObserver<T> {
  111. let result = new MultiObserver<T>();
  112. result._observers = new Array<Observer<T>>();
  113. result._observables = observables;
  114. for (var observable of observables) {
  115. let observer = observable.add(callback, mask, false, scope);
  116. if (observer) {
  117. result._observers.push(observer);
  118. }
  119. }
  120. return result;
  121. }
  122. }
  123. /**
  124. * The Observable class is a simple implementation of the Observable pattern.
  125. * 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.
  126. * This enable a more fine grained execution without having to rely on multiple different Observable objects.
  127. * 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).
  128. * 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.
  129. */
  130. export class Observable<T> {
  131. private _observers = new Array<Observer<T>>();
  132. private _eventState: EventState;
  133. private _onObserverAdded: Nullable<(observer: Observer<T>) => void>;
  134. /**
  135. * Creates a new observable
  136. * @param onObserverAdded defines a callback to call when a new observer is added
  137. */
  138. constructor(onObserverAdded?: (observer: Observer<T>) => void) {
  139. this._eventState = new EventState(0);
  140. if (onObserverAdded) {
  141. this._onObserverAdded = onObserverAdded;
  142. }
  143. }
  144. /**
  145. * Create a new Observer with the specified callback
  146. * @param callback the callback that will be executed for that Observer
  147. * @param mask the mask used to filter observers
  148. * @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.
  149. * @param scope optional scope for the callback to be called from
  150. * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
  151. * @returns the new observer created for the callback
  152. */
  153. public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null, unregisterOnFirstCall = false): Nullable<Observer<T>> {
  154. if (!callback) {
  155. return null;
  156. }
  157. var observer = new Observer(callback, mask, scope);
  158. observer.unregisterOnNextCall = unregisterOnFirstCall;
  159. if (insertFirst) {
  160. this._observers.unshift(observer);
  161. } else {
  162. this._observers.push(observer);
  163. }
  164. if (this._onObserverAdded) {
  165. this._onObserverAdded(observer);
  166. }
  167. return observer;
  168. }
  169. /**
  170. * Remove an Observer from the Observable object
  171. * @param observer the instance of the Observer to remove
  172. * @returns false if it doesn't belong to this Observable
  173. */
  174. public remove(observer: Nullable<Observer<T>>): boolean {
  175. if (!observer) {
  176. return false;
  177. }
  178. var index = this._observers.indexOf(observer);
  179. if (index !== -1) {
  180. this._observers.splice(index, 1);
  181. return true;
  182. }
  183. return false;
  184. }
  185. /**
  186. * Remove a callback from the Observable object
  187. * @param callback the callback to remove
  188. * @param scope optional scope. If used only the callbacks with this scope will be removed
  189. * @returns false if it doesn't belong to this Observable
  190. */
  191. public removeCallback(callback: (eventData: T, eventState: EventState) => void, scope?: any): boolean {
  192. for (var index = 0; index < this._observers.length; index++) {
  193. if (this._observers[index].callback === callback && (!scope || scope === this._observers[index].scope)) {
  194. this._observers.splice(index, 1);
  195. return true;
  196. }
  197. }
  198. return false;
  199. }
  200. private _deferUnregister(observer: Observer<T>): void {
  201. observer.unregisterOnNextCall = false;
  202. observer._willBeUnregistered = true;
  203. Tools.SetImmediate(() => {
  204. this.remove(observer);
  205. })
  206. }
  207. /**
  208. * Notify all Observers by calling their respective callback with the given data
  209. * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
  210. * @param eventData defines the data to send to all observers
  211. * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
  212. * @param target defines the original target of the state
  213. * @param currentTarget defines the current target of the state
  214. * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
  215. */
  216. public notifyObservers(eventData: T, mask: number = -1, target?: any, currentTarget?: any): boolean {
  217. if (!this._observers.length) {
  218. return true;
  219. }
  220. let state = this._eventState;
  221. state.mask = mask;
  222. state.target = target;
  223. state.currentTarget = currentTarget;
  224. state.skipNextObservers = false;
  225. state.lastReturnValue = eventData;
  226. for (var obs of this._observers) {
  227. if (obs._willBeUnregistered) {
  228. continue;
  229. }
  230. if (obs.mask & mask) {
  231. if (obs.scope) {
  232. state.lastReturnValue = obs.callback.apply(obs.scope, [eventData, state])
  233. } else {
  234. state.lastReturnValue = obs.callback(eventData, state);
  235. }
  236. if (obs.unregisterOnNextCall) {
  237. this._deferUnregister(obs);
  238. }
  239. }
  240. if (state.skipNextObservers) {
  241. return false;
  242. }
  243. }
  244. return true;
  245. }
  246. /**
  247. * Calling this will execute each callback, expecting it to be a promise or return a value.
  248. * If at any point in the chain one function fails, the promise will fail and the execution will not continue.
  249. * This is useful when a chain of events (sometimes async events) is needed to initialize a certain object
  250. * and it is crucial that all callbacks will be executed.
  251. * The order of the callbacks is kept, callbacks are not executed parallel.
  252. *
  253. * @param eventData The data to be sent to each callback
  254. * @param mask is used to filter observers defaults to -1
  255. * @param target defines the callback target (see EventState)
  256. * @param currentTarget defines he current object in the bubbling phase
  257. * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
  258. */
  259. public notifyObserversWithPromise(eventData: T, mask: number = -1, target?: any, currentTarget?: any): Promise<T> {
  260. // create an empty promise
  261. let p: Promise<any> = Promise.resolve(eventData);
  262. // no observers? return this promise.
  263. if (!this._observers.length) {
  264. return p;
  265. }
  266. let state = this._eventState;
  267. state.mask = mask;
  268. state.target = target;
  269. state.currentTarget = currentTarget;
  270. state.skipNextObservers = false;
  271. // execute one callback after another (not using Promise.all, the order is important)
  272. this._observers.forEach(obs => {
  273. if (state.skipNextObservers) {
  274. return;
  275. }
  276. if (obs._willBeUnregistered) {
  277. return;
  278. }
  279. if (obs.mask & mask) {
  280. if (obs.scope) {
  281. p = p.then((lastReturnedValue) => {
  282. state.lastReturnValue = lastReturnedValue;
  283. return obs.callback.apply(obs.scope, [eventData, state]);
  284. });
  285. } else {
  286. p = p.then((lastReturnedValue) => {
  287. state.lastReturnValue = lastReturnedValue;
  288. return obs.callback(eventData, state);
  289. });
  290. }
  291. if (obs.unregisterOnNextCall) {
  292. this._deferUnregister(obs);
  293. }
  294. }
  295. });
  296. // return the eventData
  297. return p.then(() => { return eventData; });
  298. }
  299. /**
  300. * Notify a specific observer
  301. * @param observer defines the observer to notify
  302. * @param eventData defines the data to be sent to each callback
  303. * @param mask is used to filter observers defaults to -1
  304. */
  305. public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
  306. let state = this._eventState;
  307. state.mask = mask;
  308. state.skipNextObservers = false;
  309. observer.callback(eventData, state);
  310. }
  311. /**
  312. * Gets a boolean indicating if the observable has at least one observer
  313. * @returns true is the Observable has at least one Observer registered
  314. */
  315. public hasObservers(): boolean {
  316. return this._observers.length > 0;
  317. }
  318. /**
  319. * Clear the list of observers
  320. */
  321. public clear(): void {
  322. this._observers = new Array<Observer<T>>();
  323. this._onObserverAdded = null;
  324. }
  325. /**
  326. * Clone the current observable
  327. * @returns a new observable
  328. */
  329. public clone(): Observable<T> {
  330. var result = new Observable<T>();
  331. result._observers = this._observers.slice(0);
  332. return result;
  333. }
  334. /**
  335. * Does this observable handles observer registered with a given mask
  336. * @param mask defines the mask to be tested
  337. * @return whether or not one observer registered with the given mask is handeled
  338. **/
  339. public hasSpecificMask(mask: number = -1): boolean {
  340. for (var obs of this._observers) {
  341. if (obs.mask & mask || obs.mask === mask) {
  342. return true;
  343. }
  344. }
  345. return false;
  346. }
  347. }
  348. }