From f3b7620259682a5afc511556209e1fdd45c238de Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 24 Feb 2026 18:35:03 +0800 Subject: Move sheet file R/W to v1 module --- systems/sheet/Cargo.toml | 2 +- systems/sheet/macros/src/lib.rs | 41 ++++---- systems/sheet/src/mapping.rs | 33 +++++++ systems/sheet/src/sheet.rs | 184 +++++++++++++++++++++++++++++------ systems/sheet/src/sheet/current.rs | 5 + systems/sheet/src/sheet/error.rs | 19 ++-- systems/sheet/src/sheet/reader.rs | 19 ++++ systems/sheet/src/sheet/v1.rs | 6 ++ systems/sheet/src/sheet/v1/reader.rs | 9 +- systems/sheet/src/sheet/v1/test.rs | 2 +- systems/sheet/src/sheet/v1/writer.rs | 8 +- systems/sheet/src/sheet/writer.rs | 8 ++ 12 files changed, 272 insertions(+), 64 deletions(-) create mode 100644 systems/sheet/src/sheet/current.rs create mode 100644 systems/sheet/src/sheet/reader.rs create mode 100644 systems/sheet/src/sheet/v1.rs create mode 100644 systems/sheet/src/sheet/writer.rs (limited to 'systems/sheet') diff --git a/systems/sheet/Cargo.toml b/systems/sheet/Cargo.toml index 074f511..657c567 100644 --- a/systems/sheet/Cargo.toml +++ b/systems/sheet/Cargo.toml @@ -13,7 +13,7 @@ asset_system = { path = "../_asset" } tokio = { version = "1.48", features = ["full"] } thiserror = "1.0.69" -just_fmt = "0.1" +just_fmt = "0.1.2" memmap2 = "0.9" sha2 = "0.10.8" diff --git a/systems/sheet/macros/src/lib.rs b/systems/sheet/macros/src/lib.rs index b485a82..c06f25e 100644 --- a/systems/sheet/macros/src/lib.rs +++ b/systems/sheet/macros/src/lib.rs @@ -3,8 +3,6 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::parse_str; -const INDEX_SOURCE_BUF: &str = - "just_enough_vcs::system::sheet_system::index_source::IndexSourceBuf"; const INDEX_SOURCE: &str = "just_enough_vcs::system::sheet_system::index_source::IndexSource"; const LOCAL_MAPPING_PATH: &str = "just_enough_vcs::system::sheet_system::mapping::LocalMapping"; @@ -38,7 +36,7 @@ fn parse_sheet_path(input: &str) -> Result<(String, Vec), String> { } /// Parse strings in the format "id/ver" -fn parse_id_version(input: &str) -> Result<(String, String), String> { +fn parse_id_version(input: &str) -> Result<(u32, u16), String> { let parts: Vec<&str> = input.split('/').collect(); if parts.len() != 2 { return Err(format!( @@ -47,16 +45,23 @@ fn parse_id_version(input: &str) -> Result<(String, String), String> { )); } - let id = parts[0].trim().to_string(); - let ver = parts[1].trim().to_string(); + let id_str = parts[0].trim(); + let ver_str = parts[1].trim(); - if id.is_empty() { + if id_str.is_empty() { return Err("ID cannot be empty".to_string()); } - if ver.is_empty() { + if ver_str.is_empty() { return Err("Version cannot be empty".to_string()); } + let id = id_str + .parse::() + .map_err(|e| format!("Failed to parse id as u32: {}", e))?; + let ver = ver_str + .parse::() + .map_err(|e| format!("Failed to parse version as u16: {}", e))?; + Ok((id, ver)) } @@ -121,14 +126,14 @@ pub fn mapping_buf(input: TokenStream) -> TokenStream { let mapping_buf_path: syn::Path = parse_str(MAPPING_BUF_PATH).expect("Failed to parse MAPPING_BUF_PATH"); - let index_source_buf_path: syn::Path = - parse_str(INDEX_SOURCE_BUF).expect("Failed to parse INDEX_SOURCE_BUF"); + let index_source_path: syn::Path = + parse_str(INDEX_SOURCE).expect("Failed to parse INDEX_SOURCE"); let expanded = quote! { #mapping_buf_path::new( #sheet.to_string(), #path_vec_tokens, - #index_source_buf_path::new(#id.to_string(), #ver.to_string()) + #index_source_path::new(#id, #ver) ) }; @@ -198,9 +203,9 @@ pub fn mapping(input: TokenStream) -> TokenStream { } enum LocalMappingParts { - Latest(String, String, String), - Version(String, String, String), - WithRef(String, String, String, String), + Latest(String, u32, u16), + Version(String, u32, u16), + WithRef(String, u32, u16, String), } impl LocalMappingParts { @@ -317,8 +322,8 @@ pub fn local_mapping(input: TokenStream) -> TokenStream { parse_str(LOCAL_MAPPING_PATH).expect("Failed to parse LOCAL_MAPPING_PATH"); let local_mapping_forward_path: syn::Path = parse_str(LOCAL_MAPPING_FORWARD_PATH).expect("Failed to parse LOCAL_MAPPING_FORWARD_PATH"); - let index_source_buf_path: syn::Path = - parse_str(INDEX_SOURCE_BUF).expect("Failed to parse INDEX_SOURCE_BUF"); + let index_source_path: syn::Path = + parse_str(INDEX_SOURCE).expect("Failed to parse INDEX_SOURCE"); match parts { LocalMappingParts::Latest(path_str, id, ver) => { @@ -328,7 +333,7 @@ pub fn local_mapping(input: TokenStream) -> TokenStream { let expanded = quote! { #local_mapping_path::new( #path_vec_tokens, - #index_source_buf_path::new(#id.to_string(), #ver.to_string()), + #index_source_path::new(#id, #ver), #local_mapping_forward_path::Latest ) }; @@ -342,7 +347,7 @@ pub fn local_mapping(input: TokenStream) -> TokenStream { let expanded = quote! { #local_mapping_path::new( #path_vec_tokens, - #index_source_buf_path::new(#id.to_string(), #ver.to_string()), + #index_source_path::new(#id, #ver), #local_mapping_forward_path::Version { version_name: #ver.to_string() } @@ -358,7 +363,7 @@ pub fn local_mapping(input: TokenStream) -> TokenStream { let expanded = quote! { #local_mapping_path::new( #path_vec_tokens, - #index_source_buf_path::new(#id.to_string(), #ver.to_string()), + #index_source_path::new(#id, #ver), #local_mapping_forward_path::Ref { sheet_name: #ref_name.to_string() } diff --git a/systems/sheet/src/mapping.rs b/systems/sheet/src/mapping.rs index 0a72a69..1743534 100644 --- a/systems/sheet/src/mapping.rs +++ b/systems/sheet/src/mapping.rs @@ -507,6 +507,11 @@ impl LocalMapping { pub fn set_forward(&mut self, forward: &LocalMappingForward) { self.forward = forward.clone(); } + + /// Replace the forward direction of the current LocalMapping + pub fn replace_forward(&mut self, forward: LocalMappingForward) { + self.forward = forward; + } } #[inline(always)] @@ -591,3 +596,31 @@ impl std::borrow::Borrow> for MappingBuf { &self.val } } + +/// Convert a string into a Node suitable for Mapping +/// +/// # Examples +/// +/// ``` +/// # use sheet_system::mapping::node; +/// assert_eq!( +/// node("./home/path1/"), +/// vec!["home".to_string(), "path1".to_string(), "".to_string()] +/// ); +/// assert_eq!( +/// node("./home/path2"), +/// vec!["home".to_string(), "path2".to_string()] +/// ); +/// assert_eq!( +/// node(".\\home\\path3\\"), +/// vec!["home".to_string(), "path3".to_string(), "".to_string()] +/// ); +/// assert_eq!( +/// node("home/path4"), +/// vec!["home".to_string(), "path4".to_string()] +/// ); +/// ``` +pub fn node(str: impl Into) -> Vec { + let str = just_fmt::fmt_path::fmt_path_str(str).unwrap_or_default(); + str.split("/").into_iter().map(|f| f.to_string()).collect() +} diff --git a/systems/sheet/src/sheet.rs b/systems/sheet/src/sheet.rs index 68c4c78..44c2878 100644 --- a/systems/sheet/src/sheet.rs +++ b/systems/sheet/src/sheet.rs @@ -1,6 +1,7 @@ use std::{ collections::HashSet, fs::File, + mem::replace, path::{Path, PathBuf}, }; @@ -11,19 +12,18 @@ use crate::{ index_source::IndexSource, mapping::{LocalMapping, LocalMappingForward, Mapping, MappingBuf}, sheet::{ - error::{ReadSheetDataError, SheetEditError}, + error::{ReadSheetDataError, SheetApplyError, SheetEditError}, reader::{read_mapping, read_sheet_data}, writer::convert_sheet_data_to_bytes, }, }; -pub mod constants; pub mod error; pub mod reader; pub mod writer; -#[cfg(test)] -pub mod test; +// Format +pub mod v1; #[derive(Default, Debug, Clone, PartialEq)] pub struct Sheet { @@ -59,6 +59,21 @@ pub struct SheetDataMmap { pub struct SheetEdit { /// Edit history list: Vec, + + /// List of lost nodes + lost_nodes: HashSet>, + + /// List of filled nodes + filled_nodes: HashSet>, +} + +impl SheetEdit { + /// Clear current SheetEdit information + pub fn clear(&mut self) { + self.list.clear(); + self.lost_nodes.clear(); + self.filled_nodes.clear(); + } } #[derive(Default, Debug, Clone, PartialEq)] @@ -119,7 +134,7 @@ impl std::fmt::Display for SheetEditItem { ) } SheetEditItem::EraseMapping { node } => { - write!(f, "Earse \"{}\"", display_node_helper(node),) + write!(f, "erase \"{}\"", display_node_helper(node),) } SheetEditItem::InsertMapping { mapping } => { write!(f, "Insert {}", mapping.to_string()) @@ -207,7 +222,7 @@ impl SheetData { Sheet { name: sheet_name.into(), data: self, - edit: SheetEdit { list: Vec::new() }, + edit: SheetEdit::default(), } } } @@ -244,8 +259,16 @@ impl Sheet { self.data } - /// Check if a mapping exists in the Sheet + /// Check if a mapping exists in this sheet (including unapplied edits) + /// The difference from `contains_mapping_actual` is: + /// This method will consider edit operations that have not yet been `apply()`-ed pub fn contains_mapping(&self, value: &Vec) -> bool { + self.data.contains_mapping(value) && !self.edit.lost_nodes.contains(value) + || self.edit.filled_nodes.contains(value) + } + + /// Check if a mapping actually exists in this sheet + pub fn contains_mapping_actual(&self, value: &Vec) -> bool { self.data.contains_mapping(value) } @@ -262,20 +285,30 @@ impl Sheet { } } - /// Insert mapping modification - pub fn insert_mapping( + /// Insert mapping move + pub fn move_mapping( &mut self, - mapping: impl Into, + from_node: Vec, + to_node: Vec, ) -> Result<(), SheetEditError> { - self.edit.list.push(SheetEditItem::InsertMapping { - mapping: mapping.into(), - }); - Ok(()) - } + if !self.contains_mapping(&from_node) { + return Err(SheetEditError::NodeNotFound(from_node.join("/"))); + } + + if self.contains_mapping(&to_node) { + return Err(SheetEditError::NodeAlreadyExist(to_node.join("/"))); + } + + self.edit.filled_nodes.remove(&from_node); + self.edit.lost_nodes.remove(&to_node); + + self.edit.lost_nodes.insert(from_node.clone()); + self.edit.filled_nodes.insert(to_node.clone()); + + self.edit + .list + .push(SheetEditItem::Move { from_node, to_node }); - /// Insert mapping erasure - pub fn earse_mapping(&mut self, node: Vec) -> Result<(), SheetEditError> { - self.edit.list.push(SheetEditItem::EraseMapping { node }); Ok(()) } @@ -285,19 +318,49 @@ impl Sheet { node_a: Vec, node_b: Vec, ) -> Result<(), SheetEditError> { + if !self.contains_mapping(&node_a) { + return Err(SheetEditError::NodeNotFound(node_a.join("/"))); + } + + if !self.contains_mapping(&node_b) { + return Err(SheetEditError::NodeNotFound(node_b.join("/"))); + } + self.edit.list.push(SheetEditItem::Swap { node_a, node_b }); Ok(()) } - /// Insert mapping move - pub fn move_mapping( + /// Insert mapping erasure + pub fn erase_mapping(&mut self, node: Vec) -> Result<(), SheetEditError> { + if !self.contains_mapping(&node) { + return Err(SheetEditError::NodeNotFound(node.join("/"))); + } + + self.edit.filled_nodes.remove(&node); + self.edit.lost_nodes.insert(node.clone()); + + self.edit.list.push(SheetEditItem::EraseMapping { node }); + Ok(()) + } + + /// Insert mapping modification + pub fn insert_mapping( &mut self, - from_node: Vec, - to_node: Vec, + mapping: impl Into, ) -> Result<(), SheetEditError> { + let mapping = mapping.into(); + let node = mapping.value(); + + if self.contains_mapping(node) { + return Err(SheetEditError::NodeAlreadyExist(node.join("/"))); + } + + self.edit.lost_nodes.remove(node); + self.edit.filled_nodes.insert(node.clone()); + self.edit .list - .push(SheetEditItem::Move { from_node, to_node }); + .push(SheetEditItem::InsertMapping { mapping }); Ok(()) } @@ -307,6 +370,10 @@ impl Sheet { node: Vec, source: IndexSource, ) -> Result<(), SheetEditError> { + if !self.contains_mapping(&node) { + return Err(SheetEditError::NodeNotFound(node.join("/"))); + } + self.edit .list .push(SheetEditItem::ReplaceSource { node, source }); @@ -319,6 +386,10 @@ impl Sheet { node: Vec, forward: LocalMappingForward, ) -> Result<(), SheetEditError> { + if !self.contains_mapping(&node) { + return Err(SheetEditError::NodeNotFound(node.join("/"))); + } + self.edit .list .push(SheetEditItem::UpdateForward { node, forward }); @@ -326,13 +397,70 @@ impl Sheet { } /// Apply changes - pub fn apply(&mut self) { - // Logic for applying changes - todo!(); + pub fn apply(&mut self) -> Result<(), SheetApplyError> { + let items = replace(&mut self.edit.list, Vec::new()); + for item in items { + match item { + SheetEditItem::DoNothing => continue, + SheetEditItem::Move { from_node, to_node } => { + let mut moved = + self.data.mappings.take(&from_node).ok_or_else(|| { + SheetApplyError::UnexpectedNotFound(from_node.join("/")) + })?; + moved.set_value(to_node); + self.data.mappings.insert(moved); + } + SheetEditItem::Swap { node_a, node_b } => { + let mut a = self + .data + .mappings + .take(&node_a) + .ok_or_else(|| SheetApplyError::UnexpectedNotFound(node_a.join("/")))?; + let mut b = self + .data + .mappings + .take(&node_b) + .ok_or_else(|| SheetApplyError::UnexpectedNotFound(node_b.join("/")))?; + let val_a = a.value().clone(); + let val_b = b.value().clone(); + a.set_value(val_b); + b.set_value(val_a); + } + SheetEditItem::EraseMapping { node } => { + if !self.data.mappings.remove(&node) { + return Err(SheetApplyError::UnexpectedNotFound(node.join("/"))); + } + } + SheetEditItem::InsertMapping { mapping } => { + let node = mapping.value().clone(); + if !self.data.mappings.insert(mapping) { + return Err(SheetApplyError::UnexpectedAlreadyExist(node.join("/"))); + } + } + SheetEditItem::ReplaceSource { node, source } => { + let mut target = self + .data + .mappings + .take(&node) + .ok_or_else(|| SheetApplyError::UnexpectedNotFound(node.join("/")))?; + target.replace_source(source); + self.data.mappings.insert(target); + } + SheetEditItem::UpdateForward { node, forward } => { + let mut target = self + .data + .mappings + .take(&node) + .ok_or_else(|| SheetApplyError::UnexpectedNotFound(node.join("/")))?; + target.replace_forward(forward); + self.data.mappings.insert(target); + } + } + } // Clear the edit list - #[allow(unreachable_code)] // Note: Remove after todo!() is completed - self.edit.list.clear(); + self.edit.clear(); + Ok(()) } } diff --git a/systems/sheet/src/sheet/current.rs b/systems/sheet/src/sheet/current.rs new file mode 100644 index 0000000..0efc993 --- /dev/null +++ b/systems/sheet/src/sheet/current.rs @@ -0,0 +1,5 @@ +#[allow(unused_imports)] +use crate::sheet::v1::reader; + +#[allow(unused_imports)] +use crate::sheet::v1::writer; diff --git a/systems/sheet/src/sheet/error.rs b/systems/sheet/src/sheet/error.rs index 79f7214..3e5661a 100644 --- a/systems/sheet/src/sheet/error.rs +++ b/systems/sheet/src/sheet/error.rs @@ -1,12 +1,19 @@ -use crate::sheet::SheetEditItem; - #[derive(Debug, thiserror::Error)] pub enum SheetEditError { - #[error("Edit `{0}` Failed: Node already exists: `{1}`")] - NodeAlreadyExist(SheetEditItem, String), + #[error("Edit Failed: Node already exists: `{0}`")] + NodeAlreadyExist(String), + + #[error("Edit Failed: Node not found: `{0}`")] + NodeNotFound(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum SheetApplyError { + #[error("Node already exists: `{0}` (Unexpected)")] + UnexpectedAlreadyExist(String), - #[error("Edit `{0}` Failed: Node not found: `{1}`")] - NodeNotFound(SheetEditItem, String), + #[error("Unexpected error: Node not found: `{0}` (Unexpected)")] + UnexpectedNotFound(String), } #[derive(Debug, thiserror::Error)] diff --git a/systems/sheet/src/sheet/reader.rs b/systems/sheet/src/sheet/reader.rs new file mode 100644 index 0000000..87347bb --- /dev/null +++ b/systems/sheet/src/sheet/reader.rs @@ -0,0 +1,19 @@ +use crate::{ + mapping::{LocalMappingForward, Mapping}, + sheet::{SheetData, error::ReadSheetDataError}, +}; + +include!("current.rs"); + +/// Reconstruct complete SheetData from full sheet data +pub fn read_sheet_data(full_sheet_data: &[u8]) -> Result { + reader::read_sheet_data(full_sheet_data) +} + +/// Read mapping information for a specific node from complete sheet data +pub fn read_mapping<'a>( + full_sheet_data: &'a [u8], + node: &[&str], +) -> Result, LocalMappingForward)>, ReadSheetDataError> { + reader::read_mapping(full_sheet_data, node) +} diff --git a/systems/sheet/src/sheet/v1.rs b/systems/sheet/src/sheet/v1.rs new file mode 100644 index 0000000..c32f92c --- /dev/null +++ b/systems/sheet/src/sheet/v1.rs @@ -0,0 +1,6 @@ +pub mod constants; +pub mod reader; +pub mod writer; + +#[cfg(test)] +pub mod test; diff --git a/systems/sheet/src/sheet/v1/reader.rs b/systems/sheet/src/sheet/v1/reader.rs index d86b097..e23e91b 100644 --- a/systems/sheet/src/sheet/v1/reader.rs +++ b/systems/sheet/src/sheet/v1/reader.rs @@ -3,16 +3,16 @@ use crate::{ mapping::{LocalMapping, LocalMappingForward, Mapping}, sheet::{ SheetData, - constants::{ + error::ReadSheetDataError, + v1::constants::{ CURRENT_SHEET_VERSION, HEADER_SIZE, INDEX_ENTRY_SIZE, MAPPING_BUCKET_MIN_SIZE, MAPPING_DIR_ENTRY_SIZE, }, - error::ReadSheetDataError, + v1::writer::calculate_path_hash, }, }; use std::collections::HashSet; -/// Reconstruct complete SheetData from full sheet data pub fn read_sheet_data(full_sheet_data: &[u8]) -> Result { if full_sheet_data.len() < HEADER_SIZE { return Err(std::io::Error::new( @@ -125,7 +125,6 @@ pub fn read_sheet_data(full_sheet_data: &[u8]) -> Result( full_sheet_data: &'a [u8], node: &[&str], @@ -176,7 +175,7 @@ pub fn read_mapping<'a>( // Calculate hash prefix for target node let node_path: Vec = node.iter().map(|s| s.to_string()).collect(); - let target_hash = crate::sheet::writer::calculate_path_hash(&node_path); + let target_hash = calculate_path_hash(&node_path); let target_bucket_key = target_hash >> 24; // Take high 8 bits as bucket key // Find corresponding bucket in mapping directory using binary search diff --git a/systems/sheet/src/sheet/v1/test.rs b/systems/sheet/src/sheet/v1/test.rs index ae20be5..dfba3c8 100644 --- a/systems/sheet/src/sheet/v1/test.rs +++ b/systems/sheet/src/sheet/v1/test.rs @@ -4,7 +4,7 @@ use crate::{ index_source::IndexSource, mapping::{LocalMapping, LocalMappingForward}, sheet::{ - SheetData, constants::HEADER_SIZE, reader::read_sheet_data, + SheetData, reader::read_sheet_data, v1::constants::HEADER_SIZE, writer::convert_sheet_data_to_bytes, }, }; diff --git a/systems/sheet/src/sheet/v1/writer.rs b/systems/sheet/src/sheet/v1/writer.rs index 5d9b257..00f0987 100644 --- a/systems/sheet/src/sheet/v1/writer.rs +++ b/systems/sheet/src/sheet/v1/writer.rs @@ -1,13 +1,12 @@ use crate::index_source::IndexSource; use crate::mapping::LocalMapping; use crate::sheet::SheetData; -use crate::sheet::constants::{ +use crate::sheet::v1::constants::{ CURRENT_SHEET_VERSION, HEADER_SIZE, INDEX_ENTRY_SIZE, MAPPING_DIR_ENTRY_SIZE, }; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, HashMap}; -/// Convert SheetData to byte array pub fn convert_sheet_data_to_bytes(sheet_data: SheetData) -> Vec { // Collect all mappings let mappings: Vec = sheet_data.mappings.into_iter().collect(); @@ -97,7 +96,6 @@ pub fn convert_sheet_data_to_bytes(sheet_data: SheetData) -> Vec { result } -/// Calculate path hash (SHA256, take first 4 bytes) pub fn calculate_path_hash(path: &[String]) -> u32 { let mut hasher = Sha256::new(); for segment in path { @@ -156,7 +154,7 @@ fn serialize_path(path: &[String]) -> Vec { /// Test only: Calculate single mapping bucket entry size #[cfg(test)] fn calculate_mapping_bucket_size(mapping: &LocalMapping) -> usize { - use crate::sheet::constants::MAPPING_BUCKET_MIN_SIZE; + use crate::sheet::v1::constants::MAPPING_BUCKET_MIN_SIZE; let path_size = serialize_path(mapping.value()).len(); let (_, forward_info_len, _) = mapping.forward().unpack(); @@ -167,7 +165,7 @@ fn calculate_mapping_bucket_size(mapping: &LocalMapping) -> usize { #[cfg(test)] mod tests { use super::*; - use crate::{mapping::LocalMappingForward, sheet::constants::MAPPING_BUCKET_MIN_SIZE}; + use crate::{mapping::LocalMappingForward, sheet::v1::constants::MAPPING_BUCKET_MIN_SIZE}; #[test] fn test_serialize_path() { diff --git a/systems/sheet/src/sheet/writer.rs b/systems/sheet/src/sheet/writer.rs new file mode 100644 index 0000000..d9e31d7 --- /dev/null +++ b/systems/sheet/src/sheet/writer.rs @@ -0,0 +1,8 @@ +use crate::sheet::SheetData; + +include!("current.rs"); + +/// Convert SheetData to byte array +pub fn convert_sheet_data_to_bytes(sheet_data: SheetData) -> Vec { + writer::convert_sheet_data_to_bytes(sheet_data) +} -- cgit