Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
//! 指数退避 + 抖动。
//!
//! 修 HMCL 的坑(analyse/03 §7.6 第 5 点):它的重试循环里没有 sleep,5xx 会变成
//! 「6 次瞬时连打」,把弱网镜像直接压垮。这里给出 1s / 2s / 4s… 上限 30s,
//! ±30% 抖动打散多实例/多文件的同步重试风暴。
//!
//! 抖动源是自带的 xorshift64*,不引 `rand`(体积 + Win7 依赖面)。
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
/// 退避策略(可序列化进设置)。
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BackoffPolicy {
/// 首次退避(毫秒)。
pub base_ms: u64,
/// 单次退避上限(毫秒)。
pub max_ms: u64,
/// 抖动比例(0.0~1.0,0.3 = ±30%)。
pub jitter_ratio: f64,
/// 每个源的尝试次数上限。
pub max_attempts: u32,
}
impl BackoffPolicy {
/// 不重试(一次失败立刻换源)。
#[must_use]
pub const fn none() -> Self {
Self {
base_ms: 0,
max_ms: 0,
jitter_ratio: 0.0,
max_attempts: 1,
}
}
/// 第 `attempt` 次重试前的**无抖动**等待时间(`attempt` 从 0 起)。
#[must_use]
pub fn delay(&self, attempt: u32) -> Duration {
let factor = 1u64.checked_shl(attempt.min(32)).unwrap_or(u64::MAX).max(1);
Duration::from_millis(self.base_ms.saturating_mul(factor).min(self.max_ms))
}
/// 第 `attempt` 次重试前的等待时间,`rng` 为 `[0,1)` 的抖动量(可复现,便于单测)。
///
/// 结果永不超过 `max_ms`,且永不低于 `base_ms`(当 `base_ms <= max_ms` 时)。
#[must_use]
pub fn delay_jittered(&self, attempt: u32, rng: f64) -> Duration {
let base_ms = self.delay(attempt).as_millis() as f64;
let ratio = self.jitter_ratio.clamp(0.0, 1.0);
let factor = 1.0 + ratio * (2.0 * rng.clamp(0.0, 1.0) - 1.0);
let ceiling = (self.max_ms as f64).max(base_ms);
let jittered_ms = (base_ms * factor).clamp(0.0, ceiling);
Duration::from_millis(jittered_ms as u64)
}
/// 用抖动源取一次等待时间。
#[must_use]
pub fn next_delay(&self, attempt: u32, jitter: &mut Jitter) -> Duration {
self.delay_jittered(attempt, jitter.next_fraction())
}
}
impl Default for BackoffPolicy {
fn default() -> Self {
Self {
base_ms: 1_000,
max_ms: 30_000,
jitter_ratio: 0.3,
max_attempts: 3,
}
}
}
/// xorshift64* 抖动源:确定性、无外部依赖。
#[derive(Debug, Clone)]
pub struct Jitter {
state: u64,
}
impl Jitter {
/// 固定种子构造(同种子的序列可复现 → 单测友好)。
#[must_use]
pub const fn new(seed: u64) -> Self {
Self {
state: if seed == 0 {
0x9E37_79B9_7F4A_7C15
} else {
seed
},
}
}
/// 下一个 64 位随机数。
pub fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
/// 下一个 `[0,1)` 的浮点。
pub fn next_fraction(&mut self) -> f64 {
// 取高 53 位映射到 [0,1),与 f64 的尾数精度一致。
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
}
impl Default for Jitter {
fn default() -> Self {
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0x1234_5678_9ABC_DEF0);
Self::new(seed)
}
}