| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- type _get_callback_<T, prop extends keyof T> = (proxy: T, value: T[prop])=>T[prop];
- type _set_callback_<T, prop extends keyof T> = (proxy: T, old: T[prop], value: T[prop])=>T[prop];
- const __Global__ = window ?? globalThis;
- /** 代理拦截数据 */
- export type DataBlocker<T> = {
- get?: {[prop in keyof T]?: _get_callback_<T, prop>}&{ignore?: Array<mtec.OmitKeys<T, Function>>},
- set?: {[prop in keyof T]?: _set_callback_<T, prop>}&{ignore?: Array<mtec.OmitKeys<T, Function>>},
- afterSet?: (prop: string|symbol, old: any, value: any, hdl: PxiHandle<T>)=>void;
- }
- export class PxiHandle<T extends Object|Array<any>> implements ProxyHandler<T>{
- constructor(private tgt: T, private name: string, private prt: PxiHandle<any>, private blocker?:DataBlocker<T>){
- if(blocker) this.in(blocker);
- this.revocable = Proxy.revocable(tgt, this);
- this.old = (Array.isArray(tgt)?[]:{}) as T;
- this.backup();
- }
- private getGetCall<prop extends keyof T>(key: prop): _get_callback_<T, prop>{
- let call: _get_callback_<T, prop>;
- if(this.blocker && this.blocker.get && Reflect.has(this.blocker.get, key)) call = Reflect.get(this.blocker.get, key) as _get_callback_<T, prop>;
- return call;
- }
- private getSetCall<prop extends keyof T>(key: prop): _set_callback_<T, prop>{
- let call: _set_callback_<T, prop>;
- if(this.blocker && this.blocker.set && Reflect.has(this.blocker.set, key)) call = Reflect.get(this.blocker.set, key) as _set_callback_<T, prop>;
- 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<any>;
- 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<T>){
- 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<typeof Proxy.revocable>;
- 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<any, PxiHandle<any>>();
- /** 当前handle的路径 */
- public get path(): string{
- return this.parent ? this.parent.path+'.'+this.name : this.name;
- }
- }
- export class DataProxy{
- /** 代理句柄映射 */
- private static id_map: Map<any, string> = new Map();
- private static record: Map<string, [any, any, PxiHandle<any>]> = new Map();
- /**
- * 生成代理对象
- * @param ins 数据实例
- * @param name 数据名称
- */
- public static proxy<I extends Object>(ins: I, name?: string, parent: PxiHandle<any> = null){
- let proxy: I, handle: PxiHandle<I>;
- 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<D>(name: string, target: D, save: boolean = false, blocker?: DataBlocker<D>, 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<D extends Object>(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 Map<string, Array<(path: string, old: any, value: any)=>void>>();
- 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<string, Function[]> = new Map();
- /**
- * 注册监听
- * @param data 要监听的数据
- * @param key 要监听的字段
- * @param call 更新时触发的回调函数
- * @param update 是否在注册时触发回调
- */
- public static follow<D extends Object, K extends mtec.OmitKeys<D, Function>>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void, update: boolean = true){
- let handle: PxiHandle<D>;
- 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<typeof DataProxy.follow>){
- 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<D extends Object, K extends mtec.OmitKeys<D, Function>>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void){
- let handle: PxiHandle<D> = DataProxy.record.get(DataProxy.id_map.get(data))[2];
- if(handle){
- let path = handle.path + '.' + key.toString();
- DataProxy.cancel({path, call});
- }
- }
- /** 指定的路径是否被监听 */
- public static hasFollow<D extends Object, K extends mtec.OmitKeys<D, Function>>(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<string, [any, any]>();
- /** 动态延迟,为了解决子节点并发更新时,父节点更新的重复触发造成的性能浪费,逻辑上不能完全防止,目前没有发现异常,暂时如此 */
- 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;
- }
- }
- }
- })())();
- }
|