From d95c9664c691f29452248a06bf55a957218af6a9 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sun, 1 Nov 2026 09:02:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(app):=20M45=20r1=20=E4=B8=A4=E6=9D=A1=20P2?= =?UTF-8?q?=EF=BC=88=E5=90=91=E5=AF=BC=E6=8B=92=E8=A3=85=E6=96=87=E6=A1=88?= =?UTF-8?q?=20+=20=E5=A4=84=E7=90=86=E5=99=A8=E6=AE=B5=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=BB=A1=E6=A0=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-1:wizard.slint 的 Forge/NeoForge 提示还写着「需要 installer processors:本轮请用 ull install」, 改成「需要 installer processors(首装会外调 java,约 1–3 分钟)」;全仓再扫一遍 「ull install / 显式拒装 / 终端跑」等措辞,user-facing 文案已无残留(余下命中全是 OptiFine/LiteLoader 的正确说明与测试注释)。 P2-2:进度条在处理器依赖库下完那一刻就满格(36/36),随后加载器库的 29 个文件才被计入 → 100% 回落再涨回,且 bytes_total 用 .max() 与文件数对不上。 - Meter 新增 expect(files, bytes):文件数与字节都 saturating_add; - install_loader 在处理器段之前就把加载器库的文件/字节一次预记账(两段之和), 加载器库阶段只剩切阶段文案,不再记账; - Reporter::expect 改为委托 Meter::expect,处理器依赖库沿用同一条曲线; - 回归测试钉住:预记账后 files_total=95、bytes_total=17MB、ratio 不再满格, 处理器库下完仍是中间态,加载器库下完才到 100%。 真机重截 + 识图:D:/tmp/ull-m45-shots/forge-mid-fixed-25s.png —— 同一时刻面板 36/65 文件、3.4 MB/15.3 MB、进度条只填了约一半(旧图 36/36 + 满格)。 --- crates/app/src/host/install.rs | 10 ++-- crates/app/src/host/install_processors.rs | 4 +- crates/app/src/host/install_tests.rs | 65 +++++++++++++++++++++++ crates/app/src/host/meter.rs | 10 ++++ crates/app/ui/widgets/wizard.slint | 4 +- 5 files changed, 85 insertions(+), 8 deletions(-) diff --git a/crates/app/src/host/install.rs b/crates/app/src/host/install.rs index 5dc56c9..55e368f 100644 --- a/crates/app/src/host/install.rs +++ b/crates/app/src/host/install.rs @@ -304,6 +304,13 @@ fn install_loader( vanilla.loader = LoaderKind::Vanilla; install_vanilla(job, engine, &vanilla)?; } + // 两段(加载器库 + 处理器依赖库)在动手前一次预记账:进度条只走一条单调曲线, + // 处理器依赖库下完不会先满格、再在加载器库开始时回落到一半。 + { + let mut guard = job.meter.lock().map_err(|_| "进度锁失效".to_string())?; + let (bytes, _) = plan.install.total_bytes(); + guard.expect(plan.files().len(), bytes); + } // Forge/NeoForge:installer processors 必须先跑完,`execute` 才允许写版本 JSON。 if release.requires_installer() { let input = StageInput { @@ -327,9 +334,6 @@ fn install_loader( } { let mut guard = job.meter.lock().map_err(|_| "进度锁失效".to_string())?; - guard.progress.files_total += u64::try_from(plan.files().len()).unwrap_or(u64::MAX); - let (bytes, _) = plan.install.total_bytes(); - guard.progress.bytes_total = guard.progress.bytes_total.max(bytes); guard.stage( InstallStage::Downloading, &format!("开始下载加载器库({} 个文件)…", plan.files().len()), diff --git a/crates/app/src/host/install_processors.rs b/crates/app/src/host/install_processors.rs index 13a01ce..cb8255c 100644 --- a/crates/app/src/host/install_processors.rs +++ b/crates/app/src/host/install_processors.rs @@ -146,9 +146,7 @@ impl Reporter<'_> { let Ok(mut guard) = self.meter.lock() else { return; }; - let files = u64::try_from(files).unwrap_or(u64::MAX); - guard.progress.files_total = guard.progress.files_total.saturating_add(files); - guard.progress.bytes_total = guard.progress.bytes_total.saturating_add(bytes); + guard.expect(files, bytes); } /// 失败留痕(文案由调用方决定怎么呈现;这里只保证日志里有一条)。 diff --git a/crates/app/src/host/install_tests.rs b/crates/app/src/host/install_tests.rs index ea46c23..5b46f36 100644 --- a/crates/app/src/host/install_tests.rs +++ b/crates/app/src/host/install_tests.rs @@ -69,6 +69,71 @@ fn request(loader: LoaderKind, version: &str) -> CreateRequest { } } +/// 一个「刚下完」的文件结局(记账用)。 +fn downloaded(bytes: u64) -> FileOutcome { + FileOutcome { + role: FileRole::Library, + name: "lib.jar".to_string(), + dest: PathBuf::from("lib.jar"), + status: FileStatus::Downloaded, + bytes, + source: None, + failure: None, + } +} + +#[test] +fn progress_is_pre_accounted_across_segments_so_the_bar_never_snaps_to_full() { + // M45 r1 P2-2 的回归:处理器依赖库下完那一刻,面板曾显示 36/36 满格 + 3.4MB/10MB, + // 之后加载器库的 29 个文件才被计入 → 进度条从 100% 回落到 55% 再涨回去。 + let request = request(LoaderKind::Forge, "1.20.1"); + let mut meter = Meter::new(&request); + + // 原版段:30 个文件全下完(install_vanilla 对总量是赋值,不是累加) + meter.progress.files_total = 30; + meter.progress.bytes_total = 3_000_000; + for _ in 0..30 { + meter.note(&downloaded(100_000)); + } + assert!((meter.progress.ratio() - 1.0).abs() < f32::EPSILON); + + // install_loader 的顺序:加载器库与处理器依赖库都在动手前一次预记账 + meter.expect(29, 10_000_000); + meter.expect(36, 4_000_000); + assert_eq!(meter.progress.files_total, 95, "两段之和一次到位"); + assert_eq!( + meter.progress.bytes_total, 17_000_000, + "字节是相加不是取最大" + ); + assert!( + meter.progress.ratio() < 0.7, + "预记账后不能还停在满格:{}", + meter.progress.ratio() + ); + + // 处理器依赖库 36 个下完 → 仍没满格(加载器库还在后面) + for _ in 0..36 { + meter.note(&downloaded(110_000)); + } + let ratio = meter.progress.ratio(); + assert!( + (0.6..1.0).contains(&ratio), + "回落到中间态而不是满格:{ratio}" + ); + assert!( + meter.progress.bytes_done < meter.progress.bytes_total, + "字节总量要覆盖已下载量:{}/{}", + meter.progress.bytes_done, + meter.progress.bytes_total + ); + + // 加载器库也下完 → 这时候才该是满格 + for _ in 0..29 { + meter.note(&downloaded(340_000)); + } + assert!((meter.progress.ratio() - 1.0).abs() < f32::EPSILON); +} + #[test] fn loader_support_matrix_is_explicit() { // M45 语义变更:Forge/NeoForge 从「GUI 显式拒装」扩进受支持集合(走 installer processors)。 diff --git a/crates/app/src/host/meter.rs b/crates/app/src/host/meter.rs index 813a54f..0962dd9 100644 --- a/crates/app/src/host/meter.rs +++ b/crates/app/src/host/meter.rs @@ -37,6 +37,16 @@ impl Meter { self.previous = None; } + /// 预记账一段待下载的文件(文件数 + 字节)。 + /// + /// 一次安装可能分几段(原版 / 处理器依赖库 / 加载器库):每段开工前都先记账, + /// 整条进度曲线才是单调的——某一段下完不会先到 100%,再因为下一段的文件被计入而回落。 + pub(super) fn expect(&mut self, files: usize, bytes: u64) { + let files = u64::try_from(files).unwrap_or(u64::MAX); + self.progress.files_total = self.progress.files_total.saturating_add(files); + self.progress.bytes_total = self.progress.bytes_total.saturating_add(bytes); + } + /// 记账一个文件的结局。 pub(super) fn note(&mut self, outcome: &FileOutcome) { self.progress.files_done += 1; diff --git a/crates/app/ui/widgets/wizard.slint b/crates/app/ui/widgets/wizard.slint index fdbc33b..dda00f2 100644 --- a/crates/app/ui/widgets/wizard.slint +++ b/crates/app/ui/widgets/wizard.slint @@ -196,8 +196,8 @@ export component NewInstanceWizard inherits Rectangle { LoaderChoice { index: 0; label: "原版(Vanilla)"; hint: "不装加载器,最快最稳"; picked => { PageState.wizard-select-loader(0); } } LoaderChoice { index: 1; label: "Fabric"; hint: "轻量模组加载器(GUI 可直接装)"; picked => { PageState.wizard-select-loader(1); } } LoaderChoice { index: 2; label: "Quilt"; hint: "Fabric 的分支(GUI 可直接装)"; picked => { PageState.wizard-select-loader(2); } } - LoaderChoice { index: 3; label: "Forge"; hint: "需要 installer processors:本轮请用 ull install"; picked => { PageState.wizard-select-loader(3); } } - LoaderChoice { index: 4; label: "NeoForge"; hint: "需要 installer processors:本轮请用 ull install"; picked => { PageState.wizard-select-loader(4); } } + LoaderChoice { index: 3; label: "Forge"; hint: "需要 installer processors(首装会外调 java,约 1–3 分钟)"; picked => { PageState.wizard-select-loader(3); } } + LoaderChoice { index: 4; label: "NeoForge"; hint: "需要 installer processors(首装会外调 java,约 1–3 分钟)"; picked => { PageState.wizard-select-loader(4); } } } // ③ 名字