命令行参数与启动器用法
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 采用「双启动器 + 声明式参数回调」的设计:
DesktopLauncher是整个程序的唯一入口。它初始化日志与临时目录后,正常情况下通过Application.launch(ArkHomeFX.class, args)拉起 JavaFX 启动器 GUI,由用户在其中选择模型并派生 Core 进程;若命令行带有--direct-start,则跳过 GUI,直接把参数原样转发给EmbeddedLauncher.main(args)。EmbeddedLauncher是 Core 应用(libGDX)的独立引导器。它额外支持--config <file>指定自定义配置文件、--load-lib <path>预加载本地动态库,随后完成遥测心跳、WindowSystem初始化、Lwjgl3 窗口配置,并最终实例化ArkPets主程序。ArgPending是贯穿两者的参数解析机制。它是一个抽象类,构造时传入匹配模式串与args数组,命中后立即以回调方式执行子类重写的process(command, addition)。这种「构造即注册、构造即处理」的风格让参数处理逻辑内联在启动序列中,按代码书写顺序依次生效。
参数的实际字符串(如 --quiet)并不散落在各处,而是集中定义在 Const.LogConfig 常量类中,保证两个启动器行为一致。
架构
上图反映了源码中的真实依赖关系:
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 方法的完整处理顺序如下:
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
关键点逐条解读:
- 禁用辅助技术(
javax.accessibility.assistive_technologies置空):避免 Java AWT 在无显示/受限环境下的额外副作用。 ArgPending.argCache = args:把原始参数数组静态缓存,供程序后续其他组件读取。- 日志先于一切:日志文件路径与保留数量来自
Const.LogConfig(logDesktopPath/logDesktopMaxKeep);初始日志级别取自配置文件ArkConfig.getConfig().logging_level,读取失败时被catch (Exception ignored)吞掉,保持默认级别继续启动。 - 四个日志级别参数按固定顺序注册:
--quiet→--warn→--info→--debug(见下文参数表),后注册的命中会覆盖先注册的。 --direct-start在 Sentry 初始化之前处理:即直接启动路径不会初始化 Sentry 会话,也不会走 JavaFX;转发EmbeddedLauncher.main(args)是同步阻塞调用,返回后立即System.exit(0)。- 临时目录初始化:
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 运行时装配。
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 初始化前预加载本地动态库:
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:
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,是理解所有命令行参数行为的核心:
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 即可按顺序看到全部参数的生效次序。
命令行启动流程
流程图的每个分支均对应源码中的真实调用点:
- 分支一(
--direct-start):DesktopLauncher在 Sentry/临时目录/JavaFX 之前就同步转发并退出,因此该路径完全绕过启动器 GUI 与 Sentry。 - 分支二(默认):
Application.launch(ArkHomeFX.class, args)把参数继续传给 JavaFX 启动器;GUI 派生 Core 进程时再以EmbeddedLauncher引导,即两条路径最终汇合到同一处 Core 装配代码。
参数参考
以下参数均已从源码逐一核实。注意「生效顺序」即代码中 new ArgPending(...) 的书写顺序——同名类参数(四个日志级别参数)互相覆盖,后命中者生效。
| 参数 | 接受附加值 | 支持的启动器 | 生效顺序 | 作用 |
|---|---|---|---|---|
--quiet(Const.LogConfig.errorArg) | 否 | DesktopLauncher / EmbeddedLauncher | 1 | 将日志级别设为 ERROR,静默模式 |
--warn(Const.LogConfig.warnArg) | 否 | DesktopLauncher / EmbeddedLauncher | 2 | 将日志级别设为 WARN |
--info(Const.LogConfig.infoArg) | 否 | DesktopLauncher / EmbeddedLauncher | 3 | 将日志级别设为 INFO |
--debug(Const.LogConfig.debugArg) | 否 | DesktopLauncher / EmbeddedLauncher | 4 | 设为 DEBUG;在 EmbeddedLauncher 中额外置 isDebugEnabled = true 并打印调试日志 |
--direct-start | 否 | 仅 DesktopLauncher | 5(Sentry/临时目录/JavaFX 之前) | 跳过 GUI,直接以全部 args 调用 EmbeddedLauncher.main |
--config | 是(文件路径) | 仅 EmbeddedLauncher | 0(先于日志级别参数之外的一切) | 指定自定义配置文件;随后 ArkConfig.getConfig(customConfig) |
--load-lib | 是(库绝对路径) | 仅 EmbeddedLauncher | 5 | System.load(addition) 预加载本地动态库 |
参数字符串常量集中定义于 Const.LogConfig,保证两个启动器对同一参数行为一致:
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
典型调用示例
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)」页