graph_api_lib/walker/steps/boxed.rs
1use crate::ElementId;
2use crate::graph::Graph;
3use crate::walker::builder::{EdgeWalkerBuilder, VertexWalkerBuilder};
4use crate::walker::{EdgeWalker, VertexWalker, Walker};
5use smallbox::{SmallBox, space};
6
7// Use a reasonable size for the SmallBox - can be tuned
8type BoxSpace = space::S32;
9
10// Helper trait for boxed vertex walkers that provides both next() and context access
11trait BoxedVertexWalkerOps<'graph, G: Graph, Context> {
12 fn next(&mut self, graph: &'graph G) -> Option<G::VertexId>;
13 fn ctx(&self) -> &Context;
14 fn ctx_mut(&mut self) -> &mut Context;
15}
16
17// Helper trait for boxed edge walkers that provides both next() and context access
18trait BoxedEdgeWalkerOps<'graph, G: Graph, Context> {
19 fn next(&mut self, graph: &'graph G) -> Option<G::EdgeId>;
20 fn ctx(&self) -> &Context;
21 fn ctx_mut(&mut self) -> &mut Context;
22}
23
24// Blanket implementation for any vertex walker
25impl<'graph, G: Graph, W, Context> BoxedVertexWalkerOps<'graph, G, Context> for W
26where
27 W: VertexWalker<'graph, Graph = G, Context = Context>,
28{
29 fn next(&mut self, graph: &'graph G) -> Option<G::VertexId> {
30 VertexWalker::next(self, graph)
31 }
32
33 fn ctx(&self) -> &Context {
34 Walker::ctx(self)
35 }
36
37 fn ctx_mut(&mut self) -> &mut Context {
38 Walker::ctx_mut(self)
39 }
40}
41
42// Blanket implementation for any edge walker
43impl<'graph, G: Graph, W, Context> BoxedEdgeWalkerOps<'graph, G, Context> for W
44where
45 W: EdgeWalker<'graph, Graph = G, Context = Context>,
46{
47 fn next(&mut self, graph: &'graph G) -> Option<G::EdgeId> {
48 EdgeWalker::next(self, graph)
49 }
50
51 fn ctx(&self) -> &Context {
52 Walker::ctx(self)
53 }
54
55 fn ctx_mut(&mut self) -> &mut Context {
56 Walker::ctx_mut(self)
57 }
58}
59
60/// A boxed vertex walker that uses SmallBox for type erasure
61/// This helps reduce monomorphization by hiding concrete walker types
62pub struct BoxedVertexWalker<'graph, G: Graph, Context> {
63 // We box the entire walker using a trait object that provides all operations
64 inner: SmallBox<Box<dyn BoxedVertexWalkerOps<'graph, G, Context> + 'graph>, BoxSpace>,
65}
66
67impl<'graph, G: Graph, Context: Clone> BoxedVertexWalker<'graph, G, Context> {
68 pub(crate) fn new<W>(walker: W) -> Self
69 where
70 W: VertexWalker<'graph, Graph = G, Context = Context> + 'graph,
71 {
72 // Box the walker as a trait object that implements our ops trait
73 let boxed: Box<dyn BoxedVertexWalkerOps<'graph, G, Context> + 'graph> = Box::new(walker);
74
75 Self {
76 inner: SmallBox::new(boxed),
77 }
78 }
79}
80
81impl<'graph, G: Graph, Context: Clone + 'static> Walker<'graph>
82 for BoxedVertexWalker<'graph, G, Context>
83{
84 type Graph = G;
85 type Context = Context;
86
87 fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
88 VertexWalker::next(self, graph).map(ElementId::Vertex)
89 }
90
91 fn ctx(&self) -> &Self::Context {
92 // Delegate to the inner walker's context
93 self.inner.as_ref().ctx()
94 }
95
96 fn ctx_mut(&mut self) -> &mut Self::Context {
97 // Delegate to the inner walker's context
98 self.inner.as_mut().ctx_mut()
99 }
100}
101
102impl<'graph, G: Graph, Context: Clone + 'static> VertexWalker<'graph>
103 for BoxedVertexWalker<'graph, G, Context>
104{
105 fn next(&mut self, graph: &'graph Self::Graph) -> Option<<Self::Graph as Graph>::VertexId> {
106 // Delegate to the inner walker's next method
107 self.inner.as_mut().next(graph)
108 }
109}
110
111/// A boxed edge walker that uses SmallBox for type erasure
112/// This helps reduce monomorphization by hiding concrete walker types
113pub struct BoxedEdgeWalker<'graph, G: Graph, Context> {
114 // We box the entire walker using a trait object that provides all operations
115 inner: SmallBox<Box<dyn BoxedEdgeWalkerOps<'graph, G, Context> + 'graph>, BoxSpace>,
116}
117
118impl<'graph, G: Graph, Context: Clone> BoxedEdgeWalker<'graph, G, Context> {
119 pub(crate) fn new<W>(walker: W) -> Self
120 where
121 W: EdgeWalker<'graph, Graph = G, Context = Context> + 'graph,
122 {
123 // Box the walker as a trait object that implements our ops trait
124 let boxed: Box<dyn BoxedEdgeWalkerOps<'graph, G, Context> + 'graph> = Box::new(walker);
125
126 Self {
127 inner: SmallBox::new(boxed),
128 }
129 }
130}
131
132impl<'graph, G: Graph, Context: Clone + 'static> Walker<'graph>
133 for BoxedEdgeWalker<'graph, G, Context>
134{
135 type Graph = G;
136 type Context = Context;
137
138 fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
139 EdgeWalker::next(self, graph).map(ElementId::Edge)
140 }
141
142 fn ctx(&self) -> &Self::Context {
143 // Delegate to the inner walker's context
144 self.inner.as_ref().ctx()
145 }
146
147 fn ctx_mut(&mut self) -> &mut Self::Context {
148 // Delegate to the inner walker's context
149 self.inner.as_mut().ctx_mut()
150 }
151}
152
153impl<'graph, G: Graph, Context: Clone + 'static> EdgeWalker<'graph>
154 for BoxedEdgeWalker<'graph, G, Context>
155{
156 fn next(&mut self, graph: &'graph Self::Graph) -> Option<<Self::Graph as Graph>::EdgeId> {
157 // Delegate to the inner walker's next method
158 self.inner.as_mut().next(graph)
159 }
160}
161
162// Extension methods for builders to add boxed() method
163impl<'graph, Mutability, Graph, Walker> VertexWalkerBuilder<'graph, Mutability, Graph, Walker>
164where
165 Graph: crate::graph::Graph,
166 Walker: VertexWalker<'graph, Graph = Graph> + 'graph,
167 Walker::Context: Clone + 'static,
168{
169 /// # Boxed Step
170 ///
171 /// The `boxed` step performs type erasure to reduce monomorphization and improve compile times.
172 /// It wraps the current walker in a `SmallBox`, breaking the chain of nested generic types
173 /// that can grow exponentially in complex traversals.
174 ///
175 /// ## When to Use
176 ///
177 /// Use `boxed()` strategically in complex walker chains:
178 /// - **After 4+ chained operations** to prevent type explosion
179 /// - **When compile times become slow** due to deep walker nesting
180 /// - **At logical checkpoints** in long traversals
181 /// - **When storing walkers** in data structures
182 ///
183 /// ## Performance Considerations
184 ///
185 /// - **Pros**: Faster compilation, smaller binaries, stack allocation for small walkers
186 /// - **Cons**: 5-15% runtime overhead from indirect calls, lost inlining opportunities
187 /// - **Best for**: Complex traversals where graph I/O dominates computation
188 ///
189 /// ## Example
190 ///
191 /// ```rust
192 /// # use graph_api_lib::*;
193 /// # use graph_api_test::{populate_graph, Vertex, Edge};
194 /// # use graph_api_simplegraph::SimpleGraph;
195 /// # let mut graph = SimpleGraph::<Vertex, Edge>::new();
196 /// # populate_graph(&mut graph);
197 /// // Without boxing - complex nested types
198 /// let complex_type = graph
199 /// .walk()
200 /// .vertices(VertexSearch::scan())
201 /// .edges(EdgeSearch::scan())
202 /// .head()
203 /// .edges(EdgeSearch::scan())
204 /// .head();
205 /// // Type: Endpoints<Edges<Endpoints<Edges<Vertices<Empty>>>>>
206 ///
207 /// // With strategic boxing - simpler types
208 /// let result: Vec<_> = graph
209 /// .walk()
210 /// .vertices(VertexSearch::scan())
211 /// .edges(EdgeSearch::scan())
212 /// .boxed() // ← Breaks type complexity here
213 /// .head()
214 /// .edges(EdgeSearch::scan())
215 /// .boxed() // ← And here for further reduction
216 /// .head()
217 /// .collect();
218 /// ```
219 ///
220 /// ## Technical Details
221 ///
222 /// This method uses `SmallBox<S32>` which attempts to store walkers on the stack
223 /// (up to 32 bytes) before falling back to heap allocation. This provides better
224 /// cache locality than regular `Box` for small walker states.
225 ///
226 /// # Note
227 /// This method works with any context type that implements `Clone + 'static`.
228 /// The context is preserved through the boxing operation and updated on each `next()` call.
229 pub fn boxed(
230 self,
231 ) -> VertexWalkerBuilder<
232 'graph,
233 Mutability,
234 Graph,
235 BoxedVertexWalker<'graph, Graph, Walker::Context>,
236 > {
237 self.with_vertex_walker(|walker| BoxedVertexWalker::new(walker))
238 }
239}
240
241impl<'graph, Mutability, Graph, Walker> EdgeWalkerBuilder<'graph, Mutability, Graph, Walker>
242where
243 Graph: crate::graph::Graph,
244 Walker: EdgeWalker<'graph, Graph = Graph> + 'graph,
245 Walker::Context: Clone + 'static,
246{
247 /// # Boxed Step (Edge Walker)
248 ///
249 /// The `boxed` step performs type erasure to reduce monomorphization and improve compile times.
250 /// It wraps the current edge walker in a `SmallBox`, breaking the chain of nested generic types
251 /// that can grow exponentially in complex traversals.
252 ///
253 /// ## When to Use
254 ///
255 /// Use `boxed()` strategically in complex walker chains:
256 /// - **After 4+ chained operations** to prevent type explosion
257 /// - **When compile times become slow** due to deep walker nesting
258 /// - **At logical checkpoints** in long traversals
259 /// - **When storing walkers** in data structures
260 ///
261 /// ## Performance Considerations
262 ///
263 /// - **Pros**: Faster compilation, smaller binaries, stack allocation for small walkers
264 /// - **Cons**: 5-15% runtime overhead from indirect calls, lost inlining opportunities
265 /// - **Best for**: Complex traversals where graph I/O dominates computation
266 ///
267 /// ## Example
268 ///
269 /// ```rust
270 /// # use graph_api_lib::*;
271 /// # use graph_api_test::{populate_graph, Vertex, Edge};
272 /// # use graph_api_simplegraph::SimpleGraph;
273 /// # let mut graph = SimpleGraph::<Vertex, Edge>::new();
274 /// # populate_graph(&mut graph);
275 /// // Strategic boxing in edge-heavy traversals
276 /// let edges: Vec<_> = graph
277 /// .walk()
278 /// .vertices(VertexSearch::scan())
279 /// .edges(EdgeSearch::scan())
280 /// .filter(|e, _| e.label().contains("knows"))
281 /// .boxed() // ← Box complex edge walker chains
282 /// .collect();
283 /// ```
284 ///
285 /// ## Technical Details
286 ///
287 /// This method uses `SmallBox<S32>` which attempts to store walkers on the stack
288 /// (up to 32 bytes) before falling back to heap allocation. This provides better
289 /// cache locality than regular `Box` for small walker states.
290 ///
291 /// # Note
292 /// This method works with any context type that implements `Clone + 'static`.
293 /// The context is preserved through the boxing operation and updated on each `next()` call.
294 pub fn boxed(
295 self,
296 ) -> EdgeWalkerBuilder<'graph, Mutability, Graph, BoxedEdgeWalker<'graph, Graph, Walker::Context>>
297 {
298 self.with_edge_walker(|walker| BoxedEdgeWalker::new(walker))
299 }
300}