graph_api_lib/walker/steps/
detour.rs

1use crate::ElementId;
2use crate::graph::Graph;
3use crate::walker::builder::{GraphAccess, ImmutableMarker, VertexWalkerBuilder, WalkerBuilder};
4use crate::walker::{VertexWalker, Walker};
5use include_doc::function_body;
6use std::cell::Cell;
7use std::marker::PhantomData;
8use std::rc::Rc;
9
10// ================ DETOUR IMPLEMENTATION ================
11
12/// A Waypoint represents a temporary position in a graph traversal.
13///
14/// It acts as a bridge between the main traversal and a detour (sub-traversal),
15/// storing the vertex ID and context needed to continue the main traversal
16/// after the detour completes.
17pub struct Waypoint<'graph, Graph, Context>
18where
19    Graph: crate::graph::Graph,
20    Context: Clone,
21{
22    _phantom: PhantomData<&'graph (Graph, Context)>,
23    // Shared cell containing the next vertex ID to visit
24    // Rc is needed here to share state between the Detour and Waypoint
25    next: Rc<Cell<Option<Graph::VertexId>>>,
26    // Shared cell containing the context from the parent traversal
27    // Rc is needed to share context between the Detour and Waypoint
28    context: Rc<Cell<Option<Context>>>,
29    // The currently active context for this waypoint
30    current_context: Option<Context>,
31}
32
33impl<'graph, Graph, Context> Walker<'graph> for Waypoint<'graph, Graph, Context>
34where
35    Graph: crate::graph::Graph,
36    Context: 'static + Clone,
37{
38    type Graph = Graph;
39    type Context = Context;
40
41    fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
42        self.next(graph).map(ElementId::Vertex)
43    }
44    fn ctx(&self) -> &Self::Context {
45        self.current_context
46            .as_ref()
47            .expect("context must be set before access")
48    }
49
50    fn ctx_mut(&mut self) -> &mut Self::Context {
51        self.current_context
52            .as_mut()
53            .expect("context cannot be retrieved before call to next")
54    }
55}
56
57impl<'graph, Graph, Context> VertexWalker<'graph> for Waypoint<'graph, Graph, Context>
58where
59    Graph: crate::graph::Graph,
60    Context: 'static + Clone,
61{
62    fn next(
63        &mut self,
64        _graph: &Self::Graph,
65    ) -> Option<<Self::Graph as crate::graph::Graph>::VertexId> {
66        // Extract the context and vertex ID from the shared cells
67        self.current_context = self.context.take();
68        self.next.take()
69    }
70}
71
72/// Detour creates a sub-traversal for each element in the main traversal.
73///
74/// It allows exploring connected elements without losing the current position
75/// in the main traversal. For each element in the parent traversal, a new
76/// sub-traversal is created using the provided path function.
77pub struct Detour<'graph, Parent, Path, Terminal>
78where
79    Parent: VertexWalker<'graph>,
80    Terminal: Walker<'graph, Graph = Parent::Graph>,
81{
82    _phantom_data: PhantomData<&'graph Terminal>,
83    // The parent traversal that provides vertices to detour from
84    parent: Parent,
85    // Function that builds the detour path for each vertex
86    path: Path,
87    // The current sub-traversal walker (created on demand)
88    walker: Option<
89        crate::walker::builder::WalkerBuilder<'graph, ImmutableMarker, Parent::Graph, Terminal>,
90    >,
91    // The current vertex ID from the parent traversal
92    next: Option<<Parent::Graph as Graph>::VertexId>,
93    // The context from the terminal (detour) traversal
94    context: Option<Terminal::Context>,
95    // Shared cell for the next vertex ID (shared with Waypoint)
96    waypoint_next: Rc<Cell<Option<<Parent::Graph as Graph>::VertexId>>>,
97    // Shared cell for the context (shared with Waypoint)
98    waypoint_context: Rc<Cell<Option<Parent::Context>>>,
99}
100
101impl<'graph, Parent, Path, Terminal> Detour<'graph, Parent, Path, Terminal>
102where
103    Parent: VertexWalker<'graph>,
104    Terminal: Walker<'graph, Graph = Parent::Graph>,
105{
106    pub(crate) fn new(parent: Parent, path: Path) -> Self {
107        Detour {
108            _phantom_data: Default::default(),
109            parent,
110            path,
111            walker: None,
112            next: None,
113            context: None,
114            waypoint_next: Default::default(),
115            waypoint_context: Default::default(),
116        }
117    }
118}
119
120impl<'graph, Parent, Path, Terminal, WalkerBuilder> Walker<'graph>
121    for Detour<'graph, Parent, Path, Terminal>
122where
123    Parent: VertexWalker<'graph>,
124    Path: Fn(
125        VertexWalkerBuilder<
126            'graph,
127            ImmutableMarker,
128            Parent::Graph,
129            Waypoint<'graph, Parent::Graph, Parent::Context>,
130        >,
131    ) -> WalkerBuilder,
132    WalkerBuilder: Into<
133        crate::walker::builder::WalkerBuilder<'graph, ImmutableMarker, Parent::Graph, Terminal>,
134    >,
135    Terminal: Walker<'graph, Graph = Parent::Graph>,
136    <Parent as Walker<'graph>>::Graph: 'graph,
137{
138    type Graph = Parent::Graph;
139    type Context = Terminal::Context;
140
141    fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
142        self.next(graph).map(ElementId::Vertex)
143    }
144
145    fn ctx(&self) -> &Self::Context {
146        self.context
147            .as_ref()
148            .expect("next must be called before trying to get context")
149    }
150
151    fn ctx_mut(&mut self) -> &mut Self::Context {
152        self.context
153            .as_mut()
154            .expect("context cannot be retrieved before call to next")
155    }
156}
157
158impl<'graph, Parent, Path, Terminal, WalkerBuilder> VertexWalker<'graph>
159    for Detour<'graph, Parent, Path, Terminal>
160where
161    Parent: VertexWalker<'graph>,
162    Path: Fn(
163        VertexWalkerBuilder<
164            'graph,
165            ImmutableMarker,
166            Parent::Graph,
167            Waypoint<'graph, Parent::Graph, Parent::Context>,
168        >,
169    ) -> WalkerBuilder,
170    WalkerBuilder: Into<
171        crate::walker::builder::WalkerBuilder<'graph, ImmutableMarker, Parent::Graph, Terminal>,
172    >,
173    Terminal: Walker<'graph, Graph = Parent::Graph>,
174    <Parent as Walker<'graph>>::Graph: 'graph,
175{
176    fn next(&mut self, graph: &'graph Self::Graph) -> Option<<Self::Graph as Graph>::VertexId> {
177        // Initialize the walker on first use
178        if self.walker.is_none() {
179            // Create a new Waypoint that shares state with this Detour
180            // The waypoint allows the detour traversal to access the current vertex and context
181            self.walker = Some(
182                (self.path)(crate::walker::builder::new(
183                    GraphAccess::Immutable(graph),
184                    Waypoint {
185                        _phantom: Default::default(),
186                        next: self.waypoint_next.clone(),
187                        context: self.waypoint_context.clone(),
188                        current_context: None,
189                    },
190                ))
191                .into(),
192            );
193        }
194        let walker = self.walker.as_mut().expect("walker must be set").walker();
195
196        loop {
197            match walker.next_element(graph) {
198                None => {
199                    // The detour traversal is exhausted, get the next vertex from parent
200                    match self.parent.next(graph) {
201                        None => {
202                            // No more vertices in parent traversal
203                            return None;
204                        }
205                        Some(next) => {
206                            // Found a new vertex from parent, set up for next detour
207                            self.next = Some(next);
208                            // Share the next vertex ID with the waypoint
209                            self.waypoint_next.replace(Some(next));
210                            // Share the context with the waypoint
211                            self.waypoint_context
212                                .replace(Some(self.parent.ctx().clone()));
213                        }
214                    }
215                }
216                Some(_ctx) => {
217                    // The detour found an element, save its context
218                    self.context = Some(walker.ctx().clone());
219                    // Return the original vertex from parent traversal
220                    // (detour only provides context, doesn't change the traversal elements)
221                    return self.next;
222                }
223            }
224        }
225    }
226}
227
228impl<'graph, Mutability, Graph, Walker> VertexWalkerBuilder<'graph, Mutability, Graph, Walker>
229where
230    Graph: crate::graph::Graph,
231    Walker: VertexWalker<'graph, Graph = Graph>,
232{
233    /// # Detour Step
234    ///
235    /// The `detour` step allows you to create a sub-traversal for each element in the current traversal.
236    /// It's like a temporary branch in the traversal that returns to the main traversal when complete.
237    /// This is powerful for exploring connected elements without losing your current position.
238    ///
239    /// ## Visual Diagram
240    ///
241    /// Before detour step (traversal position on Person A):
242    /// ```text
243    ///   [Person A]* --- knows ---> [Person B] --- created ---> [Project 1]
244    ///                                             
245    ///                                             created
246    ///                                             
247    ///                                             ↓
248    ///                                          
249    ///                                          [Project 2]
250    /// ```
251    ///
252    /// During detour execution (for each element, a sub-traversal is performed):
253    /// ```text
254    ///   Main traversal:
255    ///   [Person A]* --- knows ---> [Person B]
256    ///   
257    ///   Sub-traversal from Person A:
258    ///   [Person A] --- knows ---> [Person B]
259    ///                                            
260    ///    created
261    ///                                            
262    ///      ↓
263    ///                                          
264    ///   [Project 2]*
265    /// ```
266    ///
267    /// After detour step (traversal position returns to original elements):
268    /// ```text
269    ///   [Person A]* --- knows ---> [Person B]
270    ///                                                                                  
271    ///    created
272    ///                                            
273    ///      ↓
274    ///                                          
275    ///   [Project 2]
276    /// ```
277    ///
278    /// ## Parameters
279    ///
280    /// - `traversal_fn`: A function that takes a reference to the current element and returns a new traversal.
281    ///   The results of this traversal are collected in the context.
282    ///
283    /// ## Return Value
284    ///
285    /// A walker with the same elements as before, but with the results of the sub-traversals stored in the context.
286    ///
287    /// ## Example
288    ///
289    /// ```rust
290    #[doc = function_body!("examples/detour.rs", example, [])]
291    /// ```
292    ///
293    /// ## Notes
294    ///
295    /// - The detour doesn't change the main traversal elements - it only adds context data
296    /// - Detours can be nested for complex traversals
297    /// - The detour function can return any walker, allowing for flexible sub-traversals
298    /// - Use `push_context` inside detours to store data from the sub-traversal
299    /// - Detours are executed eagerly for each element in the traversal
300    pub fn detour<Path, Terminal, WalkerBuilderT>(
301        self,
302        predicate: Path,
303    ) -> VertexWalkerBuilder<'graph, Mutability, Graph, Detour<'graph, Walker, Path, Terminal>>
304    where
305        Path: Fn(
306            VertexWalkerBuilder<
307                'graph,
308                ImmutableMarker,
309                Graph,
310                Waypoint<'graph, Graph, Walker::Context>,
311            >,
312        ) -> WalkerBuilderT,
313        WalkerBuilderT: Into<self::WalkerBuilder<'graph, ImmutableMarker, Graph, Terminal>>,
314        Terminal: crate::walker::Walker<'graph, Graph = Graph>,
315    {
316        self.with_vertex_walker(|walker| Detour::new(walker, predicate))
317    }
318}