You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/mx/module/battle/events/BattleEventBus.ts

88 lines
2.9 KiB

3 weeks ago
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<BattleEventSubscription, MembershipSet<BattleEventListener>>();
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;
}
}