1
- use std:: {
2
- cell:: { Cell , RefCell } ,
3
- rc:: Rc ,
4
- } ;
1
+ use std:: { cell:: RefCell , rc:: Rc } ;
5
2
6
3
use wasm_bindgen:: prelude:: * ;
7
4
@@ -29,7 +26,7 @@ pub struct WebRunner {
29
26
events_to_unsubscribe : Rc < RefCell < Vec < EventToUnsubscribe > > > ,
30
27
31
28
/// Current animation frame in flight.
32
- request_animation_frame_id : Cell < Option < i32 > > ,
29
+ frame : Rc < RefCell < Option < AnimationFrameRequest > > > ,
33
30
34
31
resize_observer : Rc < RefCell < Option < ResizeObserverContext > > > ,
35
32
}
@@ -49,7 +46,7 @@ impl WebRunner {
49
46
panic_handler,
50
47
runner : Rc :: new ( RefCell :: new ( None ) ) ,
51
48
events_to_unsubscribe : Rc :: new ( RefCell :: new ( Default :: default ( ) ) ) ,
52
- request_animation_frame_id : Cell :: new ( None ) ,
49
+ frame : Default :: default ( ) ,
53
50
resize_observer : Default :: default ( ) ,
54
51
}
55
52
}
@@ -125,9 +122,9 @@ impl WebRunner {
125
122
pub fn destroy ( & self ) {
126
123
self . unsubscribe_from_all_events ( ) ;
127
124
128
- if let Some ( id ) = self . request_animation_frame_id . get ( ) {
125
+ if let Some ( frame ) = self . frame . take ( ) {
129
126
let window = web_sys:: window ( ) . unwrap ( ) ;
130
- window. cancel_animation_frame ( id) . ok ( ) ;
127
+ window. cancel_animation_frame ( frame . id ) . ok ( ) ;
131
128
}
132
129
133
130
if let Some ( runner) = self . runner . replace ( None ) {
@@ -202,16 +199,33 @@ impl WebRunner {
202
199
Ok ( ( ) )
203
200
}
204
201
202
+ /// Request an animation frame from the browser in which we can perform a paint.
203
+ ///
204
+ /// It is safe to call `request_animation_frame` multiple times in quick succession,
205
+ /// this function guarantees that only one animation frame is scheduled at a time.
205
206
pub ( crate ) fn request_animation_frame ( & self ) -> Result < ( ) , wasm_bindgen:: JsValue > {
207
+ if self . frame . borrow ( ) . is_some ( ) {
208
+ // there is already an animation frame in flight
209
+ return Ok ( ( ) ) ;
210
+ }
211
+
206
212
let window = web_sys:: window ( ) . unwrap ( ) ;
207
213
let closure = Closure :: once ( {
208
214
let runner_ref = self . clone ( ) ;
209
- move || events:: paint_and_schedule ( & runner_ref)
215
+ move || {
216
+ // We can paint now, so clear the animation frame.
217
+ // This drops the `closure` and allows another
218
+ // animation frame to be scheduled
219
+ let _ = runner_ref. frame . take ( ) ;
220
+ events:: paint_and_schedule ( & runner_ref)
221
+ }
210
222
} ) ;
211
223
212
224
let id = window. request_animation_frame ( closure. as_ref ( ) . unchecked_ref ( ) ) ?;
213
- self . request_animation_frame_id . set ( Some ( id) ) ;
214
- closure. forget ( ) ; // We must forget it, or else the callback is canceled on drop
225
+ self . frame . borrow_mut ( ) . replace ( AnimationFrameRequest {
226
+ id,
227
+ _closure : closure,
228
+ } ) ;
215
229
216
230
Ok ( ( ) )
217
231
}
@@ -232,6 +246,16 @@ impl WebRunner {
232
246
233
247
// ----------------------------------------------------------------------------
234
248
249
+ // https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html#using-fnonce-and-closureonce-with-requestanimationframe
250
+ struct AnimationFrameRequest {
251
+ /// Represents the ID of a frame in flight.
252
+ id : i32 ,
253
+
254
+ /// The callback given to `request_animation_frame`, stored here both to prevent it
255
+ /// from being canceled, and from having to `.forget()` it.
256
+ _closure : Closure < dyn FnMut ( ) -> Result < ( ) , JsValue > > ,
257
+ }
258
+
235
259
struct ResizeObserverContext {
236
260
resize_observer : web_sys:: ResizeObserver ,
237
261
closure : Closure < dyn FnMut ( js_sys:: Array ) > ,
0 commit comments