@px-petals/roku.motion-web 1.0.0 → 1.1.0

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.
Files changed (77) hide show
  1. package/bands/bands.js +3 -1
  2. package/bundle/bundle.js +56 -0
  3. package/list/list.js +12 -1
  4. package/lottie/lottie.layers.js +64 -11
  5. package/lottie/lottie.paint.js +30 -0
  6. package/lottie/lottie.paths.js +19 -4
  7. package/lottie/lottie.props.js +18 -8
  8. package/lottie/lottie.shapes.js +2 -1
  9. package/mixer/mixer.js +31 -8
  10. package/mixer/mixer.mix.js +17 -0
  11. package/motion/motion.convert.js +32 -1
  12. package/motion/motion.flow.js +20 -2
  13. package/motion/motion.machine.js +28 -13
  14. package/motion/motion.values.js +13 -9
  15. package/motion.js +196 -20
  16. package/package.json +10 -16
  17. package/player/player.js +25 -5
  18. package/readme.md +78 -3
  19. package/script/luau/lib/base/base.js +202 -0
  20. package/script/luau/lib/base/base.table.js +189 -0
  21. package/script/luau/lib/lib.args.js +152 -0
  22. package/script/luau/lib/lib.js +23 -0
  23. package/script/luau/lib/math/math.bit32.js +144 -0
  24. package/script/luau/lib/math/math.js +288 -0
  25. package/script/luau/lib/math/math.vector.js +125 -0
  26. package/script/luau/lib/string/string.format.js +143 -0
  27. package/script/luau/lib/string/string.js +160 -0
  28. package/script/luau/lib/string/string.pattern.js +505 -0
  29. package/script/luau/load/load.js +362 -0
  30. package/script/luau/load/load.ops.js +79 -0
  31. package/script/luau/luau.convert.js +259 -0
  32. package/script/luau/luau.js +132 -0
  33. package/script/luau/luau.md +111 -0
  34. package/script/luau/vm/vm.js +167 -0
  35. package/script/luau/vm/vm.meta.js +401 -0
  36. package/script/luau/vm/vm.num.js +236 -0
  37. package/script/luau/vm/vm.run.js +842 -0
  38. package/script/luau/vm/vm.value.js +406 -0
  39. package/script/play/play.js +199 -0
  40. package/script/play/play.objects.js +295 -0
  41. package/script/play/play.scope.js +91 -0
  42. package/script/rive/api/api.statics.js +31 -0
  43. package/script/rive/api/api.type.js +188 -0
  44. package/script/rive/api/artboard/artboard.js +89 -0
  45. package/script/rive/api/assets/assets.js +72 -0
  46. package/script/rive/api/data/data.value.js +99 -0
  47. package/script/rive/api/draw/draw.contours.js +163 -0
  48. package/script/rive/api/draw/draw.measure.js +196 -0
  49. package/script/rive/api/draw/draw.paint.js +235 -0
  50. package/script/rive/api/draw/draw.path.js +153 -0
  51. package/script/rive/api/draw/draw.renderer.js +117 -0
  52. package/script/rive/api/math/math.color.js +96 -0
  53. package/script/rive/api/math/math.mat2d.js +180 -0
  54. package/script/rive/api/math/math.vector.js +121 -0
  55. package/script/rive/api/media/media.audio.js +113 -0
  56. package/script/rive/api/media/media.draw.js +73 -0
  57. package/script/rive/api/media/media.image.js +61 -0
  58. package/script/rive/api/media/media.mesh.js +50 -0
  59. package/script/rive/api/pointer/pointer.event.js +64 -0
  60. package/script/rive/api/pointer/pointer.route.js +69 -0
  61. package/script/rive/api/text/text.font.js +129 -0
  62. package/script/rive/api/text/text.js +278 -0
  63. package/script/rive/api/text/text.layout.js +259 -0
  64. package/script/rive/context/context.js +76 -0
  65. package/script/rive/context/context.memory.js +196 -0
  66. package/script/rive/context/context.viewmodel.js +302 -0
  67. package/script/rive/context/lists/lists.js +160 -0
  68. package/script/rive/context/lists/lists.values.js +281 -0
  69. package/script/rive/host/host.globals.js +44 -0
  70. package/script/rive/host/host.js +533 -0
  71. package/script/rive/host/host.node.js +45 -0
  72. package/script/rive/rive.js +23 -0
  73. package/script/rive/rive.md +180 -0
  74. package/script/script.js +11 -0
  75. package/skin/skin.deform.js +514 -0
  76. package/skin/skin.ik.js +260 -0
  77. package/skin/skin.js +297 -0
@@ -0,0 +1,180 @@
1
+ # rive
2
+
3
+ The host for the scripts a Rive file carries: it runs one compiled Luau script on [the Luau VM](../luau/luau.md) with the API Rive gives it (`context`, view models, `Path`, `Paint`, `Mat2D`, `Vector`, `Color`, the renderer) and hands the player what the script did. It knows nothing of any player: the player gives it a view model through three functions and takes back view-model writes, drawings, and a path effect's output.
4
+
5
+ ```js
6
+ import { createScriptHost, createMemoryViewModel } from '@px-petals/roku.motion-web/script/rive/rive.js'
7
+
8
+ const host = createScriptHost({
9
+ bytes, // a ScriptAsset's contents, wrapper byte and all
10
+ viewModel: createMemoryViewModel({ score: 0 }), // or the player's own adapter
11
+ inputs: { speed: 2 }, // the script's inputs by name
12
+ })
13
+ host.init() // calls the script's init(self, context)
14
+ host.frame(1 / 60) // advance(self, 1/60), then update(self) if owed
15
+ const drawings = host.draw() // what draw(self, renderer) drew
16
+ ```
17
+
18
+ ## The protocol
19
+
20
+ A compiled script's main chunk returns a **factory** function; the host calls it for the script's table (`self`), then sets each input on that table over the factory's defaults and calls methods with `self` first, as [Rive's docs](https://rive.app/docs/scripting/protocols/node-scripts) describe:
21
+
22
+ | Call | When | Notes |
23
+ | --- | --- | --- |
24
+ | `init(self, context)` | `host.init()`, once | `false` (or an error) stops the script |
25
+ | `advance(self, seconds)` | `host.advance(s)`, each frame | returning `false` stops further calls until a view-model change the script listens to, or `setInput`, wakes it |
26
+ | `update(self)` | `host.update()`, when owed | owed after `init`, `setInput` and `context:markNeedsUpdate()`; a `markNeedsUpdate` from inside `update` is ignored, as in Rive |
27
+ | `draw(self, renderer)` | `host.draw()` | records, see below |
28
+ | `update(self, path, node)` | `host.effect(contours, node)` | a path effect: the input path as `PathData`, the returned `Path` as the output |
29
+ | `convert(self, input)`, `reverseConvert(self, input)` | `host.convert(value, kind?)`, `host.reverseConvert(value, kind?)` | a converter script, below |
30
+ | `perform(self, pointerEvent)` | `host.perform(event?)` | a listener action script, below |
31
+ | `evaluate(self)` | `host.evaluate()` | a transition condition script, below |
32
+ | `measure(self)`, `resize(self, size)` | `host.measure()`, `host.resize(width, height)` | a layout script, below |
33
+
34
+ `host.frame(seconds)` is `advance` then `update`, which is the order Rive runs them in. A script that needs another method (a pointer handler, say) is reached with `host.invoke(name, args)` (Lua values in, `{ ok, result }` out).
35
+
36
+ Rive's wrapper byte: a script asset stores one `0` byte before the Luau version byte. `createScriptHost({ bytes })`, `host.load(bytes)` and `stripWrapper(bytes)` take it off when the first byte is `0` (Luau bytecode never starts with 0), and take bytes without it as they are.
37
+
38
+ Errors a script raises (in any call, or in a listener it registered) are collected in `host.errors`, passed to `options.onError` and otherwise logged (`options.log`), and the script carries on; `strict: true` rethrows them instead, which is what tests want.
39
+
40
+ ### What Rive's web runtime does, as measured
41
+
42
+ `@rive-app/canvas` 2.44.2 was driven with the same view model and the same time steps as this host, and the view-model numbers the scripts write agree to float32 precision (`1.8e-5` over 360 frames at 1/60 s; `1.8e-5` over 220 frames of 1/30, 1/120, 1/24 and 1/60 s steps), including a script's wrap-around, its listeners reacting to inputs set between frames, and one script's writes feeding another's listener in the same frame. Three behaviours worth knowing:
43
+
44
+ - **The first advance only binds.** After the artboard gets a view-model instance (`autoBind: true`, or `bindViewModelInstance(file.defaultViewModel().defaultInstance())`) the first `advance` shows nothing from the scripts; the next one runs `init`, `advance` and `update` together. This host has no such lag: `init()` and the first `frame()` can run in the same tick, which is the state Rive reaches one advance later. A player that wants Rive's very first frame exactly skips both for it.
45
+ - **Scripts need a bound instance.** A script that asks `context:viewModel()` in a hierarchy with no bound data context gets nil and a script written to demand one logs `init failed: no immediate ViewModel data context`; pass `viewModel: null` to see it here, and an adapter to give it one.
46
+ - **Order matters across scripts.** A script that writes a property another one listens to makes the listener run inside the writer's call, so the listener's owner sees the new value in the same frame only if the writer ran first. Run hosts in the artboard's order.
47
+
48
+ ## The other protocols
49
+
50
+ These follow [Rive's protocol pages](https://rive.app/docs/scripting/protocols/converter-scripts). Each calls `init(self, context)` first, once, if the player has not.
51
+
52
+ - **Converter.** `host.convert(value, kind?)` wraps a number, string or boolean (a colour, as an ARGB number, with `kind: 'color'`) in a `DataValue`, calls `convert(self, input)` and returns `{ kind, value }` from the `DataValue` the script returned (a colour as its ARGB number), or `undefined` when there is no such method, the script fails, or it returns anything but a `DataValue`. `reverseConvert` is the same for `reverseConvert(self, input)`. A script makes values with `DataValue.number()`, `.string()`, `.boolean()` and `.color()`, sets `.value`, and tells an input's type with `input:isNumber()`, `isString()`, `isBoolean()` and `isColor()`. The input `DataValue` is one object reused by every call.
53
+ - **Listener action.** `host.perform({ id, x, y, previousX, previousY, type, timeStamp })` calls `perform(self, pointerEvent)` and returns `{ ok, hit }`. The script reads `id`, `position`, `previousPosition`, `type` (`down`, `up`, `click`, `enter`, `exit` or `move`) and `timeStamp`, and calls `pointerEvent:hit(isTranslucent?)`, which `hit` reports as `opaque` or `translucent` (`""` when it did not). `PointerEvent.new(id, position)` makes one. The event object is reused by every call.
54
+ - **Transition condition.** `host.evaluate()` calls `evaluate(self)` and is true only when it returned `true`; a missing method or an error blocks the transition.
55
+ - **Layout.** `host.measure()` calls `measure(self)` and returns `{ width, height }` from the `Vec2D` it returned (`null` for none); `host.resize(width, height)` calls `resize(self, size)`. `Vec2D` is the same value as `Vector`.
56
+
57
+ ## The view-model adapter
58
+
59
+ The player implements `ViewModelAdapter` (see `context/context.viewmodel.js`) over its own view model:
60
+
61
+ | Member | |
62
+ | --- | --- |
63
+ | `get(path)` | the value at a dotted path: a number, string or boolean; a colour is an ARGB number and an enum its value's name. `undefined` when there is none |
64
+ | `set(path, value)` | writes it; the adapter notifies subscribers when it changed |
65
+ | `subscribe(path, fn)` | calls `fn(value)` on a change (on a fire, for a trigger); returns how to stop |
66
+ | `kind(path)` | optional: `number`, `string`, `boolean`, `color`, `enum`, `trigger`, `viewModel`, `list`, `image`, `font`, `blob` (an asset property holds the asset's name), or `undefined` for no such property. Without it the kind is read from the value's type (so `getColor` takes numbers and `getEnum` strings) |
67
+ | `fire(path)` | optional: fires a trigger; without it a trigger is set to `true` |
68
+ | `listSize(path)`, `listGet(path, i)` | optional, for lists: the item count, and item `i` (0-based) as an adapter of its own whose paths start at its root. `listGet` returns the same adapter for the same item every time |
69
+ | `listInsert(path, item, i)`, `listRemove(path, i)`, `listSwap(path, a, b)`, `listClear(path)` | optional, for lists: the changes, each of which notifies the list's subscribers (`listClear` is one change; without it `clear` removes item by item) |
70
+ | `instance(path, name?)` | optional: a new instance of the view model at `path` (`''` for the root), from the file's instance `name` when given; it is in no list until the script inserts it |
71
+
72
+ A nested view model is a dotted path: `context:viewModel():getViewModel('arrow1').value:getNumber('position')` reads `arrow1.position`. `createMemoryViewModel(initial, { colors, enums, triggers })` is such an adapter over plain objects (nested objects are nested view models); it notifies only on a change (an array is a list of nested view models; `instance(name)` copies from `options.instances`) and is what the tests and a player with no view model of its own use.
73
+
74
+ `options.assets` says what the player has by name: `{ image(name) -> { width, height }, font(name) -> FontInfo, audio(name) -> { duration }, artboard(name) -> { width, height } }`, each returning undefined for a name it lacks (the script then gets nil). Without the option every name is accepted: an image of unknown size, an audio of no length, an artboard of no size and the fallback font (a half-em advance for every character). A `FontInfo` is `{ name, ascent, descent, capHeight, xHeight, weight, isItalic, advance(codepoint) -> em, hasGlyph, glyphId, glyphPath }`, all but `name` optional; `measuredFont(name, measure)` (`api/text/text.font.js`) makes one from a function that measures a character, which a player does on a canvas with the face `bundle/bundle.fonts.js` registers, and a converter from opentype.js. An input that is an asset is `{ kind: 'image' | 'font' | 'audio' | 'artboard', name }`.
75
+
76
+ **Pointer events:** `host.pointer(type, x, y, id?, seconds?)` (`type` is `down`, `move`, `up` or `exit`; the point in the node's own space) calls the script's `pointerDown`, `pointerMove`, `pointerUp` or `pointerExit` with a `PointerEvent` and returns `{ hit, translucent }`. `routePointer(nodes, type, x, y, id, seconds)` (`api/pointer/pointer.route.js`) offers an event to scripted nodes `{ host, world, contains }` top-most first until one claims it, and sends `pointerExit` to a node the pointer has left. The player's pointer module is to call it where it hit-tests its listeners, with `contains` its hit test of the node; the motion player runs scripted objects every frame (below) but does not route the pointer to them, so that call is not made.
77
+
78
+ Options for what a script reaches: `viewModel` (`context:viewModel()`), `rootViewModel` (default the same), `parentViewModel` (`context:dataContext():parent():viewModel()`), `globalViewModels` (`context:globalViewModel(name)`). An input whose value is an adapter becomes a `ViewModel` the script reads by property name (`self.menu.title.value`).
79
+
80
+ ## Lists and the player's values
81
+
82
+ `createValuesViewModel({ data, values, write, defaults })` (`context/lists/lists.values.js`) is an adapter over a player's flat `values` store, which is what the Motion player holds (`flow.values`). A list property there is an array of items (`{ viewModel?, instance?, values? }`, or an instance's name). The adapter gives a script each item as a view model of its own; a `push`, `insert`, `swap`, `remove`, `clear` or a write inside an item rebuilds the array and calls `write(path, items)`, which the player passes to `setInputs`, so `list/` makes, keeps or replaces its rows (a row whose item is unchanged stays; an item written to differs from before, so its row is made again). `defaults(listPath, item)` is `itemValues` over the row's data, which gives an item the values of its instance for the script to read. Call `adapter.refresh()` after the player's `values` event so listeners hear changes the player made. The motion player uses this adapter as the view model of its scripted objects (`script/play/play.objects.js`, below), writing through to its flow's values and calling `refresh()` before the hosts run whenever a value a host reads changed; the adapter is tested against the same `list/list.items.js` functions the rows are made with.
83
+
84
+ `Data.X.new()` is not here: it belongs to the protocols that build view models. `instance()` on a view model or a list item makes one.
85
+
86
+ ## Drawing
87
+
88
+ `host.draw()` returns the drawings, in order:
89
+
90
+ ```js
91
+ {
92
+ kind: 'path',
93
+ path: [{ v: [[x, y], ...], i: [[dx, dy], ...], o: [[dx, dy], ...], c: true }], // Lottie's shape value, transform applied
94
+ transform: [xx, xy, yx, yy, tx, ty], // the matrix that was in force (already in `path`)
95
+ clips: [[...contours]], // clipPath calls in force, transformed
96
+ opacity: 1, // modulateOpacity
97
+ paint: { style, color, rgba, thickness, join, cap, blendMode, feather, gradient, gradientTransform },
98
+ }
99
+ ```
100
+
101
+ `color` is the ARGB number with `modulateColor` multiplied in and `rgba` the same as four 0 to 1 numbers. The paint is copied at the draw. `Path` curves are cubics (a quad is raised to a cubic); a closed contour whose last point is its first merges them, as Lottie writes it. The list may also hold these records, each with the `transform` (shared, never changed in place), `clips`, `opacity` and `color` (modulation, ARGB) in force:
102
+
103
+ ```js
104
+ { kind: 'image', asset, width, height, transform, clips, opacity, color, blendMode, sampler: { wrapX, wrapY, filter },
105
+ mesh: null | { vertices: [x, y, ...], uvs: [u, v, ...], indices: [a, b, c, ...] } } // drawImage, drawImageMesh
106
+ { kind: 'text', runs: [{ text, font, size, lineHeight, letterSpacing, paint, foregroundColor }],
107
+ lines: [{ x, top, baseline, bottom, width, firstIndex, lastIndex, text }], text, paint, width, height,
108
+ sizing, overflow, align, wrap, wordBreak, origin, direction, maxWidth, maxHeight, paragraphSpacing,
109
+ transform, clips, opacity, color } // Text:draw
110
+ { kind: 'artboard', name, instance, time, viewModel, transform, clips, opacity, color } // Artboard:draw
111
+ ```
112
+
113
+ An image's `opacity` is `modulateOpacity` times the call's own. A text run's `font` is the asset's name and its `paint` a snapshot (null when the run has none and `Text:draw` was given none; a text with no paint anywhere is not recorded); the record's `paint` is the one `Text:draw` was given. An artboard's `instance` is 0 for the input itself and counts up for each `instance()` of it, `time` is the seconds the script `advance`d it, and `viewModel` the adapter an instance was bound to. A player turns an artboard record into the nested artboard's precomp at the transform. Audio is a separate list, `host.audio()`, taken and emptied: `{ kind: 'audio', action: 'play' | 'stop' | 'pause' | 'resume' | 'seek', asset, volume, time, fade? }`.
114
+
115
+ `host.draw(into)` fills a list the caller owns (emptied first) instead of making one, and the host reuses its recorder, so a frame costs the records and nothing else.
116
+
117
+ A path effect takes and returns these contours (`host.effect(contours, { x, y, rotation, scaleX, scaleY, worldTransform, paint })`).
118
+
119
+ ## API a script can call
120
+
121
+ | Global | Members |
122
+ | --- | --- |
123
+ | `Vector` | `xy`, `xyz`, `origin`, `cross`, `cross3`, `scaleAndAdd`, `scaleAndSub`, and `length`, `lengthSquared`, `normalized`, `distance`, `distanceSquared`, `dot`, `lerp` as functions and as methods; `+ - * /`, unary minus, `==`, `x y z`. A Vector is Luau's own vector value |
124
+ | `Color` | `rgb`, `rgba`, `lerp`, `red`, `green`, `blue`, `alpha`, `opacity`, `toFloat`; a colour is an ARGB number |
125
+ | `Mat2D` | `identity`, `values`, `withTranslation`, `withScale`, `withScaleAndTranslation`, `withRotation`, `invert` (static and method), `isIdentity`; fields `xx xy yx yy tx ty` and indices 1 to 6; `*` by a `Mat2D` or a `Vector`, `==` |
126
+ | `Path` | `new`; `moveTo`, `lineTo`, `quadTo`, `cubicTo`, `close`, `reset`, `add(path, transform?)`, `contours()`, `measure()`; reads as an array of commands (`#path`, `path[i]`, `ipairs`) each with `type` and its points |
127
+ | path measures | `PathMeasure` (`length`, `isClosed`, `positionAndTangent`, `warp`, `extract`) and `ContourMeasure` (the same, plus `next`) |
128
+ | `Paint`, `Gradient` | `Paint.new`, `Paint.with`, `copy`; `style join cap blendMode color thickness feather gradient gradientTransform`; `Gradient.linear`, `Gradient.radial` |
129
+ | `Renderer` | `drawPath`, `clipPath`, `save`, `restore`, `transform`, `modulateOpacity`, `modulateColor`, `setColorModulation`; `drawImage(image, sampler, blendMode, opacity)` and `drawImageMesh(image, sampler, vertices, uvs, triangles, blendMode, opacity)` record; `drawImageMeshInstanced` does nothing |
130
+ | `Context` | `viewModel`, `rootViewModel`, `globalViewModel`, `globalViewModelNames`, `dataContext`, `markNeedsUpdate`, `log`, `image(name)`, `font(name)`, `audio(name)` (nil for a name `options.assets` does not know); `blob` and `shader` return nil |
131
+ | `ViewModel` | `getNumber`, `getString`, `getBoolean`, `getColor`, `getEnum`, `getTrigger`, `getViewModel`, and a property by name (`vm.title`); `getList`, `getImage`, `getFont`, `getBlob` (the adapter must say the property's kind), `instance(name?)` (needs an adapter with `instance`), `getIndex` returns -1 |
132
+ | `PropertyList` | `length`, `#list`, `list[i]` (1-based, nil past the end), `push(vm)`, `insert(vm, i)`, `swap(i, j)`, `pop()`, `shift()`, `clear()`, `remove(vm)`, `removeAllOf(vm)`, `removeAt(i)`, `addListener`, `removeListener`; a bad index is an error |
133
+ | `Image`, `ImageSampler` | `Image`: `width`, `height` (0 until the player knows them). `ImageSampler(wrapX, wrapY, filter)` or `.new`, defaults `clamp`, `clamp`, `bilinear` |
134
+ | `VertexBuffer`, `TriangleBuffer` | called or `.new`; `add(...Vector)` and `add(a, b, c)`, `reset()`, `length` |
135
+ | `Text` | `Text.new()`; `append(text, { font, size = 16, lineHeight, letterSpacing, paint, foregroundColor })`, `clear`, `draw(renderer, paint?)`, `bounds`, `line(i)`, `lines`, `hitTest`, `caret`, `selectionRects`; fields `sizing overflow align wrap wordBreak origin direction maxWidth maxHeight paragraphSpacing` and `isEmpty lineCount length`; `TextLine` and `Glyph` as documented |
136
+ | `Font` | `ascent descent capHeight xHeight weight isItalic axes features`, `lineHeight`, `hasGlyph`, `axisValue`, `withOptions`, `glyphPath`; `Font.decode` returns nil |
137
+ | `Artboard` | an input from `{ kind: 'artboard', name }`: `width height frameOrigin`, `draw(renderer)`, `drawModulated`, `advance(seconds)` (true), `instance(vm?)`, `bounds()`; `propertyKey`, `animation` and `node` return nil, the pointer and gamepad methods return 0, `addToPath` does nothing |
138
+ | `Audio`, `AudioSource`, `AudioSound` | `Audio:play(source, volume?)` or `Audio.play(...)`; `AudioSource.duration`; a sound's `play stop pause resume seek seekFrame completed time timeFrame volume`, all recorded, none played |
139
+ | `PointerEvent` | `position`, `previousPosition`, `id`, `type`, `timeStamp`, `hit(translucent?)`; `PointerEvent.new(id, position)` |
140
+ | `Property` | `value` (read and write), `addListener([anchor,] fn)`, `removeListener([anchor,] fn)`; a `PropertyTrigger` adds `fire`, a `PropertyViewModel` has `value` for the nested view model |
141
+ | `late()` | returns nothing: an input's placeholder default |
142
+
143
+ The standard library is the VM's (`math`, `table`, `string`, `bit32`, ...). A path effect's `NodeReadData` has `x y rotation scaleX scaleY position scale worldTransform paint` and `asPath()`, `asPaint()`.
144
+
145
+ **Not carried:** converter scripts (`DataValue`), transition conditions, listener actions and layout scripts (their protocols and the `Data.X.new()` view-model constructors), gamepad and scroll events, shaping (text is laid out a codepoint at a time: no ligatures, kerning or bidi), `Font.decode`, colour glyphs, `drawImageMeshInstanced`, `Artboard` animations, nodes and visitors, GPU canvases and shaders, and sounds that play (audio is recorded). The host reaches a script's table, so a player can call such methods itself (`host.invoke`) but nothing builds their arguments.
146
+ **Not carried:** gamepad events, the `Data.X.new()` view-model constructors (Rive's pages define `DataValue.number()` and its siblings, which are carried), the `DataValue` kinds beyond number, string, boolean and colour, text and fonts, images and meshes, audio, GPU canvases and shaders, view-model lists and `instance()`, and `Artboard` inputs. The host reaches a script's table, so a player can call such methods itself (`host.invoke`) but nothing builds their arguments.
147
+
148
+ ## Live objects
149
+
150
+ The motion player runs a bundle's scripted nodes, drawables and path effects with `createScripts(...).objects(entries, env)` (`script/play/play.objects.js`, described in the [web readme](../../readme.md#scripted-objects)): one host per entry in the entry's `order`, over `createValuesViewModel` rooted at the entry's `scope`, with `options.assets` from the bundle's fonts (measured on a canvas in the family the player registered them under) and images. Each frame it calls `init` once, then `frame(seconds)` (`advance`, then `update`), then `draw(into)` for a drawable or `effect(contours)` for a path effect, on the player's clock. Its first step after binding runs nothing, as Rive's first advance does; a host whose `reads` did not change and has no `clock` is not called at all.
151
+
152
+ Measured on the committed test scripts (a clocked easing node, a node that reads what it wrote, a clocked drawable of two paths), Node 24 on a desktop CPU:
153
+
154
+ | | per frame |
155
+ | --- | --- |
156
+ | the three together | 6 to 10 us, 2.5 KB of garbage from the nodes and 24 KB from the drawable's `draw` (its `Path`, `Paint` and recorded contours, which this host allocates as the script calls it) |
157
+ | the follower when its read did not change | 0.1 us, nothing allocated |
158
+ | the player's loop around the hosts (read comparison, slot writes, the merge into the slot table) | allocates nothing |
159
+
160
+ Added bytes: 1.2 KB minified (0.4 KB gzipped) in the legacy main file for the hand-off to the engine and the merge into the slot table; 7.3 KB minified (2.7 KB gzipped) in the engine file, which only a bundle with scripts loads.
161
+
162
+ ## Files
163
+
164
+ | File | Holds |
165
+ | --- | --- |
166
+ | `rive.js` | the entry: `createScriptHost`, `ScriptHost`, `stripWrapper`, `createMemoryViewModel`, the contour conversions |
167
+ | `host/host.js` | `ScriptHost`: loading, the factory, the lifecycle, errors, the path effect |
168
+ | `host/host.globals.js`, `host/host.node.js` | the globals installed on the VM; `NodeReadData` |
169
+ | `context/context.js` | `Context` and `DataContext` |
170
+ | `context/context.viewmodel.js` | `ViewModel`, `Property`, `PropertyTrigger`, `PropertyViewModel` over the adapter, and the adapter's type |
171
+ | `context/context.memory.js` | the in-memory adapter |
172
+ | `api/api.type.js` | host objects for Lua (`defineType`: a table with `__index` and `__newindex` over a JS object) and argument checks |
173
+ | `api/math/` | `Vector`, `Color`, `Mat2D` |
174
+ | `api/data/` | `DataValue`, `PointerEvent` and `Vec2D` |
175
+ | `api/draw/` | `Path`, its measures, the contour conversions, `Paint` and `Gradient`, the recording `Renderer` |
176
+ | `context/lists/` | `PropertyList` (`lists.js`) and the adapter over a player's values store (`lists.values.js`) |
177
+ | `api/media/` | `Image`, `ImageSampler`, the mesh buffers, the image records (`media.draw.js`), audio |
178
+ | `api/text/` | `Text`, `Font`, and the layout (`text.layout.js`) |
179
+ | `api/artboard/`, `api/pointer/`, `api/assets/` | `Artboard`; `PointerEvent` and the router that offers a pointer to scripted nodes (`routePointer`); the assets a script reaches by name |
180
+ | `.tests/` | the tests (`sources/*.luau` compiled to `fixtures/*.luaubc` by `regen.js` with the pinned official compiler); `script.js` builds scripts with the Luau assembler |
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The scripts a bundle ships, ready to run: what a page gives the player's
3
+ * `scripts` option, so that the Luau VM and Rive host are downloaded only for
4
+ * a bundle that has scripts.
5
+ *
6
+ * ```js
7
+ * createMotion(canvas, { scripts: () => import('@px-petals/roku.motion-web/script/script.js') })
8
+ * ```
9
+ */
10
+
11
+ export { createScripts } from './play/play.js'