@vune-ui/animation 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +470 -0
- package/CHANGELOG.md +88 -0
- package/LICENSE +21 -0
- package/PERFORMANCE.md +151 -0
- package/README.md +630 -0
- package/dist/index.d.ts +474 -0
- package/dist/src/canvas/index.d.ts +15 -0
- package/dist/src/canvas/index.js +67 -0
- package/dist/src/constraints/index.d.ts +33 -0
- package/dist/src/constraints/index.js +346 -0
- package/dist/src/core/bezier.js +51 -0
- package/dist/src/core/composition.js +17 -0
- package/dist/src/core/controls.js +22 -0
- package/dist/src/core/default-engine.js +20 -0
- package/dist/src/core/easing.js +58 -0
- package/dist/src/core/engine.js +1031 -0
- package/dist/src/core/frame-budget.js +30 -0
- package/dist/src/core/index.d.ts +43 -0
- package/dist/src/core/index.js +17 -0
- package/dist/src/core/js-spring-batch.js +57 -0
- package/dist/src/core/kinetics.js +140 -0
- package/dist/src/core/math.js +20 -0
- package/dist/src/core/motion-value.js +53 -0
- package/dist/src/core/planner.js +72 -0
- package/dist/src/core/specs.js +70 -0
- package/dist/src/dom/index.d.ts +41 -0
- package/dist/src/dom/index.js +364 -0
- package/dist/src/gesture/index.d.ts +66 -0
- package/dist/src/gesture/index.js +376 -0
- package/dist/src/index.js +53 -0
- package/dist/src/interpolate/color.js +223 -0
- package/dist/src/interpolate/css.d.ts +13 -0
- package/dist/src/interpolate/css.js +34 -0
- package/dist/src/interpolate/index.d.ts +13 -0
- package/dist/src/interpolate/index.js +55 -0
- package/dist/src/interpolate/transform.js +247 -0
- package/dist/src/layout/index.d.ts +56 -0
- package/dist/src/layout/index.js +485 -0
- package/dist/src/material/index.d.ts +9 -0
- package/dist/src/material/index.js +70 -0
- package/dist/src/path/index.d.ts +37 -0
- package/dist/src/path/index.js +527 -0
- package/dist/src/render/frame-batcher.js +52 -0
- package/dist/src/scroll/index.d.ts +55 -0
- package/dist/src/scroll/index.js +233 -0
- package/dist/src/timeline/index.d.ts +147 -0
- package/dist/src/timeline/index.js +849 -0
- package/dist/src/transition/index.d.ts +88 -0
- package/dist/src/transition/index.js +369 -0
- package/dist/src/wasm/index.d.ts +29 -0
- package/dist/src/wasm/index.js +8 -0
- package/dist/src/wasm/loader.js +55 -0
- package/dist/src/wasm/shared-wasm-spring-batch.js +52 -0
- package/dist/src/wasm/wasm-spring-batch.js +52 -0
- package/dist/src/webgl/index.d.ts +22 -0
- package/dist/src/webgl/index.js +94 -0
- package/dist/src/webgpu/index.d.ts +35 -0
- package/dist/src/webgpu/index.js +73 -0
- package/dist/src/webgpu/spring-batch.js +218 -0
- package/dist/src/worker/index.d.ts +17 -0
- package/dist/src/worker/index.js +1 -0
- package/dist/src/worker/shared-spring-worker.js +218 -0
- package/dist/src/worker/shared-worker.js +75 -0
- package/dist/wasm/kernel-scalar.wasm +0 -0
- package/dist/wasm/kernel-shared-scalar.wasm +0 -0
- package/dist/wasm/kernel-shared-simd.wasm +0 -0
- package/dist/wasm/kernel-simd.wasm +0 -0
- package/package.json +113 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Core execution graph
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Public API
|
|
7
|
+
|
|
|
8
|
+
MotionValue / motion specs / Timeline graph
|
|
9
|
+
|
|
|
10
|
+
MotionEngine scheduler
|
|
11
|
+
|
|
|
12
|
+
+----------------+--------+---------+----------------+
|
|
13
|
+
| | | |
|
|
14
|
+
timing bucket kinetic release generic drivers spring bucket
|
|
15
|
+
| dense SoA
|
|
16
|
+
| |
|
|
17
|
+
TimelinePlayer JS / WASM / Worker
|
|
18
|
+
| |
|
|
19
|
+
+--------+-------+
|
|
20
|
+
|
|
|
21
|
+
MotionValue commit
|
|
22
|
+
|
|
|
23
|
+
renderer adapters
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The scheduler does not traverse a retained UI scene graph. Springs use dense numeric arrays, kinetic releases use low-count analytical solvers, and timeline players register only while they are actively running. Paused or completed timelines are removed from the driver set.
|
|
27
|
+
|
|
28
|
+
Generic drivers receive real elapsed wall time, while spring/timing numerical integration keeps its existing defensive `dt` clamp. This separation lets a timeline catch up after a suspended tab without asking a spring solver to integrate an arbitrarily large time step.
|
|
29
|
+
|
|
30
|
+
## Adaptive backend selection
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
small workload
|
|
34
|
+
|
|
|
35
|
+
v
|
|
36
|
+
JS ---- pressure/count ----> main-thread WASM SIMD
|
|
37
|
+
|
|
|
38
|
+
| pressure/count + WebGPU support (async)
|
|
39
|
+
v
|
|
40
|
+
WebGPU Compute + readback
|
|
41
|
+
|
|
|
42
|
+
| pressure/count + SAB support
|
|
43
|
+
v
|
|
44
|
+
Worker + shared WASM
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`FrameBudgetGovernor` observes main-thread execution cost, not total Worker wall time. Under sustained pressure it lowers promotion thresholds. It never changes spring semantics or intentionally drops fidelity.
|
|
48
|
+
|
|
49
|
+
Once an engine has promoted into a shared Worker batch, small workloads can still use `step()` on the caller thread over that same shared memory. Large auto-scheduled frames use the Worker. No state copy is required to switch between those two execution locations.
|
|
50
|
+
|
|
51
|
+
## Worker ownership and frame-boundary mutation buffer
|
|
52
|
+
|
|
53
|
+
A Worker step owns indices `[0, submittedCount)` until it completes. User input can still arrive while that computation is running.
|
|
54
|
+
|
|
55
|
+
Directly mutating `targets[index]`, swap-removing a spring, or replacing solver parameters during that window would be a real shared-memory data race. The engine therefore separates logical motion state from physical spring slots during an in-flight frame.
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
Worker owns shared slots
|
|
59
|
+
|
|
|
60
|
+
+---- user retarget ------> logical target updated
|
|
61
|
+
| pendingSpringSync
|
|
62
|
+
|
|
|
63
|
+
+---- user cancel --------> logical removal
|
|
64
|
+
| deferredSpringRemovals
|
|
65
|
+
|
|
|
66
|
+
+---- new spring ---------> appended beyond submittedCount
|
|
67
|
+
safe to initialize immediately
|
|
68
|
+
|
|
69
|
+
Worker completes
|
|
70
|
+
|
|
|
71
|
+
v
|
|
72
|
+
physical removals / swap-compaction
|
|
73
|
+
|
|
|
74
|
+
apply pending solver parameter changes
|
|
75
|
+
|
|
|
76
|
+
commit surviving positions + velocities
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Controls use the logical target, so calling `finish()` during a Worker frame never jumps to a stale target stored in shared memory.
|
|
80
|
+
|
|
81
|
+
Overlapping engine-level `stepAsync()` calls are serialized. The backend also serializes its command slot, giving deterministic frame order at both levels.
|
|
82
|
+
|
|
83
|
+
## Shared Worker control plane
|
|
84
|
+
|
|
85
|
+
The Worker receives the shared `WebAssembly.Memory` once during initialization. It blocks on a tiny atomic control buffer.
|
|
86
|
+
|
|
87
|
+
When `Atomics.waitAsync()` is available:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
main worker
|
|
91
|
+
| |
|
|
92
|
+
| write count + dt |
|
|
93
|
+
| store command sequence |
|
|
94
|
+
| Atomics.notify ----------------------------> |
|
|
95
|
+
| | step_springs(...)
|
|
96
|
+
| | on shared memory
|
|
97
|
+
| | store done sequence
|
|
98
|
+
| <----------------------------- Atomics.notify|
|
|
99
|
+
| read positions/velocities directly |
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
No per-frame spring payload and no per-frame completion message are required. Older environments retain a tiny `done` message fallback.
|
|
103
|
+
|
|
104
|
+
If a Worker fails, the engine restores batch position/velocity from authoritative `MotionValue`s before continuing on the local shared-WASM path. This favors continuity and deterministic state over attempting to reuse a potentially half-written failed frame.
|
|
105
|
+
|
|
106
|
+
## Direct manipulation and kinetic release
|
|
107
|
+
|
|
108
|
+
Gesture input stays outside renderer code and feeds ordinary numeric `MotionValue`s. The input side is intentionally cheap and the release side is analytical rather than frame-substepped.
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
pointer / pen / touch samples
|
|
112
|
+
|
|
|
113
|
+
+-- coalesced samples when available
|
|
114
|
+
|
|
|
115
|
+
VelocityTracker
|
|
116
|
+
typed-array ring buffer
|
|
117
|
+
weighted linear regression
|
|
118
|
+
|
|
|
119
|
+
v
|
|
120
|
+
DragController
|
|
121
|
+
|
|
|
122
|
+
+-- in-bounds value
|
|
123
|
+
+-- rubber-band projection outside bounds
|
|
124
|
+
+-- optional axis lock
|
|
125
|
+
|
|
|
126
|
+
pointer up
|
|
127
|
+
|
|
|
128
|
+
v
|
|
129
|
+
analytic exponential decay
|
|
130
|
+
|
|
|
131
|
+
+-- optional target snapping
|
|
132
|
+
|
|
|
133
|
+
cross bound?
|
|
134
|
+
/ \
|
|
135
|
+
no yes
|
|
136
|
+
| |
|
|
137
|
+
finish exact damped spring
|
|
138
|
+
|
|
|
139
|
+
settle
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Release kinetics are kept separate from the dense WASM spring batch. Direct manipulation normally involves one or two axes, so crossing a WASM boundary would cost more than evaluating the closed-form equations locally. This also keeps gesture latency independent of large background spring batches.
|
|
143
|
+
|
|
144
|
+
The decay integrator uses the exact solution of `dv/dt = -v/tau`, making position and velocity effectively independent of whether frames arrive at 60, 120, or 144 Hz. Bound settling uses the exact underdamped/critical/overdamped harmonic-oscillator solution.
|
|
145
|
+
|
|
146
|
+
## Timeline / keyframe graph
|
|
147
|
+
|
|
148
|
+
Timeline data is compiled before playback. Numeric tracks keep keyframe times and values in `Float64Array`s. Segment interpolation does not build frame objects during sampling.
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
Timeline
|
|
152
|
+
|
|
|
153
|
+
+-- numeric keyframe track ----> MotionValue
|
|
154
|
+
| times[]
|
|
155
|
+
| values[]
|
|
156
|
+
| compiled easing[]
|
|
157
|
+
|
|
|
158
|
+
+-- structured track ----------> callback / renderer adapter
|
|
159
|
+
| precompiled segment mixers
|
|
160
|
+
|
|
|
161
|
+
+-- nested TimelineClip
|
|
162
|
+
offset + speed + fill
|
|
163
|
+
|
|
|
164
|
+
+--> child Timeline
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Cubic-bezier keyframe curves are expensive if every track performs Newton/bisection inversion every frame. Timeline compilation therefore stores a shared lookup table per curve identity. Position sampling becomes a pair of typed-array reads plus linear interpolation. Numeric velocity uses the local table slope multiplied by segment duration and player direction/rate.
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
bezier spec
|
|
171
|
+
| one-time
|
|
172
|
+
v
|
|
173
|
+
256-entry LUT
|
|
174
|
+
|
|
|
175
|
+
+-- value(progress)
|
|
176
|
+
+-- slope(progress)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This is deliberately timeline-local. Single timing animations can keep the exact evaluator, while large choreographies amortize a tiny bounded approximation for much lower aggregate CPU cost. Built-in curves are regression-tested against the exact evaluator.
|
|
180
|
+
|
|
181
|
+
`TimelinePlayer` is a `MotionEngine` driver. It supports deterministic manual stepping and the normal engine clock. It maps raw elapsed time to local timeline time through iteration and direction rules, then samples the graph. Nested clips scale velocity by their playback speed so a spring started after interruption receives the correct world-time velocity.
|
|
182
|
+
|
|
183
|
+
Motion ownership is bidirectional. Before a player starts, it stops engine animations on its numeric targets. Conversely, `MotionEngine.animate()`, `animateVelocity()`, or `stop()` asks registered drivers whether they own the target and interrupts that driver first. Timeline interruption preserves current velocity; explicit pause/cancel zeroes it because motion is no longer physically continuing.
|
|
184
|
+
|
|
185
|
+
Large elapsed gaps do not dispatch one callback for every skipped loop. A repeat callback receives the final iteration plus a signed `crossedIterations` count once per engine step.
|
|
186
|
+
|
|
187
|
+
`PhaseTimeline` is a compiler on top of this graph, not a second runtime. Named phases become ordinary keyframe tracks with arrival times and optional hold duplicates.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## State transitions and presence
|
|
192
|
+
|
|
193
|
+
Named state transitions sit above the engine rather than inside the spring hot loop.
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
named state graph
|
|
197
|
+
|
|
|
198
|
+
+-- numeric MotionValue binding ----> engine.animate(value, target)
|
|
199
|
+
| preserves property velocity
|
|
200
|
+
|
|
|
201
|
+
+-- structured binding(s)
|
|
202
|
+
|
|
|
203
|
+
current rendered values
|
|
204
|
+
|
|
|
205
|
+
precompiled interpolators
|
|
206
|
+
|
|
|
207
|
+
one progress MotionValue
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This split is deliberate. A card with x/y/opacity as numeric values keeps independent physical velocity, while ten structured decoration properties do not allocate ten spring slots merely to share the same temporal curve.
|
|
211
|
+
|
|
212
|
+
`TransitionController` is a two-state specialization (`exited <-> entered`). `PresenceController` adds renderer lifecycle semantics: exit keeps `rendered=true` until successful completion, and a later enter increments a generation token so an older exit promise cannot unmount content after reversal.
|
|
213
|
+
|
|
214
|
+
## Timeline scrubbing
|
|
215
|
+
|
|
216
|
+
`TimelineScrubber` converts an arbitrary scalar input domain into timeline-local progress.
|
|
217
|
+
|
|
218
|
+
```text
|
|
219
|
+
DragController / scroll / gamepad / custom input
|
|
220
|
+
|
|
|
221
|
+
MotionValue
|
|
222
|
+
[inputMin,inputMax]
|
|
223
|
+
|
|
|
224
|
+
TimelineScrubber
|
|
225
|
+
|
|
|
226
|
+
0..1
|
|
227
|
+
|
|
|
228
|
+
TimelinePlayer.seek
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
On release, the same input `MotionValue` can run bounded inertia with snap points. The timeline package therefore remains independent of pointer/DOM code and receives deterministic seek samples through its normal player API.
|
|
232
|
+
|
|
233
|
+
## Shared / matched layout projection
|
|
234
|
+
|
|
235
|
+
A shared-layout snapshot stores keyed viewport rectangles before a tree replacement. Playback measures the new keyed targets and projects each target from its previous geometry.
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
old tree READ
|
|
239
|
+
key -> rect snapshot
|
|
240
|
+
|
|
|
241
|
+
tree replacement
|
|
242
|
+
|
|
|
243
|
+
new tree READ
|
|
244
|
+
key -> target rect
|
|
245
|
+
|
|
|
246
|
+
matched world projection
|
|
247
|
+
|
|
|
248
|
+
remove matched ancestor projection
|
|
249
|
+
|
|
|
250
|
+
one shared progress MotionValue
|
|
251
|
+
|
|
|
252
|
+
all target WRITE transforms
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The old element is not required during playback. A whole matched group uses one solver entry, so matching hundreds of nodes increases matrix/write work but not spring count. Keys are unique by contract; duplicate source or target keys are rejected. When a target is already participating in an older layout projection, the new transition captures its currently rendered rectangle before cancelling the old projection and uses that visual geometry as the new source.
|
|
256
|
+
|
|
257
|
+
## Structured interpolation
|
|
258
|
+
|
|
259
|
+
Structured values do not add branches to the spring hot loop. One scalar progress `MotionValue` drives a prebuilt interpolator.
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
progress MotionValue (0 -> 1)
|
|
263
|
+
|
|
|
264
|
+
+-- number
|
|
265
|
+
+-- color (sRGB / linear / OKLab / OKLCH)
|
|
266
|
+
+-- transform decomposition
|
|
267
|
+
+-- material
|
|
268
|
+
+-- SVG path morph
|
|
269
|
+
+-- caller supplied interpolator
|
|
270
|
+
|
|
|
271
|
+
renderer adapter
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
This keeps the hot spring SoA compact while allowing arbitrary high-level output types.
|
|
275
|
+
|
|
276
|
+
## SVG path morph pipeline
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
SVG path strings
|
|
280
|
+
|
|
|
281
|
+
parse M/L/H/V/C/S/Q/T/A/Z
|
|
282
|
+
|
|
|
283
|
+
normalize every segment to cubic Bezier
|
|
284
|
+
|
|
|
285
|
+
equalize segment counts by cubic splitting
|
|
286
|
+
|
|
|
287
|
+
closed-path direction/start alignment
|
|
288
|
+
|
|
|
289
|
+
Float64 coordinate buffers (preprocess once)
|
|
290
|
+
|
|
|
291
|
+
per-frame linear numeric sampling
|
|
292
|
+
|
|
|
293
|
+
+-- sampleInto() -> Canvas/WebGL/custom renderer
|
|
294
|
+
|
|
|
295
|
+
+-- format() -> SVG DOM d string
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
For large closed paths, start alignment evaluates a bounded set of nearest promising candidate shifts instead of every cyclic shift. This changes preprocessing from exhaustive O(n^2) scoring to approximately O(k*n) after candidate selection, with small `k`.
|
|
299
|
+
|
|
300
|
+
## Materials
|
|
301
|
+
|
|
302
|
+
The material model is pure data:
|
|
303
|
+
|
|
304
|
+
```text
|
|
305
|
+
blur
|
|
306
|
+
saturation
|
|
307
|
+
brightness
|
|
308
|
+
contrast
|
|
309
|
+
tint color
|
|
310
|
+
tint strength
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Interpolation is renderer-independent. The DOM adapter maps those values to `backdrop-filter` and `background-color`; another renderer can map the exact same material to shader uniforms.
|
|
314
|
+
|
|
315
|
+
## DOM batching
|
|
316
|
+
|
|
317
|
+
All DOM bindings share one global dirty-state queue.
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
MotionValue callbacks from many elements
|
|
321
|
+
|
|
|
322
|
+
v
|
|
323
|
+
dirty state Set
|
|
324
|
+
|
|
|
325
|
+
one scheduled flush
|
|
326
|
+
|
|
|
327
|
+
+-----+-----+-----+
|
|
328
|
+
| | |
|
|
329
|
+
element A element B ...
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The default scheduler is a microtask because engine frames already usually originate in RAF. An explicit RAF commit mode is available for integrations that update values outside an animation frame.
|
|
333
|
+
|
|
334
|
+
## Layout projection
|
|
335
|
+
|
|
336
|
+
Layout remains outside numeric core:
|
|
337
|
+
|
|
338
|
+
```text
|
|
339
|
+
LayoutTransition
|
|
340
|
+
|
|
|
341
|
+
READ first rectangles
|
|
342
|
+
|
|
|
343
|
+
user mutation
|
|
344
|
+
|
|
|
345
|
+
READ last rectangles
|
|
346
|
+
|
|
|
347
|
+
world projection matrices
|
|
348
|
+
|
|
|
349
|
+
remove selected ancestor projection
|
|
350
|
+
|
|
|
351
|
+
child-local FLIP matrices
|
|
352
|
+
|
|
|
353
|
+
progress MotionValue
|
|
354
|
+
|
|
|
355
|
+
WRITE transforms only
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Each element gets a viewport-space matrix that maps its last rectangle back to its first rectangle. If a selected ancestor is also projected, the child removes the ancestor's world projection before converting the remaining matrix into child-local coordinates.
|
|
359
|
+
|
|
360
|
+
The current layout adapter handles axis-aligned rectangle projection. Existing arbitrary transforms are preserved, but exact geometric projection of rotated/skewed layout boxes is intentionally not claimed yet.
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
## Scroll-linked execution
|
|
364
|
+
|
|
365
|
+
Scroll is treated as an external numeric input, not as another animation solver.
|
|
366
|
+
|
|
367
|
+
```text
|
|
368
|
+
scroll events / virtual scroll samples
|
|
369
|
+
|
|
|
370
|
+
one RAF-coalesced read
|
|
371
|
+
|
|
|
372
|
+
ScrollTracker
|
|
373
|
+
offset + velocity
|
|
374
|
+
|
|
|
375
|
+
normalized progress
|
|
376
|
+
|
|
|
377
|
+
+-------+--------+
|
|
378
|
+
| |
|
|
379
|
+
MotionValue users Timeline seek
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`ScrollObserver` reads DOM/window metrics only in its adapter. `ScrollTracker` itself only knows numbers and dynamic range functions. A timeline linked to scroll remains paused and receives deterministic seek samples; it does not consume a continuous `MotionEngine` driver slot.
|
|
383
|
+
|
|
384
|
+
## Compiled constraint graph
|
|
385
|
+
|
|
386
|
+
Constraint relationships are evaluated after animated source values commit.
|
|
387
|
+
|
|
388
|
+
```text
|
|
389
|
+
MotionValue sources
|
|
390
|
+
|
|
|
391
|
+
v
|
|
392
|
+
mark graph dirty
|
|
393
|
+
|
|
|
394
|
+
engine driver (one evaluation)
|
|
395
|
+
|
|
|
396
|
+
compiled topological program
|
|
397
|
+
|
|
|
398
|
+
typed value/velocity buffers
|
|
399
|
+
|
|
|
400
|
+
MotionValue outputs
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Built-in affine, clamp, sum, and mix instructions propagate both value and first derivative. A derived value therefore retains a physically meaningful instantaneous velocity if ownership later transfers to spring/inertia motion.
|
|
404
|
+
|
|
405
|
+
The graph topology is compiled once. Dependencies are topologically sorted, operation metadata moves into typed arrays, custom constraints receive retained scratch buffers, and output writes are stored as a compact index list. Cycles are rejected rather than iterated implicitly. This is a relationship graph, not a general nonlinear physics constraint solver; iterative/PBD constraints can be a separate backend later without slowing simple UI relationships.
|
|
406
|
+
|
|
407
|
+
## Canvas / WebGL / WebGPU renderer bridges
|
|
408
|
+
|
|
409
|
+
Renderer bridges consume ordinary `MotionValue`s and share a small frame batcher.
|
|
410
|
+
|
|
411
|
+
```text
|
|
412
|
+
many MotionValue commits
|
|
413
|
+
|
|
|
414
|
+
v
|
|
415
|
+
one dirty-frame flag
|
|
416
|
+
|
|
|
417
|
+
+-- Canvas: retained Float64 snapshot -> one draw callback
|
|
418
|
+
|
|
|
419
|
+
+-- WebGL: cached locations -> dirty uniform uploads
|
|
420
|
+
|
|
|
421
|
+
+-- WebGPU: retained Float32 packing -> one writeBuffer
|
|
422
|
+
|
|
|
423
|
+
`-- WebGPU Compute: storage buffer -> bounded dispatches -> one readback
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
No renderer adapter changes the solver or motion semantics. Canvas/WebGL/WebGPU work remains tree-shakable behind package subpaths. WebGPU Compute is an optional asynchronous spring backend; it is selected only for workloads large enough to amortize command encoding and readback.
|
|
427
|
+
|
|
428
|
+
## Hot-path rules
|
|
429
|
+
|
|
430
|
+
1. Never traverse inactive UI trees. Only active arrays are stepped.
|
|
431
|
+
2. Spring state stays dense; removal swap-moves the final slot.
|
|
432
|
+
3. Never call WASM once per value. One spring batch is one WASM call per solver substep.
|
|
433
|
+
4. Keep solver state in typed arrays, not transient object graphs.
|
|
434
|
+
5. JS -> WASM promotion copies active state once; WASM memory remains authoritative afterward.
|
|
435
|
+
6. Worker mode shares WASM memory instead of cloning spring arrays.
|
|
436
|
+
7. Never mutate Worker-owned shared indices from the main thread.
|
|
437
|
+
8. Convert high-level spring configuration before entering the hot loop.
|
|
438
|
+
9. Bound bad `dt` spikes before integration.
|
|
439
|
+
10. Keep DOM/layout reads and writes outside numeric core.
|
|
440
|
+
11. Finish layout reads before projection writes.
|
|
441
|
+
12. Preprocess structured interpolation and path topology once; frame work should be scalar progress + numeric mixing.
|
|
442
|
+
13. Batch DOM writes globally instead of scheduling one microtask per element.
|
|
443
|
+
14. Prefer platform compositor/WAAPI execution when an adapter knows the animation can be handed off safely.
|
|
444
|
+
15. Keep gesture sample history in fixed storage; do not allocate one object per pointer sample.
|
|
445
|
+
16. Use analytical release physics for low-count direct manipulation instead of routing it through a bulk backend.
|
|
446
|
+
17. Compile timeline keyframe times/values and structured mixers once; do not construct per-frame keyframe objects.
|
|
447
|
+
18. Share compiled cubic-bezier lookup tables across timeline segments instead of solving the same curve per track per frame.
|
|
448
|
+
19. Remove paused/completed timeline players from the engine driver set; only running clocks are stepped.
|
|
449
|
+
20. Use `MotionValue.subscribeValue()` for render bindings that do not consume velocity/version metadata.
|
|
450
|
+
21. Keep wall-clock catch-up policy separate from numerical solver stability policy.
|
|
451
|
+
22. Preserve per-property velocity for numeric state transitions; do not force unrelated values through one progress scalar.
|
|
452
|
+
23. Share one progress animation across structured state bindings and matched-layout groups when independent physical velocity is not meaningful.
|
|
453
|
+
24. Treat shared-layout keys as unique identities; reject ambiguous duplicates before animation begins.
|
|
454
|
+
25. Preallocate per-item layout matrix buffers and move transform-origin/will-change writes out of the per-frame projection loop.
|
|
455
|
+
26. Coalesce scroll events before reading scroll metrics; never read layout once per delivered scroll event.
|
|
456
|
+
27. Compile simple value relationships into one topological constraint program instead of chaining per-edge subscriptions.
|
|
457
|
+
28. Propagate derivatives through built-in constraints so ownership transfers preserve motion continuity.
|
|
458
|
+
29. Batch renderer bridge submissions once per dirty frame and retain their numeric staging buffers.
|
|
459
|
+
|
|
460
|
+
## Backend philosophy
|
|
461
|
+
|
|
462
|
+
- Small batches: JS avoids startup and boundary overhead.
|
|
463
|
+
- Medium/large numeric batches: main-thread WASM SIMD gives very low solver latency.
|
|
464
|
+
- Large batches competing with input/layout: Worker + shared WASM minimizes main-thread occupancy.
|
|
465
|
+
- Fixed compositor-friendly keyframes: WAAPI escape hatch.
|
|
466
|
+
- Canvas/WebGL/WebGPU value bridges: renderer-specific uploads/draw invalidation outside core.
|
|
467
|
+
- Large spring batches: WebGPU Compute, only when dispatch and readback overhead can be amortized.
|
|
468
|
+
- Massive particle/mesh fields: a separate future compute backend.
|
|
469
|
+
|
|
470
|
+
Backend choice remains an optimization detail. Public motion semantics should stay stable across execution locations.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
- Avoided redundant DOM transform/opacity writes and limited `will-change` hints to active compositor bindings.
|
|
6
|
+
- Made the Node shared-worker backend retry without invalid inherited runtime flags instead of falling back to the main-thread solver.
|
|
7
|
+
- Added optional WebGPU Compute spring promotion with asynchronous readback, `gpu`/`gpuThreshold` engine options, telemetry, and automatic fallback.
|
|
8
|
+
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
- Added renderer-independent `ScrollTracker` with normalized progress and velocity-preserving offset mapping.
|
|
12
|
+
- Added `ScrollObserver`, coalescing arbitrary scroll-event bursts into one scroll-metric read/sample per animation frame.
|
|
13
|
+
- Added `ScrollTimelineLink` so a paused timeline can be deterministically scrubbed by scroll progress without becoming another running engine driver.
|
|
14
|
+
- Added compiled `ConstraintGraph` with affine/follow, clamp, weighted sum, mix, and reusable-buffer custom mappings.
|
|
15
|
+
- Built-in constraints propagate numeric velocity analytically and execute in a topologically sorted typed-array program; cycles are rejected at compile time.
|
|
16
|
+
- Added engine integration for constraint graphs: source commits mark the graph dirty and schedule one post-motion driver evaluation rather than a chain of per-edge subscriptions.
|
|
17
|
+
- Added `CanvasMotionRenderer`, retaining one numeric snapshot and coalescing many value changes into one draw per frame.
|
|
18
|
+
- Added `WebGLUniformBinder`, caching uniform locations and batching dirty uniform uploads.
|
|
19
|
+
- Added `WebGPUBufferBinder`, packing scalar motion values into a retained `Float32Array` and issuing one `queue.writeBuffer()` per dirty frame.
|
|
20
|
+
- Added `scroll`, `constraints`, `canvas`, `webgl`, and `webgpu` package entry points.
|
|
21
|
+
- Added scroll, constraint, and renderer-adapter microbenchmarks.
|
|
22
|
+
- Expanded runtime coverage from 91 to 103 tests while preserving the existing JS/WASM/Worker solver path.
|
|
23
|
+
|
|
24
|
+
## 0.6.0
|
|
25
|
+
|
|
26
|
+
- Added renderer-agnostic `StateTransitionGraph` with named states and route-specific motion specs.
|
|
27
|
+
- Numeric state bindings animate their `MotionValue`s directly, preserving per-property velocity through rapid state changes.
|
|
28
|
+
- Structured state bindings precompile interpolators and share one scalar progress animation per transition.
|
|
29
|
+
- Added two-state `TransitionController` for interruptible enter/exit motion.
|
|
30
|
+
- Added `PresenceController`, which keeps content logically rendered until exit finishes and cancels pending unmount when an enter interrupts exit.
|
|
31
|
+
- Added keyed shared-layout snapshots and matched-geometry transitions across different element instances.
|
|
32
|
+
- Shared-layout groups use one progress animation for all matched targets, with nested projection correction, optional target fade, and in-flight visual-geometry capture for interruption continuity.
|
|
33
|
+
- Added duplicate shared-layout source/target key detection to avoid ambiguous geometry matches.
|
|
34
|
+
- Reused per-item matrix buffers and moved fixed layout style writes out of the frame hot path, roughly halving the synthetic 1,000-target projection-write cost during development.
|
|
35
|
+
- Added `TimelineScrubber`, mapping arbitrary numeric input ranges to timeline progress and using the existing bounded inertia engine for release snapping.
|
|
36
|
+
- Added the transition package entry point (now `@vune-ui/animation/transition`).
|
|
37
|
+
- Added transition/shared-layout microbenchmarks and expanded runtime coverage from 77 to 91 tests.
|
|
38
|
+
|
|
39
|
+
## 0.5.0
|
|
40
|
+
|
|
41
|
+
- Added precompiled `Timeline` and `TimelinePlayer` runtimes with play/pause/seek/scrub/reverse/playback-rate control.
|
|
42
|
+
- Added finite and infinite iterations plus normal/reverse/alternate/alternate-reverse playback.
|
|
43
|
+
- Added nested timeline clips with start offsets, speed scaling, and fill policies.
|
|
44
|
+
- Added typed-array numeric keyframe tracks with analytical velocity propagation into subsequent spring/inertia motion.
|
|
45
|
+
- Added cubic-bezier lookup-table compilation shared by keyframe tracks, reducing large timeline sampling cost substantially while keeping built-in curve position error below 0.0002 normalized.
|
|
46
|
+
- Added `fromTo()`, `to()`, `keyframes()`, and `stagger()` choreography helpers.
|
|
47
|
+
- Added named multi-target `PhaseTimeline` sequences with transition durations and holds.
|
|
48
|
+
- Added bidirectional motion ownership: timelines interrupt older engine/timeline owners and engine animations interrupt timelines without losing current velocity.
|
|
49
|
+
- Added generic engine drivers so non-spring clocks can share the same scheduler without entering the numeric spring backend.
|
|
50
|
+
- Timeline drivers now receive real wall-clock frame gaps while numerical spring integration retains its defensive `dt` bound.
|
|
51
|
+
- Coalesced large repeat jumps into one `onRepeat` callback carrying the number of crossed iterations.
|
|
52
|
+
- Added `MotionValue.subscribeValue()` and moved DOM/layout/interpolation hot bindings onto the value-only subscription path to avoid per-frame metadata allocation.
|
|
53
|
+
- Added timeline benchmark and expanded runtime coverage from 50 to 77 tests.
|
|
54
|
+
|
|
55
|
+
## 0.4.0
|
|
56
|
+
|
|
57
|
+
- Added analytic exponential decay and bounded inertia release animations.
|
|
58
|
+
- Added exact damped-spring settling for direct-manipulation bounds, stable across irregular frame intervals.
|
|
59
|
+
- Added `animateVelocity()`, `animateDecay()`, and `animateInertia()` APIs.
|
|
60
|
+
- Added renderer-agnostic `VelocityTracker` and `DragController`.
|
|
61
|
+
- Added dynamic bounds, momentum, snapping, direction locking, and rubber-band resistance.
|
|
62
|
+
- Reworked velocity history into a fixed typed-array ring buffer to avoid per-sample allocations.
|
|
63
|
+
- Added DOM `bindPointerDrag()` with pointer capture, cancellation cleanup, touch-action restoration, and coalesced pointer-event support.
|
|
64
|
+
- Added gesture/kinetics benchmarks.
|
|
65
|
+
- Expanded runtime coverage from 36 to 50 tests.
|
|
66
|
+
|
|
67
|
+
## 0.3.0
|
|
68
|
+
|
|
69
|
+
- Made Worker execution part of the normal auto scheduler.
|
|
70
|
+
- Added frame-boundary spring mutation buffering to remove shared-memory retarget/cancel races.
|
|
71
|
+
- Serialized engine-level async frames.
|
|
72
|
+
- Added Worker failure recovery back to authoritative `MotionValue` state.
|
|
73
|
+
- Added `Atomics.waitAsync()` completion path with message fallback.
|
|
74
|
+
- Added `FrameBudgetGovernor`, backend telemetry, and adaptive promotion thresholds.
|
|
75
|
+
- Changed default Worker mode to `auto`.
|
|
76
|
+
- Added SVG path parsing/morphing with cubic normalization, segment splitting, closed-path alignment, winding reversal, reusable numeric buffers, and bounded alignment candidate search.
|
|
77
|
+
- Added renderer-agnostic material interpolation and material presets.
|
|
78
|
+
- Added DOM material and SVG path adapters.
|
|
79
|
+
- Replaced per-element DOM microtask scheduling with one global dirty-element batch.
|
|
80
|
+
- Added path benchmarks and expanded tests/type smoke coverage.
|
|
81
|
+
|
|
82
|
+
## 0.2.0
|
|
83
|
+
|
|
84
|
+
- Added layout/FLIP animation with nested projection correction.
|
|
85
|
+
- Added transform and perceptual color interpolation.
|
|
86
|
+
- Added Worker + SharedArrayBuffer + shared WASM backend.
|
|
87
|
+
- Added shared SIMD/scalar WASM binaries.
|
|
88
|
+
- Removed the fixed 4 MiB allocator ceiling by allowing WASM memory growth.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|