Repository Wiki
isHarryh/Ark-Pets

命令行参数与启动器用法

ArkPets 桌面端程序由两级启动器(DesktopLauncher 与 EmbeddedLauncher)引导启动,命令行参数通过声明式的 ArgPending 机制在引导早期被解析,用于控制日志级别、自定义配置文件、预加载本地库以及跳过启动器 GUI 直接进入桌宠 Core 应用。

目的与范围

本页面向使用者和二次开发者,完整讲解:

  • ArkPets 的启动链路:DesktopLauncher(总入口 / JavaFX 启动器引导)与 EmbeddedLauncher(libGDX Core 应用引导)各自的职责;
  • ArgPending 声明式参数解析机制的工作方式;
  • 两个启动器实际支持的全部命令行参数(--quiet / --warn / --info / --debug / --config / --load-lib / --direct-start)及其生效顺序与副作用;
  • 启动过程中的失败模式与边界情况。

以下相关主题有意留给了兄弟页面,本页仅点到为止:

  • 配置文件 ArkConfig 的字段含义与加载逻辑:见「配置文件详解」;
  • JavaFX 启动器 GUI(ArkHomeFX,模型选择与启动面板):见「启动器界面(ArkHomeFX)」;
  • libGDX Core 应用 ArkPets 的渲染与动画流程:见「Core 应用与动画系统」。

概述

ArkPets 采用「双启动器 + 声明式参数回调」的设计:

  1. DesktopLauncher 是整个程序的唯一入口。它初始化日志与临时目录后,正常情况下通过 Application.launch(ArkHomeFX.class, args) 拉起 JavaFX 启动器 GUI,由用户在其中选择模型并派生 Core 进程;若命令行带有 --direct-start,则跳过 GUI,直接把参数原样转发给 EmbeddedLauncher.main(args)。
  2. EmbeddedLauncher 是 Core 应用(libGDX)的独立引导器。它额外支持 --config <file> 指定自定义配置文件、--load-lib <path> 预加载本地动态库,随后完成遥测心跳、WindowSystem 初始化、Lwjgl3 窗口配置,并最终实例化 ArkPets 主程序。
  3. ArgPending 是贯穿两者的参数解析机制。它是一个抽象类,构造时传入匹配模式串与 args 数组,命中后立即以回调方式执行子类重写的 process(command, addition)。这种「构造即注册、构造即处理」的风格让参数处理逻辑内联在启动序列中,按代码书写顺序依次生效。

参数的实际字符串(如 --quiet)并不散落在各处,而是集中定义在 Const.LogConfig 常量类中,保证两个启动器行为一致。

架构

Loading diagram...

上图反映了源码中的真实依赖关系:

  • DesktopLauncher 的 main 是总入口,它只在「带 --direct-start」时才与 EmbeddedLauncher 发生直接调用关系;否则进入 ArkHomeFX(JavaFX 启动器 GUI),由 GUI 再派生 Core 进程(即再次以 EmbeddedLauncher 引导)。
  • 两个启动器都不使用独立的「参数解析框架」,而是各自在 main 中按顺序 new ArgPending(...);参数字符串常量来自 Const.LogConfig。
  • EmbeddedLauncher 是唯一消费 ArkConfig.getConfig(customConfig) 重载的地方(--config 参数),DesktopLauncher 只读取默认配置中的 logging_level。

启动链路详解

DesktopLauncher:程序总入口

DesktopLauncher 位于 desktop/src/cn/harryh/arkpets/DesktopLauncher.java,注释明确说明它是 "The entrance of the whole program, also the bootstrap for ArkHomeFX"。其 main 方法的完整处理顺序如下:

java
1public class DesktopLauncher { 2 public static void main(String[] args) { 3 // Disable assistive technologies 4 System.setProperty("javax.accessibility.assistive_technologies", ""); 5 ArgPending.argCache = args; 6 // Logger 7 Logger.initialize(LogConfig.logDesktopPath, LogConfig.logDesktopMaxKeep); 8 try { 9 Logger.setLevel(Objects.requireNonNull(ArkConfig.getConfig()).logging_level); 10 } catch (Exception ignored) { 11 } 12 new ArgPending(LogConfig.errorArg, args) { 13 protected void process(String command, String addition) { 14 Logger.setLevel(Logger.ERROR); 15 } 16 }; 17 // ... --warn / --info / --debug 同构处理 ... 18 Logger.info("System", "Entering the app of DesktopLauncher"); 19 Logger.info("System", "ArkPets version is " + appVersion); 20 21 // If requested to start the core app directly 22 new ArgPending("--direct-start", args) { 23 protected void process(String command, String addition) { 24 EmbeddedLauncher.main(args); 25 System.exit(0); 26 } 27 }; 28 29 SentryHelper.init(); 30 SentryHelper.beginDesktopSession(); 31 32 // Init temp folder 33 File temp = new File(PathConfig.tempDirPath); 34 if (!(temp.exists() || temp.mkdir())) { 35 Logger.error("System", "Failed to create the temporary directory."); 36 } 37 38 // Java FX bootstrap 39 Application.launch(ArkHomeFX.class, args); 40 Logger.info("System", "Exited from DesktopLauncher successfully"); 41 System.exit(0); 42 } 43}

Source: DesktopLauncher.java

关键点逐条解读:

  1. 禁用辅助技术(javax.accessibility.assistive_technologies 置空):避免 Java AWT 在无显示/受限环境下的额外副作用。
  2. ArgPending.argCache = args:把原始参数数组静态缓存,供程序后续其他组件读取。
  3. 日志先于一切:日志文件路径与保留数量来自 Const.LogConfig(logDesktopPath / logDesktopMaxKeep);初始日志级别取自配置文件 ArkConfig.getConfig().logging_level,读取失败时被 catch (Exception ignored) 吞掉,保持默认级别继续启动。
  4. 四个日志级别参数按固定顺序注册:--quiet → --warn → --info → --debug(见下文参数表),后注册的命中会覆盖先注册的。
  5. --direct-start 在 Sentry 初始化之前处理:即直接启动路径不会初始化 Sentry 会话,也不会走 JavaFX;转发 EmbeddedLauncher.main(args) 是同步阻塞调用,返回后立即 System.exit(0)。
  6. 临时目录初始化:PathConfig.tempDirPath 不存在则创建,失败仅记录 error 日志,不中断启动。

EmbeddedLauncher:Core 应用独立引导

EmbeddedLauncher 位于 desktop/src/cn/harryh/arkpets/EmbeddedLauncher.java,注释为 "The bootstrap for ArkPets the libGDX app"。它比 DesktopLauncher 多承担三件事:解析 --config、解析 --load-lib、以及完整的 libGDX 运行时装配。

java
1public class EmbeddedLauncher { 2 public static File customConfig; 3 4 // Please note that on macOS your application needs to be started with the -XstartOnFirstThread JVM argument 5 6 public static void main(String[] args) { 7 // Disable assistive technologies 8 System.setProperty("javax.accessibility.assistive_technologies", ""); 9 ArgPending.argCache = args; 10 // Config 11 new ArgPending("--config", args) { 12 protected void process(String command, String addition) { 13 customConfig = new File(addition); 14 } 15 }; 16 ArkConfig appConfig = Objects.requireNonNull(customConfig == null ? ArkConfig.getConfig() : ArkConfig.getConfig(customConfig)); 17 // Logger 18 Logger.initialize(LogConfig.logCorePath, LogConfig.logCoreMaxKeep); 19 ...

Source: EmbeddedLauncher.java

注意源码中类顶部的显式提醒:macOS 下必须附带 JVM 参数 -XstartOnFirstThread 启动,这是 libGDX/LWJGL 对主线程的要求,与本文的命令行参数属于不同层级(JVM 参数 vs 程序参数),但使用者常需同时配置。

日志级别参数之后是 --load-lib,用于在 Core 初始化前预加载本地动态库:

java
1new ArgPending("--load-lib", args) { 2 @Override 3 protected void process(String command, String addition) { 4 Logger.info("System", "Loading the specified library \"" + addition + "\""); 5 try { 6 System.load(addition); 7 } catch (UnsatisfiedLinkError e) { 8 Logger.error("System", "Failed to load the specified library, details see below.", e); 9 } 10 } 11};

Source: EmbeddedLauncher.java

System.load(addition) 接受绝对路径(与 System.loadLibrary 的按库名搜索不同)。加载失败仅捕获 UnsatisfiedLinkError 并记录日志,不中断启动——设计意图是把该参数定位为「可选的修复/兼容手段」,而不是硬依赖。

参数解析完成后,EmbeddedLauncher 依次完成:临时目录初始化 → 遥测心跳(仅当 appConfig.enable_telemetry 为真时创建 CorePerformanceSampler)→ 写入配置快照 WAL 记录 → WindowSystem.init() → Lwjgl3 窗口配置 → 实例化 ArkPets:

java
1try { 2 WindowSystem.init(); 3 Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); 4 // Configure ANGLE 5 Logger.info("System", "Using ANGLE renderer"); 6 config.setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20, 2, 0); 7 Configuration.OPENGL_EXPLICIT_INIT.set(true); 8 // Configure FPS 9 config.setForegroundFPS(fpsDefault); 10 config.setIdleFPS(fpsDefault); 11 // Configure window layout 12 config.setDecorated(false); 13 config.setResizable(false); 14 config.setWindowedMode(coreWidthDefault, coreHeightDefault); 15 config.setWindowPosition(0, 0); 16 // Configure window title 17 final String TITLE = coreTitleManager.getIdleTitle(); 18 config.setTitle(TITLE); 19 // Configure window display 20 config.setInitialVisible(true); 21 config.setTransparentFramebuffer(true); 22 config.setInitialBackgroundColor(Color.CLEAR); 23 ... 24 // Instantiate the App 25 Lwjgl3Application app = new Lwjgl3Application(new ArkPets(TITLE, appConfig, performanceSampler), config); 26} catch (Exception e) { 27 WindowSystem.free(); 28 Logger.error("System", "A fatal error occurs in the runtime of Lwjgl3Application, details see below.", e); 29 session.crash(e); 30 System.exit(-1); 31} 32WindowSystem.free(); 33Logger.info("System", "Exited from EmbeddedLauncher successfully"); 34session.finish(); 35System.exit(0);

Source: EmbeddedLauncher.java

这一段是理解「启动器用法」边界的证据:Core 窗口被固定为无装饰、不可缩放、透明帧缓冲(setTransparentFramebuffer(true) + Color.CLEAR 初始背景),FPS 由 Const 中的 fpsDefault 决定,渲染后端固定为 ANGLE(OpenGL ES 2.0 仿真)。这些都不由命令行控制,而由 Const 常量与 ArkConfig 配置共同决定——命令行的职责被刻意收敛为「引导期开关」。

ArgPending:声明式参数解析机制

ArgPending 位于 desktop/src/cn/harryh/arkpets/utils/ArgPending.java,是理解所有命令行参数行为的核心:

java
1abstract public class ArgPending { 2 public static String[] argCache = new String[0]; 3 private static final String argPrefix = "-"; 4 private final String pattern; 5 6 /** Initializes an Argument Pending instance. 7 * @param pattern The specified argument string to be match. 8 ...

Source: ArgPending.java

从源码与两个启动器的使用方式可以确认其契约:

  • 构造即处理:所有使用点都是 new ArgPending("模式串", args) { protected void process(...) {...} },构造完成后副作用立即发生(例如 --direct-start 分支中 EmbeddedLauncher.main(args) 在构造语句内联执行),没有任何显式的 run/parse 调用。解析工作在构造器内完成。
  • 参数前缀约束:argPrefix 常量为 "-",即只有以 - 开头的项才被视为参数。
  • 回调签名:protected void process(String command, String addition)——command 为命中的参数本身,addition 为其附加值(例如 --config <file> 中的文件路径、--load-lib <path> 中的库路径)。
  • 静态缓存:argCache 保存原始 args,两个启动器都在 main 的第一行就赋值,使其后的任意 ArgPending 实例乃至其他组件都能拿到完整参数。

这种「抽象类 + 匿名内部类回调」的风格(而非函数式接口)是项目兼容旧 Java 语法习惯的选择:每个参数的处理逻辑以声明形式内联在启动序列中,阅读 main 即可按顺序看到全部参数的生效次序。

命令行启动流程

Loading diagram...

流程图的每个分支均对应源码中的真实调用点:

  • 分支一(--direct-start):DesktopLauncher 在 Sentry/临时目录/JavaFX 之前就同步转发并退出,因此该路径完全绕过启动器 GUI 与 Sentry。
  • 分支二(默认):Application.launch(ArkHomeFX.class, args) 把参数继续传给 JavaFX 启动器;GUI 派生 Core 进程时再以 EmbeddedLauncher 引导,即两条路径最终汇合到同一处 Core 装配代码。

参数参考

以下参数均已从源码逐一核实。注意「生效顺序」即代码中 new ArgPending(...) 的书写顺序——同名类参数(四个日志级别参数)互相覆盖,后命中者生效。

参数接受附加值支持的启动器生效顺序作用
--quiet(Const.LogConfig.errorArg)否DesktopLauncher / EmbeddedLauncher1将日志级别设为 ERROR,静默模式
--warn(Const.LogConfig.warnArg)否DesktopLauncher / EmbeddedLauncher2将日志级别设为 WARN
--info(Const.LogConfig.infoArg)否DesktopLauncher / EmbeddedLauncher3将日志级别设为 INFO
--debug(Const.LogConfig.debugArg)否DesktopLauncher / EmbeddedLauncher4设为 DEBUG;在 EmbeddedLauncher 中额外置 isDebugEnabled = true 并打印调试日志
--direct-start否仅 DesktopLauncher5(Sentry/临时目录/JavaFX 之前)跳过 GUI,直接以全部 args 调用 EmbeddedLauncher.main
--config是(文件路径)仅 EmbeddedLauncher0(先于日志级别参数之外的一切)指定自定义配置文件;随后 ArkConfig.getConfig(customConfig)
--load-lib是(库绝对路径)仅 EmbeddedLauncher5System.load(addition) 预加载本地动态库

参数字符串常量集中定义于 Const.LogConfig,保证两个启动器对同一参数行为一致:

java
1public static final String errorArg = "--quiet"; 2public static final String warnArg = "--warn"; 3public static final String infoArg = "--info"; 4public static final String debugArg = "--debug";

Source: Const.java

典型调用示例

bash
1# 默认:启动 JavaFX 启动器 GUI 2java -jar ArkPets.jar 3 4# 指定配置文件并直接启动 Core(跳过 GUI),开启调试日志 5java -jar ArkPets.jar --direct-start --config /path/to/custom-config.json --debug 6 7# 修复加载本地库问题:预加载指定的动态库后启动 8java -jar ArkPets.jar --direct-start --load-lib /path/to/libcustom.so 9 10# 静默模式启动器 GUI(仅 ERROR 级别日志) 11java -jar ArkPets.jar --quiet 12 13# macOS 必须附加 JVM 参数(注意这是 JVM 参数,不是程序参数) 14java -XstartOnFirstThread -jar ArkPets.jar

(以上为基于源码参数契约整理的调用方式,bash 示例为用法说明,非仓库内文件。)

失败模式、边界情况与并发

  • 配置加载失败:ArkConfig.getConfig() 返回 null 时,两个启动器都通过 Objects.requireNonNull 或 catch (Exception ignored) 处理。DesktopLauncher 的日志级别读取失败被静默忽略并继续启动;EmbeddedLauncher 的 requireNonNull(...) 若因自定义 --config 文件无效导致 getConfig(customConfig) 抛出异常/返回 null,则启动直接失败——即 --config 指向的文件必须有效。
  • --load-lib 失败不致命:仅捕获 UnsatisfiedLinkError 记录 error 日志后继续,属于容错型设计;若加载的库在后续真正被使用时缺失,错误会在更晚阶段暴露。
  • Core 运行时致命错误:Lwjgl3Application 构造/运行抛出的任何 Exception 会触发 WindowSystem.free() 清理、Logger.error 记录、session.crash(e) 遥测标记,最后 System.exit(-1) 非零退出码;正常结束则 session.finish() + System.exit(0)。
  • GLFW 错误回调:EmbeddedLauncher 注册了 GLFWErrorCallback,任何非 GLFW_NO_ERROR 的错误码都会带描述记入日志,为窗口层故障提供定位手段。
  • 临时目录创建失败:两个启动器都只记录 Failed to create the temporary directory. 的 error 日志,不终止启动(可能影响依赖临时目录的后续功能)。
  • --direct-start 的副作用边界:该路径在 SentryHelper.init() 之前执行,因此直接启动的 Core 进程没有桌面端 Sentry 会话;同时它是同步调用后立即 System.exit(0),不会回退到 GUI 路径。
  • 并发模型:命令行解析全部发生在 main 单线程内,按 new ArgPending 的书写顺序串行生效,不存在参数竞态;ArgPending.argCache 是静态字段,由首个进入的 main 独占写入。

运维与扩展要点

  • 新增参数的扩展点:在对应启动器的 main 中新增一个 new ArgPending("模式", args) { protected void process(...) } 匿名实现即可;若参数字符串需要被两个启动器共享,应加入 Const.LogConfig 之类的 Const 常量组,而不是字面量重复。
  • 日志文件位置:Desktop 与 Core 各自独立初始化日志(LogConfig.logDesktopPath / LogConfig.logCorePath 及各自保留数量 logDesktopMaxKeep / logCoreMaxKeep),排障时应区分查看启动器日志与 Core 日志。
  • 诊断优先组合:--debug(Core 侧还会置 isDebugEnabled = true 开启调试特性)+ 对应 --config 复现用户环境,是排查配置相关问题的推荐组合。
  • 性能注意:--quiet 等级别参数只影响日志输出量,不改变 FPS/渲染;FPS 由 Const.fpsDefault 决定,渲染后端固定 ANGLE GLES20,均不受命令行控制。

相关链接

  • DesktopLauncher.java — 程序总入口与 --direct-start 转发逻辑
  • EmbeddedLauncher.java — Core 引导、--config / --load-lib 与 Lwjgl3 装配
  • ArgPending.java — 声明式参数解析机制
  • Const.java — 参数字符串常量与日志/路径配置
  • 配置文件字段与加载逻辑:参见「配置文件详解」页
  • 启动器 GUI(模型选择、进程派生):参见「启动器界面(ArkHomeFX)」页