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}