|
@@ -0,0 +1,351 @@
|
|
|
+module BABYLON {
|
|
|
+ export class Prim2DClassInfo {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ export class Prim2DPropInfo {
|
|
|
+ static PROPKIND_MODEL: number = 1;
|
|
|
+ static PROPKIND_INSTANCE: number = 2;
|
|
|
+ static PROPKIND_DYNAMIC: number = 3;
|
|
|
+
|
|
|
+ id: number;
|
|
|
+ flagId: number;
|
|
|
+ kind: number;
|
|
|
+ name: string;
|
|
|
+ dirtyBoundingInfo: boolean;
|
|
|
+ typeLevelCompare: boolean;
|
|
|
+ }
|
|
|
+
|
|
|
+ export class PropertyChangedInfo {
|
|
|
+ oldValue: any;
|
|
|
+ newValue: any;
|
|
|
+ propertyName: string;
|
|
|
+ }
|
|
|
+
|
|
|
+ export interface IPropertyChanged {
|
|
|
+ propertyChanged: Observable<PropertyChangedInfo>;
|
|
|
+ }
|
|
|
+
|
|
|
+ export class ClassTreeInfo<TClass, TProp>{
|
|
|
+ constructor(baseClass: ClassTreeInfo<TClass, TProp>, typeName: string, classContentFactory: (base: TClass) => TClass) {
|
|
|
+ this._baseClass = baseClass;
|
|
|
+ this._typeName = typeName;
|
|
|
+ this._subClasses = new StringDictionary<ClassTreeInfo<TClass, TProp>>();
|
|
|
+ this._levelContent = new StringDictionary<TProp>();
|
|
|
+ this._classContentFactory = classContentFactory;
|
|
|
+ }
|
|
|
+
|
|
|
+ get classContent(): TClass {
|
|
|
+ if (!this._classContent) {
|
|
|
+ this._classContent = this._classContentFactory(this._baseClass ? this._baseClass.classContent : null);
|
|
|
+ }
|
|
|
+ return this._classContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ get typeName(): string {
|
|
|
+ return this._typeName;
|
|
|
+ }
|
|
|
+
|
|
|
+ get levelContent(): StringDictionary<TProp> {
|
|
|
+ return this._levelContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ get fullContent(): StringDictionary<TProp> {
|
|
|
+ if (!this._fullContent) {
|
|
|
+ let dic = new StringDictionary<TProp>();
|
|
|
+ let curLevel: ClassTreeInfo<TClass, TProp> = this;
|
|
|
+ while (curLevel) {
|
|
|
+ curLevel.levelContent.forEach((k, v) => dic.add(k, v));
|
|
|
+ curLevel = curLevel._baseClass;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._fullContent = dic;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this._fullContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ getLevelOf(type: Object): ClassTreeInfo<TClass, TProp> {
|
|
|
+ let typeName = Tools.getClassName(type);
|
|
|
+
|
|
|
+ // Are we already there?
|
|
|
+ if (typeName === this._typeName) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ let baseProto = Object.getPrototypeOf(type);
|
|
|
+ // If type is a class, this will get the base class proto, if type is an instance of a class, this will get the proto of the class
|
|
|
+ let baseTypeName = Tools.getClassName(baseProto);
|
|
|
+
|
|
|
+ // If both name are equal we only switch from instance to class, we need to get the next proto in the hierarchy to get the base class
|
|
|
+ if (baseTypeName === typeName) {
|
|
|
+ baseTypeName = Tools.getClassName(Object.getPrototypeOf(baseProto));
|
|
|
+ }
|
|
|
+ return this.getOrAddType(baseTypeName, typeName);
|
|
|
+ }
|
|
|
+
|
|
|
+ getOrAddType(baseTypeName, typeName: string): ClassTreeInfo<TClass, TProp> {
|
|
|
+
|
|
|
+ // Are we at the level corresponding to the baseType?
|
|
|
+ // If so, get or add the level we're looking for
|
|
|
+ if (baseTypeName === this._typeName) {
|
|
|
+ return this._subClasses.getOrAddWithFactory(typeName, k => new ClassTreeInfo<TClass, TProp>(this, typeName, this._classContentFactory));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Recurse down to keep looking for the node corresponding to the baseTypeName
|
|
|
+ return this._subClasses.first<ClassTreeInfo<TClass, TProp>>((key, val) => val.getOrAddType(baseTypeName, typeName));
|
|
|
+ }
|
|
|
+
|
|
|
+ static get<TClass, TProp>(target: Object): ClassTreeInfo<TClass, TProp> {
|
|
|
+ let dic = <ClassTreeInfo<TClass, TProp>>target["__classTreeInfo"];
|
|
|
+ if (!dic) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return dic.getLevelOf(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ static getOrRegister<TClass, TProp>(target: Object, classContentFactory: (base: TClass) => TClass): ClassTreeInfo<TClass, TProp> {
|
|
|
+ let dic = <ClassTreeInfo<TClass, TProp>>target["__classTreeInfo"];
|
|
|
+ if (!dic) {
|
|
|
+ dic = new ClassTreeInfo<TClass, TProp>(null, Tools.getClassName(target), classContentFactory);
|
|
|
+ target["__classTreeInfo"] = dic;
|
|
|
+ }
|
|
|
+ return dic;
|
|
|
+ }
|
|
|
+
|
|
|
+ private _typeName: string;
|
|
|
+ private _classContent: TClass;
|
|
|
+ private _baseClass: ClassTreeInfo<TClass, TProp>;
|
|
|
+ private _subClasses: StringDictionary<ClassTreeInfo<TClass, TProp>>;
|
|
|
+ private _levelContent: StringDictionary<TProp>;
|
|
|
+ private _fullContent: StringDictionary<TProp>;
|
|
|
+ private _classContentFactory: (base: TClass) => TClass;
|
|
|
+ }
|
|
|
+
|
|
|
+ export class SmartPropertyPrim implements IPropertyChanged {
|
|
|
+
|
|
|
+ protected setupSmartPropertyPrim() {
|
|
|
+ this._modelKey = null;
|
|
|
+ this._modelDirty = false;
|
|
|
+ this._levelBoundingInfoDirty = false;
|
|
|
+ this._instanceDirtyFlags = 0;
|
|
|
+ this._levelBoundingInfo = new BoundingInfo2D();
|
|
|
+ }
|
|
|
+
|
|
|
+ public propertyChanged: Observable<PropertyChangedInfo>;
|
|
|
+
|
|
|
+ public dispose() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public get modelKey(): string {
|
|
|
+
|
|
|
+ // No need to compute it?
|
|
|
+ if (!this._modelDirty && this._modelKey) {
|
|
|
+ return this._modelKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ let modelKey = `Class:${Tools.getClassName(this)};`;
|
|
|
+ let propDic = this.propDic;
|
|
|
+ propDic.forEach((k, v) => {
|
|
|
+ if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
|
|
|
+ let propVal = this[v.name];
|
|
|
+ modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this._modelDirty = false;
|
|
|
+ this._modelKey = modelKey;
|
|
|
+
|
|
|
+ return modelKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache<TInstData>): ModelRenderCache<TInstData> {
|
|
|
+ return <ModelRenderCache<TInstData>>SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static ModelCache: StringDictionary<ModelRenderCacheBase> = new StringDictionary<ModelRenderCacheBase>();
|
|
|
+
|
|
|
+ private get propDic(): StringDictionary<Prim2DPropInfo> {
|
|
|
+ let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(this);
|
|
|
+ if (!cti) {
|
|
|
+ throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
|
|
|
+ }
|
|
|
+ return cti.fullContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _createPropInfo(target: Object, propName: string, propId: number, dirtyBoundingInfo: boolean, typeLevelCompare: boolean, kind: number): Prim2DPropInfo {
|
|
|
+ let dic = ClassTreeInfo.getOrRegister<Prim2DClassInfo, Prim2DPropInfo>(target, () => new Prim2DClassInfo());
|
|
|
+ var node = dic.getLevelOf(target);
|
|
|
+
|
|
|
+ let propInfo = node.levelContent.get(propId.toString());
|
|
|
+ if (propInfo) {
|
|
|
+ throw new Error(`The ID ${propId} is already taken by another property declaration named: ${propInfo.name}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create, setup and add the PropInfo object to our prop dictionary
|
|
|
+ propInfo = new Prim2DPropInfo();
|
|
|
+ propInfo.id = propId;
|
|
|
+ propInfo.flagId = Math.pow(2, propId);
|
|
|
+ propInfo.kind = kind;
|
|
|
+ propInfo.name = propName;
|
|
|
+ propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
|
|
|
+ propInfo.typeLevelCompare = typeLevelCompare;
|
|
|
+ node.levelContent.add(propId.toString(), propInfo);
|
|
|
+
|
|
|
+ return propInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _checkUnchanged(curValue, newValue): boolean {
|
|
|
+ // Nothing to nothing: nothign to do!
|
|
|
+ if ((curValue === null && newValue === null) || (curValue === undefined && newValue === undefined)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check value unchanged
|
|
|
+ if ((curValue != null) && (newValue != null)) {
|
|
|
+ if (typeof (curValue.equals) == "function") {
|
|
|
+ if (curValue.equals(newValue)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (curValue === newValue) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static propChangedInfo = new PropertyChangedInfo();
|
|
|
+
|
|
|
+ private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
|
|
|
+ // Trigger propery changed
|
|
|
+ let info = SmartPropertyPrim.propChangedInfo;
|
|
|
+ info.oldValue = curValue;
|
|
|
+ info.newValue = newValue;
|
|
|
+ info.propertyName = propName;
|
|
|
+ let propMask = propInfo.flagId;
|
|
|
+ this.propertyChanged.notifyObservers(info, propMask);
|
|
|
+
|
|
|
+ // Check if we need to dirty only if the type change and make the test
|
|
|
+ var skipDirty = false;
|
|
|
+ if (typeLevelCompare && curValue != null && newValue != null) {
|
|
|
+ var cvProto = (<any>curValue).__proto__;
|
|
|
+ var nvProto = (<any>newValue).__proto__;
|
|
|
+
|
|
|
+ skipDirty = (cvProto === nvProto);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set the dirty flags
|
|
|
+ if (!skipDirty) {
|
|
|
+ if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
|
|
|
+ if ((this._instanceDirtyFlags === 0) && (!this._modelDirty)) {
|
|
|
+ this.onPrimBecomesDirty();
|
|
|
+ }
|
|
|
+ this._modelDirty = true;
|
|
|
+ } else if (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) {
|
|
|
+ if ((this._instanceDirtyFlags === 0) && (!this._modelDirty)) {
|
|
|
+ this.onPrimBecomesDirty();
|
|
|
+ }
|
|
|
+ this._instanceDirtyFlags |= propMask;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public checkPropertiesDirty(flags: number): boolean {
|
|
|
+ return (this._instanceDirtyFlags & flags) !== 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected clearPropertiesDirty(flags: number): number {
|
|
|
+ this._instanceDirtyFlags &= ~flags;
|
|
|
+ return this._instanceDirtyFlags;
|
|
|
+ }
|
|
|
+
|
|
|
+ public get levelBoundingInfo(): BoundingInfo2D {
|
|
|
+ if (this._levelBoundingInfoDirty) {
|
|
|
+ this.updateLevelBoundingInfo();
|
|
|
+ this._levelBoundingInfoDirty = false;
|
|
|
+ }
|
|
|
+ return this._levelBoundingInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected updateLevelBoundingInfo() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ protected onPrimBecomesDirty() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ static _hookProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare: boolean, dirtyBoundingInfo: boolean, kind: number): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
|
|
|
+ return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
|
|
|
+
|
|
|
+ var propInfo = SmartPropertyPrim._createPropInfo(target, <string>propName, propId, dirtyBoundingInfo, typeLevelCompare, kind);
|
|
|
+ if (piStore) {
|
|
|
+ piStore(propInfo);
|
|
|
+ }
|
|
|
+ let getter = descriptor.get, setter = descriptor.set;
|
|
|
+
|
|
|
+ // Overload the property setter implementation to add our own logic
|
|
|
+ descriptor.set = function (val) {
|
|
|
+ let curVal = getter.call(this);
|
|
|
+
|
|
|
+ if (SmartPropertyPrim._checkUnchanged(curVal, val)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cast the object we're working one
|
|
|
+ let prim = <SmartPropertyPrim>this;
|
|
|
+
|
|
|
+ // Change the value
|
|
|
+ setter.call(this, val);
|
|
|
+
|
|
|
+ // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
|
|
|
+ if (propInfo.dirtyBoundingInfo) {
|
|
|
+ prim._levelBoundingInfoDirty = true;
|
|
|
+
|
|
|
+ // Escalade the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
|
|
|
+ if (prim instanceof Prim2DBase) {
|
|
|
+ let curprim = prim.parent;
|
|
|
+ while (curprim) {
|
|
|
+ curprim._boundingInfoDirty = true;
|
|
|
+
|
|
|
+ if (curprim instanceof Group2D) {
|
|
|
+ if (curprim.isRenderableGroup) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ curprim = curprim.parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Notify change, dirty flags update
|
|
|
+ prim._handlePropChanged(curVal, val, <string>propName, propInfo, typeLevelCompare);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private _modelKey; string;
|
|
|
+ private _levelBoundingInfoDirty: boolean;
|
|
|
+ protected _levelBoundingInfo: BoundingInfo2D;
|
|
|
+ protected _boundingInfo: BoundingInfo2D;
|
|
|
+ protected _modelDirty: boolean;
|
|
|
+ protected _instanceDirtyFlags: number;
|
|
|
+ }
|
|
|
+
|
|
|
+ export function modelLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
|
|
|
+ return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_MODEL);
|
|
|
+ }
|
|
|
+
|
|
|
+ export function instanceLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
|
|
|
+ return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_INSTANCE);
|
|
|
+ }
|
|
|
+
|
|
|
+ export function dynamicLevelProperty<T>(propId: number, piStore: (pi: Prim2DPropInfo) => void, typeLevelCompare = false, dirtyBoundingInfo = false): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
|
|
|
+ return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_DYNAMIC);
|
|
|
+ }
|
|
|
+}
|