模块系统
把「一次加载要跑的初始化任务」按 FML 加载阶段分组,并在每个阶段内用固定线程池并行执行的框架。WanionLib 自己不实例化 ModuleHandler(全仓库除定义处外零引用),整套系统由引用本库的 mod 自行接线。
基本信息
| 属性 | 值 |
|---|---|
| 包 | wanion.lib.module |
| 类数量 | 6(AbstractModule、AbstractModule.Manager、AbstractModuleThread、LoadStage、SpecifiedLoadStage、ModuleHandler) |
| 并行度 | Runtime.getRuntime().availableProcessors()(newFixedThreadPool) |
| 任务返回类型 | String(经 Callable<String>) |
| 默认阶段 | LoadStage.POST_INIT |
阶段枚举 LoadStage
public enum LoadStage {
PRE_INIT(FMLPreInitializationEvent.class),
INIT(FMLInitializationEvent.class),
POST_INIT(FMLPostInitializationEvent.class),
LOAD_COMPLETE(FMLLoadCompleteEvent.class);
public final Class<? extends FMLStateEvent> stage;
...
}
| 枚举值 | 对应 FML 事件类 | 默认用途 |
|---|---|---|
PRE_INIT |
FMLPreInitializationEvent |
未标注 @SpecifiedLoadStage 时不会落在这里 |
INIT |
FMLInitializationEvent |
同上 |
POST_INIT |
FMLPostInitializationEvent |
未标注时的默认阶段 |
LOAD_COMPLETE |
FMLLoadCompleteEvent |
同上 |
| 成员 | 签名 | 行为 |
|---|---|---|
| 阶段事件类 | public final Class<? extends FMLStateEvent> stage |
枚举构造时固定 |
| 反查 | public static LoadStage getStage(@Nonnull Class<? extends FMLStateEvent> stage) |
线性遍历 values() 比对 loadStage.stage == stage(引用相等);找不到返回 null |
阶段标注 @SpecifiedLoadStage
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpecifiedLoadStage {
@Nonnull
LoadStage stage();
}
RUNTIME 保留、TYPE 目标、stage() 标注 @Nonnull。写在 AbstractModuleThread 子类上以决定它跑在哪个阶段。
AbstractModule(抽象类)
| 成员 | 签名 | 说明 |
|---|---|---|
| 管理器 | protected final Manager manager |
构造时由 moduleThreadInstantiator 自行 new Manager(...) |
| 名称 | private final String moduleName |
拼进日志文案 |
| 构造 | protected AbstractModule(@Nonnull String moduleName, @Nonnull Instantiator<AbstractModuleThread> moduleThreadInstantiator) |
|
| 初始化 | protected abstract void init() |
由 ModuleHandler 在 manager.isEmpty() 为真时恰好调用一次 |
| 启动 | final void start(@Nonnull LoadStage loadStage, @Nonnull Manager manager) |
包内可见,包外模块无法直接调 |
start() 流程:取 manager.getInstances(loadStage),为空则立即 return;否则建固定线程池 → invokeAll(threadList) → 逐个 logger.info(future.get()) 打印各任务返回的 String → 打印汇总行 "All " + threadList.size() + " " + moduleName + "s took " + took/1000000 + "ms to finish. at load stage " + loadStage.name()。InterruptedException | ExecutionException 走 logger.error("Something really bad happened on " + moduleName + " at load stage " + loadStage.name()) + printStackTrace()。
AbstractModule.Manager(AbstractModule 的 public 静态嵌套类)
| 成员 | 签名 | 行为 |
|---|---|---|
| 阶段表 | private final Map<LoadStage, Set<Class<? extends AbstractModuleThread>>> loadStageMap |
EnumMap,构造时为每个 LoadStage 预填 LinkedHashSet |
| 实例化器 | private final Instantiator<AbstractModuleThread> instantiator |
由外部模块注入 |
| 构造 | public Manager(@Nonnull Instantiator<AbstractModuleThread> instantiator) |
|
| 注册 | public boolean add(@Nonnull Class<? extends AbstractModuleThread> moduleThreadClass) |
有 @SpecifiedLoadStage 取其 stage(),否则 POST_INIT;返回 !classSet.contains(c) && classSet.add(c) |
| 全空判定 | public boolean isEmpty() |
loadStageMap.values().stream().allMatch(Set::isEmpty) |
| 单阶段判定 | public boolean isEmpty(LoadStage loadStage) |
|
| 取实例 | public List<AbstractModuleThread> getInstances(LoadStage loadStage) |
按 LinkedHashSet 顺序逐个 instantiator.instantiate(t);`InstantiationException |
因为
loadStageMap用LinkedHashSet,同一阶段内任务按注册顺序串行入列表;invokeAll保序返回Future,所以日志输出顺序 = 注册顺序,实际执行仍并行。
AbstractModuleThread(抽象类)
public abstract class AbstractModuleThread implements Callable<String> {
protected final String threadName;
public AbstractModuleThread(String threadName, String moduleName) {
this.threadName = threadName + " " + moduleName + ": ";
}
}
继承 java.util.concurrent.Callable<String>,因此子类实现 call() 并必须返回 String(该返回值会被 start() 直接打进日志)。threadName 是拼好的日志前缀(含结尾 ": "),供子类写日志时用。类本身不注册到任何事件总线。
ModuleHandler(final)
public final class ModuleHandler {
private final Set<Class<? extends AbstractModule>> moduleClasses = new HashSet<>();
private final Map<AbstractModule, AbstractModule.Manager> modules = new THashMap<>();
}
| 成员 | 签名 | 行为 |
|---|---|---|
| 注册模块 | public void addModule(AbstractModule module) |
module != null 时以 module.getClass() 去重;modules.put(module, null) —— 初始 value 故意为 null,Manager 延迟到首次启动时才取 module.manager |
| 启动 | public void startModules(FMLStateEvent event) |
LoadStage.getStage(event.getClass())(event == null 时 loadStage 直接为 null);modules 为空或阶段为 null 时直接 return;逐个:value 为 null 则 setValue(module.manager) → manager.isEmpty() 则 module.init() → !manager.isEmpty(loadStage) 则 module.start(loadStage, manager) |
modules 用 Trove 的 THashMap(非 IdentityHashMap),依赖 AbstractModule 未覆写 equals/hashCode 时的身份语义。
使用流程
- 写若干
AbstractModuleThread子类,实现call()返回String;需要非默认阶段就加@SpecifiedLoadStage(stage = LoadStage.INIT)。 - 写
AbstractModule子类,实现init(),在构造里把线程类add()到module.manager。 new ModuleHandler()→addModule(myModule)。- 在自己的
@Mod主类里对每个FMLStateEvent回调调handler.startModules(event)。