EventBus
这一页是签名与行为参考。订阅、优先级、退订与异常隔离。直接做一遍:教程 · 订阅游玩事件。
EventBus 是全进程唯一的事件总线。模组在 onInit 里经 ModContext#eventBus() 取得,返回的实例带模组归属(宿主内部是 EventBusImpl#forMod(modId) 的包装视图)。游玩事件与跨场景事件都走这一条总线,模组用事件类型区分,不需要选择「该用哪条总线」。
方法速览
| 方法 | 用途 |
|---|---|
subscribe(Class<E>, EventPriority, Consumer<E>) | 按事件类型 + 优先级订阅,返回退订令牌 |
subscribe(Class<E>, Consumer<E>) | 同上,优先级固定为 NORMAL |
post(E) | 同步分发事件并返回同一实例 |
subscribe(Class type, EventPriority priority, Consumer listener)
<E extends Event> Subscription subscribe(Class<E> type, EventPriority priority, Consumer<E> listener);行为:登记一条订阅并立即返回 Subscription。匹配规则是 type.isInstance(事件),因此订阅父类会收到子类(订阅 TickEvent 会同时收到 TickEvent.Pre 与 TickEvent.Post;订阅 Event 会收到全部事件)。type 或 listener 为 null 时抛 IllegalArgumentException;priority 为 null 时按 EventPriority.NORMAL 处理。同一个监听器实例可以重复注册,每次注册各产生一条独立订阅,各自用返回的令牌退订。subscribe 可在任意线程调用(内部加锁重建有序订阅表)。
示例:
Subscription pauseSubscription = ctx.eventBus().subscribe(
PauseEvent.class, EventPriority.HIGHEST, event -> {
if (shouldBlockPause()) {
event.setResult(EventResult.DENY);
}
});subscribe(Class type, Consumer listener)
<E extends Event> Subscription subscribe(Class<E> type, Consumer<E> listener);行为:接口上的 default 重载,等价于 subscribe(type, EventPriority.NORMAL, listener)。适合纯观察型监听器(只看不改)。
示例:
Subscription sub = ctx.eventBus().subscribe(SettingsChangedEvent.class, event -> {
if (event.changedKeys().contains("bgmVolume")) {
// 刷新本模组缓存的音量派生值
}
});post(E event)
<E extends Event> E post(E event);行为:在调用线程内当场完成全部分发,并返回传入的同一实例(便于读回 result())。null 直接返回 null;当前订阅数为 0 时立即返回(不遍历、不分配)。分发顺序为:优先级降序(HIGHEST 最先),同一优先级内按注册顺序(稳定可复现)。若事件可取消且当前结果非 DEFAULT,在通知下一个监听器之前就停止(短路);不可取消事件永远通知全部监听器。每个监听器独立 try/catch(Throwable),异常只记一条限频日志(同事件类型 5 秒内只记一次),不影响其它监听器,也不会冒泡到宿主循环。投递过程中调用 unsubscribe() 会立即生效(条目带 alive 标志,后续条目不再被调用)。
模组也可以 post:适合投递自己定义的 Event 子类,用于模组内或模组间通信。不要伪造宿主事实事件(例如自己 post 一个 GameStartEvent),那会误导其它订阅者。
示例:
final class MyModTick extends Event {
MyModTick() {
super(false);
}
}
MyModTick event = ctx.eventBus().post(new MyModTick());
if (event.isDecided()) {
// 本次分发中已有监听器改写过结果(对本事件无实际意义,仅演示读回)
}分发契约
- 同步投递:
post在调用线程内完成全部分发并返回,没有异步队列,也没有延迟投递。 - 匹配规则:
订阅类型.isInstance(事件);订阅父类可捕获子类。 - 顺序:优先级降序,同一优先级按注册顺序。
- 短路:仅当事件可取消、且结果不是
DEFAULT时停止通知后续监听器;不可取消事件永远通知全部监听器。 - 隔离:每个监听器独立
try/catch(Throwable),限频记录后继续。 - 零订阅零开销:无订阅时
post立即返回(不分配、不遍历)。 - 线程:
subscribe/unsubscribe任意线程可调用(内部加锁);post在调用线程同步分发,宿主在游戏线程 / 渲染线程投递,因此监听器回调也在该线程执行。监听器不得在回调里做阻塞 IO(网络、磁盘、future.get()等)。
订阅与退订的要点:
- 幂等:
Subscription#unsubscribe()重复调用无副作用;调用后该监听器不再收到事件。 - 强引用语义:总线对监听器持有强引用(不依赖 GC 回收),监听器对象不会被回收,订阅也不会自动失效。
- 模组归属:经
ModContext#eventBus()建立的订阅都登记在模组名下,模组停用 / 卸载时宿主调用EventBusImpl#removeMod(modId)一次性退订该模组的全部订阅。模组不需要自己记账,但主动退订可以让释放时机更可控。 - 临时订阅自己退订:只为某一段流程建立的订阅,应在不再需要时立即
unsubscribe()。
示例
import com.seecmd.mod.api.Mod;
import com.seecmd.mod.api.ModContext;
import com.seecmd.mod.api.ModInfo;
import com.seecmd.mod.api.event.EventBus;
import com.seecmd.mod.api.event.EventPriority;
import com.seecmd.mod.api.event.EventResult;
import com.seecmd.mod.api.event.SettingsChangedEvent;
import com.seecmd.mod.api.event.Subscription;
import com.seecmd.mod.api.event.TickEvent;
import com.seecmd.mod.api.event.gameplay.PauseEvent;
import java.util.List;
public final class ExampleMod implements Mod {
private double elapsedSeconds;
private Subscription pauseSubscription;
@Override
public ModInfo info() {
return ModInfo.of("example_mod", "示例模组", "1.0.0",
List.of("author"), "演示事件订阅与退订");
}
@Override
public void onInit(ModContext ctx) {
EventBus bus = ctx.eventBus();
// 每帧心跳:纯观察,放最低优先级,只看不改
bus.subscribe(TickEvent.Pre.class, EventPriority.LOWEST, event -> {
// 保持极轻:只做状态累加,不分配大对象、不做阻塞 IO
elapsedSeconds += event.deltaSeconds();
});
// 可取消事件:用最高优先级抢先决定是否跳过本次暂停
pauseSubscription = bus.subscribe(PauseEvent.class, EventPriority.HIGHEST, event -> {
if (shouldBlockPause()) {
event.setResult(EventResult.DENY);
}
});
// 临时订阅:拿到需要的信息后立即退订
Subscription once = bus.subscribe(SettingsChangedEvent.class, event -> {
if (event.changedKeys().contains("bgmVolume")) {
// 刷新本模组缓存的音量派生值
}
});
once.unsubscribe();
}
@Override
public void onUnload() {
// 幂等:重复调用无副作用;显式释放长生命周期订阅
pauseSubscription.unsubscribe();
}
private boolean shouldBlockPause() {
return false;
}
}完整签名
public interface EventBus {
<E extends Event> Subscription subscribe(Class<E> type, EventPriority priority, java.util.function.Consumer<E> listener);
<E extends Event> Subscription subscribe(Class<E> type, java.util.function.Consumer<E> listener);
<E extends Event> E post(E event);
}线程:订阅表读写加锁,post 无锁读快照并在调用线程同步分发。
稳定性:2.0 起冻结,2.x 内只增不破。