feat(app): GUI 全量接线,五页改吃 core 真实数据(M43)
app 从「mock 骨架」切到「真实 core host」:新增 src/host 装配层(env/job/ game/launch/identity/versions/install/meter/java/accounts/logs/settings/engine/ dispatch),是 app 里除 settings_bridge 外唯一 use unlucky_core 的地方,出口只有 crate::dto 的 DTO/事件(三通道纪律,AGENTS §6)。 页面接线: - 实例页:profiles+版本 JSON 继承链+会话索引 → 磁贴(真实版本/加载器徽标/最后启动/ 隔离档位/载荷缺失);启动按钮走 LaunchRequest 装配 → Launcher::spawn → JSONL 尾随 → 运行/失败回写;M30 ModeChangeWarning 进横幅通知条。 - 新建实例向导:版本 → 加载器 → 名字 → 确认 → 真实安装进度;Fabric/Quilt 可装, Forge/NeoForge 因需要 installer processors(仅 CLI 有)显式拒装并给出 CLI 命令。 - 下载页:远端 manifest(12h 缓存)+ 内嵌 unlisted → 分类(正式/预览/远古四子类); 安装进度(文件数/字节/速度/ETA,速度由相邻采样差分)+ 阶段边界可取消; Java 分组:四家发行版 + 回退链可视化(计划链/真实 ChainLog)+ 本地运行时。 - 账户页:三轨账户列表(只读公开字段)+ 离线新增 + 删除/切换 + 微软设备码登录 (用户码/链接/轮询状态;取消后丢弃结果)。凭据只在 host/identity 与 host/accounts 出现,DTO 无 token 字段(单测钉住)。 - 设置页:新增 download/java/data/ai/mcp 五组,行由 core::setting schema 键生成, 写回走 SettingStore::set(与 ull config set / AI set_config 同源 launcher.json)。 - 日志页:会话索引 → 列表;事件 JSONL → 日志行(等级高亮)+ D8 规则引擎诊断卡片; 启动中实时尾随(600ms/拍,只读新增字节);崩溃日志导入分析(hs_err/crash/latest)。 架构与守线: - 新页面状态走 ui/page_state.slint 的 PageState global(main.slint 再导出), Rust 侧 page_sync.rs 灌入、callbacks_pages.rs 接线;避免属性逐层转发顶破 400 行。 - DTO 全部 Serialize+Deserialize(IPC 契约),新增 accounts/java/settings/install/ notices 五个子模块;事件扩展 LogAppended/LaunchFinished/AccountsChanged/ DeviceLoginChanged/JavaChanged/InstallProgress/SettingsChanged/NoticesChanged。 - 截图通道扩展:--data-dir(钉数据根且 settings 同源)/--mock/--screenshot-page/ --screenshot-wizard/--screenshot-session/--screenshot-install/--screenshot-launch (真实中间态,取图后自动 stop 防孤儿进程)。 已知边界:GUI 账户「当前启动账户」仅内存态(core schema 无 auth.* 键,app 不自建键); Forge/NeoForge GUI 安装待 processors 执行器上移。
This commit is contained in:
1 parent
bc41dfcf7d
commit
ddc0577f83
64 files changed
+8047
-408
No files matched your search
@@ -0,0 +1,136 @@
|
||||
//! 后台任务的回流消息:`HostTask` 是唯一「线程 → host」的载荷。
|
||||
//!
|
||||
//! 线程只做 core 调用(网络/磁盘),产出的都是 `crate::dto` 的 DTO;
|
||||
//! host 的 `tick` 把它们翻成 [`crate::dto::CoreEvent`]。
|
||||
//! 这样「跨线程传的是什么」在一处看全,也不会出现半成品领域类型漏回 UI。
|
||||
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use crate::dto::{
|
||||
AccountView, DeviceLoginView, DiagnosisReport, InstallProgressView, InstanceId,
|
||||
InstanceRuntime, JavaPageView, LaunchFinished, LiveLogBatch, LogLine, LogOutcome, LogSession,
|
||||
NoticeView, SessionId, VersionSummary,
|
||||
};
|
||||
use crate::toast::Toast;
|
||||
|
||||
/// 后台任务 → host 的消息。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum HostTask {
|
||||
/// 版本清单(下载页)
|
||||
Versions(Result<Vec<VersionSummary>, String>),
|
||||
/// 安装进度(高频,host 只做聚合与转发)
|
||||
Install(Box<InstallProgressView>),
|
||||
/// 安装收尾(成功带实例名,失败带文案)
|
||||
InstallDone {
|
||||
/// 最终进度快照
|
||||
progress: Box<InstallProgressView>,
|
||||
/// 成功时为 `Ok(实例名)`
|
||||
result: Result<String, String>,
|
||||
},
|
||||
/// 账户列表
|
||||
Accounts(Vec<AccountView>),
|
||||
/// 设备码登录状态
|
||||
Login(Box<DeviceLoginView>),
|
||||
/// Java 页(发行版/回退链/运行时)
|
||||
Java(Box<JavaPageView>),
|
||||
/// 日志页快照(会话列表 + 选中会话的行 + 诊断)
|
||||
Logs {
|
||||
/// 会话列表
|
||||
sessions: Vec<LogSession>,
|
||||
/// 选中会话的行
|
||||
lines: Vec<LogLine>,
|
||||
/// 诊断结论
|
||||
diagnosis: Option<DiagnosisReport>,
|
||||
},
|
||||
/// 实时日志增量
|
||||
Tail(Box<LiveLogBatch>),
|
||||
/// 启动开始(pid + 会话 id)
|
||||
LaunchStarted {
|
||||
/// 实例 id
|
||||
id: InstanceId,
|
||||
/// 会话 id
|
||||
session_id: SessionId,
|
||||
/// 进程号
|
||||
pid: u32,
|
||||
},
|
||||
/// 启动阶段推进(磁贴进度环)
|
||||
Runtime {
|
||||
/// 实例 id
|
||||
id: InstanceId,
|
||||
/// 阶段状态
|
||||
runtime: InstanceRuntime,
|
||||
},
|
||||
/// 启动收尾
|
||||
LaunchEnd(Box<LaunchFinished>),
|
||||
/// 崩溃日志导入的分析结论
|
||||
Crash(Box<DiagnosisReport>),
|
||||
/// 横幅通知(隔离切换提示等)
|
||||
Notice(Box<NoticeView>),
|
||||
/// Toast
|
||||
Toast(Box<Toast>),
|
||||
/// 会话列表刷新(只换左栏)
|
||||
Sessions(Vec<LogSession>),
|
||||
}
|
||||
|
||||
/// 后台线程用的发送端(`Sender` 克隆即可)。
|
||||
pub type JobSender = Sender<HostTask>;
|
||||
|
||||
/// host 侧的接收端。
|
||||
pub type JobReceiver = Receiver<HostTask>;
|
||||
|
||||
/// 建一对通道。
|
||||
pub fn channel() -> (JobSender, JobReceiver) {
|
||||
std::sync::mpsc::channel()
|
||||
}
|
||||
|
||||
/// 起一个带名字的后台线程(名字只进 panic 信息,方便定位)。
|
||||
pub fn spawn(name: &str, job: impl FnOnce() + Send + 'static) -> std::thread::JoinHandle<()> {
|
||||
std::thread::Builder::new()
|
||||
.name(format!("ull-host-{name}"))
|
||||
.spawn(job)
|
||||
.unwrap_or_else(|error| panic!("起不来后台线程 {name}: {error}"))
|
||||
}
|
||||
|
||||
/// 人读一行「启动结束」。
|
||||
pub fn launch_summary(outcome: LogOutcome, exit_code: Option<i32>) -> String {
|
||||
match outcome {
|
||||
LogOutcome::Ok => "正常退出".to_string(),
|
||||
LogOutcome::Crashed => match exit_code {
|
||||
Some(code) => format!("崩溃(exit {code})"),
|
||||
None => "崩溃".to_string(),
|
||||
},
|
||||
LogOutcome::SilentFail => "静默失败(exit 0 但游戏没起来)".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn channel_carries_tasks_across_threads() {
|
||||
let (tx, rx) = channel();
|
||||
let handle = spawn("test", move || {
|
||||
tx.send(HostTask::Toast(Box::new(Toast {
|
||||
id: 1,
|
||||
kind: crate::toast::ToastKind::Info,
|
||||
text: "hi".to_string(),
|
||||
remaining_ms: 0,
|
||||
})))
|
||||
.expect("发送");
|
||||
});
|
||||
handle.join().expect("线程结束");
|
||||
let task = rx.recv().expect("收到");
|
||||
assert!(matches!(task, HostTask::Toast(toast) if toast.text == "hi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_summary_names_the_outcome() {
|
||||
assert_eq!(launch_summary(LogOutcome::Ok, Some(0)), "正常退出");
|
||||
assert_eq!(
|
||||
launch_summary(LogOutcome::Crashed, Some(1)),
|
||||
"崩溃(exit 1)"
|
||||
);
|
||||
assert!(launch_summary(LogOutcome::SilentFail, Some(0)).contains("静默"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user