1
1
use alloc:: { string:: String , vec:: Vec } ;
2
2
use bevy_platform:: sync:: Arc ;
3
- use core:: { cell:: RefCell , future:: Future , marker:: PhantomData , mem} ;
3
+ use core:: { cell:: { RefCell , Cell } , future:: Future , marker:: PhantomData , mem} ;
4
4
5
- use crate :: Task ;
5
+ use crate :: executor:: LocalExecutor ;
6
+ use crate :: { block_on, Task } ;
6
7
7
8
crate :: cfg:: std! {
8
9
if {
9
10
use std:: thread_local;
10
- use crate :: executor:: LocalExecutor ;
11
+
12
+ use crate :: executor:: LocalExecutor as Executor ;
11
13
12
14
thread_local! {
13
- static LOCAL_EXECUTOR : LocalExecutor <' static > = const { LocalExecutor :: new( ) } ;
15
+ static LOCAL_EXECUTOR : Executor <' static > = const { Executor :: new( ) } ;
14
16
}
15
-
16
- type ScopeResult <T > = alloc:: rc:: Rc <RefCell <Option <T >>>;
17
17
} else {
18
- use bevy_platform:: sync:: { Mutex , PoisonError } ;
19
- use crate :: executor:: Executor as LocalExecutor ;
20
18
21
- static LOCAL_EXECUTOR : LocalExecutor <' static > = const { LocalExecutor :: new( ) } ;
19
+ // Because we do not have thread-locals without std, we cannot use LocalExecutor here.
20
+ use crate :: executor:: Executor ;
22
21
23
- type ScopeResult < T > = Arc < Mutex < Option < T >>> ;
22
+ static LOCAL_EXECUTOR : Executor < ' static > = const { Executor :: new ( ) } ;
24
23
}
25
24
}
26
25
@@ -111,7 +110,7 @@ impl TaskPool {
111
110
/// This is similar to `rayon::scope` and `crossbeam::scope`
112
111
pub fn scope < ' env , F , T > ( & self , f : F ) -> Vec < T >
113
112
where
114
- F : for < ' scope > FnOnce ( & ' env mut Scope < ' scope , ' env , T > ) ,
113
+ F : for < ' scope > FnOnce ( & ' scope mut Scope < ' scope , ' env , T > ) ,
115
114
T : Send + ' static ,
116
115
{
117
116
self . scope_with_executor ( false , None , f)
@@ -130,7 +129,7 @@ impl TaskPool {
130
129
f : F ,
131
130
) -> Vec < T >
132
131
where
133
- F : for < ' scope > FnOnce ( & ' env mut Scope < ' scope , ' env , T > ) ,
132
+ F : for < ' scope > FnOnce ( & ' scope mut Scope < ' scope , ' env , T > ) ,
134
133
T : Send + ' static ,
135
134
{
136
135
// SAFETY: This safety comment applies to all references transmuted to 'env.
@@ -141,17 +140,22 @@ impl TaskPool {
141
140
// Any usages of the references passed into `Scope` must be accessed through
142
141
// the transmuted reference for the rest of this function.
143
142
144
- let executor = & LocalExecutor :: new ( ) ;
143
+ let executor = LocalExecutor :: new ( ) ;
144
+ // SAFETY: As above, all futures must complete in this function so we can change the lifetime
145
+ let executor_ref: & ' env LocalExecutor < ' env > = unsafe { mem:: transmute ( & executor) } ;
146
+
147
+ let results: RefCell < Vec < Option < T > > > = RefCell :: new ( Vec :: new ( ) ) ;
145
148
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
146
- let executor : & ' env LocalExecutor < ' env > = unsafe { mem:: transmute ( executor ) } ;
149
+ let results_ref : & ' env RefCell < Vec < Option < T > > > = unsafe { mem:: transmute ( & results ) } ;
147
150
148
- let results : RefCell < Vec < ScopeResult < T > > > = RefCell :: new ( Vec :: new ( ) ) ;
151
+ let pending_tasks : Cell < usize > = Cell :: new ( 0 ) ;
149
152
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
150
- let results : & ' env RefCell < Vec < ScopeResult < T > > > = unsafe { mem:: transmute ( & results ) } ;
153
+ let pending_tasks : & ' env Cell < usize > = unsafe { mem:: transmute ( & pending_tasks ) } ;
151
154
152
155
let mut scope = Scope {
153
- executor,
154
- results,
156
+ executor_ref,
157
+ pending_tasks,
158
+ results_ref,
155
159
scope : PhantomData ,
156
160
env : PhantomData ,
157
161
} ;
@@ -161,21 +165,17 @@ impl TaskPool {
161
165
162
166
f ( scope_ref) ;
163
167
164
- // Loop until all tasks are done
165
- while executor. try_tick ( ) { }
168
+ // Wait until the scope is complete
169
+ block_on ( executor. run ( async {
170
+ while pending_tasks. get ( ) != 0 {
171
+ futures_lite:: future:: yield_now ( ) . await ;
172
+ }
173
+ } ) ) ;
166
174
167
- let results = scope. results . borrow ( ) ;
168
175
results
169
- . iter ( )
170
- . map ( |result| crate :: cfg:: switch! { {
171
- crate :: cfg:: std => {
172
- result. borrow_mut( ) . take( ) . unwrap( )
173
- }
174
- _ => {
175
- let mut lock = result. lock( ) . unwrap_or_else( PoisonError :: into_inner) ;
176
- lock. take( ) . unwrap( )
177
- }
178
- } } )
176
+ . take ( )
177
+ . into_iter ( )
178
+ . map ( |result| result. unwrap ( ) )
179
179
. collect ( )
180
180
}
181
181
@@ -239,7 +239,7 @@ impl TaskPool {
239
239
/// ```
240
240
pub fn with_local_executor < F , R > ( & self , f : F ) -> R
241
241
where
242
- F : FnOnce ( & LocalExecutor ) -> R ,
242
+ F : FnOnce ( & Executor ) -> R ,
243
243
{
244
244
crate :: cfg:: switch! { {
245
245
crate :: cfg:: std => {
@@ -257,9 +257,11 @@ impl TaskPool {
257
257
/// For more information, see [`TaskPool::scope`].
258
258
#[ derive( Debug ) ]
259
259
pub struct Scope < ' scope , ' env : ' scope , T > {
260
- executor : & ' scope LocalExecutor < ' scope > ,
260
+ executor_ref : & ' scope LocalExecutor < ' scope > ,
261
+ // The number of pending tasks spawned on the scope
262
+ pending_tasks : & ' scope Cell < usize > ,
261
263
// Vector to gather results of all futures spawned during scope run
262
- results : & ' env RefCell < Vec < ScopeResult < T > > > ,
264
+ results_ref : & ' env RefCell < Vec < Option < T > > > ,
263
265
264
266
// make `Scope` invariant over 'scope and 'env
265
267
scope : PhantomData < & ' scope mut & ' scope ( ) > ,
@@ -295,21 +297,32 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> {
295
297
///
296
298
/// For more information, see [`TaskPool::scope`].
297
299
pub fn spawn_on_scope < Fut : Future < Output = T > + ' scope + MaybeSend > ( & self , f : Fut ) {
298
- let result = ScopeResult :: < T > :: default ( ) ;
299
- self . results . borrow_mut ( ) . push ( result. clone ( ) ) ;
300
+ // increment the number of pending tasks
301
+ let pending_tasks = self . pending_tasks ;
302
+ pending_tasks. update ( |i| i + 1 ) ;
303
+
304
+ // add a spot to keep the result, and record the index
305
+ let results_ref = self . results_ref ;
306
+ let mut results = results_ref. borrow_mut ( ) ;
307
+ let task_number = results. len ( ) ;
308
+ results. push ( None ) ;
309
+ drop ( results) ;
310
+
311
+ // create the job closure
300
312
let f = async move {
301
- let temp_result = f. await ;
302
-
303
- crate :: cfg:: std! {
304
- if {
305
- result. borrow_mut( ) . replace( temp_result) ;
306
- } else {
307
- let mut lock = result. lock( ) . unwrap_or_else( PoisonError :: into_inner) ;
308
- * lock = Some ( temp_result) ;
309
- }
310
- }
313
+ let result = f. await ;
314
+
315
+ // store the result in the allocated slot
316
+ let mut results = results_ref. borrow_mut ( ) ;
317
+ results[ task_number] = Some ( result) ;
318
+ drop ( results) ;
319
+
320
+ // decrement the pending tasks count
321
+ pending_tasks. update ( |i| i - 1 ) ;
311
322
} ;
312
- self . executor . spawn ( f) . detach ( ) ;
323
+
324
+ // spawn the job itself
325
+ self . executor_ref . spawn ( f) . detach ( ) ;
313
326
}
314
327
}
315
328
@@ -328,3 +341,32 @@ crate::cfg::std! {
328
341
impl <T : Sync > MaybeSync for T { }
329
342
}
330
343
}
344
+
345
+ #[ cfg( test) ]
346
+ mod test {
347
+ use std:: { time, thread} ;
348
+
349
+ use super :: * ;
350
+
351
+ /// This test creates a scope with a single task that goes to sleep for a
352
+ /// nontrivial amount of time. At one point, the scope would (incorrectly)
353
+ /// return early under these conditions, causing a crash.
354
+ ///
355
+ /// The correct behavior is for the scope to block until the receiver is
356
+ /// woken by the external thread.
357
+ #[ test]
358
+ fn scoped_spawn ( ) {
359
+ let ( sender, recever) = async_channel:: unbounded ( ) ;
360
+ let task_pool = TaskPool { } ;
361
+ let thread = thread:: spawn ( move || {
362
+ let duration = time:: Duration :: from_millis ( 50 ) ;
363
+ thread:: sleep ( duration) ;
364
+ let _ = sender. send ( 0 ) ;
365
+ } ) ;
366
+ task_pool. scope ( |scope| {
367
+ scope. spawn ( async {
368
+ recever. recv ( ) . await
369
+ } ) ;
370
+ } ) ;
371
+ }
372
+ }
0 commit comments