From 28a8376257dbba4bad4c3e233d0b0e23b3313180 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 16 Oct 2025 19:36:54 +0400 Subject: [PATCH 1/4] epilogue --- src/pipelines/exec/navi.rs | 112 +++++++++++++++++++++++++------------ src/pipelines/iter.rs | 37 +++++++++--- src/pipelines/mod.rs | 32 ++++++++--- 3 files changed, 129 insertions(+), 52 deletions(-) diff --git a/src/pipelines/exec/navi.rs b/src/pipelines/exec/navi.rs index 28195e8..d44d834 100644 --- a/src/pipelines/exec/navi.rs +++ b/src/pipelines/exec/navi.rs @@ -25,7 +25,7 @@ use { pub(crate) struct StepPath(SmallVec<[usize; 8]>); const PROLOGUE_INDEX: usize = usize::MIN; -const EPILOGUE_INDEX: usize = usize::MAX; +const EPILOGUE_START_INDEX: usize = usize::MAX - 1024; // Reserve space for epilogue steps const STEP0_INDEX: usize = PROLOGUE_INDEX + 1; /// Public API @@ -86,7 +86,7 @@ impl StepPath { /// Returns `true` if the path is pointing to an epilogue of a pipeline. pub(crate) fn is_epilogue(&self) -> bool { - self.leaf() == EPILOGUE_INDEX + self.leaf() >= EPILOGUE_START_INDEX } } @@ -231,9 +231,14 @@ impl StepPath { Self(smallvec![PROLOGUE_INDEX]) } - /// Returns a leaf step path pointing at the epilogue step. + /// Returns a leaf step path pointing at the first epilogue step. pub(in crate::pipelines) fn epilogue() -> Self { - Self(smallvec![EPILOGUE_INDEX]) + Self::epilogue_step(0) + } + + /// Returns a leaf step path pointing at a specific epilogue step with the given index. + pub(in crate::pipelines) fn epilogue_step(epilogue_index: usize) -> Self { + Self(smallvec![epilogue_index + EPILOGUE_START_INDEX]) } /// Returns a new step path that points to the first non-prologue and @@ -275,14 +280,18 @@ impl core::fmt::Display for StepPath { if let Some(&first) = iter.next() { match first { PROLOGUE_INDEX => write!(f, "p"), - EPILOGUE_INDEX => write!(f, "e"), + idx if idx >= EPILOGUE_START_INDEX => { + write!(f, "e{}", idx - EPILOGUE_START_INDEX) + } index => write!(f, "{index}"), }?; for &index in iter { match index { PROLOGUE_INDEX => write!(f, "_p"), - EPILOGUE_INDEX => write!(f, "_e"), + idx if idx >= EPILOGUE_START_INDEX => { + write!(f, "_e{}", idx - EPILOGUE_START_INDEX) + } index => write!(f, "_{index}"), }?; } @@ -318,7 +327,7 @@ impl<'a, P: Platform> StepNavigator<'a, P> { /// In pipelines with a prologue, this will point to the prologue step. /// In pipelines without a prologue, this will point to the first step. /// In pipelines with no steps, but with an epilogue, this will point to the - /// epilogue step. + /// first epilogue step. /// /// If the first item in the pipeline is a nested pipeline, this will dig /// deeper into the nested pipeline to find the first executable item. @@ -336,9 +345,9 @@ impl<'a, P: Platform> StepNavigator<'a, P> { // pipeline has no prologue if pipeline.steps().is_empty() { - // If there are no steps, but there is an epilogue, return it. - if pipeline.epilogue().is_some() { - return Some(Self(StepPath::epilogue(), vec![pipeline])); + // If there are no steps, but there is an epilogue, return the first epilogue step. + if !pipeline.epilogue().is_empty() { + return Self(StepPath::epilogue(), vec![pipeline]).enter(); } // this is an empty pipeline, there is nothing executable. @@ -360,9 +369,17 @@ impl<'a, P: Platform> StepNavigator<'a, P> { .prologue() .expect("Step path points to a non-existing prologue") } else if self.is_epilogue() { - enclosing_pipeline + let epilogue_index = step_index - EPILOGUE_START_INDEX; + let StepOrPipeline::Step(step) = enclosing_pipeline .epilogue() + .get(epilogue_index) .expect("Step path points to a non-existing epilogue") + else { + unreachable!( + "StepNavigator should not point to a pipeline, only to steps" + ) + }; + step } else { let StepOrPipeline::Step(step) = enclosing_pipeline .steps() @@ -400,8 +417,17 @@ impl<'a, P: Platform> StepNavigator<'a, P> { /// Returns `None` if there are no more steps to execute in the pipeline. pub(crate) fn next_ok(self) -> Option { if self.is_epilogue() { - // the loop is over. - return self.next_in_parent(); + // we are in an epilogue step, check if there are more epilogue steps + let enclosing_pipeline = self.pipeline(); + let epilogue_index = self.0.leaf() - EPILOGUE_START_INDEX; + + if epilogue_index + 1 < enclosing_pipeline.epilogue().len() { + // there are more epilogue steps, go to the next one + return Self(self.0.increment_leaf(), self.1.clone()).enter(); + } else { + // this is the last epilogue step, we are done with this pipeline + return self.next_in_parent(); + } } if self.is_prologue() { @@ -519,42 +545,58 @@ impl StepNavigator<'_, P> { "StepNavigator should always have at least one enclosing pipeline", ); - if path.is_prologue() || path.is_epilogue() { + if path.is_prologue() { assert!( - enclosing_pipeline.prologue().is_some() - || enclosing_pipeline.epilogue().is_some(), - "path is prologue or epilogue, but enclosing pipeline has none", + enclosing_pipeline.prologue().is_some(), + "path is prologue, but enclosing pipeline has none", ); - // if we are in a prologue or epilogue, we can just return ourselves. + // if we are in a prologue, we can just return ourselves. return Some(Self(path, ancestors)); } - let step_index = path - .leaf() - .checked_sub(STEP0_INDEX) - .expect("path is not prologue"); - - match enclosing_pipeline.steps().get(step_index)? { - StepOrPipeline::Step(_) => { - // if we are pointing at a step, we can just return ourselves. - Some(Self(path, ancestors)) + if path.is_epilogue() { + let epilogue_index = path.leaf() - EPILOGUE_START_INDEX; + match enclosing_pipeline.epilogue().get(epilogue_index)? { + StepOrPipeline::Step(_) => { + // if we are pointing at an epilogue step, we can just return ourselves. + Some(Self(path, ancestors)) + } + StepOrPipeline::Pipeline(_, nested) => { + // if we are pointing at a pipeline, we need to dig into its entrypoint. + Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) + } } - StepOrPipeline::Pipeline(_, nested) => { - // if we are pointing at a pipeline, we need to dig into its entrypoint. - Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) + } else { + let step_index = path + .leaf() + .checked_sub(STEP0_INDEX) + .expect("path is not prologue or epilogue"); + + match enclosing_pipeline.steps().get(step_index)? { + StepOrPipeline::Step(_) => { + // if we are pointing at a step, we can just return ourselves. + Some(Self(path, ancestors)) + } + StepOrPipeline::Pipeline(_, nested) => { + // if we are pointing at a pipeline, we need to dig into its entrypoint. + Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) + } } } } /// Finds the next step to run when a loop is finished. /// - /// The next step could be either the epilogue of the current pipeline, + /// The next step could be either the first epilogue step of the current pipeline, /// or the next step in the parent pipeline. fn after_loop(self) -> Option { - if self.pipeline().epilogue().is_some() { - // we've reached the epilogue of this pipeline, regardless of the - // looping behavior, we should go to the next step in the parent pipeline. - Some(Self(self.0.replace_leaf(EPILOGUE_INDEX), self.1.clone())) + if !self.pipeline().epilogue().is_empty() { + // we've reached the epilogue of this pipeline, go to the first epilogue step + Some(Self( + self.0.replace_leaf(EPILOGUE_START_INDEX), + self.1.clone(), + )) + .and_then(|nav| nav.enter()) } else { self.next_in_parent() } diff --git a/src/pipelines/iter.rs b/src/pipelines/iter.rs index 7fb950d..10be84f 100644 --- a/src/pipelines/iter.rs +++ b/src/pipelines/iter.rs @@ -11,7 +11,7 @@ struct Frame<'a, P: Platform> { path: StepPath, next_ix: usize, yielded_prologue: bool, - yielded_epilogue: bool, + epilogue_ix: usize, } impl<'a, P: Platform> StepPathIter<'a, P> { @@ -22,7 +22,7 @@ impl<'a, P: Platform> StepPathIter<'a, P> { next_ix: 0, path: StepPath::empty(), yielded_prologue: false, - yielded_epilogue: false, + epilogue_ix: 0, }], } } @@ -56,17 +56,36 @@ impl Iterator for StepPathIter<'_, P> { path: next_path, next_ix: 0, yielded_prologue: false, - yielded_epilogue: false, + epilogue_ix: 0, }); continue; } } } - // Yield epilogue once, if present. - if !frame.yielded_epilogue && frame.pipeline.epilogue.is_some() { - frame.yielded_epilogue = true; - return Some(frame.path.clone().concat(StepPath::epilogue())); + // Walk epilogue steps/pipelines; descend into nested pipelines. + if frame.epilogue_ix < frame.pipeline.epilogue.len() { + let ix = frame.epilogue_ix; + frame.epilogue_ix += 1; + match &frame.pipeline.epilogue[ix] { + StepOrPipeline::Step(_) => { + return Some( + frame.path.clone().concat(StepPath::epilogue_step(ix)), + ); + } + StepOrPipeline::Pipeline(_, nested) => { + let next_path = + frame.path.clone().concat(StepPath::epilogue_step(ix)); + self.stack.push(Frame { + pipeline: nested, + path: next_path, + next_ix: 0, + yielded_prologue: false, + epilogue_ix: 0, + }); + continue; + } + } } // Done with this frame; pop and continue with parent. @@ -102,7 +121,7 @@ mod tests { .with_step(Step3) .with_epilogue(EpilogueOne); - let expected = vec!["p", "1", "2", "3", "e"]; + let expected = vec!["p", "1", "2", "3", "e0"]; let actual = pipeline .iter_steps() .map(|step| step.to_string()) @@ -125,7 +144,7 @@ mod tests { .with_epilogue(EpilogueOne); let expected = vec![ - "p", "1_p", "1_1", "1_2_1", "1_2_2", "1_2_3", "1_2_e", "1_3", "e", + "p", "1_p", "1_1", "1_2_1", "1_2_2", "1_2_3", "1_2_e0", "1_3", "e0", ]; let actual = pipeline diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index ced40c4..30f35d4 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -40,7 +40,7 @@ pub enum Behavior { } pub struct Pipeline { - epilogue: Option>>, + epilogue: Vec>, prologue: Option>>, steps: Vec>, limits: Option>>, @@ -53,7 +53,7 @@ impl Default for Pipeline

{ #[track_caller] fn default() -> Self { Self { - epilogue: None, + epilogue: Vec::new(), prologue: None, steps: Vec::new(), limits: None, @@ -82,11 +82,27 @@ impl Pipeline

{ } /// A step that happens as the last step of the block after the whole payload - /// has been built. + /// has been built. Can be called multiple times to add multiple epilogue steps. #[must_use] pub fn with_epilogue(self, step: impl Step

) -> Self { let mut this = self; - this.epilogue = Some(Arc::new(StepInstance::new(step))); + this.epilogue + .push(StepOrPipeline::Step(Arc::new(StepInstance::new(step)))); + this + } + + /// Adds a nested pipeline to the epilogue. + #[must_use] + #[track_caller] + pub fn with_epilogue_pipeline( + self, + behavior: Behavior, + nested: impl IntoPipeline, + ) -> Self { + let mut this = self; + let nested_pipeline = nested.into_pipeline(); + this.epilogue + .push(StepOrPipeline::Pipeline(behavior, nested_pipeline)); this } @@ -151,7 +167,7 @@ impl Pipeline

{ impl Pipeline

{ /// Returns true if the pipeline has no steps, prologue or epilogue. pub fn is_empty(&self) -> bool { - self.prologue.is_none() && self.epilogue.is_none() && self.steps.is_empty() + self.prologue.is_none() && self.epilogue.is_empty() && self.steps.is_empty() } /// An optional name of the pipeline. @@ -176,8 +192,8 @@ impl Pipeline

{ self.prologue.as_ref() } - pub(crate) fn epilogue(&self) -> Option<&Arc>> { - self.epilogue.as_ref() + pub(crate) fn epilogue(&self) -> &[StepOrPipeline

] { + &self.epilogue } pub(crate) fn steps(&self) -> &[StepOrPipeline

] { @@ -315,7 +331,7 @@ impl core::fmt::Debug for Pipeline

{ f.debug_struct("Pipeline") .field("name", &self.name()) .field("prologue", &self.prologue.as_ref().map(|p| p.name())) - .field("epilogue", &self.epilogue.as_ref().map(|e| e.name())) + .field("epilogue", &self.epilogue) .field("steps", &self.steps) .field("limits", &self.limits.is_some()) .finish_non_exhaustive() From 8a882780aa7c729e2f584b40900a3fa65e314eb7 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 16 Oct 2025 19:52:22 +0400 Subject: [PATCH 2/4] fmt --- src/pipelines/exec/navi.rs | 22 ++++++++++++++-------- src/pipelines/mod.rs | 9 ++++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/pipelines/exec/navi.rs b/src/pipelines/exec/navi.rs index d44d834..53b307f 100644 --- a/src/pipelines/exec/navi.rs +++ b/src/pipelines/exec/navi.rs @@ -236,7 +236,8 @@ impl StepPath { Self::epilogue_step(0) } - /// Returns a leaf step path pointing at a specific epilogue step with the given index. + /// Returns a leaf step path pointing at a specific epilogue step with the + /// given index. pub(in crate::pipelines) fn epilogue_step(epilogue_index: usize) -> Self { Self(smallvec![epilogue_index + EPILOGUE_START_INDEX]) } @@ -345,7 +346,8 @@ impl<'a, P: Platform> StepNavigator<'a, P> { // pipeline has no prologue if pipeline.steps().is_empty() { - // If there are no steps, but there is an epilogue, return the first epilogue step. + // If there are no steps, but there is an epilogue, return the first + // epilogue step. if !pipeline.epilogue().is_empty() { return Self(StepPath::epilogue(), vec![pipeline]).enter(); } @@ -558,11 +560,13 @@ impl StepNavigator<'_, P> { let epilogue_index = path.leaf() - EPILOGUE_START_INDEX; match enclosing_pipeline.epilogue().get(epilogue_index)? { StepOrPipeline::Step(_) => { - // if we are pointing at an epilogue step, we can just return ourselves. + // if we are pointing at an epilogue step, we can just return + // ourselves. Some(Self(path, ancestors)) } StepOrPipeline::Pipeline(_, nested) => { - // if we are pointing at a pipeline, we need to dig into its entrypoint. + // if we are pointing at a pipeline, we need to dig into its + // entrypoint. Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) } } @@ -578,7 +582,8 @@ impl StepNavigator<'_, P> { Some(Self(path, ancestors)) } StepOrPipeline::Pipeline(_, nested) => { - // if we are pointing at a pipeline, we need to dig into its entrypoint. + // if we are pointing at a pipeline, we need to dig into its + // entrypoint. Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) } } @@ -587,11 +592,12 @@ impl StepNavigator<'_, P> { /// Finds the next step to run when a loop is finished. /// - /// The next step could be either the first epilogue step of the current pipeline, - /// or the next step in the parent pipeline. + /// The next step could be either the first epilogue step of the current + /// pipeline, or the next step in the parent pipeline. fn after_loop(self) -> Option { if !self.pipeline().epilogue().is_empty() { - // we've reached the epilogue of this pipeline, go to the first epilogue step + // we've reached the epilogue of this pipeline, go to the first epilogue + // step Some(Self( self.0.replace_leaf(EPILOGUE_START_INDEX), self.1.clone(), diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index 30f35d4..2969eec 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -82,11 +82,13 @@ impl Pipeline

{ } /// A step that happens as the last step of the block after the whole payload - /// has been built. Can be called multiple times to add multiple epilogue steps. + /// has been built. Can be called multiple times to add multiple epilogue + /// steps. #[must_use] pub fn with_epilogue(self, step: impl Step

) -> Self { let mut this = self; - this.epilogue + this + .epilogue .push(StepOrPipeline::Step(Arc::new(StepInstance::new(step)))); this } @@ -101,7 +103,8 @@ impl Pipeline

{ ) -> Self { let mut this = self; let nested_pipeline = nested.into_pipeline(); - this.epilogue + this + .epilogue .push(StepOrPipeline::Pipeline(behavior, nested_pipeline)); this } From 6c3d73404fdc37b1bd66eaaf79eff2a81493bcad Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 16 Oct 2025 20:54:44 +0400 Subject: [PATCH 3/4] clippy --- src/pipelines/exec/navi.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/pipelines/exec/navi.rs b/src/pipelines/exec/navi.rs index 53b307f..98c2b8c 100644 --- a/src/pipelines/exec/navi.rs +++ b/src/pipelines/exec/navi.rs @@ -426,10 +426,9 @@ impl<'a, P: Platform> StepNavigator<'a, P> { if epilogue_index + 1 < enclosing_pipeline.epilogue().len() { // there are more epilogue steps, go to the next one return Self(self.0.increment_leaf(), self.1.clone()).enter(); - } else { - // this is the last epilogue step, we are done with this pipeline - return self.next_in_parent(); } + // this is the last epilogue step, we are done with this pipeline + return self.next_in_parent(); } if self.is_prologue() { @@ -595,17 +594,17 @@ impl StepNavigator<'_, P> { /// The next step could be either the first epilogue step of the current /// pipeline, or the next step in the parent pipeline. fn after_loop(self) -> Option { - if !self.pipeline().epilogue().is_empty() { - // we've reached the epilogue of this pipeline, go to the first epilogue - // step - Some(Self( - self.0.replace_leaf(EPILOGUE_START_INDEX), - self.1.clone(), - )) - .and_then(|nav| nav.enter()) - } else { - self.next_in_parent() - } + if self.pipeline().epilogue().is_empty() { + self.next_in_parent() + } else { + // we've reached the epilogue of this pipeline, go to the first epilogue + // step + Some(Self( + self.0.replace_leaf(EPILOGUE_START_INDEX), + self.1.clone(), + )) + .and_then(|nav| nav.enter()) + } } /// Finds the next step to run after the prologue of the current pipeline. From 4ef3f92f5af072d6d51077b2bb6456ec1987f596 Mon Sep 17 00:00:00 2001 From: julio4 <30329843+julio4@users.noreply.github.com> Date: Fri, 17 Oct 2025 20:00:57 +0900 Subject: [PATCH 4/4] fix: remove pipeline epilogue --- src/pipelines/exec/navi.rs | 220 ++++++++++++++++++------------------- src/pipelines/iter.rs | 20 +--- src/pipelines/mod.rs | 24 +--- 3 files changed, 109 insertions(+), 155 deletions(-) diff --git a/src/pipelines/exec/navi.rs b/src/pipelines/exec/navi.rs index 98c2b8c..099823b 100644 --- a/src/pipelines/exec/navi.rs +++ b/src/pipelines/exec/navi.rs @@ -19,7 +19,7 @@ use { /// and knows how to navigate through the pipeline structure depending on the /// current step output and the pipeline structure. /// -/// A Step path cannot be empty, it must always contain at least one element, +/// A Step path cannot be empty; it must always contain at least one element, /// which is the case for pipelines with only steps and no nested pipelines. #[derive(PartialEq, Eq, Clone, From, Into, Hash)] pub(crate) struct StepPath(SmallVec<[usize; 8]>); @@ -149,6 +149,7 @@ impl StepPath { Self(new_path) } + /// Returns a path with the leaf replaced with the given `new_leaf` pub(super) fn replace_leaf(self, new_leaf: usize) -> Self { let mut new_path = self.0; *new_path.last_mut().expect("StepPath cannot be empty") = new_leaf; @@ -183,15 +184,15 @@ impl StepPath { /// Given two paths, where one is an ancestor of the other, returns the /// intermediate paths between them. /// - /// If the other path is not an ancestor of the other an empty vector is + /// If the other path is not an ancestor of the other, an empty vector is /// returned. /// - /// if the paths are equal, an empty vector is returned. + /// If the paths are equal, an empty vector is returned. /// - /// if `self` is an ancestor of `other` then this will return all the steps + /// If `self` is an ancestor of `other`, then this will return all the steps /// descending from `self` to `other`. /// - /// if `other` is an ancestor of `self` then this will return all the steps + /// If `other` is an ancestor of `self`, then this will return all the steps /// descending from `other` to `self`. pub(super) fn between(&self, other: &StepPath) -> Vec { if !self.is_ancestor_of(other) && !other.is_ancestor_of(self) { @@ -242,8 +243,7 @@ impl StepPath { Self(smallvec![epilogue_index + EPILOGUE_START_INDEX]) } - /// Returns a new step path that points to the first non-prologue and - /// non-epilogue step. + /// Returns a new step path that points to the first non-prologue step. pub(in crate::pipelines) fn step0() -> Self { Self::step(0) } @@ -260,7 +260,7 @@ impl StepPath { Self(new_path) } - /// Creates an empty step path. This is an invalid state and public APIs + /// Creates an empty step path. This is an invalid state, and public APIs /// should never be able to construct an empty `StepPath`. /// /// It is the caller's responsibility to ensure that the returned empty step @@ -310,8 +310,8 @@ impl core::fmt::Debug for StepPath { /// This type is used to navigate through a pipeline. /// It keeps track of the current step and the hierarchy of enclosing pipelines. /// -/// All public APIs of this type only allow creating instance that point at a -/// step. Internally it creates temporary versions of itself that point at +/// All public APIs of this type only allow creating instances that point at a +/// step. Internally, it creates temporary versions of itself that point at /// pipelines when navigating through the pipeline structure, but those /// instances should never be available to external users of this type. #[derive(Clone)] @@ -371,21 +371,22 @@ impl<'a, P: Platform> StepNavigator<'a, P> { .prologue() .expect("Step path points to a non-existing prologue") } else if self.is_epilogue() { - let epilogue_index = step_index - EPILOGUE_START_INDEX; - let StepOrPipeline::Step(step) = enclosing_pipeline + enclosing_pipeline .epilogue() - .get(epilogue_index) - .expect("Step path points to a non-existing epilogue") - else { - unreachable!( - "StepNavigator should not point to a pipeline, only to steps" + .get( + step_index + .checked_sub(EPILOGUE_START_INDEX) + .expect("step index should be >= epilogue start index"), ) - }; - step + .expect("Step path points to a non-existing epilogue") } else { let StepOrPipeline::Step(step) = enclosing_pipeline .steps() - .get(step_index - STEP0_INDEX) + .get( + step_index + .checked_sub(STEP0_INDEX) + .expect("step index should be >= step0 index"), + ) .expect("Step path points to a non-existing step") else { unreachable!( @@ -421,14 +422,18 @@ impl<'a, P: Platform> StepNavigator<'a, P> { if self.is_epilogue() { // we are in an epilogue step, check if there are more epilogue steps let enclosing_pipeline = self.pipeline(); - let epilogue_index = self.0.leaf() - EPILOGUE_START_INDEX; + let epilogue_index = self + .0 + .leaf() + .checked_sub(EPILOGUE_START_INDEX) + .expect("invalid epilogue step index in the step path"); if epilogue_index + 1 < enclosing_pipeline.epilogue().len() { // there are more epilogue steps, go to the next one return Self(self.0.increment_leaf(), self.1.clone()).enter(); } - // this is the last epilogue step, we are done with this pipeline - return self.next_in_parent(); + // this is the last epilogue step, we are done with this pipeline + return self.next_in_parent(); } if self.is_prologue() { @@ -448,17 +453,17 @@ impl<'a, P: Platform> StepNavigator<'a, P> { .0 .leaf() .checked_sub(STEP0_INDEX) - .expect("invalid step index in step path"); + .expect("invalid step index in the step path"); let is_last = position + 1 >= enclosing_pipeline.steps().len(); match (self.behavior(), is_last) { (Loop, true) => { - // we are the last step in a loop pipeline, go to first step. + // we are the last step in a loop pipeline, go to the first step. Self(self.0.replace_leaf(STEP0_INDEX), self.1.clone()).enter() } (Once, true) => { - // we are last step in a non-loop pipeline, this is the end of a + // we are at the last step in a non-loop pipeline, this is the end of a // single iteration loop. self.after_loop() } @@ -492,7 +497,7 @@ impl StepNavigator<'_, P> { &self.0 } - /// Returns the loop behaviour of the pipeline containing the current step. + /// Returns the loop behavior of the pipeline containing the current step. fn behavior(&self) -> Behavior { // top-level pipelines are always `Once`. if self.0.is_toplevel() { @@ -516,7 +521,7 @@ impl StepNavigator<'_, P> { let StepOrPipeline::Pipeline(behavior, _) = grandparent_pipeline .steps() .get(parent_index) - .expect("parent pipeline should contain the current step") + .expect("the parent pipeline should contain the current step") else { unreachable!("all ancestors of a step must be pipelines"); }; @@ -549,26 +554,18 @@ impl StepNavigator<'_, P> { if path.is_prologue() { assert!( enclosing_pipeline.prologue().is_some(), - "path is prologue, but enclosing pipeline has none", + "path is prologue, but the enclosing pipeline has none", ); // if we are in a prologue, we can just return ourselves. return Some(Self(path, ancestors)); } if path.is_epilogue() { - let epilogue_index = path.leaf() - EPILOGUE_START_INDEX; - match enclosing_pipeline.epilogue().get(epilogue_index)? { - StepOrPipeline::Step(_) => { - // if we are pointing at an epilogue step, we can just return - // ourselves. - Some(Self(path, ancestors)) - } - StepOrPipeline::Pipeline(_, nested) => { - // if we are pointing at a pipeline, we need to dig into its - // entrypoint. - Some(StepNavigator(path, ancestors).join(Self::entrypoint(nested)?)) - } - } + assert!( + !enclosing_pipeline.epilogue().is_empty(), + "path is epilogue, but the enclosing pipeline has none", + ); + Some(Self(path, ancestors)) } else { let step_index = path .leaf() @@ -592,19 +589,19 @@ impl StepNavigator<'_, P> { /// Finds the next step to run when a loop is finished. /// /// The next step could be either the first epilogue step of the current - /// pipeline, or the next step in the parent pipeline. + /// pipeline or the next step in the parent pipeline. fn after_loop(self) -> Option { if self.pipeline().epilogue().is_empty() { - self.next_in_parent() - } else { - // we've reached the epilogue of this pipeline, go to the first epilogue - // step - Some(Self( - self.0.replace_leaf(EPILOGUE_START_INDEX), - self.1.clone(), - )) - .and_then(|nav| nav.enter()) - } + self.next_in_parent() + } else { + // we've reached the epilogue of this pipeline, go to the first epilogue + // step + Some(Self( + self.0.replace_leaf(EPILOGUE_START_INDEX), + self.1.clone(), + )) + .and_then(|nav| nav.enter()) + } } /// Finds the next step to run after the prologue of the current pipeline. @@ -627,7 +624,7 @@ impl StepNavigator<'_, P> { "StepNavigator should always have at least one enclosing pipeline", ); - // is last step in the enclosing pipeline? + // is the last step in the enclosing pipeline? if step_index + 1 >= enclosing_pipeline.steps().len() { match ancestor.behavior() { Loop => ancestor.after_prologue(), @@ -670,7 +667,9 @@ mod test { fake_step!(Epilogue1); fake_step!(Epilogue2); - fake_step!(Epilogue3); + + fake_step!(EpilogueStep1); + fake_step!(EpilogueStep2); fake_step!(Prologue1); fake_step!(Prologue2); @@ -691,6 +690,35 @@ mod test { fake_step!(StepII); fake_step!(StepIII); + fn top_pipeline() -> Pipeline { + Pipeline::::named("top") + .with_step(Step1) + .with_step(Step2) + .with_pipeline(Loop, |nested: Pipeline| { + nested + .with_name("nested1") + .with_prologue(Prologue1) + .with_step(StepA) + .with_pipeline( + Loop, + (StepX, StepY, StepZ) + .with_name("nested1.1") + .with_prologue(Prologue2) + .with_epilogue(Epilogue2), + ) + .with_step(StepC) + .with_pipeline( + Once, + (StepI, StepII, StepIII) + .with_name("nested1.2") + .with_epilogue(EpilogueStep1) + .with_epilogue(EpilogueStep2), + ) + }) + .with_step(Step4) + .with_epilogue(Epilogue1) + } + impl StepPath { fn append_prologue(self) -> Self { self.concat(StepPath::prologue()) @@ -700,6 +728,10 @@ mod test { self.concat(StepPath::epilogue()) } + fn append_epilogue_step(self, step_index: usize) -> Self { + self.concat(StepPath::epilogue_step(step_index)) + } + fn append_step(self, step_index: usize) -> Self { self.concat(StepPath::step(step_index)) } @@ -723,11 +755,11 @@ mod test { }}; } - // one step with no prologue + // one step assert_entrypoint!( Pipeline::::default().with_step(Step1), StepPath::step0(), - // name autogenerated from source location + // name autogenerated from the source location vec![format!("navi_{}", line!() - 3)] ); @@ -740,7 +772,7 @@ mod test { vec!["one"] ); - // no steps, but with epilogue + // no steps, but with a one-step epilogue assert_entrypoint!( Pipeline::::named("one").with_epilogue(Epilogue1), StepPath::epilogue(), @@ -751,7 +783,7 @@ mod test { assert_entrypoint!( Pipeline::::named("one") .with_pipeline(Loop, (Step1,).with_name("two")), - StepPath::step0().concat(StepPath::step0()), + StepPath::step0().append_step(0), vec!["one", "two"] ); @@ -765,7 +797,7 @@ mod test { vec!["one", "two"] ); - // one nested pipeline with no steps, but with epilogue + // one nested pipeline with no steps, but with a one-step epilogue assert_entrypoint!( Pipeline::::named("one") .with_pipeline(Loop, Pipeline::named("two").with_epilogue(Epilogue1)), @@ -787,7 +819,7 @@ mod test { vec!["one", "two", "three"] ); - // two levels of nested steps with prologue at first level + // two levels of nested steps with prologue at the first level assert_entrypoint!( Pipeline::::named("one").with_pipeline( Loop, @@ -799,7 +831,7 @@ mod test { vec!["one", "two"] ); - // two levels of nested steps with prologue at second level + // two levels of nested steps with prologue at the second level assert_entrypoint!( Pipeline::::named("one").with_pipeline( Loop, @@ -858,31 +890,7 @@ mod test { #[test] fn control_flow() { - let pipeline = Pipeline::::named("top") - .with_step(Step1) - .with_step(Step2) - .with_pipeline(Loop, |nested: Pipeline| { - nested - .with_step(StepA) - .with_pipeline( - Loop, - (StepX, StepY, StepZ) - .with_name("nested1.1") - .with_prologue(Prologue1) - .with_epilogue(Epilogue1), - ) - .with_step(StepC) - .with_pipeline( - Once, - (StepI, StepII, StepIII) - .with_name("nested1.2") - .with_epilogue(Epilogue2), - ) - .with_name("nested1") - .with_prologue(Prologue2) - }) - .with_step(Step4) - .with_epilogue(Epilogue3); + let pipeline = top_pipeline(); let cursor = StepNavigator::entrypoint(&pipeline).unwrap(); assert_eq!(cursor.0, StepPath::step0()); @@ -927,7 +935,13 @@ mod test { assert_eq!(cursor.0, StepPath::step(2).append_step(3).append_step(1)); let cursor = cursor.next_break().unwrap(); - assert_eq!(cursor.0, StepPath::step(2).append_step(3).append_epilogue()); + assert_eq!( + cursor.0, + StepPath::step(2).append_step(3).append_epilogue_step(0) + ); + + let cursor = cursor.next_ok().unwrap(); + StepPath::step(2).append_step(3).append_epilogue_step(1); let cursor = cursor.next_ok().unwrap(); assert_eq!(cursor.0, StepPath::step(2).append_step(0)); @@ -953,31 +967,7 @@ mod test { #[test] fn create_navigator() { - let pipeline = Pipeline::::named("top") - .with_step(Step1) - .with_step(Step2) - .with_pipeline(Loop, |nested: Pipeline| { - nested - .with_step(StepA) - .with_pipeline( - Loop, - (StepX, StepY, StepZ) - .with_name("nested1.1") - .with_prologue(Prologue1) - .with_epilogue(Epilogue1), - ) - .with_step(StepC) - .with_pipeline( - Once, - (StepI, StepII, StepIII) - .with_name("nested1.2") - .with_epilogue(Epilogue2), - ) - .with_name("nested1") - .with_prologue(Prologue2) - }) - .with_step(Step4) - .with_epilogue(Epilogue3); + let pipeline = top_pipeline(); let cursor = StepNavigator::entrypoint(&pipeline).unwrap(); assert_eq!(cursor.0, StepPath::step0()); @@ -1017,10 +1007,10 @@ mod test { ); assert_eq!(navigator.instance().name(), cursor.instance().name()); - // navigator goes to first executable step rooted at the path + // navigator goes to the first executable step rooted at the path let navigator = StepPath::step(2).navigator(&pipeline).unwrap(); assert_eq!(navigator.0, StepPath::step(2).append_prologue()); - assert_eq!(navigator.instance().name(), "Prologue2"); + assert_eq!(navigator.instance().name(), "Prologue1"); } #[test] diff --git a/src/pipelines/iter.rs b/src/pipelines/iter.rs index 10be84f..b688362 100644 --- a/src/pipelines/iter.rs +++ b/src/pipelines/iter.rs @@ -67,25 +67,7 @@ impl Iterator for StepPathIter<'_, P> { if frame.epilogue_ix < frame.pipeline.epilogue.len() { let ix = frame.epilogue_ix; frame.epilogue_ix += 1; - match &frame.pipeline.epilogue[ix] { - StepOrPipeline::Step(_) => { - return Some( - frame.path.clone().concat(StepPath::epilogue_step(ix)), - ); - } - StepOrPipeline::Pipeline(_, nested) => { - let next_path = - frame.path.clone().concat(StepPath::epilogue_step(ix)); - self.stack.push(Frame { - pipeline: nested, - path: next_path, - next_ix: 0, - yielded_prologue: false, - epilogue_ix: 0, - }); - continue; - } - } + return Some(frame.path.clone().concat(StepPath::epilogue_step(ix))); } // Done with this frame; pop and continue with parent. diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index 2969eec..d39ca5f 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -40,7 +40,7 @@ pub enum Behavior { } pub struct Pipeline { - epilogue: Vec>, + epilogue: Vec>>, prologue: Option>>, steps: Vec>, limits: Option>>, @@ -87,25 +87,7 @@ impl Pipeline

{ #[must_use] pub fn with_epilogue(self, step: impl Step

) -> Self { let mut this = self; - this - .epilogue - .push(StepOrPipeline::Step(Arc::new(StepInstance::new(step)))); - this - } - - /// Adds a nested pipeline to the epilogue. - #[must_use] - #[track_caller] - pub fn with_epilogue_pipeline( - self, - behavior: Behavior, - nested: impl IntoPipeline, - ) -> Self { - let mut this = self; - let nested_pipeline = nested.into_pipeline(); - this - .epilogue - .push(StepOrPipeline::Pipeline(behavior, nested_pipeline)); + this.epilogue.push(Arc::new(StepInstance::new(step))); this } @@ -195,7 +177,7 @@ impl Pipeline

{ self.prologue.as_ref() } - pub(crate) fn epilogue(&self) -> &[StepOrPipeline

] { + pub(crate) fn epilogue(&self) -> &[Arc>] { &self.epilogue }