模块系统

把「一次加载要跑的初始化任务」按 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 时的身份语义。

使用流程

  1. 写若干 AbstractModuleThread 子类,实现 call() 返回 String;需要非默认阶段就加 @SpecifiedLoadStage(stage = LoadStage.INIT)。
  2. 写 AbstractModule 子类,实现 init(),在构造里把线程类 add() 到 module.manager。
  3. new ModuleHandler() → addModule(myModule)。
  4. 在自己的 @Mod 主类里对每个 FMLStateEvent 回调调 handler.startModules(event)。

相关条目