@@ -6,13 +6,13 @@ use std::{
6
6
} ,
7
7
} ;
8
8
9
- use crate :: { DroppableFuture , TaskIdentifier } ;
9
+ use crate :: { DroppableFuture , TaskIdentifier , TickedTimer } ;
10
10
11
11
#[ derive( Debug ) ]
12
12
pub enum TaskState {
13
13
Spawn ( TaskIdentifier ) ,
14
14
Wake ( TaskIdentifier ) ,
15
- Tick ( TaskIdentifier ) ,
15
+ Tick ( TaskIdentifier , f64 ) ,
16
16
Drop ( TaskIdentifier ) ,
17
17
}
18
18
@@ -28,6 +28,8 @@ pub struct TickedAsyncExecutor<O> {
28
28
// Broadcast recv channel should be notified when there are new messages in the queue
29
29
// Broadcast channel must also be able to remove older/stale messages (like a RingBuffer)
30
30
observer : O ,
31
+
32
+ tick_event : tokio:: sync:: watch:: Sender < f64 > ,
31
33
}
32
34
33
35
impl Default for TickedAsyncExecutor < fn ( TaskState ) > {
46
48
num_woken_tasks : Arc :: new ( AtomicUsize :: new ( 0 ) ) ,
47
49
num_spawned_tasks : Arc :: new ( AtomicUsize :: new ( 0 ) ) ,
48
50
observer,
51
+ tick_event : tokio:: sync:: watch:: channel ( 1.0 ) . 0 ,
49
52
}
50
53
}
51
54
@@ -87,23 +90,43 @@ where
87
90
88
91
/// Run the woken tasks once
89
92
///
93
+ /// `delta` is used for timing based operations
94
+ /// - `TickedTimer` uses this delta value to tick till completion
95
+ ///
96
+ /// `maybe_limit` is used to limit the number of woken tasks run per tick
97
+ /// - None would imply that there is no limit (all woken tasks would run)
98
+ /// - Some(limit) would imply that [0..limit] woken tasks would run,
99
+ /// even if more tasks are woken.
100
+ ///
90
101
/// Tick is !Sync i.e cannot be invoked from multiple threads
91
102
///
92
103
/// NOTE: Will not run tasks that are woken/scheduled immediately after `Runnable::run`
93
- pub fn tick ( & self ) {
104
+ pub fn tick ( & self , delta : f64 ) {
105
+ let _r = self . tick_event . send ( delta) ;
106
+
107
+ // Clamp woken tasks to limit
94
108
let num_woken_tasks = self . num_woken_tasks . load ( Ordering :: Relaxed ) ;
95
109
self . channel
96
110
. 1
97
111
. try_iter ( )
98
112
. take ( num_woken_tasks)
99
113
. for_each ( |( identifier, runnable) | {
100
- ( self . observer ) ( TaskState :: Tick ( identifier) ) ;
114
+ ( self . observer ) ( TaskState :: Tick ( identifier, delta ) ) ;
101
115
runnable. run ( ) ;
102
116
} ) ;
103
117
self . num_woken_tasks
104
118
. fetch_sub ( num_woken_tasks, Ordering :: Relaxed ) ;
105
119
}
106
120
121
+ pub fn create_timer ( & self ) -> TickedTimer {
122
+ let tick_recv = self . tick_event . subscribe ( ) ;
123
+ TickedTimer { tick_recv }
124
+ }
125
+
126
+ pub fn tick_channel ( & self ) -> tokio:: sync:: watch:: Receiver < f64 > {
127
+ self . tick_event . subscribe ( )
128
+ }
129
+
107
130
fn droppable_future < F > (
108
131
& self ,
109
132
identifier : TaskIdentifier ,
@@ -141,6 +164,9 @@ where
141
164
#[ cfg( test) ]
142
165
mod tests {
143
166
use super :: * ;
167
+ use std:: time:: { Duration , Instant } ;
168
+
169
+ const DELTA : f64 = 1000.0 / 60.0 ;
144
170
145
171
#[ test]
146
172
fn test_multiple_tasks ( ) {
@@ -157,10 +183,10 @@ mod tests {
157
183
} )
158
184
. detach ( ) ;
159
185
160
- executor. tick ( ) ;
186
+ executor. tick ( DELTA ) ;
161
187
assert_eq ! ( executor. num_tasks( ) , 2 ) ;
162
188
163
- executor. tick ( ) ;
189
+ executor. tick ( DELTA ) ;
164
190
assert_eq ! ( executor. num_tasks( ) , 0 ) ;
165
191
}
166
192
@@ -179,7 +205,7 @@ mod tests {
179
205
}
180
206
} ) ;
181
207
assert_eq ! ( executor. num_tasks( ) , 2 ) ;
182
- executor. tick ( ) ;
208
+ executor. tick ( DELTA ) ;
183
209
184
210
executor
185
211
. spawn_local ( "CancelTasks" , async move {
@@ -192,7 +218,85 @@ mod tests {
192
218
193
219
// Since we have cancelled the tasks above, the loops should eventually end
194
220
while executor. num_tasks ( ) != 0 {
195
- executor. tick ( ) ;
221
+ executor. tick ( DELTA ) ;
196
222
}
197
223
}
224
+
225
+ #[ test]
226
+ fn test_ticked_timer ( ) {
227
+ let executor = TickedAsyncExecutor :: default ( ) ;
228
+
229
+ for _ in 0 ..10 {
230
+ let timer: TickedTimer = executor. create_timer ( ) ;
231
+ executor
232
+ . spawn ( "ThreadedTimer" , async move {
233
+ timer. sleep_for ( 256.0 ) . await ;
234
+ } )
235
+ . detach ( ) ;
236
+ }
237
+
238
+ for _ in 0 ..10 {
239
+ let timer = executor. create_timer ( ) ;
240
+ executor
241
+ . spawn_local ( "LocalTimer" , async move {
242
+ timer. sleep_for ( 256.0 ) . await ;
243
+ } )
244
+ . detach ( ) ;
245
+ }
246
+
247
+ let now = Instant :: now ( ) ;
248
+ let mut instances = vec ! [ ] ;
249
+ while executor. num_tasks ( ) != 0 {
250
+ let current = Instant :: now ( ) ;
251
+ executor. tick ( DELTA ) ;
252
+ instances. push ( current. elapsed ( ) ) ;
253
+ std:: thread:: sleep ( Duration :: from_millis ( 16 ) ) ;
254
+ }
255
+ let elapsed = now. elapsed ( ) ;
256
+ println ! ( "Elapsed: {:?}" , elapsed) ;
257
+ println ! ( "Total: {:?}" , instances) ;
258
+
259
+ // Test Timer cancellation
260
+ let timer = executor. create_timer ( ) ;
261
+ executor
262
+ . spawn ( "ThreadedFuture" , async move {
263
+ timer. sleep_for ( 1000.0 ) . await ;
264
+ } )
265
+ . detach ( ) ;
266
+
267
+ let timer = executor. create_timer ( ) ;
268
+ executor
269
+ . spawn_local ( "LocalFuture" , async move {
270
+ timer. sleep_for ( 1000.0 ) . await ;
271
+ } )
272
+ . detach ( ) ;
273
+
274
+ let mut tick_event = executor. tick_channel ( ) ;
275
+ executor
276
+ . spawn ( "ThreadedTickFuture" , async move {
277
+ loop {
278
+ let _r = tick_event. changed ( ) . await ;
279
+ if _r. is_err ( ) {
280
+ break ;
281
+ }
282
+ }
283
+ } )
284
+ . detach ( ) ;
285
+
286
+ let mut tick_event = executor. tick_channel ( ) ;
287
+ executor
288
+ . spawn_local ( "LocalTickFuture" , async move {
289
+ loop {
290
+ let _r = tick_event. changed ( ) . await ;
291
+ if _r. is_err ( ) {
292
+ break ;
293
+ }
294
+ }
295
+ } )
296
+ . detach ( ) ;
297
+
298
+ executor. tick ( DELTA ) ;
299
+ assert_eq ! ( executor. num_tasks( ) , 4 ) ;
300
+ drop ( executor) ;
301
+ }
198
302
}
0 commit comments