type _get_callback_ = (proxy: T, value: T[prop])=>T[prop]; type _set_callback_ = (proxy: T, old: T[prop], value: T[prop])=>T[prop]; const __Global__ = window ?? globalThis; /** 代理拦截数据 */ export type DataBlocker = { get?: {[prop in keyof T]?: _get_callback_}&{ignore?: Array>}, set?: {[prop in keyof T]?: _set_callback_}&{ignore?: Array>}, afterSet?: (prop: string|symbol, old: any, value: any, hdl: PxiHandle)=>void; } export class PxiHandle> implements ProxyHandler{ constructor(private tgt: T, private name: string, private prt: PxiHandle, private blocker?:DataBlocker){ if(blocker) this.in(blocker); this.revocable = Proxy.revocable(tgt, this); this.old = (Array.isArray(tgt)?[]:{}) as T; this.backup(); } private getGetCall(key: prop): _get_callback_{ let call: _get_callback_; if(this.blocker && this.blocker.get && Reflect.has(this.blocker.get, key)) call = Reflect.get(this.blocker.get, key) as _get_callback_; return call; } private getSetCall(key: prop): _set_callback_{ let call: _set_callback_; if(this.blocker && this.blocker.set && Reflect.has(this.blocker.set, key)) call = Reflect.get(this.blocker.set, key) as _set_callback_; return call; } private backup(key?: any){ let value: any; if(key===undefined){ value = mtec.cloneData(this.target); this.old = value; }else{ value = Reflect.get(this.target, key); value = mtec.cloneData(value); Reflect.set(this.old, key, value); } return value; } valueOf(){ let temp: any; if(Array.isArray(this.tgt)){ temp = []; let proxy = this.proxy as Array; for(let i = 0; i < proxy.length; i++) temp.push(Reflect.get(proxy, i)); }else{ temp = {}; let proxy = this.proxy as Object; Reflect.ownKeys(proxy).forEach(key=>Reflect.set(temp, key, Reflect.get(proxy, key))); } return temp as T; } private async publish(key: string|symbol, old: any, value: any){ if(key!==undefined){ let path = this.path + '.' + String(key); DataProxy.publish(path, old, value); } if(this.parent) this.parent.publish(this.name, this.old, this.target); } public in(blocker: DataBlocker){ if(this.blocker === undefined) this.blocker = {}; let props: any[] = []; if(Reflect.has(blocker, 'get')){ if(this.blocker.get === undefined) this.blocker.get = {}; Reflect.ownKeys(blocker.get).forEach(p=>{ Reflect.set(this.blocker.get, p, Reflect.get(blocker.get, p)); if(!props.includes(p)) props.push(p); }); } if(Reflect.has(blocker, 'set')){ if(this.blocker.set === undefined) this.blocker.set = {}; Reflect.ownKeys(blocker.set).forEach(p=>{ Reflect.set(this.blocker.set, p, Reflect.get(blocker.set, p)); if(!props.includes(p)) props.push(p); }); } Reflect.ownKeys(blocker).filter(k=>!['get', 'set'].includes(k as any)) .forEach(p=>Reflect.has(blocker, p)?Reflect.set(this.blocker, p, Reflect.get(blocker, p)):void 0); props.filter(p=>{ let value = Reflect.get(this.tgt, p); return typeof value === 'object' && value !== null; }).forEach(p=>{ let value = Reflect.get(this.tgt, p); if(!this.children.has(p) || this.children.get(p).target!==value){ let handle = DataProxy.proxy(value, String(p), this).handle; this.children.set(p, handle); } }); } /** 销毁所有代理 */ public revoke(){ this.children.forEach(h=>h.revoke()); this.children.clear(); DataProxy.revoke(this); this.revocable.revoke(); } get(target: T, prop: string|symbol, receiver: any): T[keyof T]{ let value: any; if(this.blocker?.get?.ignore?.includes(prop as any)) value = Reflect.get(target, prop); else{ value = this.children.has(prop) ? this.children.get(prop).proxy : Reflect.get(target, prop); if(typeof value === 'function') value = Reflect.apply(Function.bind, value, [receiver]); else{ let call = this.getGetCall(prop as any); value = call ? call(receiver, value) : value; } } if(typeof value === 'object' && value !== null && !this.children.has(prop)){ let handle = DataProxy.proxy(value, String(prop), this).handle; this.children.set(prop, handle); } return value; } set(target: T, prop: string|symbol, value: any, receiver: any): boolean{ if(this.blocker && this.blocker.set && this.blocker.set.ignore && this.blocker.set.ignore.includes(prop as any)){ Reflect.set(target, prop, undefined); return true; } let old = this.backup(prop); let call = this.getSetCall(prop as any); if(typeof value === 'object' && value !== null && !this.children.has(prop)){ let handle = DataProxy.proxy(value, String(prop), this).handle; this.children.set(prop, handle); } let new_value = call ? call(receiver, old, value) : value; if(this.children.has(prop) && this.children.get(prop).target !== new_value){ let hdl = this.children.get(prop); if(!new_value){ hdl.revoke(); this.children.delete(prop); }else if(Array.isArray(new_value)){ hdl.proxy.splice(0, hdl.proxy.length); hdl.proxy.push(...new_value); }else Object.assign(hdl.proxy, new_value); } Reflect.set(target, prop, this.children.has(prop) ? this.children.get(prop).valueOf() : new_value); if(this.blocker?.afterSet) this.blocker.afterSet(prop, old, new_value, this); this.publish(prop, old, new_value); return true; } private revocable: ReturnType; private old: T; /** 本级代理 */ public get proxy(){ return this.revocable.proxy as T; } /** 代理对象 */ public get target(){ return this.tgt; } /** 父级handle */ public get parent(){ return this.prt; } /** 子级handle */ private children = new Map>(); /** 当前handle的路径 */ public get path(): string{ return this.parent ? this.parent.path+'.'+this.name : this.name; } } export class DataProxy{ /** 代理句柄映射 */ private static id_map: Map = new Map(); private static record: Map]> = new Map(); /** * 生成代理对象 * @param ins 数据实例 * @param name 数据名称 */ public static proxy(ins: I, name?: string, parent: PxiHandle = null){ let proxy: I, handle: PxiHandle; if(DataProxy.id_map.has(ins)){ let id = DataProxy.id_map.get(ins); let [target, pxi, handle] = DataProxy.record.get(id); proxy = pxi, handle = handle; }else{ let id = mtec.string.randomToken(8, 36, token=>!DataProxy.record.has(token)); handle = new PxiHandle(ins, name??id, parent); proxy = handle.proxy; [ins, handle, handle.proxy].forEach(i=>DataProxy.id_map.set(i, id)); DataProxy.record.set(id, [ins, handle.proxy, handle]); } return {proxy, handle}; } /** 是否已注册代理 */ public static hasProxy(ins: any){ return DataProxy.id_map.has(ins); } /** * 初始化一个代理 * @param target 代理对象 * @param save 是否需要本地存储 * @param init 初始化句柄的回调 * @returns */ public static initProxy(name: string, target: D, save: boolean = false, blocker?: DataBlocker, prefix?: string){ let result = DataProxy.proxy(target, name); if(save){ let item_key = (prefix??'')+name; mtec.local.read(item_key, target); result.handle.in({ afterSet(prop, old, valu, handle){ mtec.local.save(item_key, handle.target); } }); } if(blocker) result.handle.in(blocker); Reflect.set(__Global__, 'data_'+name, result.proxy); return result.proxy } /** 销毁监听代理和记录 */ public static revoke(data: D){ if(DataProxy.id_map.has(data)){ let id = DataProxy.id_map.get(data); let [ins, pxi, handle] = DataProxy.record.get(id); [ins, pxi, handle].forEach(i=>DataProxy.id_map.delete(i)); let path_list: string[] = []; let path = handle.path; DataProxy.call_pool.forEach((l, p)=>p.startsWith(path)?path_list.push(p):void 0); path_list.forEach(p=>DataProxy.call_pool.delete(p)); handle.revoke(); DataProxy.record.delete(id); } } private static monitor_call = new Mapvoid>>(); public static monitor(data: Object, call: (path: string, old: any, value: any)=>void){ let id = DataProxy.id_map.get(data); if(!DataProxy.record.has(id)){ mtec.log.tag('无法对未代理的实例进行监听: orange', data); return void 0; } let [ins, pxi, hdl] = DataProxy.record.get(id); let path = hdl.path; if(!DataProxy.monitor_call.has(path)){ DataProxy.monitor_call.set(path, []); } DataProxy.monitor_call.get(path).push(call); } public static free(data: Object){ let id = DataProxy.id_map.get(data); if(!DataProxy.record.has(id)){ return void 0; } let [ins, pxi, hdl] = DataProxy.record.get(id); let path = hdl.path; if(DataProxy.monitor_call.has(path)){ DataProxy.monitor_call.delete(path); } } /** 监听回调池 */ private static call_pool: Map = new Map(); /** * 注册监听 * @param data 要监听的数据 * @param key 要监听的字段 * @param call 更新时触发的回调函数 * @param update 是否在注册时触发回调 */ public static follow>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void, update: boolean = true){ let handle: PxiHandle; if(DataProxy.id_map.has(data)){ handle = DataProxy.record.get(DataProxy.id_map.get(data))[2]; }else{ mtec.log.tag('无法对未代理的实例进行监听: orange', data); return void 0; } let path = handle.path + '.' + key.toString(); if(!DataProxy.call_pool.has(path)){ DataProxy.call_pool.set(path, []); } DataProxy.call_pool.get(path).push(call); if(update){ let [target, proxy] = DataProxy.record.get(DataProxy.id_map.get(data)); call(target[key], proxy[key]); } return {path, call} as {path: string, call: Function}; } /** 注销监听 */ public static cancel(info: ReturnType){ if(!DataProxy.call_pool.has(info.path)){ return void 0; } let list = DataProxy.call_pool.get(info.path); if(list.length > 0){ list.splice(list.indexOf(info.call), 1); } if(list.length <= 0){ DataProxy.call_pool.delete(info.path); } } /** * 取消指定的监听 * @param data 要监听的数据 * @param key 要监听的字段 * @param call 更新时触发的回调函数 */ public static out>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void){ let handle: PxiHandle = DataProxy.record.get(DataProxy.id_map.get(data))[2]; if(handle){ let path = handle.path + '.' + key.toString(); DataProxy.cancel({path, call}); } } /** 指定的路径是否被监听 */ public static hasFollow>(data: D, key: K){ let id = DataProxy.id_map.get(data); if(id===undefined){ return false; } let handle = DataProxy.record.get(id)[2]; if(handle===undefined){ return false; } let path = handle.path + '.' + key.toString(); let list = DataProxy.call_pool.get(path); return list===undefined ? false : list.length > 0; } /** 是否有任务正在执行中 */ private static tasking: boolean = false; /** 更新日志记录 */ private static record_map = new Map(); /** 动态延迟,为了解决子节点并发更新时,父节点更新的重复触发造成的性能浪费,逻辑上不能完全防止,目前没有发现异常,暂时如此 */ private static delay = (()=>{ let c = createCountAverageIns(); c.add(30); return c; })(); private static delay_stamp = performance.now(); private static updateDelay(){ let now = performance.now(); let diff = now-DataProxy.delay_stamp; DataProxy.delay_stamp = now; if(diff<=30 && diff>0) DataProxy.delay.add(diff); } // ⬆⬆⬆ 该段代码的是为了防止,自节点的并发更新,导致父节点的更新事件重复触发的问题 /** 打包生成任务 */ private static packaging(){ if(DataProxy.record_map.size===0) return void 0; let record = Array.from(DataProxy.record_map); DataProxy.record_map.clear(); let list = record.filter(el=>DataProxy.call_pool.has(el[0])).map(item=>{ return { path: item[0], data: item[1] } }); if(list.length>0) DataProxy.taskLoop(list); else DataProxy.tasking = false; } /** 循环执行任务列表中的任务 */ private static async taskLoop(list: {path: string, data: [any, any]}[]){ while(list.length>0){ let task = list.shift(); //if(!task.path.includes('clock')) abi.log.tag('run task: green', task.path, ...task.data); await Promise.allSettled(DataProxy.call_pool.get(task.path).map(f=>new Promise((s, j)=>{ f(...task.data); s(void 0); }))); DataProxy.distributeTask(task); } if(DataProxy.record_map.size > 0) DataProxy.packaging(); else DataProxy.tasking = false; } /** 触发一个任务支线 */ private static async distributeTask(task: {path: string, data: [any, any]}){ let path_list = task.path.split('.'); // 触发被标记的代理监听 if(DataProxy.monitor_call.has(path_list[0])) DataProxy.monitor_call.get(path_list[0]).forEach(f=>f(task.path, ...task.data)); } /** * 发布更新日志 * @param path 更新路径 * @param old 原始值 * @param value 更新值 */ public static publish(path: string, old: any, value: any){ //abi.log.tag('publish-->:blue', DataTransfer.tasking, path, JSON.stringify([old, value])); DataProxy.record_map.set(path, [old, value]); if(!DataProxy.tasking){ DataProxy.tasking = true; // ⬇⬇⬇ 该段代码的是为了防止,字段的并发更新,导致父节点的更新事件重复触发的问题 let id = setTimeout((dm: typeof DataProxy)=>{ dm.packaging(); clearTimeout(id); }, DataProxy.delay.average, DataProxy); }else DataProxy.updateDelay(); // ⬆⬆⬆ 该段代码的是为了防止,字段的并发更新,导致父节点的更新事件重复触发的问题 } } function createCountAverageIns(){ return new ((()=>{ if(Reflect.has(window ?? globalThis, 'hnc') && mtec.CountAverage){ return mtec.CountAverage; }else{ return class{ private count = 0; private _average: number; constructor(init?: number){ if(init) this.add(init); } public get average(){ return this._average ?? 0; } public add(value: number){ this.count++; this._average = this.average + (value - this.average) / this.count; return this._average; } public clean(){ this.count = 0; this._average = 0; } } } })())(); }