/// namespace Leaf { export class ViewModel extends Model{ private depChain: DependencyChain = new DependencyChain constructor(public readonly _name: string, protected _fields: (keyof TVMs)[] = []) { super(_name, _fields) this.depChain.events.listenBy(this, "declare", (dep) => { console.log("Dependency declare", dep) this.events.listenBy(`dep/${dep.src}:${dep.target}`, `change/${dep.src}`, () => { this.events.emit(`depChange/${dep.target}`) }) this.events.listenBy(`dep/${dep.src}:${dep.target}`, `depChange/${dep.src}`, () => { this.events.emit(`depChange/${dep.target}`) }) }) this.depChain.events.listenBy(this, "revoke", (dep) => { this.events.stopListenBy(`dep/${dep.src}:${dep.target}`) }) } public tree = new DataTree(this) public set(key: keyof TVMs, value: any, silent?: boolean): boolean { if (typeof value === "function" || typeof this.get(key) === "function") { this.depChain.updateFunctionDependency(key as string, value) } return super.set(key, value) } } export class DependencyChain { events = new Leaf.EventEmitter<{ declare: { src: string target: string } revoke: { src: string target: string } }>() extractFunctionDependency(fn: Function): string[] { if (typeof fn !== "function") return [] let fnString = fn.toString() let reg = /VM\.(\w+)\b/g let match let result: string[] = [] while (match = reg.exec(fnString)) { result.push(match[1]) } return result } maxDepth: number = 10 updateFunctionDependency(name: string, fn: Function) { let deps: string[] = this.extractFunctionDependency(fn) this.validateDependency(name, deps) let oldDeps = this.dependencyMap[name] let diff = Util.arrayDiff(deps, oldDeps) for (let dep of diff.left) { this.events.emit("declare", { target: name, src: dep }) } for (let dep of diff.right) { this.events.emit("revoke", { target: name, src: dep }) } this.dependencyMap[name] = deps } validateDependency(target: string, src: string[], depth: number = 0): boolean { if (src.indexOf(target) >= 0) { throw new Error(`Recursive dependency ${target}`) } if (depth > this.maxDepth) { throw new Error(`Max dependency depth exceed :${this.maxDepth}`) } let nextCheck: string[] = [] for (let item of src) { if (this.dependencyMap[item]) { let nextDeps = this.dependencyMap[item] for (let nt of nextDeps) { // already checked if (src.indexOf(nt) >= 0) continue if (nextCheck.indexOf(nt) >= 0) continue nextCheck.push(nt) } } } if (nextCheck.length === 0) return true return this.validateDependency(target, nextCheck, depth + 1) } dependencyMap: { [index: string]: string[] } = {} } }