并行 mod 发现
基本信息
| 属性 | 值 |
|---|---|
| 类 | com.mitchej123.jarjar.discovery.ParallellModDiscoverer |
| 父类 | cpw.mods.fml.common.discovery.ModDiscoverer |
| 线程池 | DiscoveryPool(共享 ForkJoinPool) |
| 任务类型 | ModScanTask extends RecursiveTask<ModCandidateV2> |
| 来源 | Fabric ModDiscoverer (Apache 2.0 / LGPL v3) |
| 取代 | 原生串行 ModDiscoverer |
ParallellModDiscoverer 是 JarJar 提速 Mod 发现的核心:从 Fabric-loader 移植并适配 FML。把"扫文件 → 解析 manifest → 抽 Jar-in-Jar → ASM 扫描所有 .class"全部异步化,主线程只在 future.get() 处收割。
构造函数
public ParallellModDiscoverer(File modsDir, ModClassLoader modClassLoader) {
this.mcDir = modsDir;
candidateFinders.add(new ClasspathModCandidateFinder(modClassLoader));
candidateFinders.add(new DirectoryModCandidateFinder(
modsDir.toPath(),
ModListHelper.additionalMods.values().toArray(new File[0]),
true));
File versionSpecificModsDir = new File(modsDir, FMLInjectionData.mccversion);
if (versionSpecificModsDir.isDirectory()) {
candidateFinders.add(new DirectoryModCandidateFinder(versionSpecificModsDir.toPath(), true));
}
}
三类 finder:
ClasspathModCandidateFinder- Minecraft jar 与已加载库DirectoryModCandidateFinder-<mc>/mods/+ModListHelper.additionalModsDirectoryModCandidateFinder-<mc>/<mccversion>/(Forge 1.7.10 时代给特定 MC 版本提供的子目录)
discoverMods 流程
ForkJoinPool pool = DiscoveryPool.get();
Set<Path> processedPaths = new HashSet<>(); // 抑制同 path 重复入队
List<Future<ModCandidateV2>> futures = new ArrayList<>();
ModCandidateFinder.ModCandidateConsumer taskSubmitter = (paths, requiresRemap, isMinecraft, isClasspath) -> {
List<Path> pendingPaths = new ArrayList<>(paths.size());
for (Path path : paths) {
assert path.equals(LoaderUtil.normalizeExistingPath(path));
if (processedPaths.add(path)) pendingPaths.add(path);
}
if (!pendingPaths.isEmpty()) futures.add(pool.submit(new ModScanTask(pendingPaths, requiresRemap, isMinecraft, isClasspath)));
};
for (ModCandidateFinder finder : candidateFinders) {
finder.findCandidates(taskSubmitter);
}
主线程遍历 finder,每个 finder 提交的 path 经 pathNormalizationCache 归一化(path.toRealPath())后入队。
收割
int timeout = Integer.getInteger(JARJAR_DEBUG_DISCOVERY_TIMEOUT, -1);
if (timeout <= 0) timeout = Integer.MAX_VALUE;
for (Future<ModCandidateV2> future : futures) {
ModCandidateV2 candidate = future.get(timeout, TimeUnit.SECONDS);
if (candidate != null) candidates.add(candidate);
}
// 然后处理嵌套 mod 的 future
超时抛 RuntimeException,包含 jarjar.debug.discoveryTimeout JVM 属性提示。
ModScanTask
每个 task 扫描单个 jar:
try (JarFile jar = new JarFile(modFile))JarUtil.examineJarCandidate(jar, modFile, null, false, isMinecraft, isClasspath):读 manifest / mcmod.info / coremod / AT / nested jars- 若
modCandidate.hasNestedJars():用jijDedupMap去重 fork;首个任务localTask不 fork,留本线程 - 枚举
jar.entries():跳过__MACOSX/META-INF/versions//module-info.class/$.class - 对每个
.class用ASMModParserV2解析(new ASMModParserV2(classBytes, entryName)) - 通过
ModContainerFactoryV2.buildV2(...)转ModContainerWrapper,加入wrappedModList+modList - 把所有
ASMModParserV2加入asmDataCollection,等sendToTable时统一发送给 ASMDataTable
生命周期收尾
modCandidates.addAll(ret);
modCandidates.sort(Comparator.comparing(candidate -> candidate.getModContainer().getName()));
for (ModCandidateV2 c : modCandidates) {
c.releaseNestedJars(); // 释放 NestedJar 内存引用
}
candidateFinders.clear();
jijDedupMap.clear();
nestedModInitDatas.clear();
LoaderV2.identifyMods 在 Discoverer.getModCandidates() 之后调用 releaseParsedData(),丢弃 ASM 字节缓存与 MetadataCollection 引用。
超时
throw new RuntimeException(String.format("Mod discovery took too long! ... The timeout can be changed with the system property %s (-D%<s=<desired timeout in seconds>).",
timeout, JARJAR_DEBUG_DISCOVERY_TIMEOUT));
相关条目
- DiscoveryPool 共享线程池 - 提供 ForkJoinPool
- ModCandidateV2 数据模型 - 并行扫描的返回类型
- JarUtil 嵌套抽取与版本识别 -
examineJarCandidate是扫描核心 - LoaderV2 - 消费 ParallellModDiscoverer 的输出
- 系统属性开关 -
jarjar.debug.discoveryTimeout