babylon.smartPropertyPrim.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. // Special case, array, this WON'T WORK IN ALL CASES, all entries have to be of the same type and it must be a BJS well known one
  176. if (propVal && propVal.constructor === Array) {
  177. let firstVal = propVal[0];
  178. if (!firstVal) {
  179. propVal = 0;
  180. } else {
  181. propVal = Tools.hashCodeFromStream(Tools.arrayOrStringFeeder(propVal));
  182. }
  183. }
  184. modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
  185. }
  186. });
  187. this._modelDirty = false;
  188. this._modelKey = modelKey;
  189. return modelKey;
  190. }
  191. /**
  192. * States if the Primitive is dirty and should be rendered again next time.
  193. * @returns true is dirty, false otherwise
  194. */
  195. public get isDirty(): boolean {
  196. return (this._instanceDirtyFlags !== 0) || this._modelDirty;
  197. }
  198. /**
  199. * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
  200. * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
  201. */
  202. private get propDic(): StringDictionary<Prim2DPropInfo> {
  203. if (!this._propInfo) {
  204. let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
  205. if (!cti) {
  206. throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
  207. }
  208. this._propInfo = cti.fullContent;
  209. }
  210. return this._propInfo;
  211. }
  212. private static _createPropInfo(target: Object, propName: string, propId: number, dirtyBoundingInfo: boolean, typeLevelCompare: boolean, kind: number): Prim2DPropInfo {
  213. let dic = ClassTreeInfo.getOrRegister<Prim2DClassInfo, Prim2DPropInfo>(target, () => new Prim2DClassInfo());
  214. var node = dic.getLevelOf(target);
  215. let propInfo = node.levelContent.get(propId.toString());
  216. if (propInfo) {
  217. throw new Error(`The ID ${propId} is already taken by another property declaration named: ${propInfo.name}`);
  218. }
  219. // Create, setup and add the PropInfo object to our prop dictionary
  220. propInfo = new Prim2DPropInfo();
  221. propInfo.id = propId;
  222. propInfo.flagId = Math.pow(2, propId);
  223. propInfo.kind = kind;
  224. propInfo.name = propName;
  225. propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
  226. propInfo.typeLevelCompare = typeLevelCompare;
  227. node.levelContent.add(propName, propInfo);
  228. return propInfo;
  229. }
  230. private static _checkUnchanged(curValue, newValue): boolean {
  231. // Nothing to nothing: nothing to do!
  232. if ((curValue === null && newValue === null) || (curValue === undefined && newValue === undefined)) {
  233. return true;
  234. }
  235. // Check value unchanged
  236. if ((curValue != null) && (newValue != null)) {
  237. if (typeof (curValue.equals) == "function") {
  238. if (curValue.equals(newValue)) {
  239. return true;
  240. }
  241. } else {
  242. if (curValue === newValue) {
  243. return true;
  244. }
  245. }
  246. }
  247. return false;
  248. }
  249. private static propChangedInfo = new PropertyChangedInfo();
  250. public markAsDirty(propertyName: string) {
  251. if (this.isDisposed) {
  252. return;
  253. }
  254. let i = propertyName.indexOf(".");
  255. if (i !== -1) {
  256. propertyName = propertyName.substr(0, i);
  257. }
  258. var propInfo = this.propDic.get(propertyName);
  259. if (!propInfo) {
  260. return;
  261. }
  262. var newValue = this[propertyName];
  263. this._handlePropChanged(undefined, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
  264. }
  265. private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
  266. // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
  267. if (propInfo.dirtyBoundingInfo) {
  268. this._levelBoundingInfoDirty = true;
  269. // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
  270. if (this instanceof Prim2DBase) {
  271. let curprim = (<any>this).parent;
  272. while (curprim) {
  273. curprim._boundingInfoDirty = true;
  274. if (curprim instanceof Group2D) {
  275. if (curprim.isRenderableGroup) {
  276. break;
  277. }
  278. }
  279. curprim = curprim.parent;
  280. }
  281. }
  282. }
  283. // Trigger property changed
  284. let info = SmartPropertyPrim.propChangedInfo;
  285. info.oldValue = curValue;
  286. info.newValue = newValue;
  287. info.propertyName = propName;
  288. let propMask = propInfo.flagId;
  289. this.propertyChanged.notifyObservers(info, propMask);
  290. // If the property belong to a group, check if it's a cached one, and dirty its render sprite accordingly
  291. if (this instanceof Group2D) {
  292. this.handleGroupChanged(propInfo);
  293. }
  294. // Check if we need to dirty only if the type change and make the test
  295. var skipDirty = false;
  296. if (typeLevelCompare && curValue != null && newValue != null) {
  297. var cvProto = (<any>curValue).__proto__;
  298. var nvProto = (<any>newValue).__proto__;
  299. skipDirty = (cvProto === nvProto);
  300. }
  301. // Set the dirty flags
  302. if (!skipDirty) {
  303. if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
  304. if (!this.isDirty) {
  305. this.onPrimBecomesDirty();
  306. }
  307. this._modelDirty = true;
  308. } else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
  309. if (!this.isDirty) {
  310. this.onPrimBecomesDirty();
  311. }
  312. this._instanceDirtyFlags |= propMask;
  313. }
  314. }
  315. }
  316. protected handleGroupChanged(prop: Prim2DPropInfo) {
  317. }
  318. /**
  319. * Check if a given set of properties are dirty or not.
  320. * @param flags a ORed combination of Prim2DPropInfo.flagId values
  321. * @return true if at least one property is dirty, false if none of them are.
  322. */
  323. public checkPropertiesDirty(flags: number): boolean {
  324. return (this._instanceDirtyFlags & flags) !== 0;
  325. }
  326. /**
  327. * Clear a given set of properties.
  328. * @param flags a ORed combination of Prim2DPropInfo.flagId values
  329. * @return the new set of property still marked as dirty
  330. */
  331. protected clearPropertiesDirty(flags: number): number {
  332. this._instanceDirtyFlags &= ~flags;
  333. return this._instanceDirtyFlags;
  334. }
  335. public _resetPropertiesDirty() {
  336. this._instanceDirtyFlags = 0;
  337. }
  338. /**
  339. * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
  340. * @returns {}
  341. */
  342. public get levelBoundingInfo(): BoundingInfo2D {
  343. if (this._levelBoundingInfoDirty) {
  344. this.updateLevelBoundingInfo();
  345. this._levelBoundingInfoDirty = false;
  346. }
  347. return this._levelBoundingInfo;
  348. }
  349. /**
  350. * This method must be overridden by a given Primitive implementation to compute its boundingInfo
  351. */
  352. protected updateLevelBoundingInfo() {
  353. }
  354. /**
  355. * Property method called when the Primitive becomes dirty
  356. */
  357. protected onPrimBecomesDirty() {
  358. }
  359. static _hookProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare: boolean, dirtyBoundingInfo: boolean, kind: number): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  360. return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
  361. var propInfo = SmartPropertyPrim._createPropInfo(target, <string>propName, propId, dirtyBoundingInfo, typeLevelCompare, kind);
  362. if (piStore) {
  363. piStore(propInfo);
  364. }
  365. let getter = descriptor.get, setter = descriptor.set;
  366. // Overload the property setter implementation to add our own logic
  367. descriptor.set = function (val) {
  368. // check for disposed first, do nothing
  369. if (this.isDisposed) {
  370. return;
  371. }
  372. let curVal = getter.call(this);
  373. if (SmartPropertyPrim._checkUnchanged(curVal, val)) {
  374. return;
  375. }
  376. // Cast the object we're working one
  377. let prim = <SmartPropertyPrim>this;
  378. // Change the value
  379. setter.call(this, val);
  380. // Notify change, dirty flags update
  381. prim._handlePropChanged(curVal, val, <string>propName, propInfo, typeLevelCompare);
  382. }
  383. }
  384. }
  385. private _modelKey; string;
  386. private _propInfo: StringDictionary<Prim2DPropInfo>;
  387. private _isDisposed: boolean;
  388. protected _levelBoundingInfoDirty: boolean;
  389. protected _levelBoundingInfo: BoundingInfo2D;
  390. protected _boundingInfo: BoundingInfo2D;
  391. protected _modelDirty: boolean;
  392. protected _instanceDirtyFlags: number;
  393. }
  394. export function modelLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  395. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_MODEL);
  396. }
  397. export function instanceLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  398. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_INSTANCE);
  399. }
  400. export function dynamicLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  401. return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_DYNAMIC);
  402. }
  403. }