///
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[]
} = {}
}
}