bevy_core/
task_pool_options.rs

1use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
2use bevy_utils::tracing::trace;
3
4/// Defines a simple way to determine how many threads to use given the number of remaining cores
5/// and number of total cores
6#[derive(Clone, Debug)]
7pub struct TaskPoolThreadAssignmentPolicy {
8    /// Force using at least this many threads
9    pub min_threads: usize,
10    /// Under no circumstance use more than this many threads for this pool
11    pub max_threads: usize,
12    /// Target using this percentage of total cores, clamped by `min_threads` and `max_threads`. It is
13    /// permitted to use 1.0 to try to use all remaining threads
14    pub percent: f32,
15}
16
17impl TaskPoolThreadAssignmentPolicy {
18    /// Determine the number of threads to use for this task pool
19    fn get_number_of_threads(&self, remaining_threads: usize, total_threads: usize) -> usize {
20        assert!(self.percent >= 0.0);
21        let mut desired = (total_threads as f32 * self.percent).round() as usize;
22
23        // Limit ourselves to the number of cores available
24        desired = desired.min(remaining_threads);
25
26        // Clamp by min_threads, max_threads. (This may result in us using more threads than are
27        // available, this is intended. An example case where this might happen is a device with
28        // <= 2 threads.
29        desired.clamp(self.min_threads, self.max_threads)
30    }
31}
32
33/// Helper for configuring and creating the default task pools. For end-users who want full control,
34/// set up [`TaskPoolPlugin`](super::TaskPoolPlugin)
35#[derive(Clone, Debug)]
36pub struct TaskPoolOptions {
37    /// If the number of physical cores is less than `min_total_threads`, force using
38    /// `min_total_threads`
39    pub min_total_threads: usize,
40    /// If the number of physical cores is greater than `max_total_threads`, force using
41    /// `max_total_threads`
42    pub max_total_threads: usize,
43
44    /// Used to determine number of IO threads to allocate
45    pub io: TaskPoolThreadAssignmentPolicy,
46    /// Used to determine number of async compute threads to allocate
47    pub async_compute: TaskPoolThreadAssignmentPolicy,
48    /// Used to determine number of compute threads to allocate
49    pub compute: TaskPoolThreadAssignmentPolicy,
50}
51
52impl Default for TaskPoolOptions {
53    fn default() -> Self {
54        TaskPoolOptions {
55            // By default, use however many cores are available on the system
56            min_total_threads: 1,
57            max_total_threads: usize::MAX,
58
59            // Use 25% of cores for IO, at least 1, no more than 4
60            io: TaskPoolThreadAssignmentPolicy {
61                min_threads: 1,
62                max_threads: 4,
63                percent: 0.25,
64            },
65
66            // Use 25% of cores for async compute, at least 1, no more than 4
67            async_compute: TaskPoolThreadAssignmentPolicy {
68                min_threads: 1,
69                max_threads: 4,
70                percent: 0.25,
71            },
72
73            // Use all remaining cores for compute (at least 1)
74            compute: TaskPoolThreadAssignmentPolicy {
75                min_threads: 1,
76                max_threads: usize::MAX,
77                percent: 1.0, // This 1.0 here means "whatever is left over"
78            },
79        }
80    }
81}
82
83impl TaskPoolOptions {
84    /// Create a configuration that forces using the given number of threads.
85    pub fn with_num_threads(thread_count: usize) -> Self {
86        TaskPoolOptions {
87            min_total_threads: thread_count,
88            max_total_threads: thread_count,
89            ..Default::default()
90        }
91    }
92
93    /// Inserts the default thread pools into the given resource map based on the configured values
94    pub fn create_default_pools(&self) {
95        let total_threads = bevy_tasks::available_parallelism()
96            .clamp(self.min_total_threads, self.max_total_threads);
97        trace!("Assigning {} cores to default task pools", total_threads);
98
99        let mut remaining_threads = total_threads;
100
101        {
102            // Determine the number of IO threads we will use
103            let io_threads = self
104                .io
105                .get_number_of_threads(remaining_threads, total_threads);
106
107            trace!("IO Threads: {}", io_threads);
108            remaining_threads = remaining_threads.saturating_sub(io_threads);
109
110            IoTaskPool::get_or_init(|| {
111                TaskPoolBuilder::default()
112                    .num_threads(io_threads)
113                    .thread_name("IO Task Pool".to_string())
114                    .build()
115            });
116        }
117
118        {
119            // Determine the number of async compute threads we will use
120            let async_compute_threads = self
121                .async_compute
122                .get_number_of_threads(remaining_threads, total_threads);
123
124            trace!("Async Compute Threads: {}", async_compute_threads);
125            remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
126
127            AsyncComputeTaskPool::get_or_init(|| {
128                TaskPoolBuilder::default()
129                    .num_threads(async_compute_threads)
130                    .thread_name("Async Compute Task Pool".to_string())
131                    .build()
132            });
133        }
134
135        {
136            // Determine the number of compute threads we will use
137            // This is intentionally last so that an end user can specify 1.0 as the percent
138            let compute_threads = self
139                .compute
140                .get_number_of_threads(remaining_threads, total_threads);
141
142            trace!("Compute Threads: {}", compute_threads);
143
144            ComputeTaskPool::get_or_init(|| {
145                TaskPoolBuilder::default()
146                    .num_threads(compute_threads)
147                    .thread_name("Compute Task Pool".to_string())
148                    .build()
149            });
150        }
151    }
152}