babylon.smartPropertyPrim.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. module BABYLON {
  2. export class Prim2DClassInfo {
  3. }
  4. export class Prim2DPropInfo {
  5. static PROPKIND_MODEL: number = 1;
  6. static PROPKIND_INSTANCE: number = 2;
  7. static PROPKIND_DYNAMIC: number = 3;
  8. id: number;
  9. flagId: number;
  10. kind: number;
  11. name: string;
  12. dirtyBoundingInfo: boolean;
  13. typeLevelCompare: boolean;
  14. }
  15. export class PropertyChangedInfo {
  16. oldValue: any;
  17. newValue: any;
  18. propertyName: string;
  19. }
  20. export interface IPropertyChanged {
  21. propertyChanged: Observable<PropertyChangedInfo>;
  22. }
  23. export class ClassTreeInfo<TClass, TProp>{
  24. constructor(baseClass: ClassTreeInfo<TClass, TProp>, type: Object, classContentFactory: (base: TClass) => TClass) {
  25. this._baseClass = baseClass;
  26. this._type = type;
  27. this._subClasses = new Array<{ type: Object, node: ClassTreeInfo<TClass, TProp> }>();
  28. this._levelContent = new StringDictionary<TProp>();
  29. this._classContentFactory = classContentFactory;
  30. }
  31. get classContent(): TClass {
  32. if (!this._classContent) {
  33. this._classContent = this._classContentFactory(this._baseClass ? this._baseClass.classContent : null);
  34. }
  35. return this._classContent;
  36. }
  37. get type(): Object {
  38. return this._type;
  39. }
  40. get levelContent(): StringDictionary<TProp> {
  41. return this._levelContent;
  42. }
  43. get fullContent(): StringDictionary<TProp> {
  44. if (!this._fullContent) {
  45. let dic = new StringDictionary<TProp>();
  46. let curLevel: ClassTreeInfo<TClass, TProp> = this;
  47. while (curLevel) {
  48. curLevel.levelContent.forEach((k, v) => dic.add(k, v));
  49. curLevel = curLevel._baseClass;
  50. }
  51. this._fullContent = dic;
  52. }
  53. return this._fullContent;
  54. }
  55. getLevelOf(type: Object): ClassTreeInfo<TClass, TProp> {
  56. // Are we already there?
  57. if (type === this._type) {
  58. return this;
  59. }
  60. let baseProto = Object.getPrototypeOf(type);
  61. let curProtoContent = this.getOrAddType(Object.getPrototypeOf(baseProto), baseProto);
  62. if (!curProtoContent) {
  63. this.getLevelOf(baseProto);
  64. }
  65. return this.getOrAddType(baseProto, type);
  66. }
  67. getOrAddType(baseType: Object, type: Object): ClassTreeInfo<TClass, TProp> {
  68. // Are we at the level corresponding to the baseType?
  69. // If so, get or add the level we're looking for
  70. if (baseType === this._type) {
  71. for (let subType of this._subClasses) {
  72. if (subType.type === type) {
  73. return subType.node;
  74. }
  75. }
  76. let node = new ClassTreeInfo<TClass, TProp>(this, type, this._classContentFactory);
  77. let info = { type: type, node: node};
  78. this._subClasses.push(info);
  79. return info.node;
  80. }
  81. // Recurse down to keep looking for the node corresponding to the baseTypeName
  82. for (let subType of this._subClasses) {
  83. let info = subType.node.getOrAddType(baseType, type);
  84. if (info) {
  85. return info;
  86. }
  87. }
  88. return null;
  89. }
  90. static get<TClass, TProp>(type: Object): ClassTreeInfo<TClass, TProp> {
  91. let dic = <ClassTreeInfo<TClass, TProp>>type["__classTreeInfo"];
  92. if (!dic) {
  93. return null;
  94. }
  95. return dic.getLevelOf(type);
  96. }
  97. static getOrRegister<TClass, TProp>(type: Object, classContentFactory: (base: TClass) => TClass): ClassTreeInfo<TClass, TProp> {
  98. let dic = <ClassTreeInfo<TClass, TProp>>type["__classTreeInfo"];
  99. if (!dic) {
  100. dic = new ClassTreeInfo<TClass, TProp>(null, type, classContentFactory);
  101. type["__classTreeInfo"] = dic;
  102. }
  103. return dic;
  104. }
  105. private _type: Object;
  106. private _classContent: TClass;
  107. private _baseClass: ClassTreeInfo<TClass, TProp>;
  108. private _subClasses: Array<{type: Object, node: ClassTreeInfo<TClass, TProp>}>;
  109. private _levelContent: StringDictionary<TProp>;
  110. private _fullContent: StringDictionary<TProp>;
  111. private _classContentFactory: (base: TClass) => TClass;
  112. }
  113. @className("SmartPropertyPrim")
  114. export class SmartPropertyPrim implements IPropertyChanged {
  115. protected setupSmartPropertyPrim() {
  116. this._modelKey = null;
  117. this._modelDirty = false;
  118. this._levelBoundingInfoDirty = false;
  119. this._instanceDirtyFlags = 0;
  120. this._isDisposed = false;
  121. this._levelBoundingInfo = new BoundingInfo2D();
  122. this.animations = new Array<Animation>();
  123. }
  124. /**
  125. * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
  126. * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
  127. */
  128. public propertyChanged: Observable<PropertyChangedInfo>;
  129. /**
  130. * Check if the object is disposed or not.
  131. * @returns true if the object is dispose, false otherwise.
  132. */
  133. public get isDisposed(): boolean {
  134. return this._isDisposed;
  135. }
  136. /**
  137. * Disposable pattern, this method must be overloaded by derived types in order to clean up hardware related resources.
  138. * @returns false if the object is already dispose, true otherwise. Your implementation must call super.dispose() and check for a false return and return immediately if it's the case.
  139. */
  140. public dispose(): boolean {
  141. if (this.isDisposed) {
  142. return false;
  143. }
  144. // Don't set to null, it may upset somebody...
  145. this.animations.splice(0);
  146. this._isDisposed = true;
  147. return true;
  148. }
  149. /**
  150. * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
  151. */
  152. public animations: Animation[];
  153. /**
  154. * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
  155. * Look at Sprite2D for more information
  156. */
  157. public getAnimatables(): IAnimatable[] {
  158. return new Array<IAnimatable>();
  159. }
  160. /**
  161. * Property giving the Model Key associated to the property.
  162. * This value is constructed from the type of the primitive and all the name/value of its properties declared with the modelLevelProperty decorator
  163. * @returns the model key string.
  164. */
  165. public get modelKey(): string {
  166. // No need to compute it?
  167. if (!this._modelDirty && this._modelKey) {
  168. return this._modelKey;
  169. }
  170. let modelKey = `Class:${Tools.getClassName(this)};`;
  171. let propDic = this.propDic;
  172. propDic.forEach((k, v) => {
  173. if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
  174. let propVal = this[v.name];
  175. modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
  176. }
  177. });
  178. this._modelDirty = false;
  179. this._modelKey = modelKey;
  180. return modelKey;
  181. }
  182. /**
  183. * States if the Primitive is dirty and should be rendered again next time.
  184. * @returns true is dirty, false otherwise
  185. */
  186. public get isDirty(): boolean {
  187. return (this._instanceDirtyFlags !== 0) || this._modelDirty;
  188. }
  189. /**
  190. * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
  191. * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
  192. */
  193. private get propDic(): StringDictionary<Prim2DPropInfo> {
  194. if (!this._propInfo) {
  195. let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
  196. if (!cti) {
  197. throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
  198. }
  199. this._propInfo = cti.fullContent;
  200. }
  201. return this._propInfo;
  202. }
  203. private static _createPropInfo(target: Object, propName: string, propId: number, dirtyBoundingInfo: boolean, typeLevelCompare: boolean, kind: number): Prim2DPropInfo {
  204. let dic = ClassTreeInfo.getOrRegister<Prim2DClassInfo, Prim2DPropInfo>(target, () => new Prim2DClassInfo());
  205. var node = dic.getLevelOf(target);
  206. let propInfo = node.levelContent.get(propId.toString());
  207. if (propInfo) {
  208. throw new Error(`The ID ${propId} is already taken by another property declaration named: ${propInfo.name}`);
  209. }
  210. // Create, setup and add the PropInfo object to our prop dictionary
  211. propInfo = new Prim2DPropInfo();
  212. propInfo.id = propId;
  213. propInfo.flagId = Math.pow(2, propId);
  214. propInfo.kind = kind;
  215. propInfo.name = propName;
  216. propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
  217. propInfo.typeLevelCompare = typeLevelCompare;
  218. node.levelContent.add(propName, propInfo);
  219. return propInfo;
  220. }
  221. private static _checkUnchanged(curValue, newValue): boolean {
  222. // Nothing to nothing: nothing to do!
  223. if ((curValue === null && newValue === null) || (curValue === undefined && newValue === undefined)) {
  224. return true;
  225. }
  226. // Check value unchanged
  227. if ((curValue != null) && (newValue != null)) {
  228. if (typeof (curValue.equals) == "function") {
  229. if (curValue.equals(newValue)) {
  230. return true;
  231. }
  232. } else {
  233. if (curValue === newValue) {
  234. return true;
  235. }
  236. }
  237. }
  238. return false;
  239. }
  240. private static propChangedInfo = new PropertyChangedInfo();
  241. public markAsDirty(propertyName: string) {
  242. let i = propertyName.indexOf(".");
  243. if (i !== -1) {
  244. propertyName = propertyName.substr(0, i);
  245. }
  246. var propInfo = this.propDic.get(propertyName);
  247. if (!propInfo) {
  248. return;
  249. }
  250. var newValue = this[propertyName];
  251. this._handlePropChanged(undefined, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
  252. }
  253. private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
  254. // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
  255. if (propInfo.dirtyBoundingInfo) {
  256. this._levelBoundingInfoDirty = true;
  257. // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
  258. if (this instanceof Prim2DBase) {
  259. let curprim = (<any>this).parent;
  260. while (curprim) {
  261. curprim._boundingInfoDirty = true;
  262. if (curprim instanceof Group2D) {
  263. if (curprim.isRenderableGroup) {
  264. break;
  265. }
  266. }
  267. curprim = curprim.parent;
  268. }
  269. }
  270. }
  271. // Trigger property changed
  272. let info = SmartPropertyPrim.propChangedInfo;
  273. info.oldValue = curValue;
  274. info.newValue = newValue;
  275. info.propertyName = propName;
  276. let propMask = propInfo.flagId;
  277. this.propertyChanged.notifyObservers(info, propMask);
  278. // If the property belong to a group, check if it's a cached one, and dirty its render sprite accordingly
  279. if (this instanceof Group2D) {
  280. this.handleGroupChanged(propInfo);
  281. }
  282. // Check if we need to dirty only if the type change and make the test
  283. var skipDirty = false;
  284. if (typeLevelCompare && curValue != null && newValue != null) {
  285. var cvProto = (<any>curValue).__proto__;
  286. var nvProto = (<any>newValue).__proto__;
  287. skipDirty = (cvProto === nvProto);
  288. }
  289. // Set the dirty flags
  290. if (!skipDirty) {
  291. if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
  292. if (!this.isDirty) {
  293. this.onPrimBecomesDirty();
  294. }
  295. this._modelDirty = true;
  296. } else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
  297. if (!this.isDirty) {
  298. this.onPrimBecomesDirty();
  299. }
  300. this._instanceDirtyFlags |= propMask;
  301. }
  302. }
  303. }
  304. protected handleGroupChanged(prop: Prim2DPropInfo) {
  305. }
  306. /**
  307. * Check if a given set of properties are dirty or not.
  308. * @param flags a ORed combination of Prim2DPropInfo.flagId values
  309. * @return true if at least one property is dirty, false if none of them are.
  310. */
  311. public checkPropertiesDirty(flags: number): boolean {
  312. return (this._instanceDirtyFlags & flags) !== 0;
  313. }
  314. /**
  315. * Clear a given set of properties.
  316. * @param flags a ORed combination of Prim2DPropInfo.flagId values
  317. * @return the new set of property still marked as dirty
  318. */
  319. protected clearPropertiesDirty(flags: number): number {
  320. this._instanceDirtyFlags &= ~flags;
  321. return this._instanceDirtyFlags;
  322. }
  323. /**
  324. * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
  325. * @returns {}
  326. */
  327. public get levelBoundingInfo(): BoundingInfo2D {
  328. if (this._levelBoundingInfoDirty) {
  329. this.updateLevelBoundingInfo();
  330. this._levelBoundingInfoDirty = false;
  331. }
  332. return this._levelBoundingInfo;
  333. }
  334. /**
  335. * This method must be overridden by a given Primitive implementation to compute its boundingInfo
  336. */
  337. protected updateLevelBoundingInfo() {
  338. }
  339. /**
  340. * Property method called when the Primitive becomes dirty
  341. */
  342. protected onPrimBecomesDirty() {
  343. }
  344. static _hookProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare: boolean, dirtyBoundingInfo: boolean, kind: number): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  345. return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
  346. var propInfo = SmartPropertyPrim._createPropInfo(target, <string>propName, propId, dirtyBoundingInfo, typeLevelCompare, kind);
  347. if (piStore) {
  348. piStore(propInfo);
  349. }
  350. let getter = descriptor.get, setter = descriptor.set;
  351. // Overload the property setter implementation to add our own logic
  352. descriptor.set = function (val) {
  353. // check for disposed first, do nothing
  354. if (this.isDisposed) {
  355. return;
  356. }
  357. let curVal = getter.call(this);
  358. if (SmartPropertyPrim._checkUnchanged(curVal, val)) {
  359. return;
  360. }
  361. // Cast the object we're working one
  362. let prim = <SmartPropertyPrim>this;
  363. // Change the value
  364. setter.call(this, val);
  365. // Notify change, dirty flags update
  366. prim._handlePropChanged(curVal, val, <string>propName, propInfo, typeLevelCompare);
  367. }
  368. }
  369. }
  370. private _modelKey; string;
  371. private _propInfo: StringDictionary<Prim2DPropInfo>;
  372. private _levelBoundingInfoDirty: boolean;
  373. private _isDisposed: boolean;
  374. protected _levelBoundingInfo: BoundingInfo2D;
  375. protected _boundingInfo: BoundingInfo2D;
  376. protected _modelDirty: boolean;
  377. protected _instanceDirtyFlags: number;
  378. }
  379. export function modelLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  380. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_MODEL);
  381. }
  382. export function instanceLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  383. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_INSTANCE);
  384. }
  385. export function dynamicLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  386. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_DYNAMIC);
  387. }
  388. }