parcode/
lib.rs

1//! # Parcode V4
2//!
3//! A high-performance, graph-based serialization library for Rust that enables lazy loading,
4//! zero-copy operations, and parallel processing of complex data structures.
5//!
6//! ## Overview
7//!
8//! Parcode is fundamentally different from traditional serialization libraries. Instead of treating
9//! data as a monolithic blob, Parcode analyzes the structural relationships within your data and
10//! creates a dependency graph where each node represents a serializable chunk. This architectural
11//! approach enables several powerful capabilities:
12//!
13//! ### Key Features
14//!
15//! *   **Parallel Serialization:** Independent chunks are serialized and compressed concurrently
16//!     using Rayon's work-stealing scheduler, maximizing CPU utilization.
17//! *   **Parallel Deserialization:** Reading operations leverage memory mapping and parallel
18//!     reconstruction to minimize I/O latency and maximize throughput.
19//! *   **Zero-Copy Operations:** Data is read directly from memory-mapped files where possible,
20//!     eliminating unnecessary allocations and copies.
21//! *   **Lazy Loading:** Navigate deep object hierarchies without loading data from disk. Only
22//!     the metadata is read initially, and actual data is loaded on-demand.
23//! *   **Surgical Access:** Load only specific fields, vector elements, or map entries without
24//!     deserializing the entire structure.
25//! *   **O(1) Map Lookups:** Hash-based sharding enables constant-time lookups in large `HashMaps`
26//!     without loading the entire collection.
27//! *   **Streaming/Partial Reads:** Large collections can be iterated over without loading the
28//!     entire dataset into RAM.
29//! *   **Generic I/O:** Serialize to any destination implementing `std::io::Write` (Files, Memory Buffers, Network Streams).
30//! *   **Forensic Inspection:** Built-in tools to analyze the structure of Parcode files without deserializing them.
31//!
32//! ## Architecture
33//!
34//! ### The Graph Model
35//!
36//! When you serialize an object with Parcode, the library constructs a directed acyclic graph (DAG)
37//! where:
38//! - Each node represents a serializable chunk of data
39//! - Edges represent dependencies (parent-child relationships)
40//! - Leaf nodes contain primitive data or small collections
41//! - Internal nodes contain metadata and references to their children
42//!
43//! This graph is then executed bottom-up: leaves are processed first, and parents are processed
44//! only after all their children have completed. This enables maximum parallelism while maintaining
45//! data integrity.
46//!
47//! ### File Format (V4)
48//!
49//! The physical layout on disk follows a "children-first" ordering:
50//! ```text
51//! [Leaf Chunk 1] [Leaf Chunk 2] ... [Parent Chunk] [Root Chunk] [Global Header]
52//! ```
53//!
54//! Each chunk is self-contained with:
55//! ```text
56//! [Payload] [Children Table (Optional)] [MetaByte]
57//! ```
58//!
59//! The Global Header at the end of the file points to the Root Chunk, which serves as the
60//! entry point for reading operations.
61//!
62//! ## Core Concepts
63//!
64//! ### `TaskGraph`
65//!
66//! The [`graph::TaskGraph`] is the central structure representing the object graph to be serialized.
67//! It acts as an arena allocator for nodes and manages the dependency relationships between them.
68//! The graph lifetime is tied to the input data, enabling zero-copy serialization.
69//!
70//! ### Executor
71//!
72//! The [`executor`] module contains the engine that drives parallel execution of the graph.
73//! It orchestrates the serialization → compression → I/O pipeline, using atomic operations
74//! for lock-free dependency tracking and Rayon for work distribution.
75//!
76//! ### Inspector
77//!
78//! The [`inspector`] module provides tools for forensic analysis of Parcode files. It allows you to
79//! visualize the internal chunk structure, compression ratios, and data distribution without
80//! needing to deserialize the actual payloads.
81//!
82//! ### Reader
83//!
84//! The [`ParcodeFile`] is responsible for memory-mapping the file and reconstructing objects.
85//! It provides both eager (full deserialization) and lazy (on-demand) reading strategies,
86//! automatically selecting the optimal approach based on the data type.
87//!
88//! ### Visitor
89//!
90//! The [`visitor::ParcodeVisitor`] trait allows types to define how they should be decomposed
91//! into graph nodes. The `#[derive(ParcodeObject)]` macro automatically implements this trait,
92//! analyzing field attributes to determine which fields should be inlined vs. stored as
93//! separate chunks.
94//!
95//! ## Usage Patterns
96//!
97//! ### Basic Serialization
98//!
99//! ```rust
100//! use parcode::{Parcode, ParcodeObject};
101//! use serde::{Serialize, Deserialize};
102//!
103//! #[derive(Serialize, Deserialize, ParcodeObject)]
104//! struct PlayerData {
105//!     name: String,
106//!     stats: Vec<u32>,
107//! }
108//!
109//! #[derive(Serialize, Deserialize, ParcodeObject)]
110//! struct GameState {
111//!     level: u32,
112//!     score: u64,
113//!     #[parcode(chunkable)]
114//!     player_data: PlayerData,
115//! }
116//!
117//! // Save
118//! let state = GameState {
119//!     level: 1,
120//!     score: 1000,
121//!     player_data: PlayerData {
122//!         name: "Hero".to_string(),
123//!         stats: vec![10, 20, 30],
124//!     },
125//! };
126//! Parcode::save("game_lib.par", &state).unwrap();
127//!
128//! // Load (eager)
129//! let loaded: GameState = Parcode::load("game_lib.par").unwrap();
130//! # std::fs::remove_file("game_lib.par").ok();
131//! ```
132//!
133//! ### Lazy Loading
134//!
135//! ```rust
136//! use parcode::{Parcode, ParcodeFile, ParcodeObject};
137//! use serde::{Serialize, Deserialize};
138//!
139//! #[derive(Serialize, Deserialize, ParcodeObject)]
140//! struct PlayerData {
141//!     name: String,
142//!     stats: Vec<u32>,
143//! }
144//!
145//! #[derive(Serialize, Deserialize, ParcodeObject)]
146//! struct GameState {
147//!     level: u32,
148//!     score: u64,
149//!     #[parcode(chunkable)]
150//!     player_data: PlayerData,
151//! }
152//!
153//! // Setup file
154//! let state = GameState {
155//!     level: 1,
156//!     score: 1000,
157//!     player_data: PlayerData {
158//!         name: "Hero".to_string(),
159//!         stats: vec![10, 20, 30],
160//!     },
161//! };
162//! Parcode::save("game_lazy.par", &state).unwrap();
163//!
164//! let file = Parcode::open("game_lazy.par").unwrap();
165//! let state_lazy = file.root::<GameState>().unwrap();
166//!
167//! // Access local fields instantly (already in memory)
168//! println!("Level: {}", state_lazy.level);
169//!
170//! // Load remote fields on-demand
171//! let player_name = state_lazy.player_data.name;
172//! # std::fs::remove_file("game_lazy.par").ok();
173//! ```
174//!
175//! ### `HashMap` Sharding
176//!
177//! ```rust
178//! use parcode::{Parcode, ParcodeFile, ParcodeObject};
179//! use serde::{Serialize, Deserialize};
180//! use std::collections::HashMap;
181//!
182//! #[derive(Serialize, Deserialize, ParcodeObject, Clone, Debug)]
183//! struct User { name: String }
184//!
185//! #[derive(Serialize, Deserialize, ParcodeObject, Debug)]
186//! struct Database {
187//!     #[parcode(map)]  // Enable O(1) lookups
188//!     users: HashMap<u64, User>,
189//! }
190//!
191//! // Setup
192//! let mut users = HashMap::new();
193//! users.insert(12345, User { name: "Alice".to_string() });
194//! let db = Database { users };
195//! Parcode::save("db_map.par", &db).unwrap();
196//!
197//! let file = Parcode::open("db_map.par").unwrap();
198//! let db_lazy = file.root::<Database>().unwrap();
199//! let user = db_lazy.users.get(&12345u64).expect("User not found");
200//! # std::fs::remove_file("db_map.par").ok();
201//! ```
202//!
203//! ## Performance Considerations
204//!
205//! - **Write Performance:** Scales linearly with CPU cores for independent chunks
206//! - **Read Performance:** Memory-mapped I/O provides near-instant startup times
207//! - **Memory Usage:** Lazy loading keeps memory footprint minimal
208//! - **Compression:** Optional LZ4 compression (feature: `lz4_flex`) trades CPU for I/O bandwidth
209//!
210//! ### Safety and Error Handling
211//!
212//! Parcode is designed with safety as a priority:
213//!
214//! * **Encapsulated Unsafe:** `unsafe` code is used sparingly and only in the `reader` module
215//!   to achieve zero-copy parallel stitching of vectors. These sections are strictly auditied.
216//! * **No Panics:** No `unwrap()` or `panic!()` calls in the library (enforced by clippy lints).
217//! * **Comprehensive Errors:** All failures correspond to a [`ParcodeError`] type.
218//! * **Robust I/O:** Mutex poisoning and partial writes are handled gracefully.
219
220#![deny(unsafe_code)]
221#![deny(clippy::unwrap_used)]
222#![deny(clippy::panic)]
223#![warn(missing_docs)]
224
225// --- PUBLIC API MODULES ---
226pub mod api;
227pub mod compression;
228pub mod error;
229pub mod format;
230pub mod inspector;
231pub mod reader;
232pub mod visitor;
233
234// --- INTERNAL IMPLEMENTATION MODULES (Hidden from Docs) ---
235#[doc(hidden)]
236pub mod executor;
237#[doc(hidden)]
238pub mod graph;
239#[doc(hidden)]
240pub mod io;
241#[doc(hidden)]
242pub mod map;
243
244// Private modules
245mod visitor_impls;
246
247// --- MACRO SUPPORT MODULES ---
248
249/// Runtime utilities used by the derived code.
250#[doc(hidden)]
251pub mod rt;
252
253/// Internal re-exports for the macro to ensure dependencies are available.
254#[doc(hidden)]
255pub mod internal {
256    pub use bincode;
257    pub use serde;
258}
259
260// --- RE-EXPORTS ---
261
262#[cfg(feature = "lz4_flex")]
263pub use compression::Lz4Compressor;
264pub use compression::{Compressor, NoCompression};
265
266pub use api::{Parcode, ParcodeOptions};
267pub use error::{ParcodeError, Result};
268pub use reader::{ParcodeFile, ParcodeItem, ParcodeNative};
269
270pub use parcode_derive::ParcodeObject;
271pub use rt::{ParcodeCollectionPromise, ParcodeMapPromise, ParcodePromise};
272
273/// Constants used throughout the library.
274pub mod constants {
275    /// The default buffer size for I/O operations.
276    pub const DEFAULT_BUFFER_SIZE: usize = 8 * 1024;
277}