All files / lib/resources event-manager.ts

100% Statements 152/152
100% Branches 20/20
100% Functions 9/9
100% Lines 152/152

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 1521x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 578x 6415x 6415x 6415x 578x 578x 578x 578x 578x 578x 6421x 6421x 578x 578x 578x 578x 578x 578x 6415x 6415x 578x 578x 578x 578x 578x 578x 1x 1x 578x 578x 578x 578x 578x 578x 578x 578x 578x 2662x 2662x 2662x 2662x 578x 578x 578x 578x 578x 578x 578x 578x 578x 983x 983x 983x 983x 578x 578x 578x 578x 578x 578x 578x 578x 3643x 3643x 645x 645x 3643x 3643x 3643x 3643x 578x 578x 578x 578x 578x 578x 578x 578x 5x 1x 1x 1x 4x 4x 4x 4x 4x 4x 5x 13x 4x 4x 13x 9x 9x 13x 4x 4x 4x 5x 1x 1x 1x 1x 3x 3x 3x 3x 578x
import {EventEmitter} from "events";
import {iCPSEvent} from "./events-types.js";
import {Resources} from "./main.js";
 
/**
 * Callback type for event listeners
 */
export type ListenerFunction = (...args: any[]) => void;
 
/**
 * Internal storage type for event registry
 */
type EventRegistryObject = {
    event: iCPSEvent,
    listener: ListenerFunction
}
 
/**
 * This class handles access to the central event bus, provides static helper functions and keeps track of classes listening to events
 */
export class EventManager {
    /**
     * The central event bus
     */
    _eventBus: EventEmitter = new EventEmitter();
 
    /**
     * Keeps track of classes listening to events
     * This maps object instances to an array of events they are listening to
     */
    _eventRegistry: Map<any, EventRegistryObject[]> = new Map();
 
    /**
     * Keeps track of the number of times an event was emitted
     */
    _eventCounter: Map<iCPSEvent, number> = new Map();
 
    /**
     * Emits an event on the event bus
     * @param event - The event to emit
     * @param args - The arguments to pass to the event
     * @returns True if the event had listeners
     */
    emit(event: iCPSEvent, ...args: any[]): boolean {
        this.increaseEventCounter(event);
        return this._eventBus.emit(event, ...args);
    }
 
    /**
     * @param event - The event to check
     * @returns The number of times an event was emitted since the last reset
     */
    getEventCount(event: iCPSEvent): number {
        return this._eventCounter.get(event) ?? 0;
    }
 
    /**
     * Increases the event counter of the associated event by one
     * @param event - The event to increase the counter of
     */
    increaseEventCounter(event: iCPSEvent) {
        this._eventCounter.set(event, this.getEventCount(event) + 1);
    }
 
    /**
     * Resets the event counter of the associated event
     * @param event - The event to reset the counter of
     */
    resetEventCounter(event: iCPSEvent) {
        this._eventCounter.set(event, 0);
    }
 
    /**
     * This function adds an event listener to the event bus and registers it in the event registry
     * @param source - The source of the registration request, used to track the listeners and enable cleanup
     * @param event - The event to listen to
     * @param listener - The listener function to call upon the event
     * @returns This instance for chaining
     */
    on(source: any, event: iCPSEvent, listener: ListenerFunction): EventManager {
        this.addListenerToRegistry(source, event, listener);
        this._eventBus.on(event, listener);
        return this;
    }
 
    /**
     * This function adds an one-time event listener to the event bus and registers it in the event registry
     * @param source - The source of the registration request, used to track the listeners and enable cleanup
     * @param event - The event to listen to
     * @param listener - The listener function to call upon the event
     * @returns This instance for chaining
     */
    once(source: any, event: iCPSEvent, listener: ListenerFunction): EventManager {
        this.addListenerToRegistry(source, event, listener);
        this._eventBus.once(event, listener);
        return this;
    }
 
    /**
     * This functions registers the listener with the event registry
     * @param source - The source of the registration request
     * @param event - The event to listen to
     * @param listener - The registered listener function
     */
    addListenerToRegistry(source: any, event: iCPSEvent, listener: ListenerFunction) {
        Resources.logger(this).debug(`Registering listener for event ${event} from source ${source.constructor.name}`);
        if (!this._eventRegistry.has(source)) {
            this._eventRegistry.set(source, []);
        }
 
        const sourceRegistry = this._eventRegistry.get(source);
        sourceRegistry.push({event, listener});
    }
 
    /**
     * This function removes all listeners from the event bus and the event registry for the provided source
     * @param source - The source to remove listeners for
     * @param event - Optional event to remove listeners for - otherwise all will be removed
     * @returns This instance for chaining
     */
    removeListenersFromRegistry(source: any, event?: iCPSEvent): EventManager {
        if (!this._eventRegistry.has(source)) {
            Resources.logger(this).debug(`No listeners registered for source ${source.constructor.name}`);
            return this;
        }
 
        // We need to remove reference to the listener from the registry to avoid memory leaks
        // Storing non-removed listeners in a new array and replacing the old one with it
        const updatedSourceRegistry: EventRegistryObject[] = [];
 
        let removedListenerCount = 0;
        for (const registryObject of this._eventRegistry.get(source)) {
            if (event === undefined || registryObject.event === event) { // Removing listener and not pushing to new array
                this._eventBus.removeListener(registryObject.event, registryObject.listener);
                removedListenerCount++;
            } else { // Keeping listener and pushing to new array
                updatedSourceRegistry.push(registryObject);
            }
        }
 
        Resources.logger(this).debug(`Removed ${removedListenerCount} listeners for source ${source.constructor.name}`);
 
        if (updatedSourceRegistry.length === 0) {
            Resources.logger(this).debug(`No more listeners for source ${source.constructor.name} registered`);
            this._eventRegistry.delete(source);
            return this;
        }
 
        this._eventRegistry.set(source, updatedSourceRegistry);
        return this;
    }
}