@gravity-ui/graph
Version:
Modern graph editor component
113 lines (112 loc) • 3.72 kB
JavaScript
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;
}
}
}
}