Skip to content

Commit ec8fd57

Browse files
authored
When a task scope produces <= 1 task to run, run it on the calling thread immediately. (#932)
While generally speaking the calling thread would have picked up the task first anyways, I don't think it makes much sense usually to block the calling thread until another thread wakes and does the work.
1 parent 7d4cb70 commit ec8fd57

File tree

1 file changed

+39
-33
lines changed

1 file changed

+39
-33
lines changed

crates/bevy_tasks/src/task_pool.rs

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -167,44 +167,50 @@ impl TaskPool {
167167
let executor: &async_executor::Executor = &*self.executor;
168168
let executor: &'scope async_executor::Executor = unsafe { mem::transmute(executor) };
169169

170-
let fut = async move {
171-
let mut scope = Scope {
172-
executor,
173-
spawned: Vec::new(),
174-
};
175-
176-
f(&mut scope);
170+
let mut scope = Scope {
171+
executor,
172+
spawned: Vec::new(),
173+
};
177174

178-
let mut results = Vec::with_capacity(scope.spawned.len());
179-
for task in scope.spawned {
180-
results.push(task.await);
181-
}
175+
f(&mut scope);
176+
177+
if scope.spawned.is_empty() {
178+
Vec::default()
179+
} else if scope.spawned.len() == 1 {
180+
vec![future::block_on(&mut scope.spawned[0])]
181+
} else {
182+
let fut = async move {
183+
let mut results = Vec::with_capacity(scope.spawned.len());
184+
for task in scope.spawned {
185+
results.push(task.await);
186+
}
182187

183-
results
184-
};
188+
results
189+
};
185190

186-
// Pin the future on the stack.
187-
pin!(fut);
191+
// Pin the future on the stack.
192+
pin!(fut);
193+
194+
// SAFETY: This function blocks until all futures complete, so we do not read/write the
195+
// data from futures outside of the 'scope lifetime. However, rust has no way of knowing
196+
// this so we must convert to 'static here to appease the compiler as it is unable to
197+
// validate safety.
198+
let fut: Pin<&mut (dyn Future<Output = Vec<T>> + Send)> = fut;
199+
let fut: Pin<&'static mut (dyn Future<Output = Vec<T>> + Send + 'static)> =
200+
unsafe { mem::transmute(fut) };
201+
202+
// The thread that calls scope() will participate in driving tasks in the pool forward
203+
// until the tasks that are spawned by this scope() call complete. (If the caller of scope()
204+
// happens to be a thread in this thread pool, and we only have one thread in the pool, then
205+
// simply calling future::block_on(spawned) would deadlock.)
206+
let mut spawned = self.executor.spawn(fut);
207+
loop {
208+
if let Some(result) = future::block_on(future::poll_once(&mut spawned)) {
209+
break result;
210+
}
188211

189-
// SAFETY: This function blocks until all futures complete, so we do not read/write the
190-
// data from futures outside of the 'scope lifetime. However, rust has no way of knowing
191-
// this so we must convert to 'static here to appease the compiler as it is unable to
192-
// validate safety.
193-
let fut: Pin<&mut (dyn Future<Output = Vec<T>> + Send)> = fut;
194-
let fut: Pin<&'static mut (dyn Future<Output = Vec<T>> + Send + 'static)> =
195-
unsafe { mem::transmute(fut) };
196-
197-
// The thread that calls scope() will participate in driving tasks in the pool forward
198-
// until the tasks that are spawned by this scope() call complete. (If the caller of scope()
199-
// happens to be a thread in this thread pool, and we only have one thread in the pool, then
200-
// simply calling future::block_on(spawned) would deadlock.)
201-
let mut spawned = self.executor.spawn(fut);
202-
loop {
203-
if let Some(result) = future::block_on(future::poll_once(&mut spawned)) {
204-
break result;
212+
self.executor.try_tick();
205213
}
206-
207-
self.executor.try_tick();
208214
}
209215
}
210216

0 commit comments

Comments
 (0)