85 lines
1.9 KiB
JavaScript
85 lines
1.9 KiB
JavaScript
/**
|
|
* EventBus.js
|
|
* Simple publish/subscribe event system for decoupled communication
|
|
* between game systems.
|
|
*/
|
|
|
|
class EventBus {
|
|
constructor() {
|
|
/** @type {Map<string, Array<{fn: Function, once: boolean}>>} */
|
|
this._listeners = new Map();
|
|
}
|
|
|
|
/**
|
|
* Subscribe to an event.
|
|
* @param {string} event
|
|
* @param {Function} fn
|
|
* @returns {Function} Unsubscribe function.
|
|
*/
|
|
on(event, fn) {
|
|
if (!this._listeners.has(event)) {
|
|
this._listeners.set(event, []);
|
|
}
|
|
const entry = { fn, once: false };
|
|
this._listeners.get(event).push(entry);
|
|
return () => this.off(event, fn);
|
|
}
|
|
|
|
/**
|
|
* Subscribe to an event, but only fire once.
|
|
* @param {string} event
|
|
* @param {Function} fn
|
|
*/
|
|
once(event, fn) {
|
|
if (!this._listeners.has(event)) {
|
|
this._listeners.set(event, []);
|
|
}
|
|
this._listeners.get(event).push({ fn, once: true });
|
|
}
|
|
|
|
/**
|
|
* Unsubscribe from an event.
|
|
* @param {string} event
|
|
* @param {Function} fn
|
|
*/
|
|
off(event, fn) {
|
|
const list = this._listeners.get(event);
|
|
if (!list) return;
|
|
const idx = list.findIndex((entry) => entry.fn === fn);
|
|
if (idx !== -1) list.splice(idx, 1);
|
|
}
|
|
|
|
/**
|
|
* Emit an event with optional data.
|
|
* @param {string} event
|
|
* @param {*} [data]
|
|
*/
|
|
emit(event, data) {
|
|
const list = this._listeners.get(event);
|
|
if (!list || list.length === 0) return;
|
|
|
|
// Iterate in reverse so we can safely remove "once" entries
|
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
const entry = list[i];
|
|
entry.fn(data);
|
|
if (entry.once) {
|
|
list.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all listeners for a specific event, or all events.
|
|
* @param {string} [event]
|
|
*/
|
|
clear(event) {
|
|
if (event) {
|
|
this._listeners.delete(event);
|
|
} else {
|
|
this._listeners.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EventBus;
|