UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

113 lines (112 loc) 3.72 kB
import { noop } from "./functions"; const TIME_PER_FRAME_FOR_GC = 5; // 5 mc const globalObject = typeof window === "undefined" ? global : window; const rIC = globalObject.requestIdleCallback || globalObject.setTimeout; const getTime = () => performance.now(); class FnWrapper { constructor(fn, once) { this.fn = fn; this.once = once; this.canBeDeleted = false; } run(...args) { this.fn.apply(null, Array.from(args)); if (this.once) { this.destroy(); } } destroy() { this.fn = noop; this.canBeDeleted = true; } } export class Emitter { constructor() { this.eventsForGC = new Set(); this.eventsForGC = new Set(); this.mapEventToFnWrapper = new Map(); this.mapEventToMapFnToFnWrapper = new Map(); } on(event, fn) { this._on(event, fn, false); return this; } once(event, fn) { this._on(event, fn, true); return this; } off(event, fn) { if (event === undefined) { this.eventsForGC = new Set(); this.mapEventToFnWrapper = new Map(); this.mapEventToMapFnToFnWrapper = new Map(); return this; } if (this.mapEventToMapFnToFnWrapper?.has(event)) { if (typeof fn === "function" && this.mapEventToMapFnToFnWrapper.get(event)?.has(fn)) { this.mapEventToMapFnToFnWrapper.get(event)?.get(fn)?.destroy(); this.eventsForGC?.add(event); this._launchGC(); } if (fn === undefined) { const fnWrappers = this.mapEventToFnWrapper?.get(event) ?? []; for (let i = 0; i < fnWrappers.length; i += 1) { fnWrappers[i].destroy(); } this.eventsForGC?.add(event); this._launchGC(); } } return this; } emit(event, ...args) { if (!this.mapEventToFnWrapper?.has(event)) return this; const fnWrappers = this.mapEventToFnWrapper.get(event) ?? []; for (let i = 0; i < fnWrappers.length; i += 1) { fnWrappers[i].run(...args); } return this; } destroy() { this.eventsForGC = new Set(); this.mapEventToFnWrapper = new Map(); this.mapEventToMapFnToFnWrapper = new Map(); } _on(event, fn, once) { if (!this.mapEventToFnWrapper?.has(event)) { this.mapEventToFnWrapper?.set(event, []); this.mapEventToMapFnToFnWrapper?.set(event, new WeakMap()); } const fnWrapper = new FnWrapper(fn, Boolean(once)); this.mapEventToFnWrapper?.get(event)?.push(fnWrapper); this.mapEventToMapFnToFnWrapper?.get(event)?.set(fn, fnWrapper); } _launchGC() { if (this.gcLaunched) return; this.gcLaunched = true; rIC(() => { this.gcLaunched = false; this._walkGC(); }); } _walkGC() { const startTime = getTime(); for (const event of this.eventsForGC ?? []) { const newFnWrappers = []; const fnWrappers = this.mapEventToFnWrapper?.get(event) ?? []; for (let i = 0; i < fnWrappers.length; i += 1) { if (!fnWrappers[i].canBeDeleted) { newFnWrappers.push(fnWrappers[i]); } } this.mapEventToFnWrapper?.set(event, newFnWrappers); this.eventsForGC?.delete(event); if (getTime() - startTime >= TIME_PER_FRAME_FOR_GC) { this._launchGC(); return; } } } }