Mod 生命周期
Mod 加载过程中,各种生命周期事件会在各 Mod 专属的事件总线上触发。这些事件期间会执行许多操作,例如注册对象、准备数据生成或与其他 Mod 通信。
原创说明图,依据 Forge 1.21.x 官方文档 绘制。
事件监听器应使用 @EventBusSubscriber(bus = Bus.MOD) 注册,或在 Mod 构造函数中注册:
@Mod.EventBusSubscriber(modid = "mymod", bus = Mod.EventBusSubscriber.Bus.MOD)
public class MyModEventSubscriber {
@SubscribeEvent
static void onCommonSetup(FMLCommonSetupEvent event) { ... }
}
@Mod("mymod")
public class MyMod {
public MyMod(FMLModLoadingContext context) {
context.getModEventBus().addListener(this::onCommonSetup);
}
private void onCommonSetup(FMLCommonSetupEvent event) { ... }
}
警告:大多数生命周期事件并行触发,所有 Mod 会同时收到相同的事件。Mod 必须注意线程安全,例如调用其他 Mod 的 API 或访问原版系统时。通过
ParallelDispatchEvent#enqueueWork将代码推迟到稍后执行。
注册表事件
注册表事件在 Mod 实例构造后触发,共有三个:NewRegistryEvent、DataPackRegistryEvent$NewRegistry 和 RegisterEvent。它们在 Mod 加载期间同步触发。
NewRegistryEvent 允许开发者使用 RegistryBuilder 类注册自定义注册表。
DataPackRegistryEvent$NewRegistry 允许开发者提供用于从 JSON 编码和解码对象的 Codec,以注册自定义数据包注册表。
RegisterEvent 用于向注册表注册对象,每个注册表都会触发一次。
注意:尽可能优先使用 DeferredRegister,而不是直接使用注册表事件。DeferredRegister 会代为处理时机,不易出错。
数据生成
如果游戏配置为运行数据生成器,GatherDataEvent 将是最后触发的事件。此事件用于将 Mod 的数据提供器注册到相应的数据生成器,也以同步方式触发。
通用设置
FMLCommonSetupEvent 用于物理客户端和物理服务端共有的操作,例如注册能力。
端专属设置
端专属设置事件在相应的物理端触发:FMLClientSetupEvent 在物理客户端触发,FMLDedicatedServerSetupEvent 在独立服务器触发。物理端专属的初始化应在此进行,例如注册客户端按键绑定。
InterModComms
此阶段可以向其他 Mod 发送消息,实现跨 Mod 兼容。共有两个事件:InterModEnqueueEvent 和 InterModProcessEvent。
InterModComms 类负责保存 Mod 的消息。由于其底层使用 ConcurrentMap,可以在线程并行的生命周期事件期间安全地调用它的方法。
在 InterModEnqueueEvent 期间,使用 InterModComms#sendTo 向不同 Mod 发送消息。这些方法接收目标 Mod ID、消息数据关联的键,以及保存消息数据的 supplier。也可以指定消息发送方,默认为调用者的 Mod ID。
随后,在 InterModProcessEvent 期间,使用 InterModComms#getMessages 获取所有已收到消息的流。传入的 Mod ID 几乎总是调用该方法的 Mod 自身 ID。还可以指定谓词来过滤消息键。方法返回 IMCMessage 流,每条消息包含数据发送方、接收方、数据键和 supplier 提供的数据本身。
注意:还有两个生命周期事件:
FMLConstructModEvent在 Mod 实例构造完成后立即触发,但早于RegisterEvent;FMLLoadCompleteEvent在InterModComms事件之后触发,表示 Mod 加载过程完成。
原文:Mod Lifecycle。
译注:原文示例使用
FMLModLoadingContext获取事件总线,而文档其他示例使用FMLJavaModLoadingContext;请核对目标版本的 API,示例尚未编译验证。