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

+174
View File
@@ -0,0 +1,174 @@
//! 不可变快照 + 单写者草稿(Snapshot / Draft)。
//!
//! 依据:`DevDocs/analyse/01-architecture-overview.md` §3.3(HMCL 最值得抄的一处设计)、
//! `DevDocs/PlanDoc/01-tech-stack-and-architecture.md` 公理 2。
//!
//! 收益:**读端永远看到一整份自洽的数据**,不会出现"遍历到一半列表被改"
//! (HMCL 老式 `synchronized` 实现的典型 bug)。写端同一时刻只允许一个草稿,
//! 发布 = 指针交换,读端持有的旧快照继续有效直到自己放手。
//!
//! **实现说明**:读路径持一把共享锁,只为把 `Arc` 克隆出来(纳秒级),随后所有遍历都在锁外;
//! 真正的无锁读需要 `arc-swap` 这类依赖,P0 不引入(core 依赖面越窄越好)。
//! 与 HMCL 的另一处差异:**发布不需要 UI 线程桥接**——发布是纯指针交换,
//! 订阅通知属于 app 层(`crates/app`),core 不做线程模型假设。
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use super::error::GameError;
/// 一份不可变快照:克隆只是 `Arc` 计数,内容只读。
pub struct Snapshot<T> {
data: Arc<T>,
}
impl<T> Snapshot<T> {
/// 用给定内容构造快照。
pub fn new(data: T) -> Self {
Self {
data: Arc::new(data),
}
}
/// 读内容。
pub fn get(&self) -> &T {
&self.data
}
/// 共享同一份内容的强引用。
pub fn shared(&self) -> Arc<T> {
Arc::clone(&self.data)
}
}
impl<T> Clone for Snapshot<T> {
fn clone(&self) -> Self {
Self {
data: Arc::clone(&self.data),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Snapshot<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Snapshot").field(&self.data).finish()
}
}
impl<T: PartialEq> PartialEq for Snapshot<T> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
/// 写权凭证:唯一的写权持有者,`drop` 即释放(发布与放弃都走它)。
struct WriteToken<'a> {
store: &'a AtomicBool,
}
impl<'a> WriteToken<'a> {
fn acquire(flag: &'a AtomicBool) -> Result<Self, GameError> {
if flag
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return Err(GameError::DraftBusy);
}
Ok(Self { store: flag })
}
}
impl Drop for WriteToken<'_> {
fn drop(&mut self) {
self.store.store(false, Ordering::Release);
}
}
/// 快照仓库:持有当前快照,串行化写入。
pub struct Store<T> {
current: RwLock<Arc<T>>,
writing: AtomicBool,
}
impl<T: Clone> Store<T> {
/// 以初始内容建立仓库。
pub fn new(initial: T) -> Self {
Self {
current: RwLock::new(Arc::new(initial)),
writing: AtomicBool::new(false),
}
}
/// 取当前快照(读路径:一次共享锁 + `Arc` 克隆,锁内不做别的)。
pub fn snapshot(&self) -> Snapshot<T> {
let guard = self.current.read().unwrap_or_else(|e| e.into_inner());
Snapshot {
data: Arc::clone(&guard),
}
}
/// 打开草稿(写会话)。同一时刻只允许一个,违反单写者约束返回
/// [`GameError::DraftBusy`]。
pub fn open_draft(&self) -> Result<Draft<'_, T>, GameError> {
let token = WriteToken::acquire(&self.writing)?;
let working = self.snapshot().get().clone();
Ok(Draft {
store: self,
token,
working,
})
}
/// 整表替换(加载完成后一次性发布,仍受单写者约束)。
pub fn replace(&self, value: T) -> Result<Snapshot<T>, GameError> {
let mut draft = self.open_draft()?;
*draft.edit() = value;
Ok(draft.commit())
}
}
/// 草稿:持有写权的可变副本。
///
/// 未 [`commit`](Draft::commit) 就 `drop` 会**放弃改动并释放写权**(不会污染快照)。
pub struct Draft<'a, T> {
store: &'a Store<T>,
token: WriteToken<'a>,
working: T,
}
impl<T> Draft<'_, T> {
/// 读草稿内容。
pub fn view(&self) -> &T {
&self.working
}
/// 改草稿内容。
pub fn edit(&mut self) -> &mut T {
&mut self.working
}
/// 发布:把草稿内容换成仓库当前快照,并释放写权。
pub fn commit(self) -> Snapshot<T> {
let Draft {
store,
token,
working,
} = self;
let snapshot = Snapshot::new(working);
{
let mut guard = store.current.write().unwrap_or_else(|e| e.into_inner());
*guard = snapshot.shared();
}
drop(token);
snapshot
}
}
impl<T: fmt::Debug> fmt::Debug for Draft<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Draft")
.field("working", &self.working)
.finish()
}
}