Skip to content

Commit 5d52c6f

Browse files
authored
Merge pull request #234 from Imberflur/send-dispatcher
Add a dispatcher that can be sent between threads
2 parents 5450bf3 + 3f15a49 commit 5d52c6f

File tree

6 files changed

+168
-42
lines changed

6 files changed

+168
-42
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,6 @@ jobs:
7373
# Miri currently reports leaks in some tests so we disable that check
7474
# here (might be due to ptr-int-ptr in crossbeam-epoch so might be
7575
# resolved in future versions of that crate).
76-
run: MIRIFLAGS="-Zmiri-ignore-leaks" cargo miri test
76+
#
77+
# crossbeam-epoch doesn't pass with stacked borrows https://github.com/crossbeam-rs/crossbeam/issues/545
78+
run: MIRIFLAGS="-Zmiri-ignore-leaks -Zmiri-tree-borrows" cargo miri test

src/dispatch/dispatcher.rs

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use smallvec::SmallVec;
22

3-
use crate::{dispatch::stage::Stage, system::RunNow, world::World};
3+
use crate::{
4+
dispatch::{stage::Stage, SendDispatcher},
5+
system::RunNow,
6+
world::World,
7+
};
48

59
/// This wrapper is used to share a replaceable ThreadPool with other
610
/// dispatchers. Useful with batch dispatchers.
@@ -10,19 +14,15 @@ pub type ThreadPoolWrapper = Option<::std::sync::Arc<::rayon::ThreadPool>>;
1014
/// The dispatcher struct, allowing
1115
/// systems to be executed in parallel.
1216
pub struct Dispatcher<'a, 'b> {
13-
stages: Vec<Stage<'a>>,
17+
inner: SendDispatcher<'a>,
1418
thread_local: ThreadLocal<'b>,
15-
#[cfg(feature = "parallel")]
16-
thread_pool: ::std::sync::Arc<::std::sync::RwLock<ThreadPoolWrapper>>,
1719
}
1820

1921
impl<'a, 'b> Dispatcher<'a, 'b> {
2022
/// Sets up all the systems which means they are gonna add default values
2123
/// for the resources they need.
2224
pub fn setup(&mut self, world: &mut World) {
23-
for stage in &mut self.stages {
24-
stage.setup(world);
25-
}
25+
self.inner.setup(world);
2626

2727
for sys in &mut self.thread_local {
2828
sys.setup(world);
@@ -34,9 +34,7 @@ impl<'a, 'b> Dispatcher<'a, 'b> {
3434
/// / or resources from the `World` which are associated with external
3535
/// resources.
3636
pub fn dispose(self, world: &mut World) {
37-
for stage in self.stages {
38-
stage.dispose(world);
39-
}
37+
self.inner.dispose(world);
4038

4139
for sys in self.thread_local {
4240
sys.dispose(world);
@@ -56,12 +54,7 @@ impl<'a, 'b> Dispatcher<'a, 'b> {
5654
/// Please note that this method assumes that no resource
5755
/// is currently borrowed. If that's the case, it panics.
5856
pub fn dispatch(&mut self, world: &World) {
59-
#[cfg(feature = "parallel")]
60-
self.dispatch_par(world);
61-
62-
#[cfg(not(feature = "parallel"))]
63-
self.dispatch_seq(world);
64-
57+
self.inner.dispatch(world);
6558
self.dispatch_thread_local(world);
6659
}
6760

@@ -77,18 +70,7 @@ impl<'a, 'b> Dispatcher<'a, 'b> {
7770
/// is currently borrowed. If that's the case, it panics.
7871
#[cfg(feature = "parallel")]
7972
pub fn dispatch_par(&mut self, world: &World) {
80-
let stages = &mut self.stages;
81-
82-
self.thread_pool
83-
.read()
84-
.unwrap()
85-
.as_ref()
86-
.unwrap()
87-
.install(move || {
88-
for stage in stages {
89-
stage.execute(world);
90-
}
91-
});
73+
self.inner.dispatch_par(world);
9274
}
9375

9476
/// Dispatches the systems (except thread local systems) sequentially.
@@ -99,9 +81,7 @@ impl<'a, 'b> Dispatcher<'a, 'b> {
9981
/// Please note that this method assumes that no resource
10082
/// is currently borrowed. If that's the case, it panics.
10183
pub fn dispatch_seq(&mut self, world: &World) {
102-
for stage in &mut self.stages {
103-
stage.execute_seq(world);
104-
}
84+
self.inner.dispatch_seq(world);
10585
}
10686

10787
/// Dispatch only thread local systems sequentially.
@@ -114,16 +94,28 @@ impl<'a, 'b> Dispatcher<'a, 'b> {
11494
}
11595
}
11696

97+
/// Converts this to a [`SendDispatcher`].
98+
///
99+
/// Fails and returns the original distpatcher if it contains thread local systems.
100+
pub fn try_into_sendable(self) -> Result<SendDispatcher<'a>, Self> {
101+
let Dispatcher {
102+
inner: _,
103+
thread_local,
104+
} = &self;
105+
106+
if thread_local.is_empty() {
107+
Ok(self.inner)
108+
} else {
109+
Err(self)
110+
}
111+
}
112+
117113
/// This method returns the largest amount of threads this dispatcher
118114
/// can make use of. This is mainly for debugging purposes so you can see
119115
/// how well your systems can make use of multi-threading.
120116
#[cfg(feature = "parallel")]
121117
pub fn max_threads(&self) -> usize {
122-
self.stages
123-
.iter()
124-
.map(Stage::max_threads)
125-
.max()
126-
.unwrap_or(0)
118+
self.inner.max_threads()
127119
}
128120
}
129121

@@ -154,9 +146,11 @@ pub fn new_dispatcher<'a, 'b>(
154146
thread_pool: ::std::sync::Arc<::std::sync::RwLock<ThreadPoolWrapper>>,
155147
) -> Dispatcher<'a, 'b> {
156148
Dispatcher {
157-
stages,
149+
inner: SendDispatcher {
150+
stages,
151+
thread_pool,
152+
},
158153
thread_local,
159-
thread_pool,
160154
}
161155
}
162156

@@ -166,7 +160,7 @@ pub fn new_dispatcher<'a, 'b>(
166160
thread_local: ThreadLocal<'b>,
167161
) -> Dispatcher<'a, 'b> {
168162
Dispatcher {
169-
stages,
163+
inner: SendDispatcher { stages },
170164
thread_local,
171165
}
172166
}

src/dispatch/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub use self::{
99
},
1010
builder::DispatcherBuilder,
1111
dispatcher::Dispatcher,
12+
send_dispatcher::SendDispatcher,
1213
};
1314

1415
#[cfg(feature = "parallel")]
@@ -18,5 +19,6 @@ mod builder;
1819
mod dispatcher;
1920
#[cfg(feature = "parallel")]
2021
mod par_seq;
22+
mod send_dispatcher;
2123
mod stage;
2224
mod util;

src/dispatch/send_dispatcher.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#[cfg(feature = "parallel")]
2+
use crate::dispatch::dispatcher::ThreadPoolWrapper;
3+
use crate::{dispatch::stage::Stage, system::RunNow, world::World};
4+
5+
/// `Send`able version of [`Dispatcher`](crate::dispatch::Dispatcher).
6+
///
7+
/// Can't hold thread local systems.
8+
///
9+
/// Create using [`Dispatcher::try_into_sendable`](crate::dispatch::Dispatcher::try_into_sendable).
10+
pub struct SendDispatcher<'a> {
11+
pub(super) stages: Vec<Stage<'a>>,
12+
#[cfg(feature = "parallel")]
13+
pub(super) thread_pool: ::std::sync::Arc<::std::sync::RwLock<ThreadPoolWrapper>>,
14+
}
15+
16+
impl<'a> SendDispatcher<'a> {
17+
/// Sets up all the systems which means they are gonna add default values
18+
/// for the resources they need.
19+
pub fn setup(&mut self, world: &mut World) {
20+
for stage in &mut self.stages {
21+
stage.setup(world);
22+
}
23+
}
24+
25+
/// Calls the `dispose` method of all systems and allows them to release
26+
/// external resources. It is common this method removes components and
27+
/// / or resources from the `World` which are associated with external
28+
/// resources.
29+
pub fn dispose(self, world: &mut World) {
30+
for stage in self.stages {
31+
stage.dispose(world);
32+
}
33+
}
34+
35+
/// Dispatch all the systems with given resources and context
36+
/// and then run thread local systems.
37+
///
38+
/// This function automatically redirects to
39+
///
40+
/// * [SendDispatcher::dispatch_par] in case it is supported
41+
/// * [SendDispatcher::dispatch_seq] otherwise
42+
///
43+
/// and runs `dispatch_thread_local` afterwards.
44+
///
45+
/// Please note that this method assumes that no resource
46+
/// is currently borrowed. If that's the case, it panics.
47+
pub fn dispatch(&mut self, world: &World) {
48+
#[cfg(feature = "parallel")]
49+
self.dispatch_par(world);
50+
51+
#[cfg(not(feature = "parallel"))]
52+
self.dispatch_seq(world);
53+
}
54+
55+
/// Dispatches the systems (except thread local systems)
56+
/// in parallel given the resources to operate on.
57+
///
58+
/// This operation blocks the
59+
/// executing thread.
60+
///
61+
/// Only available with "parallel" feature enabled.
62+
///
63+
/// Please note that this method assumes that no resource
64+
/// is currently borrowed. If that's the case, it panics.
65+
#[cfg(feature = "parallel")]
66+
pub fn dispatch_par(&mut self, world: &World) {
67+
let stages = &mut self.stages;
68+
69+
self.thread_pool
70+
.read()
71+
.unwrap()
72+
.as_ref()
73+
.unwrap()
74+
.install(move || {
75+
for stage in stages {
76+
stage.execute(world);
77+
}
78+
});
79+
}
80+
81+
/// Dispatches the systems (except thread local systems) sequentially.
82+
///
83+
/// This is useful if parallel overhead is
84+
/// too big or the platform does not support multithreading.
85+
///
86+
/// Please note that this method assumes that no resource
87+
/// is currently borrowed. If that's the case, it panics.
88+
pub fn dispatch_seq(&mut self, world: &World) {
89+
for stage in &mut self.stages {
90+
stage.execute_seq(world);
91+
}
92+
}
93+
94+
/// This method returns the largest amount of threads this dispatcher
95+
/// can make use of. This is mainly for debugging purposes so you can see
96+
/// how well your systems can make use of multi-threading.
97+
#[cfg(feature = "parallel")]
98+
pub fn max_threads(&self) -> usize {
99+
self.stages
100+
.iter()
101+
.map(Stage::max_threads)
102+
.max()
103+
.unwrap_or(0)
104+
}
105+
}
106+
107+
impl<'a, 'b> RunNow<'a> for SendDispatcher<'b> {
108+
fn run_now(&mut self, world: &World) {
109+
self.dispatch(world);
110+
}
111+
112+
fn setup(&mut self, world: &mut World) {
113+
self.setup(world);
114+
}
115+
116+
fn dispose(self: Box<Self>, world: &mut World) {
117+
(*self).dispose(world);
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
#[test]
124+
fn send_dispatcher_is_send() {
125+
fn is_send<T: Send>() {}
126+
is_send::<super::SendDispatcher>();
127+
}
128+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub use crate::dispatch::{Par, ParSeq, RunWithPool, Seq};
110110
pub use crate::{
111111
dispatch::{
112112
BatchAccessor, BatchController, BatchUncheckedWorld, Dispatcher, DispatcherBuilder,
113-
MultiDispatchController, MultiDispatcher,
113+
MultiDispatchController, MultiDispatcher, SendDispatcher,
114114
},
115115
meta::{CastFrom, MetaIter, MetaIterMut, MetaTable},
116116
system::{

src/world/res_downcast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Code is based on https://github.com/chris-morgan/mopa
1+
//! Code is based on <https://github.com/chris-morgan/mopa>
22
//! with the macro inlined for `Resource`. License files can be found in the
33
//! directory of this source file, see COPYRIGHT, LICENSE-APACHE and
44
//! LICENSE-MIT.

0 commit comments

Comments
 (0)