Skip to content

Commit 20de222

Browse files
authored
[css-animationworklet] Separate Stateful and Stateless animator interfaces #812 (#827)
Fixes issue #812 . * Introduce two new interfaces StatefulAnimator and StatelessAnimator. Add sections to describe the difference. * Update Animator Definition to has stateful flag and initialize the flag based on the animator prototype object. * Update the algorithm for migration process to not use onDestroy callback but use state getter. * Update the examples to use these new interfaces and show how getter should be used.
1 parent 2c93173 commit 20de222

File tree

1 file changed

+149
-37
lines changed

1 file changed

+149
-37
lines changed

css-animationworklet/Overview.bs

+149-37
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
7373
url: map-objects; text:map object
7474
url: get-o-p; text: Get
7575
url: set-o-p-v-throw; text: Set
76+
url: samevalue; text: SameValue
7677
urlPrefix: native-error-types-used-in-this-standard-
7778
text: TypeError
7879
urlPrefix: https://www.w3.org/TR/hr-time-2/#dom-; type: dfn
@@ -182,18 +183,44 @@ partial namespace CSS {
182183
};
183184
</xmp>
184185

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+
185212
<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 {
189215
};
190216
</xmp>
191217

192218

219+
193220
<div class='note'>
194-
Note: This is how the class should look.
221+
This is how the class should look.
195222
<pre class='lang-javascript'>
196-
class FooAnimator {
223+
class FooAnimator extends StatelessAnimator{
197224
constructor(options) {
198225
// Called when a new animator is instantiated.
199226
}
@@ -204,10 +231,70 @@ interface AnimationWorkletGlobalScope : WorkletGlobalScope {
204231
</pre>
205232
</div>
206233

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>
207293

208294

209295
Animator Definition {#animator-definition-desc}
210-
====================
296+
-------------------
297+
211298
An <dfn>animator definition</dfn> is a <a>struct</a> which describes the author defined custom
212299
animation as needed by {{AnimationWorkletGlobalScope}}. It consists of:
213300

@@ -217,14 +304,22 @@ animation as needed by {{AnimationWorkletGlobalScope}}. It consists of:
217304

218305
- An <dfn>animate function</dfn> which is a <a>Function</a> <a>callback function</a> type.
219306

220-
- A <dfn>destroy function</dfn> which is a <a>Function</a> <a>callback function</a> type.
307+
- A <dfn>stateful flag</dfn>
221308

222309

223310
Registering an Animator Definition {#registering-animator-definition}
224311
-------------------------------------
225312
An {{AnimationWorkletGlobalScope}} has a <dfn>animator name to animator definition map</dfn>.
226313
The map gets populated when {{registerAnimator(name, animatorCtorValue)}} is called.
227314

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+
228323
<div algorithm="register-animator">
229324

230325
When the <dfn method for=AnimationWorkletGlobalScope>registerAnimator(|name|, |animatorCtorValue|)</dfn>
@@ -246,22 +341,18 @@ following steps:
246341

247342
4. Let |prototype| be the result of <a>Get</a>(|animatorCtorValue|, "prototype").
248343

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.
251349

252350
6. Let |animateValue| be the result of <a>Get</a>(|prototype|, "animate").
253351

254352
7. Let |animate| be the result of <a>converting</a> |animateValue| to the <a>Function</a>
255353
<a>callback function</a> type. If an exception is thrown, rethrow the exception and abort
256354
all these steps.
257355

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-
265356
8. Let |definition| be a new <a>animator definition</a> with:
266357

267358
- <a>animator name</a> being |name|
@@ -270,7 +361,7 @@ following steps:
270361

271362
- <a>animate function</a> being |animate|
272363

273-
- <a>destroy function</a> being |destroy|
364+
- <a>stateful flag</a> being |stateful|
274365

275366

276367
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
300391

301392
- An <dfn>animator serialized options</dfn> which is a serializable object.
302393

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+
303397

304398
Creating an Animator Instance {#creating-animator-instance}
305399
-----------------------------------------------------------
@@ -349,7 +443,7 @@ To <dfn>create a new animator instance</dfn> given a |name|, |timeline|, |effect
349443
Running Animators {#running-animators}
350444
--------------------------------------
351445

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
353447
associated <a>animation requested flag</a> is <a>frame-requested</a> then the the user agent
354448
<em>must</em> <a>run animators</a> for the current frame.
355449

@@ -390,6 +484,11 @@ instance set</a>. For each such |instance| the user agent <em>must</em> perform
390484
Note: Although inefficient, it is legal for the user agent to <a>run animators</a> multiple times
391485
in the same frame.
392486

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+
393492
Removing an Animator Instance {#removing-animator}
394493
-----------------------------------------
395494

@@ -406,11 +505,8 @@ To <dfn>remove an animator instance</dfn> given |instance| and |workletGlobalSco
406505
Migrating an Animator Instance {#migrating-animator}
407506
-----------------------------------------
408507

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.
414510

415511
<div algorithm="migrate-animator">
416512

@@ -429,17 +525,17 @@ To <dfn>migrate an animator instance</dfn> from one {{WorkletGlobalScope}} to an
429525

430526
If |definition| does not exist then abort the following steps.
431527

432-
3. Let |destroyFunction| be the <a>destroy function</a> of |definition|.
528+
3. Let |stateful| be the <a>stateful flag</a> of |definition|.
433529

530+
4. If |stateful| is <a>false</a> then abort the following steps.
434531

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.
438534

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|).
440536
If any exception is thrown, then abort the following steps.
441537

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
443539
|sourceWorkletGlobalScope|.
444540

445541
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
456552

457553
</div>
458554

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+
459558

460559
Requesting Animation Frames {#requesting-animation-frames}
461560
----------------------------------------------------------
@@ -772,10 +871,15 @@ animation.play();
772871

773872
// Inside AnimationWorkletGlobalScope
774873

775-
registerAnimator('hidey-bar', class {
776-
constructor(options) {
874+
registerAnimator('hidey-bar', class HidybarAnimator extends StatefulAnimator {
875+
constructor(options, state) {
777876
this.scrollTimeline_ = options.scrollTimeline;
778877
this.documentTimeline_ = options.documentTimeline;
878+
879+
if (state) {
880+
this.startTime_ = state.startTime;
881+
this.direction_ = state.direction;
882+
}
779883
}
780884

781885
animate(currentTime, effect) {
@@ -800,6 +904,13 @@ registerAnimator('hidey-bar', class {
800904
// Drive the output effect by setting its local time.
801905
effect.localTime = localTime;
802906
}
907+
908+
getter state() {
909+
return {
910+
startTime: this.startTime_,
911+
direction: this.direction_
912+
}
913+
}
803914
});
804915

805916
</xmp>
@@ -850,15 +961,11 @@ animation.play();
850961

851962
<xmp class='lang-javascript'>
852963
// Inside AnimationWorkletGlobalScope.
853-
registerAnimator('twitter-header', class {
964+
registerAnimator('twitter-header', class HeaderAnimator extends StatelessAnimator {
854965
constructor(options) {
855966
this.timing_ = new CubicBezier('ease-out');
856967
}
857968

858-
clamp(value, min, max) {
859-
return Math.min(Math.max(value, min), max);
860-
}
861-
862969
animate(currentTime, effect) {
863970
const scroll = currentTime; // scroll is in [0, 1000] range
864971

@@ -867,6 +974,11 @@ registerAnimator('twitter-header', class {
867974
effect.children[1].localTime = this.timing_(clamp(scroll, 0, 500));
868975
}
869976
});
977+
978+
function clamp(value, min, max) {
979+
return Math.min(Math.max(value, min), max);
980+
}
981+
870982
</xmp>
871983

872984
Example 3: Parallax backgrounds. {#example-3}
@@ -917,7 +1029,7 @@ fastParallax.play();
9171029

9181030
<xmp class='lang-javascript'>
9191031
// Inside AnimationWorkletGlobalScope.
920-
registerAnimator('parallax', class {
1032+
registerAnimator('parallax', class ParallaxAnimator extends StatelessAnimator {
9211033
constructor(options) {
9221034
this.rate_ = options.rate;
9231035
}

0 commit comments

Comments
 (0)