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}