zero_pool/
pool.rs

1use crate::{
2    TaskFnPointer, TaskItem, TaskParamPointer, queue::Queue, task_future::TaskFuture,
3    uniform_tasks_to_pointers, worker::spawn_worker,
4};
5use std::{sync::Arc, thread::JoinHandle};
6
7pub struct ZeroPool {
8    queue: Arc<Queue>,
9    workers: Vec<JoinHandle<()>>,
10}
11
12impl ZeroPool {
13    /// Creates a new thread pool with worker count equal to available parallelism
14    ///
15    /// Worker count is determined by `std::thread::available_parallelism()`,
16    /// falling back to 1 if unavailable. This is usually the optimal choice
17    /// for CPU-bound workloads.
18    ///
19    /// # Examples
20    ///
21    /// ```rust
22    /// use zero_pool::ZeroPool;
23    /// let pool = ZeroPool::new();
24    /// ```
25    pub fn new() -> Self {
26        let worker_count = std::thread::available_parallelism()
27            .map(std::num::NonZeroUsize::get)
28            .unwrap_or(1);
29        Self::with_workers(worker_count)
30    }
31
32    /// Creates a new thread pool with the specified number of workers
33    ///
34    /// Use this when you need precise control over the worker count,
35    /// for example when coordinating with other thread pools or
36    /// when you know the optimal count for your specific workload.
37    ///
38    /// # Panics
39    ///
40    /// Panics if `worker_count` is 0.
41    ///
42    /// ```rust
43    /// use zero_pool::ZeroPool;
44    /// let pool = ZeroPool::with_workers(4);
45    /// ```
46    pub fn with_workers(worker_count: usize) -> Self {
47        assert!(worker_count > 0, "Must have at least one worker");
48
49        let queue = Arc::new(Queue::new());
50
51        let workers: Vec<JoinHandle<()>> = (0..worker_count)
52            .map(|id| spawn_worker(id, queue.clone()))
53            .collect();
54
55        ZeroPool { queue, workers }
56    }
57
58    /// Submit a single task using raw function and parameter pointers
59    ///
60    /// This is the lowest-level submission method with minimal overhead.
61    /// Most users should prefer the type-safe `submit_task` method.
62    ///
63    /// # Safety
64    ///
65    /// The parameter pointer must remain valid until the returned future completes.
66    /// The caller must ensure the pointer points to the correct parameter type
67    /// expected by the task function.
68    ///
69    /// # Examples
70    ///
71    /// ```rust
72    /// use zero_pool::{ZeroPool, TaskFnPointer, TaskParamPointer, zp_define_task_fn, zp_write};
73    ///
74    /// struct MyTaskParams { value: u64, result: *mut u64 }
75    ///
76    /// zp_define_task_fn!(my_task, MyTaskParams, |params| {
77    ///     zp_write!(params.result, params.value * 2);
78    /// });
79    ///
80    /// let pool = ZeroPool::new();
81    /// let mut result = 0u64;
82    /// let task_params = MyTaskParams { value: 42, result: &mut result };
83    /// let future = pool.submit_raw_task(
84    ///     my_task as TaskFnPointer,
85    ///     &task_params as *const _ as TaskParamPointer
86    /// );
87    /// future.wait();
88    /// assert_eq!(result, 84);
89    /// ```
90    pub fn submit_raw_task(&self, task_fn: TaskFnPointer, params: TaskParamPointer) -> TaskFuture {
91        self.queue.push_single_task(task_fn, params)
92    }
93
94    /// Submit a batch of tasks using raw work items
95    ///
96    /// This method provides optimal performance for pre-converted task batches.
97    /// Use `uniform_tasks_to_pointers` to convert typed tasks efficiently,
98    /// or build work items manually for mixed task types.
99    ///
100    /// # Examples
101    ///
102    /// ```rust
103    /// use zero_pool::{ZeroPool, uniform_tasks_to_pointers, zp_define_task_fn, zp_write};
104    ///
105    /// struct MyTaskParams { value: u64, result: *mut u64 }
106    ///
107    /// zp_define_task_fn!(my_task, MyTaskParams, |params| {
108    ///     zp_write!(params.result, params.value * 2);
109    /// });
110    ///
111    /// let pool = ZeroPool::new();
112    /// let mut results = vec![0u64; 2];
113    /// let task_params = vec![
114    ///     MyTaskParams { value: 1, result: &mut results[0] },
115    ///     MyTaskParams { value: 2, result: &mut results[1] }
116    /// ];
117    /// let tasks = uniform_tasks_to_pointers(my_task, &task_params);
118    /// let future = pool.submit_raw_task_batch(&tasks);
119    /// future.wait();
120    /// assert_eq!(results[0], 2);
121    /// assert_eq!(results[1], 4);
122    /// ```
123    pub fn submit_raw_task_batch(&self, tasks: &[TaskItem]) -> TaskFuture {
124        self.queue.push_task_batch(tasks)
125    }
126
127    /// Submit a single typed task with automatic pointer conversion
128    ///
129    /// This method provides type safety while maintaining performance.
130    /// The parameter struct must remain valid until the future completes.
131    /// This is the recommended method for submitting individual tasks.
132    ///
133    /// # Examples
134    ///
135    /// ```rust
136    /// use zero_pool::{ZeroPool, zp_define_task_fn, zp_write};
137    ///
138    /// struct MyTaskParams { value: u64, result: *mut u64 }
139    ///
140    /// zp_define_task_fn!(my_task_fn, MyTaskParams, |params| {
141    ///     zp_write!(params.result, params.value * 2);
142    /// });
143    ///
144    /// let pool = ZeroPool::new();
145    /// let mut result = 0u64;
146    /// let task_params = MyTaskParams { value: 42, result: &mut result };
147    /// let future = pool.submit_task(my_task_fn, &task_params);
148    /// future.wait();
149    /// assert_eq!(result, 84);
150    /// ```
151    pub fn submit_task<T>(&self, task_fn: TaskFnPointer, params: &T) -> TaskFuture {
152        let params_ptr = params as *const T as TaskParamPointer;
153        self.submit_raw_task(task_fn, params_ptr)
154    }
155
156    /// Submit a batch of uniform tasks with automatic pointer conversion
157    ///
158    /// All tasks in the batch must be the same type and use the same task function.
159    /// This method handles the pointer conversion automatically and is the most
160    /// convenient way to submit large batches of similar work.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// use zero_pool::{ZeroPool, zp_define_task_fn, zp_write};
166    ///
167    /// struct MyTaskParams { value: u64, result: *mut u64 }
168    ///
169    /// zp_define_task_fn!(my_task_fn, MyTaskParams, |params| {
170    ///     zp_write!(params.result, params.value * 2);
171    /// });
172    ///
173    /// let pool = ZeroPool::new();
174    /// let mut results = vec![0u64; 1000];
175    /// let task_params: Vec<_> = (0..1000)
176    ///     .map(|i| MyTaskParams { value: i as u64, result: &mut results[i] })
177    ///     .collect();
178    /// let future = pool.submit_batch_uniform(my_task_fn, &task_params);
179    /// future.wait();
180    /// assert_eq!(results[0], 0);
181    /// assert_eq!(results[1], 2);
182    /// assert_eq!(results[999], 1998);
183    /// ```
184    pub fn submit_batch_uniform<T>(&self, task_fn: TaskFnPointer, params_vec: &[T]) -> TaskFuture {
185        let tasks = uniform_tasks_to_pointers(task_fn, params_vec);
186        self.submit_raw_task_batch(&tasks)
187    }
188}
189
190impl Drop for ZeroPool {
191    fn drop(&mut self) {
192        self.queue.shutdown();
193
194        let workers = std::mem::take(&mut self.workers);
195        for handle in workers {
196            let _ = handle.join();
197        }
198    }
199}