SloppyDepLoader

可选依赖的自动下载器。与 CodeChickenCore 的 DepLoader 不同,依赖找不到时不会退出游戏——名字里的 “sloppy” 就是这个意思。类注释还写明它本身"非常不原创,主要抄自 CodeChickenCore,而后者又主要抄自 FML",致谢 ChickenBones 和 cpw。

基本信息

属性 值
类型 共享模块 / 依赖下载
命名空间常量 SloppyDepLoader.NS = "SloppyDepLoader"
日志名 "SloppyDepLoader"
配置文件 有:config/sloppydeploader.cfg,见 配置文件
触发 pre-init,由 MCLib 核心 在"本副本最新"时调用 preInit()

公共 API

成员 签名 说明
SloppyDepLoaderAPI.addDependenciesForMod public static void addDependenciesForMod(String modid, SloppyDependency... sloppyDependencies) 把依赖序列化成 ; 分隔的字符串存进共享 map。可在 pre-init 之前的任意时候调用(通常放在 static 块里)
SloppyDepLoader.preInit public void preInit() 若无人使用直接返回;否则重载配置,enabled 为真时逐条解析并 load()

preInit 内部按 , 再切 5 段构造 SloppyDependency(用 Arrays.copyOf(k.split(","), 5),不足自动补 null)。

SloppyDependency

包路径 makamys.mclib.sloppydeploader。五个参数全为包私有字段。

参数 字段 说明
仓库 URL repo 依赖 jar 的发布地址
文件名 filename 需要解析出版本号的文件名
探测类 testClass 存在则说明依赖已装,跳过下载
开发版文件名 dev(Optional) 非混淆环境(可读到 net.minecraft.world.World 的字节码)时替换 filename 使用
文件名正则 pattern(Optional) 缺省用 (\w+).*?([\d\.]+)[-\w]*\.[^\d]+
构造函数 / 方法 说明
SloppyDependency(String[] params) 取 5 段
SloppyDependency(String repo, String filename, String testClass, String dev, String pattern) 完整形式;空串转 Optional.empty()
SloppyDependency(String repo, String filename, String testClass) dev/pattern 为 null
serializeToString() String.join(",", {repo, filename, testClass, dev, pattern}),空 Optional 写成空串
toString() SloppyDependency{<filename> @ <repo>}

版本号解析

SloppyDepLoader.VersionedFile 持有 public final Pattern pattern、public final String filename、public final ComparableVersion version、public final String name。构造时用正则匹配文件名,捕获组 1 为名字、组 2 为版本;匹配不上则 name 和 version 都为 null,matches() 返回 false。

文件名格式非法时 addSloppyDep 抛 RuntimeException("Invalid filename format for dependency: " + filename)。自定义正则语法错误时 LOGGER.error("Invalid filename pattern: " + ...) 并退回缺省正则。

版本合并规则

DepLoadInst.addDep 调 mergeNew,同名依赖取版本更高者,coreLib 标志取两者或。已存在更新版本时 LOGGER.trace("Not adding dependency ... because a newer version of it has been added already")。

checkExisting 扫描两个目录:mods/ 和 mods/<mcVer>/(后者在构造时 mkdirs)。

情况 行为
在 mods/ 找到但搬移失败 deleteMod(f) 删除后继续
目录版本 cmp < 0(比请求的旧) LOGGER.info("Deleted old version ..."),删除并触发下载
目录版本 cmp > 0(比请求的新) LOGGER.warn("Warning: version of <name>, <版本> is newer than request <版本>"),直接采用现有文件
相等 视为已满足

下载落到 mods/<mcVer>/ 下。

已有依赖的探测

addSloppyDep 在加入队列前先判断:

  1. 用 LaunchClassLoader.getClassBytes("net.minecraft.world.World") 是否为 null 判断是否混淆环境;非混淆时才启用 dev 文件名。
  2. 用 SloppyDepLoader.class.getResource("/" + testClass.replace('.', '/') + ".class") 探测依赖是否已存在;存在则 LOGGER.trace("Skipping dependency ... because test class ... is present") 直接返回。

下载

项 值
下载缓冲 ByteBuffer.allocateDirect(1 << 23)(8 MiB,静态共享)
超时 连接与读取各 5000 ms
User-Agent 源码写成 "" + owner + " Downloader",owner 是常量 "Sloppy DepLoader",运行时拼成 Sloppy DepLoader Downloader
大小上限 超过缓冲区容量直接抛异常并判定"下载无效"
失败处理 删除半成品文件并 LOGGER.error("A download error occured downloading ..."),不抛异常、不弹窗、不退出
进度显示 用 IDownloadDisplay 接口;实际代码里恒用 DummyDownloader(全空实现),即静默下载

IDownloadDisplay 接口方法:resetProgress(int sizeGuess)、setPokeThread(Thread)、updateProgress(int fullLength)、shouldStopIt()、updateProgressString(String, Object...)、makeDialog()、showErrorDialog(String name, String url)。DummyDownloader 是其全空实现。

并发

SloppyDepDownloadManager(包路径同 sloppydeploader):

成员 值
线程池 new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, workQueue),核心 0、上限 1
队列 LinkedBlockingQueue<Runnable>
enqueueDownload(Supplier task) CompletableFuture.supplyAsync(task, executor) 并收集到 futures
allDone() 所有 future 都 isDone()
getDownloadedList() 未全部完成时返回空 list;否则逐个 get(),异常映射为 null 后过滤掉

下载在独立线程池异步跑,pre-init 不阻塞;重启提示在 主菜单打开时 补。

内部类

类型 说明
SloppyDepLoader.DepLoadInst 依赖集合与目录扫描的主体,构造时 MCLib.FML_MASTER.register(this)
SloppyDepLoader.Dependency 字段 public String url、public VersionedFile file、public String existing、public boolean coreLib、public boolean downloaded
SloppyDepLoader.VersionedFile 见上文
SloppyDepLoader.IDownloadDisplay / DummyDownloader 进度显示抽象
SloppyDepLoader.DepLoadInst.SloppyDepDownloadTask 内部类,实现 Supplier<String>,返回下载成功的文件名

coreLib 标志在本 fork 中实际恒为 false(addSloppyDep 里写死 boolean coreLib = false;),其注释描述的"coremod 必需依赖需立即加入 classpath"路径未实现。

使用示例(源码内 sloppydeploader/test/SDLTest.java)

示例 mod 在 static 块里先 SloppyDepLoaderAPI.addDependenciesForMod(...) 声明两个依赖(一个真实可下载的 jar、一个故意不存在的),再调 MCLib.init()。顺序很关键:依赖声明必须早于 pre-init。

相关条目