Refactor configuration access to use latest_arc() instead of latest_ref()

- Updated multiple instances in the codebase to replace calls to latest_ref() with latest_arc() for improved performance and memory management.
- This change affects various modules including validate, enhance, feat (backup, clash, config, profile, proxy, window), utils (draft, i18n, init, network, resolve, server).
- Ensured that all references to configuration data are now using the new arc-based approach to enhance concurrency and reduce cloning overhead.

refactor: update imports to explicitly include ClashInfo and Config in command files
This commit is contained in:
Tunglies
2025-11-03 05:41:53 +08:00
Unverified
parent 48a19f99e2
commit 2287ea5f0b
37 changed files with 533 additions and 331 deletions

View File

@@ -1,179 +1,368 @@
use parking_lot::RwLock;
use std::sync::Arc;
use parking_lot::{
MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard,
RwLockUpgradableReadGuard, RwLockWriteGuard,
};
pub type SharedBox<T> = Arc<Box<T>>;
type DraftInner<T> = (SharedBox<T>, Option<SharedBox<T>>);
/// Draft 管理committed 与 optional draft 都以 Arc<Box<T>> 存储,
// (committed_snapshot, optional_draft_snapshot)
#[derive(Debug, Clone)]
pub struct Draft<T: Clone + ToOwned> {
inner: Arc<RwLock<(T, Option<T>)>>,
pub struct Draft<T: Clone> {
inner: Arc<RwLock<DraftInner<T>>>,
}
impl<T: Clone + ToOwned> From<T> for Draft<T> {
fn from(data: T) -> Self {
impl<T: Clone> Draft<T> {
pub fn new(data: T) -> Self {
Self {
inner: Arc::new(RwLock::new((data, None))),
inner: Arc::new(RwLock::new((Arc::new(Box::new(data)), None))),
}
}
}
/// Implements draft management for `Box<T>`, allowing for safe concurrent editing and committing of draft data.
/// # Type Parameters
/// - `T`: The underlying data type, which must implement `Clone` and `ToOwned`.
///
/// # Methods
/// - `data_mut`: Returns a mutable reference to the committed data.
/// - `draft_mut`: Creates or retrieves a mutable reference to the draft data, cloning the committed data if no draft exists.
/// - `latest_ref`: Returns an immutable reference to the draft data if it exists, otherwise to the committed data.
/// - `apply`: Commits the draft data, replacing the committed data and returning the old committed value if a draft existed.
/// - `discard`: Discards the draft data and returns it if it existed.
impl<T: Clone + ToOwned> Draft<Box<T>> {
/// 正式数据视图
pub fn data_ref(&self) -> MappedRwLockReadGuard<'_, Box<T>> {
RwLockReadGuard::map(self.inner.read(), |inner| &inner.0)
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc
pub fn data_arc(&self) -> SharedBox<T> {
let guard = self.inner.read();
Arc::clone(&guard.0)
}
/// 可写正式数据
pub fn data_mut(&self) -> MappedRwLockWriteGuard<'_, Box<T>> {
RwLockWriteGuard::map(self.inner.write(), |inner| &mut inner.0)
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
/// 这也是零拷贝:只 clone Arc不 clone T
pub fn latest_arc(&self) -> SharedBox<T> {
let guard = self.inner.read();
guard
.1
.as_ref()
.cloned()
.unwrap_or_else(|| Arc::clone(&guard.0))
}
/// 创建或获取草稿并返回可写引用
pub fn draft_mut(&self) -> MappedRwLockWriteGuard<'_, Box<T>> {
let guard = self.inner.upgradable_read();
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T
/// - 若草稿被其他读者共享Arc::make_mut 会做一次 T.clone最小必要拷贝
pub fn edit_draft<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
// 先获得写锁以创建或取出草稿 Arc 的可变引用位置
let mut guard = self.inner.write();
if guard.1.is_none() {
let mut guard = RwLockUpgradableReadGuard::upgrade(guard);
guard.1 = Some(guard.0.clone());
return RwLockWriteGuard::map(guard, |inner| {
inner.1.as_mut().unwrap_or_else(|| {
unreachable!("Draft was just created above, this should never fail")
})
});
// 创建草稿 snapshotArc clonecheap
guard.1 = Some(Arc::clone(&guard.0));
}
// 已存在草稿,升级为写锁映射
RwLockWriteGuard::map(RwLockUpgradableReadGuard::upgrade(guard), |inner| {
inner
.1
.as_mut()
.unwrap_or_else(|| unreachable!("Draft should exist when guard.1.is_some()"))
})
// 此时 guaranteed: guard.1 is Some(Arc<Box<T>>)
#[allow(clippy::unwrap_used)]
let arc_box = guard.1.as_mut().unwrap();
// Arc::make_mut: 如果只有一个引用则返回可变引用;否则会克隆底层 Box<T>(要求 T: Clone
let boxed = Arc::make_mut(arc_box); // &mut Box<T>
// 对 Box<T> 解引用得到 &mut T
f(&mut **boxed)
}
/// 零拷贝只读视图:返回草稿(若存在)或正式值
pub fn latest_ref(&self) -> MappedRwLockReadGuard<'_, Box<T>> {
RwLockReadGuard::map(self.inner.read(), |inner| {
inner.1.as_ref().unwrap_or(&inner.0)
})
}
/// 提交草稿,返回旧正式数据
/// 将草稿提交到已提交位置(替换),并清除草稿
pub fn apply(&self) {
let guard = self.inner.upgradable_read();
if guard.1.is_none() {
return;
}
let mut guard = RwLockUpgradableReadGuard::upgrade(guard);
if let Some(draft) = guard.1.take() {
guard.0 = draft;
let mut guard = self.inner.write();
if let Some(d) = guard.1.take() {
guard.0 = d;
}
}
/// 丢弃草稿,返回被丢弃的草稿
/// 丢弃草稿(如果存在)
pub fn discard(&self) {
self.inner.write().1.take();
let mut guard = self.inner.write();
guard.1 = None;
}
/// 异步修改正式数据,闭包直接获得 Box<T> 所有权
pub async fn with_data_modify<F, Fut, R, E>(&self, f: F) -> Result<R, E>
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
where
T: Send + Sync + 'static,
F: FnOnce(Box<T>) -> Fut + Send,
Fut: std::future::Future<Output = Result<(Box<T>, R), E>> + Send,
E: From<anyhow::Error>,
Fut: std::future::Future<Output = Result<(Box<T>, R), anyhow::Error>> + Send,
{
// 克隆正式数据
let local = {
// 读取已提交快照cheap Arc clone, 然后得到 Box<T> 所有权 via clone
// 注意:为了让闭包接收 Box<T> 所有权,我们需要 clone 底层 T不可避免
let local: Box<T> = {
let guard = self.inner.read();
guard.0.clone()
// 将 Arc<Box<T>> 的 Box<T> clone 出来(会调用 T: Clone
(*guard.0).clone()
};
// 异步闭包执行,返回修改后的 Box<T> 和业务结果 R
let (new_local, res) = f(local).await?;
// 写回正式数据
// 将新的 Box<T> 放到已提交位置(包进 Arc
let mut guard = self.inner.write();
guard.0 = new_local;
guard.0 = Arc::new(new_local);
Ok(res)
}
}
#[test]
fn test_draft_box() {
use crate::config::IVerge;
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
// 1. 创建 Draft<Box<IVerge>>
let verge = Box::new(IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
..IVerge::default()
});
let draft = Draft::from(verge);
// 2. 读取正式数据data_mut
{
let data = draft.data_mut();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
#[derive(Clone, Debug, Default, PartialEq)]
struct IVerge {
enable_auto_launch: Option<bool>,
enable_tun_mode: Option<bool>,
}
// 3. 初次获取草稿draft_mut 会自动 clone 一份)
{
let draft_view = draft.draft_mut();
assert_eq!(draft_view.enable_auto_launch, Some(true));
assert_eq!(draft_view.enable_tun_mode, Some(false));
// Minimal single-threaded executor for immediately-ready futures
fn block_on_ready<F: Future>(fut: F) -> F::Output {
fn no_op_raw_waker() -> RawWaker {
fn clone(_: *const ()) -> RawWaker {
no_op_raw_waker()
}
fn wake(_: *const ()) {}
fn wake_by_ref(_: *const ()) {}
fn drop(_: *const ()) {}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let waker = unsafe { Waker::from_raw(no_op_raw_waker()) };
let mut cx = Context::from_waker(&waker);
let mut fut = Box::pin(fut);
loop {
match Pin::as_mut(&mut fut).poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => std::thread::yield_now(),
}
}
}
// 4. 修改草稿
{
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(false);
d.enable_tun_mode = Some(true);
#[test]
fn test_draft_basic_flow() {
let verge = IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
};
let draft = Draft::new(verge);
// 读取正式数据data_arc
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
}
// 修改草稿(使用 edit_draft
draft.edit_draft(|d| {
d.enable_auto_launch = Some(false);
d.enable_tun_mode = Some(true);
});
// 正式数据未变
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
}
// 草稿已变
{
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(true));
}
// 提交草稿
draft.apply();
// 正式数据已更新
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
}
// 新一轮草稿并修改
draft.edit_draft(|d| {
d.enable_auto_launch = Some(true);
});
{
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(true));
assert_eq!(latest.enable_tun_mode, Some(true));
}
// 丢弃草稿
draft.discard();
// 丢弃后再次创建草稿,会从已提交重新 clone
{
draft.edit_draft(|d| {
// 原 committed 是 enable_auto_launch = Some(false)
assert_eq!(d.enable_auto_launch, Some(false));
// 再修改一下
d.enable_tun_mode = Some(false);
});
// 草稿中值已修改,但正式数据仍是 apply 后的值
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
}
}
// 正式数据未变
assert_eq!(draft.data_mut().enable_auto_launch, Some(true));
assert_eq!(draft.data_mut().enable_tun_mode, Some(false));
#[test]
fn test_arc_pointer_behavior_on_edit_and_apply() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
});
// 草稿已变
{
let latest = draft.latest_ref();
assert_eq!(latest.enable_auto_launch, Some(false));
// 初始 latest == committed
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed, &latest));
// 第一次 edit由于与 committed 共享Arc::make_mut 会克隆
draft.edit_draft(|d| d.enable_tun_mode = Some(true));
let committed_after_first_edit = draft.data_arc();
let draft_after_first_edit = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(
&committed_after_first_edit,
&draft_after_first_edit
));
// 提交会把 committed 指向草稿的 Arc
let prev_draft_ptr = std::sync::Arc::as_ptr(&draft_after_first_edit);
draft.apply();
let committed_after_apply = draft.data_arc();
assert_eq!(
std::sync::Arc::as_ptr(&committed_after_apply),
prev_draft_ptr
);
// 第二次编辑:此时草稿唯一持有(无其它引用),不应再克隆
// 获取草稿 Arc 的指针并立即丢弃本地引用,避免增加 strong_count
draft.edit_draft(|d| d.enable_auto_launch = Some(false));
let latest1 = draft.latest_arc();
let latest1_ptr = std::sync::Arc::as_ptr(&latest1);
drop(latest1); // 确保只有 Draft 内部持有草稿 Arc
// 再次编辑uniqueArc::make_mut 不应克隆)
draft.edit_draft(|d| d.enable_tun_mode = Some(false));
let latest2 = draft.latest_arc();
let latest2_ptr = std::sync::Arc::as_ptr(&latest2);
assert_eq!(latest1_ptr, latest2_ptr, "Unique edit should not clone Arc");
assert_eq!(latest2.enable_auto_launch, Some(false));
assert_eq!(latest2.enable_tun_mode, Some(false));
}
#[test]
fn test_discard_restores_latest_to_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 创建草稿并修改
draft.edit_draft(|d| d.enable_auto_launch = Some(true));
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(&committed, &latest));
// 丢弃草稿后 latest 应回到 committed
draft.discard();
let committed2 = draft.data_arc();
let latest2 = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed2, &latest2));
assert_eq!(latest2.enable_auto_launch, Some(false));
}
#[test]
fn test_edit_draft_returns_closure_result() {
let draft = Draft::new(IVerge::default());
let ret = draft.edit_draft(|d| {
d.enable_tun_mode = Some(true);
123usize
});
assert_eq!(ret, 123);
let latest = draft.latest_arc();
assert_eq!(latest.enable_tun_mode, Some(true));
}
// 5. 提交草稿
draft.apply();
#[test]
fn test_with_data_modify_ok_and_replaces_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 正式数据已更新
{
let data = draft.data_mut();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
// 使用 with_data_modify 异步(立即就绪)地更新 committed
let res = block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(true);
Ok((Box::new(*v), "done")) // Dereference v to get Box<T>
}));
assert_eq!(
{
#[allow(clippy::unwrap_used)]
res.unwrap()
},
"done"
);
let committed = draft.data_arc();
assert_eq!(committed.enable_auto_launch, Some(true));
assert_eq!(committed.enable_tun_mode, Some(false));
}
// 6. 新建并修改下一轮草稿
{
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(true);
#[test]
fn test_with_data_modify_error_propagation() {
let draft = Draft::new(IVerge::default());
#[allow(clippy::unwrap_used)]
let err = block_on_ready(draft.with_data_modify(|v| async move {
drop(v);
Err::<(Box<IVerge>, ()), _>(anyhow!("boom"))
}))
.unwrap_err();
assert_eq!(format!("{err}"), "boom");
}
assert_eq!(draft.draft_mut().enable_auto_launch, Some(true));
// 7. 丢弃草稿
draft.discard();
#[test]
fn test_with_data_modify_does_not_touch_existing_draft() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 8. 草稿已被丢弃,新的 draft_mut() 会重新 clone
assert_eq!(draft.draft_mut().enable_auto_launch, Some(false));
// 创建草稿并修改
draft.edit_draft(|d| {
d.enable_auto_launch = Some(true);
d.enable_tun_mode = Some(true);
});
let draft_before = draft.latest_arc();
let draft_before_ptr = std::sync::Arc::as_ptr(&draft_before);
// 同时通过 with_data_modify 修改 committed
#[allow(clippy::unwrap_used)]
block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(false); // 与草稿不同
Ok((Box::new(*v), ())) // Dereference v to get Box<T>
}))
.unwrap();
// 草稿应保持不变
let draft_after = draft.latest_arc();
assert_eq!(
std::sync::Arc::as_ptr(&draft_after),
draft_before_ptr,
"Existing draft should not be replaced by with_data_modify"
);
assert_eq!(draft_after.enable_auto_launch, Some(true));
assert_eq!(draft_after.enable_tun_mode, Some(true));
// 丢弃草稿后 latest == committed且 committed 为异步修改结果
draft.discard();
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(false));
}
}

View File

@@ -43,7 +43,7 @@ pub fn get_supported_languages() -> Vec<String> {
pub async fn current_language() -> String {
Config::verge()
.await
.latest_ref()
.latest_arc()
.language
.as_deref()
.map(String::from)

View File

@@ -31,7 +31,7 @@ pub async fn init_logger() -> Result<()> {
// TODO 提供 runtime 级别实时修改
let (log_level, log_max_size, log_max_count) = {
let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref();
let verge = verge_guard.latest_arc();
(
verge.get_log_level(),
verge.app_log_max_size.unwrap_or(128),
@@ -81,7 +81,7 @@ pub async fn init_logger() -> Result<()> {
pub async fn sidecar_writer() -> Result<FileLogWriter> {
let (log_max_size, log_max_count) = {
let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref();
let verge = verge_guard.latest_arc();
(
verge.app_log_max_size.unwrap_or(128),
verge.app_log_max_count.unwrap_or(8),
@@ -109,7 +109,7 @@ pub async fn sidecar_writer() -> Result<FileLogWriter> {
pub async fn service_writer_config() -> Result<WriterConfig> {
let (log_max_size, log_max_count) = {
let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref();
let verge = verge_guard.latest_arc();
(
verge.app_log_max_size.unwrap_or(128),
verge.app_log_max_count.unwrap_or(8),
@@ -134,7 +134,7 @@ pub async fn delete_log() -> Result<()> {
let auto_log_clean = {
let verge = Config::verge().await;
let verge = verge.latest_ref();
let verge = verge.latest_arc();
verge.auto_log_clean.unwrap_or(0)
};
@@ -509,7 +509,7 @@ pub async fn startup_script() -> Result<()> {
let app_handle = handle::Handle::app_handle();
let script_path = {
let verge = Config::verge().await;
let verge = verge.latest_ref();
let verge = verge.latest_arc();
verge.startup_script.clone().unwrap_or_else(|| "".into())
};

View File

@@ -165,10 +165,10 @@ impl NetworkManager {
ProxyType::None => None,
ProxyType::Localhost => {
let port = {
let verge_port = Config::verge().await.latest_ref().verge_mixed_port;
let verge_port = Config::verge().await.latest_arc().verge_mixed_port;
match verge_port {
Some(port) => port,
None => Config::clash().await.latest_ref().get_mixed_port(),
None => Config::clash().await.latest_arc().get_mixed_port(),
}
};
let proxy_scheme = format!("http://127.0.0.1:{port}");

View File

@@ -179,7 +179,7 @@ pub(super) async fn refresh_tray_menu() {
pub(super) async fn init_window() {
let is_silent_start = Config::verge()
.await
.latest_ref()
.latest_arc()
.enable_silent_start
.unwrap_or(false);
#[cfg(target_os = "macos")]

View File

@@ -22,7 +22,7 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
let app_handle = handle::Handle::app_handle();
let config = Config::verge().await;
let latest = config.latest_ref();
let latest = config.latest_arc();
let start_page = latest.start_page.as_deref().unwrap_or("/");
match tauri::WebviewWindowBuilder::new(

View File

@@ -89,15 +89,15 @@ pub fn embed_server() {
let clash_config = Config::clash().await;
let pac_content = verge_config
.latest_ref()
.latest_arc()
.pac_file_content
.clone()
.unwrap_or_else(|| DEFAULT_PAC.into());
let pac_port = verge_config
.latest_ref()
.latest_arc()
.verge_mixed_port
.unwrap_or_else(|| clash_config.latest_ref().get_mixed_port());
.unwrap_or_else(|| clash_config.latest_arc().get_mixed_port());
let pac = warp::path!("commands" / "pac").map(move || {
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));