|
| 1 | +//! # Doublets Implementation |
| 2 | +//! |
| 3 | +//! This module documents how Doublets implements the common `Doublets<T>` interface. |
| 4 | +//! Each operation is implemented using direct memory access to specialized data structures. |
| 5 | +//! |
| 6 | +//! ## Storage Types |
| 7 | +//! |
| 8 | +//! ### United (Unit) Store |
| 9 | +//! |
| 10 | +//! ```rust,ignore |
| 11 | +//! // Volatile (in-memory): |
| 12 | +//! let store = unit::Store::new(Alloc::new(Global))?; |
| 13 | +//! |
| 14 | +//! // Non-volatile (file-mapped): |
| 15 | +//! let store = unit::Store::new(FileMapped::new(file)?)?; |
| 16 | +//! ``` |
| 17 | +//! |
| 18 | +//! Each link is stored as a contiguous `LinkPart<T>`: |
| 19 | +//! ```text |
| 20 | +//! +--------+--------+--------+ |
| 21 | +//! | id | source | target | |
| 22 | +//! +--------+--------+--------+ |
| 23 | +//! ``` |
| 24 | +//! |
| 25 | +//! ### Split Store |
| 26 | +//! |
| 27 | +//! ```rust,ignore |
| 28 | +//! // Volatile (in-memory): |
| 29 | +//! let store = split::Store::new( |
| 30 | +//! Alloc::new(Global), // data |
| 31 | +//! Alloc::new(Global), // index |
| 32 | +//! )?; |
| 33 | +//! |
| 34 | +//! // Non-volatile (file-mapped): |
| 35 | +//! let store = split::Store::new( |
| 36 | +//! FileMapped::new(data_file)?, |
| 37 | +//! FileMapped::new(index_file)?, |
| 38 | +//! )?; |
| 39 | +//! ``` |
| 40 | +//! |
| 41 | +//! Separates data and indexes for better cache efficiency: |
| 42 | +//! ```text |
| 43 | +//! DataPart: IndexPart: |
| 44 | +//! +--------+--------+ +----------------+ |
| 45 | +//! | source | target | | source_tree | |
| 46 | +//! +--------+--------+ | target_tree | |
| 47 | +//! +----------------+ |
| 48 | +//! ``` |
| 49 | +//! |
| 50 | +//! ## Operations |
| 51 | +//! |
| 52 | +//! ### Create Point Link |
| 53 | +//! |
| 54 | +//! Interface method: `store.create_point()` |
| 55 | +//! |
| 56 | +//! ```rust,ignore |
| 57 | +//! // Implementation (conceptual): |
| 58 | +//! let id = self.allocate_next_id(); |
| 59 | +//! self.links[id] = Link { id, source: id, target: id }; |
| 60 | +//! self.source_index.insert(id, id); // index by source |
| 61 | +//! self.target_index.insert(id, id); // index by target |
| 62 | +//! ``` |
| 63 | +//! |
| 64 | +//! - Allocates next available ID from internal counter |
| 65 | +//! - Writes (id, id, id) tuple directly to memory/file |
| 66 | +//! - Updates source and target index trees |
| 67 | +//! - Time complexity: O(log n) for index updates |
| 68 | +//! |
| 69 | +//! ### Update Link |
| 70 | +//! |
| 71 | +//! Interface method: `store.update(id, source, target)` |
| 72 | +//! |
| 73 | +//! ```rust,ignore |
| 74 | +//! // Implementation (conceptual): |
| 75 | +//! let old = self.links[id]; |
| 76 | +//! self.source_index.remove(old.source, id); |
| 77 | +//! self.target_index.remove(old.target, id); |
| 78 | +//! self.links[id] = Link { id, source, target }; |
| 79 | +//! self.source_index.insert(source, id); |
| 80 | +//! self.target_index.insert(target, id); |
| 81 | +//! ``` |
| 82 | +//! |
| 83 | +//! - Direct memory access to read old values: O(1) |
| 84 | +//! - Updates index trees: O(log n) |
| 85 | +//! - Writes new values: O(1) |
| 86 | +//! |
| 87 | +//! ### Delete Link |
| 88 | +//! |
| 89 | +//! Interface method: `store.delete(id)` |
| 90 | +//! |
| 91 | +//! ```rust,ignore |
| 92 | +//! // Implementation (conceptual): |
| 93 | +//! let old = self.links[id]; |
| 94 | +//! self.source_index.remove(old.source, id); |
| 95 | +//! self.target_index.remove(old.target, id); |
| 96 | +//! self.links[id] = EMPTY; |
| 97 | +//! self.free_list.push(id); // reuse slot later |
| 98 | +//! ``` |
| 99 | +//! |
| 100 | +//! - Direct memory access: O(1) |
| 101 | +//! - Index tree updates: O(log n) |
| 102 | +//! - Marks slot for reuse |
| 103 | +//! |
| 104 | +//! ### Query All Links (Each All) |
| 105 | +//! |
| 106 | +//! Interface method: `store.each(handler)` or `store.each_by([any, any, any], handler)` |
| 107 | +//! |
| 108 | +//! ```rust,ignore |
| 109 | +//! // Implementation (conceptual): |
| 110 | +//! for id in 1..=self.count { |
| 111 | +//! if self.links[id].is_valid() { |
| 112 | +//! handler(self.links[id]); |
| 113 | +//! } |
| 114 | +//! } |
| 115 | +//! ``` |
| 116 | +//! |
| 117 | +//! - Iterates through internal link array sequentially |
| 118 | +//! - Skips empty/deleted slots |
| 119 | +//! - Time complexity: O(n) where n = total allocated slots |
| 120 | +//! |
| 121 | +//! ### Query by ID (Each Identity) |
| 122 | +//! |
| 123 | +//! Interface method: `store.each_by([id, any, any], handler)` |
| 124 | +//! |
| 125 | +//! ```rust,ignore |
| 126 | +//! // Implementation (conceptual): |
| 127 | +//! if let Some(link) = self.links.get(id) { |
| 128 | +//! if link.is_valid() { |
| 129 | +//! handler(link); |
| 130 | +//! } |
| 131 | +//! } |
| 132 | +//! ``` |
| 133 | +//! |
| 134 | +//! - Direct array index access: O(1) |
| 135 | +//! - Returns link at `links[id]` if it exists |
| 136 | +//! - Fastest possible lookup |
| 137 | +//! |
| 138 | +//! ### Query by Source (Each Outgoing) |
| 139 | +//! |
| 140 | +//! Interface method: `store.each_by([any, source, any], handler)` |
| 141 | +//! |
| 142 | +//! ```rust,ignore |
| 143 | +//! // Implementation (conceptual): |
| 144 | +//! for id in self.source_index.get_all(source) { |
| 145 | +//! handler(self.links[id]); |
| 146 | +//! } |
| 147 | +//! ``` |
| 148 | +//! |
| 149 | +//! - Uses source index tree to find all links with given source |
| 150 | +//! - Time complexity: O(log n + k) where k = matching links |
| 151 | +//! - Finds all outgoing edges from a node |
| 152 | +//! |
| 153 | +//! ### Query by Target (Each Incoming) |
| 154 | +//! |
| 155 | +//! Interface method: `store.each_by([any, any, target], handler)` |
| 156 | +//! |
| 157 | +//! ```rust,ignore |
| 158 | +//! // Implementation (conceptual): |
| 159 | +//! for id in self.target_index.get_all(target) { |
| 160 | +//! handler(self.links[id]); |
| 161 | +//! } |
| 162 | +//! ``` |
| 163 | +//! |
| 164 | +//! - Uses target index tree to find all links with given target |
| 165 | +//! - Time complexity: O(log n + k) where k = matching links |
| 166 | +//! - Finds all incoming edges to a node |
| 167 | +//! |
| 168 | +//! ### Query by Source AND Target (Each Concrete) |
| 169 | +//! |
| 170 | +//! Interface method: `store.each_by([any, source, target], handler)` |
| 171 | +//! |
| 172 | +//! ```rust,ignore |
| 173 | +//! // Implementation (conceptual): |
| 174 | +//! // Uses whichever index has fewer entries, then filters |
| 175 | +//! let candidates = self.source_index.get_all(source); // or target_index |
| 176 | +//! for id in candidates { |
| 177 | +//! if self.links[id].target == target { // or source check |
| 178 | +//! handler(self.links[id]); |
| 179 | +//! } |
| 180 | +//! } |
| 181 | +//! ``` |
| 182 | +//! |
| 183 | +//! - Uses one index tree, then filters by the other field |
| 184 | +//! - Time complexity: O(log n + k) for tree traversal |
| 185 | +//! |
| 186 | +//! ### Get Link by ID |
| 187 | +//! |
| 188 | +//! Interface method: `store.get_link(id)` |
| 189 | +//! |
| 190 | +//! ```rust,ignore |
| 191 | +//! // Implementation (conceptual): |
| 192 | +//! self.links.get(id).filter(|l| l.is_valid()).copied() |
| 193 | +//! ``` |
| 194 | +//! |
| 195 | +//! - Direct array access: O(1) |
| 196 | +//! |
| 197 | +//! ## Index Structure |
| 198 | +//! |
| 199 | +//! Doublets uses balanced trees for source and target indexes: |
| 200 | +//! |
| 201 | +//! ```text |
| 202 | +//! Source Index Tree: |
| 203 | +//! [5] |
| 204 | +//! / \ |
| 205 | +//! [3] [7] |
| 206 | +//! / \ |
| 207 | +//! [1] [9] |
| 208 | +//! |
| 209 | +//! Each node contains: (source_value -> list of link IDs) |
| 210 | +//! ``` |
| 211 | +//! |
| 212 | +//! ## Performance Characteristics |
| 213 | +//! |
| 214 | +//! | Operation | Time Complexity | Notes | |
| 215 | +//! |-----------------|-----------------|---------------------------------------| |
| 216 | +//! | Create | O(log n) | Index tree insertions | |
| 217 | +//! | Update | O(log n) | Index tree remove + insert | |
| 218 | +//! | Delete | O(log n) | Index tree removals | |
| 219 | +//! | Each All | O(n) | Full array scan | |
| 220 | +//! | Each Identity | O(1) | Direct array access | |
| 221 | +//! | Each Outgoing | O(log n + k) | Tree lookup + k results | |
| 222 | +//! | Each Incoming | O(log n + k) | Tree lookup + k results | |
| 223 | +//! | Each Concrete | O(log n + k) | Tree lookup + filter | |
| 224 | +//! |
| 225 | +//! ## Memory Layout |
| 226 | +//! |
| 227 | +//! ### United Store |
| 228 | +//! ```text |
| 229 | +//! [Header][Link 1][Link 2][Link 3]...[Link n] |
| 230 | +//! ^ ^ |
| 231 | +//! | +-- Each link: (id, source, target) |
| 232 | +//! +-- Contains: count, free_list_head, etc. |
| 233 | +//! ``` |
| 234 | +//! |
| 235 | +//! ### Split Store |
| 236 | +//! ```text |
| 237 | +//! Data File: |
| 238 | +//! [Header][Data 1][Data 2]...[Data n] |
| 239 | +//! ^ |
| 240 | +//! +-- Each data: (source, target) |
| 241 | +//! |
| 242 | +//! Index File: |
| 243 | +//! [Header][Source Tree Nodes][Target Tree Nodes] |
| 244 | +//! ``` |
| 245 | +
|
| 246 | +// This is a documentation-only module |
0 commit comments