Files
Unlucky-Launcher/crates/presenter/src/dto/launch_args.rs
T
AskaEth e2021645d7 feat(launch): M157 实例级启动参数(五项回落 + graphicsBackend 三态)
core: 新增 game/launch_overrides(LaunchOverrides 五项 All-Option + 三级回落 + 文本补丁校验)与 launch/overrides(拼装落位:全局 java.extra_args 进 user.jvm、实例 jvmArgs 进 override_jvm、gameArgs 进 user.game、memory 进 -Xmx、backend 进 --graphicsBackend 并复用既有版本门槛告警);实例 schema 登记 launch.* 五键。

presenter: dto/LaunchArgsView+LaunchArgsPatch,主机侧读/写与门槛判定(backend_allowed),AppCommand::SetInstanceLaunchArgs。

cli: 新命令 instances launch-args(读/写/--clear),launch 装配链消费回落值。

app-iced: 实例详情页「启动参数」区(JVM/游戏参数、内存、Java、三态后端 chips + 灰化提示 + 保存/恢复),表单状态归 App、i18n 中英文案同步。
2026-11-07 10:36:35 +08:00

137 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 实例启动参数 DTO(M157):实例配置页「启动参数」区的唯一数据源 + 写回补丁。
//!
//! 口径三条:
//! 1. **实例层与生效值分开给**:`jvm_args` 等是实例文件里的原文(空 = 跟随全局),
//! `*_effective` 是三级回落(实例 → 全局 → 默认)之后真正会下发的值。UI 要把两件事同时
//! 画出来(「留空 = 跟随全局 {global}」这种提示与「生效值」是两回事);
//! 2. **图形后端的可选值与门槛由 host 算好**:`graphics_backend_options` 来自 core 的词表
//! ([`LaunchArgsView::graphics_backend_options`]),`graphics_backend_allowed` 来自 core 的
//! 版本门槛判定——UI 不复制任何一条判据(任务书:判定用 core capability);
//! 3. **本模块不含 core 类型**(D42:dto 只放扁平 IPC 类型)。
//!
//! 补丁的「不改 / 清空」二分:`None` = 这一项不动;`Some("")` = 清空(回到跟随)。
use serde::{Deserialize, Serialize};
/// 实例启动参数的**读取**视图(详情页「启动参数」区)。
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct LaunchArgsView {
/// 实例 id。
pub instance_id: String,
/// 实例层 JVM 参数原文(空 = 只用全局 `java.extra_args`)。
pub jvm_args: String,
/// 实例层游戏参数原文(空 = 不追加)。
pub game_args: String,
/// 实例层最大堆(`None` = 跟随全局)。
pub memory_mb: Option<u32>,
/// 生效最大堆(三级回落 + 出厂默认,恒有值)。
pub memory_effective_mb: u32,
/// 实例层 Java 可执行文件原文(空 = 跟随全局 / 自动挑)。
pub java_path: String,
/// 生效 Java 可执行文件(空 = 自动挑;展示用,不代表已探测)。
pub java_effective: String,
/// 实例层图形后端(`default` / `opengl` / `vulkan`)。
pub graphics_backend: String,
/// 生效图形后端(三级回落之后)。
pub graphics_backend_effective: String,
/// 该实例版本能不能用 `--graphicsBackend`(core 的版本门槛判定;`false` ⇒ UI 灰化 + 提示)。
pub graphics_backend_allowed: bool,
/// 判定所依据的游戏版本(提示文案「需 MC 26.2+」旁边给出实际版本)。
pub mc_version: String,
/// 门槛的最低版本(core 的 `GRAPHICS_BACKEND_MIN_VERSION`;文案与测试共用一份来源)。
pub graphics_backend_min_version: String,
/// 图形后端三态的词表(顺序即下拉顺序;来自 core,不让 UI 手抄)。
pub graphics_backend_options: Vec<String>,
}
impl LaunchArgsView {
/// 三态下拉的 id → 是不是当前生效值(UI 高亮用;未知值原样比较)。
#[must_use]
pub fn is_effective_backend(&self, id: &str) -> bool {
self.graphics_backend_effective == id
}
}
/// 实例启动参数的**写回**补丁(GUI 表单与 CLI 共用)。
///
/// 每个字段:`None` = 不改;`Some("")` = 清空(回到跟随下一层)。
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct LaunchArgsPatch {
/// 实例层 JVM 参数(文本,空白分隔)。
pub jvm_args: Option<String>,
/// 实例层游戏参数(文本)。
pub game_args: Option<String>,
/// 实例层最大堆(文本形态,host 解析;空 = 跟随全局)。
pub memory_mb: Option<String>,
/// 实例层 Java 可执行文件。
pub java_path: Option<String>,
/// 实例层图形后端(`default` / `opengl` / `vulkan`)。
pub graphics_backend: Option<String>,
}
impl LaunchArgsPatch {
/// 是否什么都不改(UI 没动过表单时的短路)。
#[must_use]
pub fn is_empty(&self) -> bool {
self.jvm_args.is_none()
&& self.game_args.is_none()
&& self.memory_mb.is_none()
&& self.java_path.is_none()
&& self.graphics_backend.is_none()
}
/// 只改图形后端(下拉切换的最小补丁)。
#[must_use]
pub fn backend(id: &str) -> Self {
Self {
graphics_backend: Some(id.to_string()),
..Self::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_patch_touches_nothing() {
assert!(LaunchArgsPatch::default().is_empty());
assert!(!LaunchArgsPatch::backend("vulkan").is_empty());
// `Some("")` 是「清空」而不是「不改」——两者不能折叠成一个判据。
let clear = LaunchArgsPatch {
jvm_args: Some(String::new()),
..LaunchArgsPatch::default()
};
assert!(!clear.is_empty());
}
#[test]
fn view_round_trips_as_ipc() {
let view = LaunchArgsView {
instance_id: "inst-1".to_string(),
jvm_args: "-XX:+UseG1GC".to_string(),
game_args: String::new(),
memory_mb: Some(4096),
memory_effective_mb: 4096,
java_path: String::new(),
java_effective: "C:/jdk21/bin/java.exe".to_string(),
graphics_backend: "vulkan".to_string(),
graphics_backend_effective: "vulkan".to_string(),
graphics_backend_allowed: true,
mc_version: "26.2-snapshot-2".to_string(),
graphics_backend_min_version: "26.2-snapshot-2".to_string(),
graphics_backend_options: vec![
"default".to_string(),
"opengl".to_string(),
"vulkan".to_string(),
],
};
let text = serde_json::to_string(&view).expect("序列化");
let back: LaunchArgsView = serde_json::from_str(&text).expect("反序列化");
assert_eq!(back, view);
assert!(back.is_effective_backend("vulkan"));
assert!(!back.is_effective_backend("opengl"));
}
}