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)?;
127//!
128//! // Load (eager)
129//! let loaded: GameState = Parcode::load("game_lib.par")?;
130//! # std::fs::remove_file("game_lib.par").ok();
131//! # Ok::<(), parcode::ParcodeError>(())
132//! ```
133//!
134//! ### Lazy Loading
135//!
136//! ```rust
137//! use parcode::{Parcode, ParcodeFile, ParcodeObject};
138//! use serde::{Serialize, Deserialize};
139//!
140//! #[derive(Serialize, Deserialize, ParcodeObject)]
141//! struct PlayerData {
142//! name: String,
143//! stats: Vec<u32>,
144//! }
145//!
146//! #[derive(Serialize, Deserialize, ParcodeObject)]
147//! struct GameState {
148//! level: u32,
149//! score: u64,
150//! #[parcode(chunkable)]
151//! player_data: PlayerData,
152//! }
153//!
154//! // Setup file
155//! let state = GameState {
156//! level: 1,
157//! score: 1000,
158//! player_data: PlayerData {
159//! name: "Hero".to_string(),
160//! stats: vec![10, 20, 30],
161//! },
162//! };
163//! Parcode::save("game_lazy.par", &state)?;
164//!
165//! let file = Parcode::open("game_lazy.par")?;
166//! let state_lazy = file.root::<GameState>()?;
167//!
168//! // Access local fields instantly (already in memory)
169//! println!("Level: {}", state_lazy.level);
170//!
171//! // Load remote fields on-demand
172//! let player_name = state_lazy.player_data.name;
173//! # std::fs::remove_file("game_lazy.par").ok();
174//! # Ok::<(), parcode::ParcodeError>(())
175//! ```
176//!
177//! ### `HashMap` Sharding
178//!
179//! ```rust
180//! use parcode::{Parcode, ParcodeFile, ParcodeObject};
181//! use serde::{Serialize, Deserialize};
182//! use std::collections::HashMap;
183//!
184//! #[derive(Serialize, Deserialize, ParcodeObject, Clone, Debug)]
185//! struct User { name: String }
186//!
187//! #[derive(Serialize, Deserialize, ParcodeObject, Debug)]
188//! struct Database {
189//! #[parcode(map)] // Enable O(1) lookups
190//! users: HashMap<u64, User>,
191//! }
192//!
193//! // Setup
194//! let mut users = HashMap::new();
195//! users.insert(12345, User { name: "Alice".to_string() });
196//! let db = Database { users };
197//! Parcode::save("db_map.par", &db)?;
198//!
199//! let file = Parcode::open("db_map.par")?;
200//! let db_lazy = file.root::<Database>()?;
201//! let user = db_lazy.users.get(&12345u64).expect("User not found");
202//! # std::fs::remove_file("db_map.par").ok();
203//! # Ok::<(), parcode::ParcodeError>(())
204//! ```
205//!
206//! ## Performance Considerations
207//!
208//! - **Write Performance:** Scales linearly with CPU cores for independent chunks
209//! - **Read Performance:** Memory-mapped I/O provides near-instant startup times
210//! - **Memory Usage:** Lazy loading keeps memory footprint minimal
211//! - **Compression:** Optional LZ4 compression (feature: `lz4_flex`) trades CPU for I/O bandwidth
212//!
213//! ### Safety and Error Handling
214//!
215//! Parcode is designed with safety as a priority:
216//!
217//! * **Encapsulated Unsafe:** `unsafe` code is used sparingly and only in the `reader` module
218//! to achieve zero-copy parallel stitching of vectors. These sections are strictly auditied.
219//! * **No Panics:** No `unwrap()` or `panic!()` calls in the library (enforced by clippy lints).
220//! * **Comprehensive Errors:** All failures correspond to a [`ParcodeError`] type.
221//! * **Robust I/O:** Mutex poisoning and partial writes are handled gracefully.
222
223#![deny(unsafe_code)]
224#![deny(clippy::unwrap_used)]
225#![deny(clippy::panic)]
226#![warn(missing_docs)]
227
228// --- PUBLIC API MODULES ---
229pub mod api;
230pub mod compression;
231pub mod error;
232pub mod format;
233pub mod inspector;
234pub mod reader;
235pub mod visitor;
236
237// --- INTERNAL IMPLEMENTATION MODULES (Hidden from Docs) ---
238#[doc(hidden)]
239pub mod executor;
240#[doc(hidden)]
241pub mod graph;
242#[doc(hidden)]
243pub mod io;
244#[doc(hidden)]
245pub mod map;
246
247// Private modules
248mod visitor_impls;
249
250// --- MACRO SUPPORT MODULES ---
251
252/// Runtime utilities used by the derived code.
253#[doc(hidden)]
254pub mod rt;
255
256/// Internal re-exports for the macro to ensure dependencies are available.
257#[doc(hidden)]
258pub mod internal {
259 pub use bincode;
260 pub use serde;
261}
262
263// --- RE-EXPORTS ---
264
265#[cfg(feature = "lz4_flex")]
266pub use compression::Lz4Compressor;
267pub use compression::{Compressor, NoCompression};
268
269pub use api::{Parcode, ParcodeOptions};
270pub use error::{ParcodeError, Result};
271pub use reader::{ParcodeFile, ParcodeItem, ParcodeNative};
272
273pub use parcode_derive::ParcodeObject;
274pub use rt::{ParcodeCollectionPromise, ParcodeMapPromise, ParcodePromise};
275
276/// Constants used throughout the library.
277pub mod constants {
278 /// The default buffer size for I/O operations.
279 pub const DEFAULT_BUFFER_SIZE: usize = 8 * 1024;
280}