@@ -73,6 +73,7 @@ urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
73
73
url: map-objects; text:map object
74
74
url: get-o-p; text: Get
75
75
url: set-o-p-v-throw; text: Set
76
+ url: samevalue; text: SameValue
76
77
urlPrefix: native-error-types-used-in-this-standard-
77
78
text: TypeError
78
79
urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
@@ -182,18 +183,44 @@ partial namespace CSS {
182
183
};
183
184
</xmp>
184
185
186
+
187
+
188
+ Animator {#animator-desc}
189
+ ========
190
+
191
+ An <dfn>Animator</dfn> represents an animation instance that is running on the animation thread.
192
+ Animators are identified by a unique name and determine how the animation progresses its keyframe
193
+ effects given current input time. <a>Animator</a> instances live in {{AnimationWorkletGlobalScope}}
194
+ and each one is associated with a {{WorkletAnimation}} instance. An animator can only be
195
+ instantiated by construction of a {{WorkletAnimation}} .
196
+
197
+
198
+ Two animators types are supported: {{StatelessAnimator}} and {{StatefulAnimator}} each
199
+ providing a different state management strategy.
200
+
201
+
202
+ StatelessAnimator Interface {#stateless-animator-desc}
203
+ ---------------------------
204
+
205
+ This interface represents a stateless animator. This type of animator does not depend on any local
206
+ state either stored on the instance or global scope. Effectively, the animate function of an
207
+ {{StatelessAnimator}} can be treated as a pure function with the expectation that given the same
208
+ input, it produces the same output.
209
+
210
+
211
+
185
212
<xmp class='idl'>
186
- [Exposed=AnimationWorklet, Global=AnimationWorklet]
187
- interface AnimationWorkletGlobalScope : WorkletGlobalScope {
188
- void registerAnimator(DOMString name, VoidFunction animatorCtor);
213
+ [Exposed=AnimationWorklet, Global=AnimationWorklet, Constructor (optional any options)]
214
+ interface StatelessAnimator {
189
215
};
190
216
</xmp>
191
217
192
218
219
+
193
220
<div class='note'>
194
- Note: This is how the class should look.
221
+ This is how the class should look.
195
222
<pre class='lang-javascript'>
196
- class FooAnimator {
223
+ class FooAnimator extends StatelessAnimator {
197
224
constructor(options) {
198
225
// Called when a new animator is instantiated.
199
226
}
@@ -204,10 +231,70 @@ interface AnimationWorkletGlobalScope : WorkletGlobalScope {
204
231
</pre>
205
232
</div>
206
233
234
+ Note: The statelessness allows an animation worklet to perform optimization such as producing
235
+ multiple animation frames in parallel and performing very cheap teardown and setup. Using
236
+ StatelessAnimator is highly recommended to enable such optimizations.
237
+
238
+ StatefulAnimator Interface {#stateful-animator-desc}
239
+ ---------------------------
240
+
241
+ This interface represents a stateful animator. This animator can have local state and animation
242
+ worklet guarantees that it maintains this state as long as the stateful animator fulfills the
243
+ contract required by this interface as described following.
244
+
245
+
246
+ <a>Animation worklet</a> maintains a set of {{WorkletGlobalScope}} s which may exist across different
247
+ threads or processes. Animation worklet may temporarily terminate a global scope (e.g., to preserve
248
+ resources) or move a running <a>animator instance</a> across different global scopes (e.g., if its
249
+ effect is mutable only in a certain thread). Animation worklet guarantees that a stateful animator
250
+ instance's state is maintained even if the instance is respawned in a different global scope.
251
+
252
+ The basic mechanism for maintaining the state is that the animation worklet snapshots the local
253
+ state that is exposed via {{StatefulAnimator/state}} attribute and then reifies it at a later time
254
+ to be passed into the constructor when the animator instance is respawned in a potentially different
255
+ global scope. This processes is specified is details in <a>migrate an animator instance</a>
256
+ algorithm.
257
+
258
+ A user-defined stateful animator is expected to fulfill the required contract which is that its
259
+ state attribute is an object representing its state that can be serialized using structured
260
+ serialized algorithm and that it can also recreate its state given that same object passed to
261
+ its constructor.
262
+
263
+ <xmp class='idl'>
264
+ [Exposed=AnimationWorklet, Global=AnimationWorklet,
265
+ Constructor (optional any options, optional any state)]
266
+ interface StatefulAnimator {
267
+ attribute any state;
268
+ };
269
+ </xmp>
270
+
271
+
272
+ <div class='note'>
273
+ This is how the class should look.
274
+ <pre class='lang-javascript'>
275
+ class BarAnimator extends StatefulAnimator {
276
+ constructor(options, state) {
277
+ // Called when a new animator is instantiated (either first time or after being respawned).
278
+ this.currentVelocity = state ? state.velocity : 0;
279
+ }
280
+ animate(currentTime, effect) {
281
+ // Animation frame logic goes here and can rely on this.currentVelocity.
282
+ this.currentVelocity += 0.1;
283
+ }
284
+ get state {
285
+ // The returned object should be serializable using structured clonable algorithm.
286
+ return {
287
+ velocity: this.currentVelocity;
288
+ }
289
+ }
290
+ }
291
+ </pre>
292
+ </div>
207
293
208
294
209
295
Animator Definition {#animator-definition-desc}
210
- ====================
296
+ -------------------
297
+
211
298
An <dfn>animator definition</dfn> is a <a>struct</a> which describes the author defined custom
212
299
animation as needed by {{AnimationWorkletGlobalScope}} . It consists of:
213
300
@@ -217,14 +304,22 @@ animation as needed by {{AnimationWorkletGlobalScope}}. It consists of:
217
304
218
305
- An <dfn>animate function</dfn> which is a <a>Function</a> <a>callback function</a> type.
219
306
220
- - A <dfn>destroy function </dfn> which is a <a>Function</a> <a>callback function</a> type.
307
+ - A <dfn>stateful flag </dfn>
221
308
222
309
223
310
Registering an Animator Definition {#registering-animator-definition}
224
311
-------------------------------------
225
312
An {{AnimationWorkletGlobalScope}} has a <dfn>animator name to animator definition map</dfn> .
226
313
The map gets populated when {{registerAnimator(name, animatorCtorValue)}} is called.
227
314
315
+
316
+ <xmp class='idl'>
317
+ [ Exposed=AnimationWorklet, Global=AnimationWorklet ]
318
+ interface AnimationWorkletGlobalScope : WorkletGlobalScope {
319
+ void registerAnimator(DOMString name, VoidFunction animatorCtor);
320
+ };
321
+ </xmp>
322
+
228
323
<div algorithm="register-animator">
229
324
230
325
When the <dfn method for=AnimationWorkletGlobalScope>registerAnimator(|name|, |animatorCtorValue|)</dfn>
@@ -246,22 +341,18 @@ following steps:
246
341
247
342
4. Let |prototype| be the result of <a>Get</a> (|animatorCtorValue|, "prototype").
248
343
249
- 5. If the result of <a>Type</a> (|prototype|) is not Object, <a>throw</a> a <a>TypeError</a>
250
- and abort all these steps.
344
+
345
+ 5. If <a>SameValue</a> (|prototype|, {{StatelessAnimator}} ) is <b> true</b> set |stateful| to be
346
+ <b> false</b> , otherwise if <a>SameValue</a> (|prototype|, {{StatefulAnimator}} ) is
347
+ <b> true</b> set |stateful| to be <b> true</b> , otherwise <a>throw</a> a <a>TypeError</a> and
348
+ abort all these steps.
251
349
252
350
6. Let |animateValue| be the result of <a>Get</a> (|prototype|, "animate").
253
351
254
352
7. Let |animate| be the result of <a>converting</a> |animateValue| to the <a>Function</a>
255
353
<a>callback function</a> type. If an exception is thrown, rethrow the exception and abort
256
354
all these steps.
257
355
258
- 8. Let |destroyValue| be the result of <a>Get</a> (|prototype|, "onDestroy").
259
-
260
- 9. Let |destroy| be the result of <a>converting</a> |destroyValue| to the <a>Function</a>
261
- <a>callback function</a> type. If an exception is thrown, rethrow the exception and abort
262
- all these steps.
263
-
264
-
265
356
8. Let |definition| be a new <a>animator definition</a> with:
266
357
267
358
- <a>animator name</a> being |name|
@@ -270,7 +361,7 @@ following steps:
270
361
271
362
- <a>animate function</a> being |animate|
272
363
273
- - <a>destroy function </a> being |destroy |
364
+ - <a>stateful flag </a> being |stateful |
274
365
275
366
276
367
9. Add the key-value pair (|name| - |definition|) to the <a>animator name to animator
@@ -300,6 +391,9 @@ and owns the instance specific state such as animation effect and timelines. It
300
391
301
392
- An <dfn>animator serialized options</dfn> which is a serializable object.
302
393
394
+ A <dfn>stateful animator instance</dfn> is an <a>animator instance</a> whose corresponding
395
+ <a>animator definition</a> 's <a>stateful flag</a> is <b> true</b> .
396
+
303
397
304
398
Creating an Animator Instance {#creating-animator-instance}
305
399
-----------------------------------------------------------
@@ -349,7 +443,7 @@ To <dfn>create a new animator instance</dfn> given a |name|, |timeline|, |effect
349
443
Running Animators {#running-animators}
350
444
--------------------------------------
351
445
352
- When a user agent wants to produce a new animation frame, if for any <a>animator instance</a> the
446
+ When the user agent wants to produce a new animation frame, if for any <a>animator instance</a> the
353
447
associated <a>animation requested flag</a> is <a>frame-requested</a> then the the user agent
354
448
<em> must</em> <a>run animators</a> for the current frame.
355
449
@@ -390,6 +484,11 @@ instance set</a>. For each such |instance| the user agent <em>must</em> perform
390
484
Note: Although inefficient, it is legal for the user agent to <a>run animators</a> multiple times
391
485
in the same frame.
392
486
487
+
488
+ Issue: should be explicit as to what happens if the animateFunction throws an exception. At least
489
+ we should have wording that the localTime values of the keyframes are ignored to avoid incorrect
490
+ partial updates.
491
+
393
492
Removing an Animator Instance {#removing-animator}
394
493
-----------------------------------------
395
494
@@ -406,11 +505,8 @@ To <dfn>remove an animator instance</dfn> given |instance| and |workletGlobalSco
406
505
Migrating an Animator Instance {#migrating-animator}
407
506
-----------------------------------------
408
507
409
- User agents are responsible for assigning an <a>animator instance</a> to a {{WorkletGlobalScope}} .
410
- There can be many such {{WorkletGlobalScope}} s, which may exist across different threads or
411
- processes. To give the most flexibility to user agents in this respect, we allow migration of an
412
- <a>animator instance</a> while it is running. The basic mechanism is to serialize the internal state
413
- of any author-defined effect, and restore it after migration.
508
+ The migration process allows <a>stateful animator instance</a> to be migrated to a different
509
+ {{WorkletGlobalScope}} without losing their local state.
414
510
415
511
<div algorithm="migrate-animator">
416
512
@@ -429,17 +525,17 @@ To <dfn>migrate an animator instance</dfn> from one {{WorkletGlobalScope}} to an
429
525
430
526
If |definition| does not exist then abort the following steps.
431
527
432
- 3. Let |destroyFunction | be the <a>destroy function </a> of |definition|.
528
+ 3. Let |stateful | be the <a>stateful flag </a> of |definition|.
433
529
530
+ 4. If |stateful| is <a>false</a> then abort the following steps.
434
531
435
- 4. <a>Invoke</a> |destroyFunction| with |instance| as the <a>callback this value</a> and
436
- let |state| be the result of the invocation. If any exception is thrown, rethrow the
437
- exception and abort the following steps.
532
+ 5. Let |state| be the result of <a>Get</a> (|instance|, "state"). If any exception is thrown,
533
+ rethrow the exception and abort the following steps.
438
534
439
- 5 . Set |serializedState| to be the result of <a>StructuredSerialize</a> (|state|).
535
+ 6 . Set |serializedState| to be the result of <a>StructuredSerialize</a> (|state|).
440
536
If any exception is thrown, then abort the following steps.
441
537
442
- 6 . Run the procedure to <a>remove an animator instance</a> given |instance|, and
538
+ 7 . Run the procedure to <a>remove an animator instance</a> given |instance|, and
443
539
|sourceWorkletGlobalScope|.
444
540
445
541
2. Wait for the above task to complete. If the task is aborted, abort the following steps.
@@ -456,6 +552,9 @@ To <dfn>migrate an animator instance</dfn> from one {{WorkletGlobalScope}} to an
456
552
457
553
</div>
458
554
555
+ If an animator state getter throws the user agent will remove the animator but does not recreate it.
556
+ This effectively removes the animator instance.
557
+
459
558
460
559
Requesting Animation Frames {#requesting-animation-frames}
461
560
----------------------------------------------------------
@@ -772,10 +871,15 @@ animation.play();
772
871
773
872
// Inside AnimationWorkletGlobalScope
774
873
775
- registerAnimator('hidey-bar' , class {
776
- constructor(options) {
874
+ registerAnimator('hidey-bar' , class HidybarAnimator extends StatefulAnimator {
875
+ constructor(options, state ) {
777
876
this.scrollTimeline_ = options.scrollTimeline;
778
877
this.documentTimeline_ = options.documentTimeline;
878
+
879
+ if (state) {
880
+ this.startTime_ = state.startTime;
881
+ this.direction_ = state.direction;
882
+ }
779
883
}
780
884
781
885
animate(currentTime, effect) {
@@ -800,6 +904,13 @@ registerAnimator('hidey-bar', class {
800
904
// Drive the output effect by setting its local time.
801
905
effect.localTime = localTime;
802
906
}
907
+
908
+ getter state() {
909
+ return {
910
+ startTime: this.startTime_,
911
+ direction: this.direction_
912
+ }
913
+ }
803
914
});
804
915
805
916
</xmp>
@@ -850,15 +961,11 @@ animation.play();
850
961
851
962
<xmp class='lang-javascript'>
852
963
// Inside AnimationWorkletGlobalScope.
853
- registerAnimator('twitter-header' , class {
964
+ registerAnimator('twitter-header' , class HeaderAnimator extends StatelessAnimator {
854
965
constructor(options) {
855
966
this.timing_ = new CubicBezier('ease-out' );
856
967
}
857
968
858
- clamp(value, min, max) {
859
- return Math.min(Math.max(value, min), max);
860
- }
861
-
862
969
animate(currentTime, effect) {
863
970
const scroll = currentTime; // scroll is in [0, 1000] range
864
971
@@ -867,6 +974,11 @@ registerAnimator('twitter-header', class {
867
974
effect.children[1] .localTime = this.timing_(clamp(scroll, 0, 500));
868
975
}
869
976
});
977
+
978
+ function clamp(value, min, max) {
979
+ return Math.min(Math.max(value, min), max);
980
+ }
981
+
870
982
</xmp>
871
983
872
984
Example 3: Parallax backgrounds. {#example-3}
@@ -917,7 +1029,7 @@ fastParallax.play();
917
1029
918
1030
<xmp class='lang-javascript'>
919
1031
// Inside AnimationWorkletGlobalScope.
920
- registerAnimator('parallax' , class {
1032
+ registerAnimator('parallax' , class ParallaxAnimator extends StatelessAnimator {
921
1033
constructor(options) {
922
1034
this.rate_ = options.rate;
923
1035
}
0 commit comments