zrx_executor/executor/task/collection.rs
1// Copyright (c) Zensical LLC <https://zensical.org>
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under CLA
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Task collection.
27
28use std::vec::IntoIter;
29
30use super::Task;
31
32// ----------------------------------------------------------------------------
33// Structs
34// ----------------------------------------------------------------------------
35
36/// Task collection.
37///
38/// This data type represents a collection of tasks that can either be consumed
39/// through iteration, or executed recursively via [`Tasks::execute`]. Anything
40/// returned by [`Task::execute`] must be convertible into [`Tasks`], including
41/// another task, multiple tasks, and the unit value.
42///
43/// # Examples
44///
45/// ```
46/// # use std::error::Error;
47/// # fn main() -> Result<(), Box<dyn Error>> {
48/// use zrx_executor::task::Tasks;
49/// use zrx_executor::Executor;
50///
51/// // Create executor and submit task
52/// let executor = Executor::default();
53/// executor.submit(|| {
54/// println!("Task 1");
55///
56/// // Create subtasks
57/// let mut tasks = Tasks::new();
58/// tasks.add(|| println!("Task 1.1"));
59/// tasks.add(|| println!("Task 1.2"));
60/// tasks.add(|| println!("Task 1.3"));
61/// tasks
62/// })?;
63/// # Ok(())
64/// # }
65/// ```
66#[derive(Debug, Default)]
67pub struct Tasks {
68 /// Vector of tasks.
69 inner: Vec<Box<dyn Task>>,
70}
71
72// ----------------------------------------------------------------------------
73// Implementations
74// ----------------------------------------------------------------------------
75
76impl Tasks {
77 /// Creates a task collection.
78 ///
79 /// # Examples
80 ///
81 /// ```
82 /// use zrx_executor::task::Tasks;
83 ///
84 /// // Create task collection
85 /// let tasks = Tasks::new();
86 /// ```
87 #[must_use]
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 /// Adds a task to the task collection.
93 ///
94 /// This method adds a [`Task`] to the collection, which can then either be
95 /// consumed via [`Tasks::into_iter`] or executed via [`Tasks::execute`],
96 /// depending on the specifics of the execution strategy.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use zrx_executor::task::Tasks;
102 ///
103 /// // Create task collection and add tasks
104 /// let mut tasks = Tasks::new();
105 /// tasks.add(|| println!("Task 1"));
106 /// tasks.add(|| println!("Task 2"));
107 /// tasks.add(|| println!("Task 3"));
108 /// ```
109 #[inline]
110 pub fn add<T>(&mut self, task: T) -> &mut Self
111 where
112 T: Task,
113 {
114 self.inner.push(Box::new(task));
115 self
116 }
117
118 /// Executes all tasks in the task collection.
119 ///
120 /// This method executes all tasks recursively in depth-first order, so if
121 /// a task returns further subtasks, they are executed before all others.
122 ///
123 /// # Examples
124 ///
125 /// ```
126 /// use zrx_executor::task::Tasks;
127 ///
128 /// // Create task collection and add tasks
129 /// let mut tasks = Tasks::new();
130 /// tasks.add(|| println!("Task 1"));
131 /// tasks.add(|| println!("Task 2"));
132 /// tasks.add(|| println!("Task 3"));
133 ///
134 /// // Execute task collection
135 /// tasks.execute();
136 /// ```
137 pub fn execute(mut self) {
138 // Since we're using the inner vector as a stack, we need to reverse it
139 // to ensure that the first task added is the first one executed
140 self.inner.reverse();
141 while let Some(task) = self.inner.pop() {
142 // Execute the current task, and if it returns further subtasks,
143 // push them onto the stack in reverse order
144 self.inner.extend(task.execute().into_iter().rev());
145 }
146 }
147}
148
149#[allow(clippy::must_use_candidate)]
150impl Tasks {
151 /// Returns the number of tasks.
152 #[inline]
153 pub fn len(&self) -> usize {
154 self.inner.len()
155 }
156
157 /// Returns whether there are any tasks.
158 #[inline]
159 pub fn is_empty(&self) -> bool {
160 self.inner.is_empty()
161 }
162}
163
164// ----------------------------------------------------------------------------
165// Trait implementations
166// ----------------------------------------------------------------------------
167
168impl From<()> for Tasks {
169 /// Creates a task collection from the unit value.
170 ///
171 /// This implementation makes the API more flexible, as it allows to just
172 /// return nothing from a task, which is probably the common case.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// use zrx_executor::task::Tasks;
178 ///
179 /// // Create task collection from unit value
180 /// let tasks = Tasks::from(());
181 /// assert!(tasks.is_empty());
182 /// ```
183 #[inline]
184 fn from((): ()) -> Self {
185 Tasks::default()
186 }
187}
188
189impl<T> From<T> for Tasks
190where
191 T: Task,
192{
193 /// Creates a task collection from a task.
194 ///
195 /// This implementation creates a task collection from a single task, which
196 /// allows to conveniently return a single closure from a task.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use zrx_executor::task::Tasks;
202 ///
203 /// // Create task collection from task
204 /// let tasks = Tasks::from(|| println!("Task"));
205 /// assert_eq!(tasks.len(), 1);
206 /// ```
207 #[inline]
208 fn from(task: T) -> Self {
209 Self::from_iter([task])
210 }
211}
212
213// ----------------------------------------------------------------------------
214
215impl<I> FromIterator<I> for Tasks
216where
217 I: Task,
218{
219 /// Creates a task collection from an iterator.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use zrx_executor::task::Tasks;
225 ///
226 /// // Create task collection from iterator
227 /// let tasks = Tasks::from_iter([
228 /// || println!("Task 1"),
229 /// || println!("Task 2"),
230 /// || println!("Task 3"),
231 /// ]);
232 /// ```
233 #[inline]
234 fn from_iter<T>(iter: T) -> Self
235 where
236 T: IntoIterator<Item = I>,
237 {
238 let mut tasks = Tasks::new();
239 for task in iter {
240 tasks.add(task);
241 }
242 tasks
243 }
244}
245
246impl IntoIterator for Tasks {
247 type Item = Box<dyn Task>;
248 type IntoIter = IntoIter<Self::Item>;
249
250 /// Creates an iterator over the task collection.
251 ///
252 /// # Examples
253 ///
254 /// ```
255 /// use zrx_executor::task::Tasks;
256 ///
257 /// // Create task collection and add tasks
258 /// let mut tasks = Tasks::new();
259 /// tasks.add(|| println!("Task 1"));
260 /// tasks.add(|| println!("Task 2"));
261 /// tasks.add(|| println!("Task 3"));
262 ///
263 /// // Create iterator over tasks
264 /// for task in tasks {
265 /// task.execute();
266 /// }
267 /// ```
268 #[inline]
269 fn into_iter(self) -> Self::IntoIter {
270 self.inner.into_iter()
271 }
272}