import type { BattleEvent, BattleEventListener } from './BattleEvents'; import { BATTLE_EVENT_ALL, BattleEventType, type BattleEventSubscription } from './BattleEventType'; import { createMembershipSet, membershipAdd, membershipForEach, type MembershipSet } from '../../../tools/MembershipSet'; /** * 战斗事件总线 * - 逻辑层 emit,表现层 / UI on 订阅 * - 默认帧末 flush,减少同帧多次刷新 */ export class BattleEventBus { private _listeners = new Map>(); private _pending: BattleEvent[] = []; private _immediate = false; /** 当前正在派发的事件(监听重入 flush 时压栈恢复) */ private _event: BattleEvent | null = null; /** 单个监听:抛错不影响同帧其余监听(含 UnitSpawned 出怪表现) */ private readonly _callListener = (fn: BattleEventListener): void => { const event = this._event; if (!event) return; try { fn(event); } catch (err) { console.error('[BattleEventBus] listener', event.type, err); } }; /** * @param type 事件类型或通配符 * @param listener 回调 */ on(type: BattleEventSubscription, listener: BattleEventListener): void { if (!this._listeners.has(type)) { this._listeners.set(type, createMembershipSet()); } membershipAdd(this._listeners.get(type)!, listener); } /** * @param type 事件类型或通配符 * @param listener 回调 */ off(type: BattleEventSubscription, listener: BattleEventListener): void { this._listeners.get(type)?.delete(listener); } /** * @param event 战斗事件 * @param immediate 是否立即派发(跳过帧末批处理) */ emit(event: BattleEvent, immediate = false): void { if (immediate || this._immediate) { this._dispatch(event); } else { this._pending.push(event); } } /** * 派发当前批次。派发中新入队的事件留到下次 flush。 * 派发中再次 flush 只处理新批次,不得改外层下标。 */ flush(): void { const batch = this._pending; const n = batch.length; if (n === 0) return; this._pending = []; for (let i = 0; i < n; i++) { this._dispatch(batch[i]); } } /** @param enabled 是否启用立即派发模式 */ setImmediateMode(enabled: boolean): void { this._immediate = enabled; } private _dispatch(event: BattleEvent): void { const prev = this._event; this._event = event; const specific = this._listeners.get(event.type); if (specific) membershipForEach(specific, this._callListener); const all = this._listeners.get(BATTLE_EVENT_ALL); if (all) membershipForEach(all, this._callListener); this._event = prev; } }