CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
单提交快照:main 只保留已发布版本的代码树,开发史在 dev。
81 lines
3.0 KiB
Rust
81 lines
3.0 KiB
Rust
//! 安装进度的记账器(文件计数 / 字节 / 速度 / ETA)。
|
||
//!
|
||
//! `execute` 的文件级回调已经把每个文件的结局给了我们,这里只做聚合;
|
||
//! 速度用相邻两次采样的字节差算(core 的事件只给绝对值)。
|
||
|
||
use std::sync::Arc;
|
||
use std::time::Instant;
|
||
|
||
use unlucky_core::game::install::{FileOutcome, FileStatus, ProgressSink};
|
||
|
||
use crate::dto::{derive_speed, CreateRequest, InstallProgressView, InstallStage};
|
||
|
||
use super::install::loader_label;
|
||
use super::job::{HostTask, JobSender};
|
||
/// 一次安装的上下文(进度回调共享)。
|
||
pub(super) struct Meter {
|
||
pub(super) progress: InstallProgressView,
|
||
previous: Option<(u64, Instant)>,
|
||
}
|
||
|
||
impl Meter {
|
||
pub(super) fn new(request: &CreateRequest) -> Self {
|
||
Self {
|
||
progress: InstallProgressView::start(
|
||
&request.version,
|
||
&request.instance_name(),
|
||
&loader_label(request.loader, &request.loader_version),
|
||
),
|
||
previous: None,
|
||
}
|
||
}
|
||
|
||
/// 换阶段(重置速度采样)。
|
||
pub(super) fn stage(&mut self, stage: InstallStage, message: &str) {
|
||
self.progress.stage = stage;
|
||
self.progress.message = message.to_string();
|
||
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;
|
||
if outcome.status != FileStatus::Skipped {
|
||
self.progress.bytes_done = self.progress.bytes_done.saturating_add(outcome.bytes);
|
||
}
|
||
let now = Instant::now();
|
||
if let Some(previous) = self.previous {
|
||
if let Some((speed, eta)) = derive_speed(
|
||
previous,
|
||
(self.progress.bytes_done, now),
|
||
self.progress.bytes_total,
|
||
) {
|
||
self.progress.speed_bps = speed;
|
||
self.progress.eta_secs = eta;
|
||
}
|
||
}
|
||
self.previous = Some((self.progress.bytes_done, now));
|
||
}
|
||
}
|
||
|
||
/// 进度 sink(`execute` 的文件级回调)。
|
||
pub(super) fn sink_for(tx: JobSender, meter: Arc<std::sync::Mutex<Meter>>) -> ProgressSink {
|
||
Arc::new(move |outcome: &FileOutcome| {
|
||
let Ok(mut meter) = meter.lock() else {
|
||
return;
|
||
};
|
||
meter.note(outcome);
|
||
let _ = tx.send(HostTask::Install(Box::new(meter.progress.clone())));
|
||
})
|
||
}
|