Files
tankwar_proj/js/base/EventBus.js
2026-04-10 22:59:39 +08:00

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;