FOUND Coverage Report


src/
File: common/pipeline/pipelines.hpp
Date: 2026-03-24 21:41:51
Lines:
59/59
100.0%
Functions:
125/125
100.0%
Branches:
20/20
100.0%

Line Branch Exec Source
1 #ifndef SRC_COMMON_PIPELINE_PIPELINES_HPP_
2 #define SRC_COMMON_PIPELINE_PIPELINES_HPP_
3
4 #include <optional>
5 #include <stdexcept>
6 #include <cassert>
7
8 #include "common/pipeline/stages.hpp"
9
10 /// The default number of pipeline stages
11 #define DEFAULT_NUM_STAGES 10
12
13 namespace found {
14
15 /**
16 * A Pipeline<Input, Output, N> is an abstract Pipeline
17 * that takes an Input, outputs an Output, with N stages
18 *
19 * @param Input The input type
20 * @param Output The output type
21 * @param N The number of stages it uses
22 *
23 * @pre Output must be able to be default constructed
24 */
25 template<typename Input,
26 typename Output,
27 size_t N = DEFAULT_NUM_STAGES>
28 class Pipeline : public FunctionStage<Input, Output> {
29 public:
30 /**
31 * Runs a Pipeline
32 *
33 * @note Because Pipelines already construct their
34 * return type into the correct destination, there
35 * is no need to set the product to the result of the
36 * Run, so we optimize here. This override actually
37 * isn't necessary for code correctness, but is great
38 * for optimization.
39 */
40 32 void DoAction() override {
41
1/1
✓ Branch 2 taken 6 times.
32 this->Run(this->resource);
42 32 }
43
44 /**
45 * Runs this Pipeline
46 *
47 * @param input The input to the Pipeline
48 *
49 * @return The output of the Pipeline
50 *
51 * @post This method must also construct
52 * Output into the correct destination
53 * so that Pipeline::GetProduct() may be
54 * used
55 *
56 * @note In this function only, Pipeline::GetProduct()
57 * indicates whether storage is present or not for
58 * the output.
59 */
60 virtual Output Run(const Input &input) = 0;
61
62 protected:
63 /// The stages of this
64 Action *stages[N];
65 /// The number of stages
66 size_t size = 0;
67 /// Whether we're complete
68 bool ready = false;
69 /// The final product. This is only sometimes used
70 std::optional<Output> finalProduct;
71
72 /**
73 * Adds a stage to this pipeline
74 *
75 * @param stage The stage to add to the pipeline
76 *
77 * @throw std::invalid_argument iff this is already completed
78 *
79 * @pre This method is called when the number of registered stages is
80 * less than N - 1
81 *
82 */
83 226 inline void AddStageHelper(Action &stage) {
84
3/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 111 times.
✓ Branch 4 taken 2 times.
226 if (this->ready) throw std::invalid_argument("Pipeline is already ready");
85 222 this->stages[size++] = &stage;
86 222 }
87
88 /**
89 * Completes a pipeline with a stage
90 *
91 * @param stage The stage to add
92 *
93 * @throw std::invalid_argument iff this is already completed
94 *
95 * @pre This method is called when the number of
96 * registered stages is less than N
97 *
98 * @pre The pipeline is not ready yet (this->ready == false)
99 *
100 * @post The pipeline is ready (this->ready == true)
101 */
102 18 inline void CompleteHelper(Action &stage) {
103 assert(this->size < N);
104
3/3
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 1 times.
18 if (this->ready) throw std::invalid_argument("Pipeline is already ready");
105 16 this->stages[size++] = &stage;
106 16 this->ready = true;
107 16 }
108
109 /**
110 * Runs the entire pipeline
111 *
112 * @pre The pipeline is ready (this->ready == true)
113 */
114 116 inline void DoActionHelper() {
115
2/2
✓ Branch 0 taken 115 times.
✓ Branch 1 taken 58 times.
346 for (size_t i = 0; i < this->size; i++) {
116 230 this->stages[i]->DoAction();
117 }
118 116 }
119 };
120
121 /**
122 * SequentialPipeline is composite Stage (i.e. A Stage
123 * that is made up of many stages). You can
124 * add 1 to many stages into a SequentialPipeline so long
125 * as the Input to the first stage is the same
126 * Input to the SequentialPipeline, and the Output to the
127 * last stage is the Output of the SequentialPipeline.
128 *
129 * A SequentialPipeline runs by feeding the input to the first
130 * stage, taking its output and feeding it to the next
131 * stage, and so on until the last stage outputs
132 * the output.
133 *
134 * @param Input The SequentialPipeline's Input
135 * @param Output The SequentialPipeline's Output
136 * @param N Number of stages
137 *
138 * @pre Output must be able to be default constructed (i.e. output
139 * has trait Output::Output())
140 */
141 template<typename Input, typename Output, size_t N = DEFAULT_NUM_STAGES>
142 class SequentialPipeline : public Pipeline<Input, Output, N> {
143 public:
144 /**
145 * Constructs an empty SequentialPipeline
146 */
147 112 SequentialPipeline() = default;
148
149 /**
150 * Adds a stage to this pipeline
151 *
152 * @param stage The stage to add to the pipeline
153 *
154 * @return this, with the new stage added (for chaining)
155 *
156 * @throws invalid_argument iff this is the first time this
157 * method is called, and I does not match Input OR if the SequentialPipeline
158 * is already complete (aka this::Complete was called successfully)
159 *
160 * @pre Iff this method has already been called, O from the last
161 * parameter must match I of the next parameter
162 * @pre This method is called when the number of registered stages is
163 * less than N - 1
164 */
165 212 template<typename I, typename O> SequentialPipeline &AddStage(FunctionStage<I, O> &stage) {
166
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 51 times.
212 if (this->size == 0) {
167 if (!std::is_same<Input, I>::value) {
168
1/1
✓ Branch 2 taken 1 times.
2 throw std::invalid_argument("The initial input type is not correct");
169 }
170 108 this->firstResource = reinterpret_cast<Input *>(&stage.GetResource());
171 } else {
172 // Chain here, and blindly trust the user
173 102 *this->lastProduct = static_cast<void *>(&stage.GetResource());
174 }
175 // Add to our list
176 210 Pipeline<Input, Output, N>::AddStageHelper(stage);
177 // Now, reset the lastProduct to be of this stage
178 208 this->lastProduct = reinterpret_cast<void **>(&stage.GetProduct());
179 // Return the pipeline for chaining
180 208 return *this;
181 }
182
183 /**
184 * Adds a stage to the pipeline and marks it as the last stage,
185 * preventing further manipulation of the SequentialPipeline
186 *
187 * @param stage The stage to add
188 *
189 * @return this, with the last stage added (to run this::Run)
190 *
191 * @throws invalid_argument iff this is the first time this
192 * method is called, and I does not match Input OR if the SequentialPipeline
193 * is already complete (aka this::Complete was called successfully)
194 *
195 * @pre This method is called when the number of registered stages is less
196 * than N
197 */
198 110 template<typename I> SequentialPipeline &Complete(FunctionStage<I, Output> &stage) {
199 assert(this->size < N);
200
3/3
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 54 times.
✓ Branch 4 taken 1 times.
110 if (this->ready) throw std::invalid_argument("Pipeline is already ready");
201 108 this->AddStage(stage);
202 108 this->ready = true;
203 108 return *this;
204 }
205
206 /**
207 * Executes this SequentialPipeline
208 *
209 * @param input The input to this SequentialPipeline
210 *
211 * @return The output of the SequentialPipeline
212 *
213 * @note A SequentialPipeline runs by feeding the input to the first
214 * stage, taking its output and feeding it to the next
215 * stage, and so on until the last stage outputs
216 * the output.
217 *
218 * @pre The pipeline must have been completed successfully,
219 * i.e. before this::Run is called, this::Complete must have
220 * been called successfully
221 */
222 75 Output Run(const Input &input) override {
223 assert(!this->finalProduct);
224 // MANUAL VERIFICATION: The below branch is fully tested via pipeline-test.cpp
225
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21 times.
75 if (!this->ready)
226
1/1
✓ Branch 2 taken 1 times.
2 throw std::runtime_error("This is an illegal action: the pipeline is not ready yet");
227 // Don't use "Input resource" unless necessary
228 73 *this->firstResource = input;
229 // If this pipeline is not composed, construct output
230 // into ourself, otherwise construct into the outer pipeline
231
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 8 times.
73 if (this->product == nullptr) {
232 // engage our finalProduct
233 53 this->finalProduct.emplace();
234 53 *this->lastProduct = &this->finalProduct.value();
235 53 this->product = &this->finalProduct.value();
236 } else {
237 20 *this->lastProduct = this->product;
238 }
239 73 Pipeline<Input, Output, N>::DoActionHelper();
240 73 return *this->product;
241 }
242
243 private:
244 /// The pointer to the variable that will store the first input
245 Input *firstResource = nullptr;
246 /// A temporary variable that always points to the last Stage's product field
247 void **lastProduct = nullptr;
248 };
249
250 /**
251 * A ModifyingPipeline modifies a resource with a given
252 * set of stages
253 *
254 * @param T The resource to modify
255 * @param N The number of stages that modify the resource
256 */
257 template<typename T, size_t N = DEFAULT_NUM_STAGES>
258 class ModifyingPipeline : public Pipeline<T, T, N> {
259 public:
260 /**
261 * Constructs a ModifyingPipeline
262 */
263 18 ModifyingPipeline() = default;
264
265 /**
266 * Adds a stage to this
267 *
268 * @param stage The ModifyingStage<T> to add to this
269 *
270 * @return this, with the added stage
271 */
272 16 ModifyingPipeline &AddStage(ModifyingStage<T> &stage) {
273 assert(this->size < N - 1);
274 16 Pipeline<T, T, N>::AddStageHelper(stage);
275 14 return *this;
276 }
277
278 /**
279 * Completes a pipeline with a stage
280 *
281 * @param stage The stage to add
282 *
283 * @return this, with the completed pipeline
284 *
285 * @pre This method is called when the number of
286 * registered stages is less than N
287 */
288 18 ModifyingPipeline &Complete(ModifyingStage<T> &stage) {
289 assert(this->size < N);
290 18 Pipeline<T, T, N>::CompleteHelper(stage);
291 16 return *this;
292 }
293
294 /**
295 * Executes this Modifying Pipeline
296 *
297 * @param input The input to modify
298 *
299 * @return input, but modified by all
300 * the registered stages, in order
301 */
302 7 T Run(const T &input) override {
303 assert(!this->finalProduct);
304 7 if (!this->ready)
305 1 throw std::runtime_error("This is an illegal action: the pipeline is not ready yet");
306 // Construct here if there is no next product,
307 // or construct there if there is
308 6 if (this->product == nullptr) {
309 // engage our finalProduct
310 2 this->finalProduct.emplace();
311 2 this->product = &this->finalProduct.value();
312 }
313 6 *this->product = input;
314 19 for (size_t i = 0; i < this->size; i++) {
315 13 dynamic_cast<ModifyingStage<T> *>(this->stages[i])->SetResource(*this->product);
316 }
317 6 Pipeline<T, T, N>::DoActionHelper();
318 6 return *this->product;
319 }
320 };
321
322 } // namespace found
323
324 #endif // SRC_COMMON_PIPELINE_PIPELINES_HPP_
325