feat(rasc): document the AnimaScript std and close its gaps with the Luau API (#14524) b80a7fc662 * fix(rasc): one static closure per plain function so references compare equal, and reject a second operator per symbol * feat(runtime): wasm host ops for transitions, path effect nodes, pointer stamps, artboard paths and gamepads, and test blobs * feat(rasc): document std/rive with /// and close its gaps with the Luau API * fix(scripting): Luau docs and declarations match the runtime, translator color setters and path effect nodes * feat(scripting_workspace): /// docs in AnimaScript hovers and the API docs generator * docs(cli): regenerate the Luau API bundle from the corrected builtins * fix(runtime): bound gamepad payload counts one at a time so they cannot wrap * docs(rasc): fence AnimaScript examples as as, with the grammar registered under that alias * docs(cli): animascript protocols guide and generated std api reference * feat(scripting_workspace): translate Transition, Mat4 and every string.format specifier to AnimaScript * docs(cli): regenerate the AnimaScript API bundle with as fences * feat(lsp): completion items carry kind, signature and docs for Luau and AnimaScript * fix(rasc): print floats with schubfach shortest digits and drop formatNumber's exact search * feat(rasc): infer type arguments from a callback's return and parameter types * docs(cli): regenerate the AnimaScript API bundle for the promise docs * fix(rasc): class errors reach the real sink from peeks and callback annotations, duplicate operators report once in source spelling * fix(rasc): a callback's own type never binds a type parameter positionally, and the hint names parameter annotations when only they would bind it * refactor(rasc): static closures share one empty closure class * perf(rasc): call umul128 instead of inlining it eight times into schubfach * fix(wasm): scope path effect nodes to their update and wire the shim's artboard ops * refactor(rasc): drop Pattern.match and captures, gsub copies text in runs * fix(rasc): unwatched properties leave their owner so listen cycles stop growing * fix(rasc): saturate buffer and date integer conversions like Luau, trim buffer allocations * fix(rasc): keep host composed geometry when a path is released * perf(rasc): skip bounds checks on fixed Mat4 entries * fix(rasc): raise Luau's GPU constructor argument errors instead of a silent null handle * fix(rasc): match Luau's Vector lerp and normalized bit for bit * test(cli): GPUBuffer data length guard on both lanes * refactor(cli): share the api bundle writer between the Luau and AnimaScript generators * fix(cli): keep operator[]= and generic defaults in the AnimaScript api bundle * docs(cli): note AnimaScript gamepad indices are 0 based * ci: test the AnimaScript bundle generator and run rive-cli checks on std changes * fix(scripting): document a Luau completion or hover only from the --- block directly above it * perf(scripting): fill Luau completion details only for the entries the filter keeps * fix(lsp): badge types as structs, enum values as enum members and overloads as methods * fix(scripting): keep a one line enum's values from inheriting the enum's /// comment * docs(scripting): AnimaScript API overview, module Functions pages, member anchors and no unsafe in signatures * ci: check the AnimaScript bundle publishes what the site reference does * docs(cli): regenerate both api bundles after the review fixes * refactor(rasc): spell our std and fixture nullable types T?, with ?. and ?? where clearer * fix(scripting_workspace): emit and document nullable types as T? * test(lsp): completion after ?. carries the member's docs * docs(cli): regenerate the AnimaScript api bundle for T? Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 9a22341..35d7f61 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -1462fc31e1a1ce6a9e649ac0f812222157c8551c +b80a7fc662e92e5cb8f6daa80a6427d08f1d7b06
diff --git a/include/rive/data_bind/data_values/data_value_color.hpp b/include/rive/data_bind/data_values/data_value_color.hpp index d204d9a..5acc492 100644 --- a/include/rive/data_bind/data_values/data_value_color.hpp +++ b/include/rive/data_bind/data_values/data_value_color.hpp
@@ -10,6 +10,12 @@ { private: int m_value = 0; + void setChannel(int shift, int value) + { + uint32_t mask = 0xFFu << shift; + m_value = (int)(((uint32_t)m_value & ~mask) | + (((uint32_t)value & 0xFFu) << shift)); + } public: DataValueColor(int value) : m_value(value) {}; @@ -25,10 +31,11 @@ int red() { return (m_value >> 16) & 0xFF; } int green() { return (m_value >> 8) & 0xFF; } int blue() { return m_value & 0xFF; } - void alpha(int value) { m_value = (m_value & 0x00FFFFFF) | (value << 24); } - void red(int value) { m_value = (m_value & 0xFF00FFFF) | (value << 16); } - void green(int value) { m_value = (m_value & 0xFFFF00FF) | (value << 8); } - void blue(int value) { m_value = (m_value & 0xFFFFFF00) | value; } + // Channels keep their low 8 bits, like Color's, so they never spill. + void alpha(int value) { setChannel(24, value); } + void red(int value) { setChannel(16, value); } + void green(int value) { setChannel(8, value); } + void blue(int value) { setChannel(0, value); } bool compare(DataValue* comparand) override { if (comparand->is<DataValueColor>())
diff --git a/include/rive/scripted/script_backend.hpp b/include/rive/scripted/script_backend.hpp index 9bbb9f8..bac64ae 100644 --- a/include/rive/scripted/script_backend.hpp +++ b/include/rive/scripted/script_backend.hpp
@@ -187,6 +187,11 @@ { Artboard* artboard = nullptr; Mat2D transform; + + // Defined in transition_child_ref.hpp, shared by both backends. + void draw(Renderer* renderer) const; + float width() const; + float height() const; }; /// Transition protocol: self.managesTo, read as a field rather than /// called. A script opts out of compositing the incoming child with an
diff --git a/include/rive/scripted/transition_child_ref.hpp b/include/rive/scripted/transition_child_ref.hpp new file mode 100644 index 0000000..3264cb6 --- /dev/null +++ b/include/rive/scripted/transition_child_ref.hpp
@@ -0,0 +1,35 @@ +#ifndef _RIVE_TRANSITION_CHILD_REF_HPP_ +#define _RIVE_TRANSITION_CHILD_REF_HPP_ + +#include "rive/artboard.hpp" +#include "rive/renderer.hpp" +#include "rive/scripted/script_backend.hpp" + +namespace rive +{ +// drawInternal ignores isHidden, so a child kept out of the normal draw loop +// still renders here. +inline void ScriptBackend::TransitionChildRef::draw(Renderer* renderer) const +{ + if (artboard == nullptr) + { + return; + } + renderer->save(); + renderer->transform(transform); + artboard->drawInternal(renderer); + renderer->restore(); +} + +inline float ScriptBackend::TransitionChildRef::width() const +{ + return artboard != nullptr ? artboard->width() : 0.0f; +} + +inline float ScriptBackend::TransitionChildRef::height() const +{ + return artboard != nullptr ? artboard->height() : 0.0f; +} +} // namespace rive + +#endif
diff --git a/include/rive/wasm/gamepad_wire.hpp b/include/rive/wasm/gamepad_wire.hpp index 644636f..4d7e32e 100644 --- a/include/rive/wasm/gamepad_wire.hpp +++ b/include/rive/wasm/gamepad_wire.hpp
@@ -1,7 +1,11 @@ #ifndef _RIVE_WASM_GAMEPAD_WIRE_HPP_ #define _RIVE_WASM_GAMEPAD_WIRE_HPP_ +#include "rive/animation/listener_invocation.hpp" + #include <cstdint> +#include <cstring> +#include <optional> namespace rive { @@ -31,6 +35,156 @@ uint32_t axisCount = 0; }; +/// Flattens a gamepad invocation into the wire; false for other kinds. The +/// snapshot, null on a disconnect, supplies the floats after the wire. +inline bool packGamepadWire(const ListenerInvocation& invocation, + GamepadWire& wire, + const GamepadSnapshot*& snapshot) +{ + if (const GamepadConnectedInvocation* c = invocation.asGamepadConnected()) + { + wire.kind = GamepadWire::kindConnected; + snapshot = &c->snapshot; + } + else if (const GamepadEventInvocation* e = invocation.asGamepadEvent()) + { + wire.kind = GamepadWire::kindEvent; + snapshot = &e->fullState; + wire.changeKind = (uint32_t)e->change.kind; + wire.changeIndex = e->change.index; + wire.changeValue = e->change.value; + wire.hasStandardButtonIntent = e->hasStandardButtonIntent ? 1 : 0; + wire.standardButton = (uint32_t)e->standardButton; + wire.hasStandardAxisIntent = e->hasStandardAxisIntent ? 1 : 0; + wire.standardAxis = (uint32_t)e->standardAxis; + } + else if (const GamepadDisconnectedInvocation* d = + invocation.asGamepadDisconnected()) + { + wire.kind = GamepadWire::kindDisconnected; + wire.deviceId = d->deviceId; + } + else + { + return false; + } + if (snapshot != nullptr) + { + wire.deviceId = snapshot->deviceId; + wire.mapping = (uint32_t)snapshot->mapping; + wire.buttonMaskLo = (uint32_t)snapshot->buttonMask; + wire.buttonMaskHi = (uint32_t)(snapshot->buttonMask >> 32); + wire.buttonCount = (uint32_t)snapshot->buttonValues.size(); + wire.axisCount = (uint32_t)snapshot->axes.size(); + } + return true; +} + +inline uint32_t gamepadPayloadSize(const GamepadWire& wire) +{ + return (uint32_t)(sizeof(wire) + + (wire.buttonCount + wire.axisCount) * sizeof(float)); +} + +inline void writeGamepadPayload(uint8_t* out, + const GamepadWire& wire, + const GamepadSnapshot* snapshot) +{ + memcpy(out, &wire, sizeof(wire)); + if (snapshot == nullptr) + { + return; + } + uint8_t* values = out + sizeof(wire); + if (wire.buttonCount != 0) + { + memcpy(values, + snapshot->buttonValues.data(), + wire.buttonCount * sizeof(float)); + } + if (wire.axisCount != 0) + { + memcpy(values + wire.buttonCount * sizeof(float), + snapshot->axes.data(), + wire.axisCount * sizeof(float)); + } +} + +/// False when byteCount cannot hold the wire and the floats it declares. +inline bool decodeGamepadWire(const uint8_t* data, + size_t byteCount, + GamepadWire& wire) +{ + if (byteCount < sizeof(wire)) + { + return false; + } + memcpy(&wire, data, sizeof(wire)); + uint64_t floatCount = uint64_t(wire.buttonCount) + wire.axisCount; + return uint64_t(byteCount) >= sizeof(wire) + floatCount * sizeof(float); +} + +// The payload may sit at any guest address, so floats are copied, not read. +inline GamepadSnapshot decodeGamepadSnapshot(const GamepadWire& wire, + const uint8_t* data) +{ + GamepadSnapshot snapshot; + snapshot.deviceId = wire.deviceId; + snapshot.mapping = (GamepadMappingKind)wire.mapping; + snapshot.buttonMask = + (uint64_t(wire.buttonMaskHi) << 32) | wire.buttonMaskLo; + const uint8_t* values = data + sizeof(wire); + snapshot.buttonValues.resize(wire.buttonCount); + snapshot.axes.resize(wire.axisCount); + if (wire.buttonCount != 0) + { + memcpy(snapshot.buttonValues.data(), + values, + wire.buttonCount * sizeof(float)); + } + if (wire.axisCount != 0) + { + memcpy(snapshot.axes.data(), + values + wire.buttonCount * sizeof(float), + wire.axisCount * sizeof(float)); + } + return snapshot; +} + +inline GamepadEventInvocation decodeGamepadEvent(const GamepadWire& wire, + const uint8_t* data) +{ + GamepadEventInvocation event; + event.fullState = decodeGamepadSnapshot(wire, data); + event.change.kind = (GamepadInputChangeKind)wire.changeKind; + event.change.index = (uint8_t)wire.changeIndex; + event.change.value = wire.changeValue; + event.hasStandardButtonIntent = wire.hasStandardButtonIntent != 0; + event.standardButton = (StandardGamepadButton)wire.standardButton; + event.hasStandardAxisIntent = wire.hasStandardAxisIntent != 0; + event.standardAxis = (StandardGamepadAxis)wire.standardAxis; + return event; +} + +/// The invocation a decoded wire carries; empty for an unknown kind. +inline std::optional<ListenerInvocation> decodeGamepadInvocation( + const GamepadWire& wire, + const uint8_t* data) +{ + switch (wire.kind) + { + case GamepadWire::kindConnected: + return ListenerInvocation::gamepadConnected( + decodeGamepadSnapshot(wire, data)); + case GamepadWire::kindEvent: + return ListenerInvocation::gamepadEvent( + decodeGamepadEvent(wire, data)); + case GamepadWire::kindDisconnected: + return ListenerInvocation::gamepadDisconnected(wire.deviceId); + } + return std::nullopt; +} + } // namespace rive #endif
diff --git a/include/rive/wasm/rive_bindings_v1.h b/include/rive/wasm/rive_bindings_v1.h index e065559..69f0022 100644 --- a/include/rive/wasm/rive_bindings_v1.h +++ b/include/rive/wasm/rive_bindings_v1.h
@@ -396,6 +396,8 @@ void rive_artboard_bounds(uint32_t artboard, float* out, uint32_t outCount); RIVE_BINDING_IMPORT("rive_artboard_v1", "pointer_event") uint32_t rive_artboard_pointer_event(uint32_t artboard, uint32_t kind, uint32_t pointerId, float x, float y); +RIVE_BINDING_IMPORT("rive_artboard_v1", "gamepad_event") +uint32_t rive_artboard_gamepad_event(uint32_t artboard, const uint8_t* payload, uint32_t byteCount); RIVE_BINDING_IMPORT("rive_artboard_v1", "animation") uint32_t rive_artboard_animation(uint32_t artboard, const char* name, uint32_t length); RIVE_BINDING_IMPORT("rive_artboard_v1", "animation_release") @@ -406,6 +408,8 @@ uint32_t rive_artboard_animation_advance(uint32_t animation, float seconds); RIVE_BINDING_IMPORT("rive_artboard_v1", "animation_set_time") void rive_artboard_animation_set_time(uint32_t animation, float value, uint32_t mode); +RIVE_BINDING_IMPORT("rive_artboard_v1", "add_to_path") +void rive_artboard_add_to_path(uint32_t artboard, uint32_t path, float xx, float xy, float yx, float yy, float tx, float ty); RIVE_BINDING_IMPORT("rive_artboard_v1", "node") uint32_t rive_artboard_node(uint32_t artboard, const char* name, uint32_t length); RIVE_BINDING_IMPORT("rive_artboard_v1", "node_release") @@ -654,6 +658,8 @@ void rive_gpu_bind_group_layout_release(uint32_t layout); RIVE_BINDING_IMPORT("rive_gpu_v1", "bind_group_layout_from_shader") uint32_t rive_gpu_bind_group_layout_from_shader(uint32_t shaderModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount); +RIVE_BINDING_IMPORT("rive_gpu_v1", "bind_group_layout_from_shaders") +uint32_t rive_gpu_bind_group_layout_from_shaders(uint32_t vertexModule, uint32_t fragmentModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount); RIVE_BINDING_IMPORT("rive_gpu_v1", "bind_group_new") uint32_t rive_gpu_bind_group_new(uint32_t layout, const rive_gpu_bind_group_ubo_v1* ubos, uint32_t uboByteCount, const rive_gpu_bind_group_texture_v1* textures, uint32_t textureByteCount, const rive_gpu_bind_group_sampler_v1* samplers, uint32_t samplerByteCount); RIVE_BINDING_IMPORT("rive_gpu_v1", "bind_group_release") @@ -773,6 +779,18 @@ RIVE_BINDING_IMPORT("rive_shader_v1", "release") void rive_shader_release(uint32_t shader); +// rive_test_v1 +RIVE_BINDING_IMPORT("rive_test_v1", "blob") +uint32_t rive_test_blob(const char* name, uint32_t nameLength, uint8_t* out, uint32_t outCount); + +// rive_transition_v1 +RIVE_BINDING_IMPORT("rive_transition_v1", "child_draw") +void rive_transition_child_draw(uint32_t child, uint32_t renderer); +RIVE_BINDING_IMPORT("rive_transition_v1", "child_width") +float rive_transition_child_width(uint32_t child); +RIVE_BINDING_IMPORT("rive_transition_v1", "child_height") +float rive_transition_child_height(uint32_t child); + // rive_renderer_v1 RIVE_BINDING_IMPORT("rive_renderer_v1", "save") void rive_renderer_save(uint32_t renderer);
diff --git a/include/rive/wasm/wasm_scripting_vm.hpp b/include/rive/wasm/wasm_scripting_vm.hpp index 8557a26..0c1d6d2 100644 --- a/include/rive/wasm/wasm_scripting_vm.hpp +++ b/include/rive/wasm/wasm_scripting_vm.hpp
@@ -104,6 +104,7 @@ audioSound, drawable, text, + transitionChild, count, }; struct Slot @@ -121,6 +122,9 @@ void release(uint32_t handle, Tag tag); }; HandleTable& handles() { return m_handles; } + /// Borrowed node handles, released when the path effect update that + /// minted them returns. + std::vector<uint32_t>& scopedNodes() { return m_scopedNodes; } // Saves the draw visit in flight has open on the renderer it was handed, // so a trap can close them before the rest of the frame draws. @@ -441,6 +445,19 @@ void setTimeoutMs(int ms); int timeoutMs() const { return m_timeoutMs; } + /// Backs a Tests suite's blob(name); empty when the blob is missing, + /// which every blob is outside test runs. + using TestBlobLookup = + std::function<Span<const uint8_t>(const std::string&)>; + void setTestBlobs(TestBlobLookup lookup) + { + m_testBlobs = std::move(lookup); + } + Span<const uint8_t> testBlob(const std::string& name) const + { + return m_testBlobs ? m_testBlobs(name) : Span<const uint8_t>(); + } + /// Budget for VMs created after the call; the host's trust decision for /// files whose modules it baked itself (e.g. dangerouslyFast content). static void defaultTimeoutMs(int ms) { sm_defaultTimeoutMs = ms; } @@ -483,6 +500,10 @@ bool init(Span<const uint8_t> module); uint32_t guestString(const char* text, const char* allocator = "malloc"); void guestFree(uint32_t ptr); + // A call scoped handle over a transition child, 0 for an empty one. + uint32_t mintTransitionChild(const TransitionChildRef& ref); + void releaseTransitionChild(uint32_t handle); + void releaseScopedNodes(size_t from); // A method name in guest memory: made once per instance when the module // can keep it past the frame, else copied for this call alone. class GuestName @@ -526,7 +547,9 @@ std::vector<std::string> m_unresolvedImports; static int sm_defaultTimeoutMs; int m_timeoutMs = sm_defaultTimeoutMs; + TestBlobLookup m_testBlobs; HandleTable m_handles; + std::vector<uint32_t> m_scopedNodes; /// Canvas frames begun and not ended, as handles with a monotonic token /// so a script call's exit reclaims only the frames it began. struct OpenCanvasFrame
diff --git a/src/lua/lua_transition.cpp b/src/lua/lua_transition.cpp index 835830b..b848263 100644 --- a/src/lua/lua_transition.cpp +++ b/src/lua/lua_transition.cpp
@@ -1,36 +1,25 @@ #ifdef WITH_RIVE_SCRIPTING #include "rive/lua/rive_lua_libs.hpp" -#include "rive/nested_artboard.hpp" -#include "rive/artboard.hpp" +#include "rive/scripted/transition_child_ref.hpp" using namespace rive; void TransitionChild::draw(Renderer* renderer) { - // Invalidated (post-frame) or not mounted: nothing to draw. - if (m_artboard == nullptr) - { - return; - } - // Place the child by its world transform then draw its content. The - // transition script has already wrapped this call in renderer state - // (opacity / transform / clip) to author the effect. drawInternal does not - // consult isHidden(), so a child excluded from the normal draw loop still - // renders here. - renderer->save(); - renderer->transform(m_worldTransform); - m_artboard->drawInternal(renderer); - renderer->restore(); + ScriptBackend::TransitionChildRef{m_artboard, m_worldTransform}.draw( + renderer); } float TransitionChild::width() const { - return m_artboard != nullptr ? m_artboard->width() : 0.0f; + return ScriptBackend::TransitionChildRef{m_artboard, m_worldTransform} + .width(); } float TransitionChild::height() const { - return m_artboard != nullptr ? m_artboard->height() : 0.0f; + return ScriptBackend::TransitionChildRef{m_artboard, m_worldTransform} + .height(); } static int transition_child_draw(lua_State* L)
diff --git a/src/viewmodel/runtime/viewmodel_instance_color_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_color_runtime.cpp index 76b4506..98c864e 100644 --- a/src/viewmodel/runtime/viewmodel_instance_color_runtime.cpp +++ b/src/viewmodel/runtime/viewmodel_instance_color_runtime.cpp
@@ -1,5 +1,6 @@ #include "rive/viewmodel/runtime/viewmodel_instance_color_runtime.hpp" +#include "rive/shapes/paint/color.hpp" // Default namespace for Rive Cpp code using namespace rive; @@ -35,7 +36,7 @@ void ViewModelInstanceColorRuntime::argb(int a, int r, int g, int b) { - auto color = (a << 24) | (r << 16) | (g << 8) | b; + auto color = colorARGB(a, r, g, b); m_viewModelInstanceValue->as<ViewModelInstanceColor>()->propertyValue( color); }
diff --git a/src/wasm/idl/bindings.py b/src/wasm/idl/bindings.py index 9d7f45c..8a5be1a 100644 --- a/src/wasm/idl/bindings.py +++ b/src/wasm/idl/bindings.py
@@ -434,6 +434,11 @@ f32('x'), f32('y'), ], ret='u32'), + # artboard:gamepad*: a GamepadWire payload with its button and axis + # floats; returns how many scripted drawables took the event. + op('gamepad_event', [handle('artboard'), + buf('uint8_t', 'payload', 'byteCount')], + ret='u32'), op('animation', [handle('artboard'), string('name', 'length')], ret='u32'), op('animation_release', [handle('animation')]), @@ -446,6 +451,10 @@ f32('value'), u32('mode'), ]), + # artboard:addToPath: appends the visible shapes into a host path. + op('add_to_path', [handle('artboard'), handle('path'), f32('xx'), + f32('xy'), f32('yx'), f32('yy'), f32('tx'), + f32('ty')]), op('node', [handle('artboard'), string('name', 'length')], ret='u32'), op('node_release', [handle('node')]), # x, y, rotation, scaleX, scaleY. @@ -804,6 +813,14 @@ u32('groupIndex'), buf('uint32_t', 'dynamicUBOs', 'dynamicUBOCount'), ], ret='u32'), + # The same over a pipeline whose stages live in two modules, so the + # group covers what either stage binds. + op('bind_group_layout_from_shaders', [ + handle('vertexModule'), + handle('fragmentModule'), + u32('groupIndex'), + buf('uint32_t', 'dynamicUBOs', 'dynamicUBOCount'), + ], ret='u32'), op('bind_group_new', [ handle('layout'), buf('rive_gpu_bind_group_ubo_v1', 'ubos', 'uboByteCount'), @@ -964,6 +981,20 @@ ], ret='u32'), op('release', [handle('shader')]), ]), + # Tests suites' blob(name): the full byte count, size then fill, 0 when + # the blob is missing or empty; outside test runs every blob is missing. + ns('rive_test_v1', 'test', [ + op('blob', [string('name', 'nameLength'), + mutbuf('uint8_t', 'out', 'outCount')], ret='u32', + stub='zero'), + ]), + # The children a Transition's changed and draw receive, valid for that + # call only. + ns('rive_transition_v1', 'transition', [ + op('child_draw', [handle('child'), handle('renderer')]), + op('child_width', [handle('child')], ret='f32'), + op('child_height', [handle('child')], ret='f32'), + ]), ns('rive_renderer_v1', 'renderer', [ op('save', [handle('renderer')]), op('restore', [handle('renderer')]),
diff --git a/src/wasm/wasm_natives_gen.hpp b/src/wasm/wasm_natives_gen.hpp index 08d3b4e..1b729f7 100644 --- a/src/wasm/wasm_natives_gen.hpp +++ b/src/wasm/wasm_natives_gen.hpp
@@ -291,11 +291,13 @@ void artboardSetFrameOriginImpl(WasmScriptingVM* vm, uint32_t artboard, uint32_t value); void artboardBoundsImpl(WasmScriptingVM* vm, uint32_t artboard, float* out, uint32_t outCount); uint32_t artboardPointerEventImpl(WasmScriptingVM* vm, uint32_t artboard, uint32_t kind, uint32_t pointerId, float x, float y); +uint32_t artboardGamepadEventImpl(WasmScriptingVM* vm, uint32_t artboard, const uint8_t* payload, uint32_t byteCount); uint32_t artboardAnimationImpl(WasmScriptingVM* vm, uint32_t artboard, const char* name, uint32_t length); void artboardAnimationReleaseImpl(WasmScriptingVM* vm, uint32_t animation); float artboardAnimationDurationImpl(WasmScriptingVM* vm, uint32_t animation); uint32_t artboardAnimationAdvanceImpl(WasmScriptingVM* vm, uint32_t animation, float seconds); void artboardAnimationSetTimeImpl(WasmScriptingVM* vm, uint32_t animation, float value, uint32_t mode); +void artboardAddToPathImpl(WasmScriptingVM* vm, uint32_t artboard, uint32_t path, float xx, float xy, float yx, float yy, float tx, float ty); uint32_t artboardNodeImpl(WasmScriptingVM* vm, uint32_t artboard, const char* name, uint32_t length); void artboardNodeReleaseImpl(WasmScriptingVM* vm, uint32_t node); void artboardNodeTransformImpl(WasmScriptingVM* vm, uint32_t node, float* out, uint32_t outCount); @@ -416,6 +418,7 @@ uint32_t gpuBindGroupLayoutNewImpl(WasmScriptingVM* vm, uint32_t groupIndex, const rive_gpu_bind_group_layout_entry_v1* entries, uint32_t entryByteCount); void gpuBindGroupLayoutReleaseImpl(WasmScriptingVM* vm, uint32_t layout); uint32_t gpuBindGroupLayoutFromShaderImpl(WasmScriptingVM* vm, uint32_t shaderModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount); +uint32_t gpuBindGroupLayoutFromShadersImpl(WasmScriptingVM* vm, uint32_t vertexModule, uint32_t fragmentModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount); uint32_t gpuBindGroupNewImpl(WasmScriptingVM* vm, uint32_t layout, const rive_gpu_bind_group_ubo_v1* ubos, uint32_t uboByteCount, const rive_gpu_bind_group_texture_v1* textures, uint32_t textureByteCount, const rive_gpu_bind_group_sampler_v1* samplers, uint32_t samplerByteCount); void gpuBindGroupReleaseImpl(WasmScriptingVM* vm, uint32_t bindGroup); uint32_t gpuPipelineNewImpl(WasmScriptingVM* vm, const rive_gpu_pipeline_desc_v1* desc, uint32_t descByteCount, const uint8_t* blob, uint32_t blobCount); @@ -501,6 +504,10 @@ uint32_t shaderLinearImpl(WasmScriptingVM* vm, float sx, float sy, float ex, float ey, uint32_t colors, uint32_t stops, uint32_t count); uint32_t shaderRadialImpl(WasmScriptingVM* vm, float cx, float cy, float radius, uint32_t colors, uint32_t stops, uint32_t count); void shaderReleaseImpl(WasmScriptingVM* vm, uint32_t shader); +uint32_t testBlobImpl(WasmScriptingVM* vm, const char* name, uint32_t nameLength, uint8_t* out, uint32_t outCount); +void transitionChildDrawImpl(WasmScriptingVM* vm, uint32_t child, uint32_t renderer); +float transitionChildWidthImpl(WasmScriptingVM* vm, uint32_t child); +float transitionChildHeightImpl(WasmScriptingVM* vm, uint32_t child); void rendererSaveImpl(WasmScriptingVM* vm, uint32_t renderer); void rendererRestoreImpl(WasmScriptingVM* vm, uint32_t renderer); void rendererTransformImpl(WasmScriptingVM* vm, uint32_t renderer, float xx, float xy, float yx, float yy, float tx, float ty); @@ -981,6 +988,11 @@ WasmScriptingVM* vm = vmFromEnv(env); return artboardPointerEventImpl(vm, artboard, kind, pointerId, x, y); } +uint32_t artboardGamepadEvent(wasm_exec_env_t env, uint32_t artboard, const uint8_t* payload, uint32_t byteCount) +{ + WasmScriptingVM* vm = vmFromEnv(env); + return artboardGamepadEventImpl(vm, artboard, payload, byteCount); +} uint32_t artboardAnimation(wasm_exec_env_t env, uint32_t artboard, const char* name, uint32_t length) { WasmScriptingVM* vm = vmFromEnv(env); @@ -1007,6 +1019,11 @@ WasmScriptingVM* vm = vmFromEnv(env); artboardAnimationSetTimeImpl(vm, animation, value, mode); } +void artboardAddToPath(wasm_exec_env_t env, uint32_t artboard, uint32_t path, float xx, float xy, float yx, float yy, float tx, float ty) +{ + WasmScriptingVM* vm = vmFromEnv(env); + artboardAddToPathImpl(vm, artboard, path, xx, xy, yx, yy, tx, ty); +} uint32_t artboardNode(wasm_exec_env_t env, uint32_t artboard, const char* name, uint32_t length) { WasmScriptingVM* vm = vmFromEnv(env); @@ -1688,6 +1705,15 @@ } return gpuBindGroupLayoutFromShaderImpl(vm, shaderModule, groupIndex, dynamicUBOs, dynamicUBOCount); } +uint32_t gpuBindGroupLayoutFromShaders(wasm_exec_env_t env, uint32_t vertexModule, uint32_t fragmentModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount) +{ + WasmScriptingVM* vm = vmFromEnv(env); + if (dynamicUBOCount != 0 && !wasm_runtime_validate_native_addr(wasm_runtime_get_module_inst(env), (void*)dynamicUBOs, (uint64_t)dynamicUBOCount * 4)) + { + return {}; + } + return gpuBindGroupLayoutFromShadersImpl(vm, vertexModule, fragmentModule, groupIndex, dynamicUBOs, dynamicUBOCount); +} uint32_t gpuBindGroupNew(wasm_exec_env_t env, uint32_t layout, const rive_gpu_bind_group_ubo_v1* ubos, uint32_t uboByteCount, const rive_gpu_bind_group_texture_v1* textures, uint32_t textureByteCount, const rive_gpu_bind_group_sampler_v1* samplers, uint32_t samplerByteCount) { WasmScriptingVM* vm = vmFromEnv(env); @@ -2029,6 +2055,27 @@ WasmScriptingVM* vm = vmFromEnv(env); shaderReleaseImpl(vm, shader); } +uint32_t testBlob(wasm_exec_env_t env, const char* name, uint32_t nameLength, uint8_t* out, uint32_t outCount) +{ + WasmScriptingVM* vm = vmFromEnv(env); + WasmStringArg nameUtf8(vm, name, nameLength); + return testBlobImpl(vm, nameUtf8.data(), nameUtf8.size(), out, outCount); +} +void transitionChildDraw(wasm_exec_env_t env, uint32_t child, uint32_t renderer) +{ + WasmScriptingVM* vm = vmFromEnv(env); + transitionChildDrawImpl(vm, child, renderer); +} +float transitionChildWidth(wasm_exec_env_t env, uint32_t child) +{ + WasmScriptingVM* vm = vmFromEnv(env); + return transitionChildWidthImpl(vm, child); +} +float transitionChildHeight(wasm_exec_env_t env, uint32_t child) +{ + WasmScriptingVM* vm = vmFromEnv(env); + return transitionChildHeightImpl(vm, child); +} void rendererSave(wasm_exec_env_t env, uint32_t renderer) { WasmScriptingVM* vm = vmFromEnv(env); @@ -2175,11 +2222,13 @@ {"set_frame_origin", (void*)artboardSetFrameOrigin, "(ii)", nullptr}, {"bounds", (void*)artboardBounds, "(i*~)", (void*)&wasm_runtime_rive_leaf_native}, {"pointer_event", (void*)artboardPointerEvent, "(iiiff)i", nullptr}, + {"gamepad_event", (void*)artboardGamepadEvent, "(i*~)i", nullptr}, {"animation", (void*)artboardAnimation, "(i*~)i", nullptr}, {"animation_release", (void*)artboardAnimationRelease, "(i)", nullptr}, {"animation_duration", (void*)artboardAnimationDuration, "(i)f", nullptr}, {"animation_advance", (void*)artboardAnimationAdvance, "(if)i", nullptr}, {"animation_set_time", (void*)artboardAnimationSetTime, "(ifi)", nullptr}, + {"add_to_path", (void*)artboardAddToPath, "(iiffffff)", nullptr}, {"node", (void*)artboardNode, "(i*~)i", nullptr}, {"node_release", (void*)artboardNodeRelease, "(i)", nullptr}, {"node_transform", (void*)artboardNodeTransform, "(i*~)", (void*)&wasm_runtime_rive_leaf_native}, @@ -2318,6 +2367,7 @@ {"bind_group_layout_new", (void*)gpuBindGroupLayoutNew, "(i*~)i", nullptr}, {"bind_group_layout_release", (void*)gpuBindGroupLayoutRelease, "(i)", nullptr}, {"bind_group_layout_from_shader", (void*)gpuBindGroupLayoutFromShader, "(ii*~)i", nullptr}, + {"bind_group_layout_from_shaders", (void*)gpuBindGroupLayoutFromShaders, "(iii*~)i", nullptr}, {"bind_group_new", (void*)gpuBindGroupNew, "(i*~*~*~)i", nullptr}, {"bind_group_release", (void*)gpuBindGroupRelease, "(i)", nullptr}, {"pipeline_new", (void*)gpuPipelineNew, "(*~*~)i", nullptr}, @@ -2429,6 +2479,16 @@ {"release", (void*)shaderRelease, "(i)", (void*)&wasm_runtime_rive_leaf_native}, }; +NativeSymbol kTestNatives[] = { + {"blob", (void*)testBlob, "(*~*~)i", nullptr}, +}; + +NativeSymbol kTransitionNatives[] = { + {"child_draw", (void*)transitionChildDraw, "(ii)", nullptr}, + {"child_width", (void*)transitionChildWidth, "(i)f", nullptr}, + {"child_height", (void*)transitionChildHeight, "(i)f", nullptr}, +}; + NativeSymbol kRendererNatives[] = { {"save", (void*)rendererSave, "(i)", (void*)&wasm_runtime_rive_leaf_native}, {"restore", (void*)rendererRestore, "(i)", (void*)&wasm_runtime_rive_leaf_native}, @@ -2513,6 +2573,14 @@ kShaderNatives, sizeof(kShaderNatives) / sizeof(NativeSymbol)) && wasm_runtime_register_natives( + "rive_test_v1", + kTestNatives, + sizeof(kTestNatives) / sizeof(NativeSymbol)) && + wasm_runtime_register_natives( + "rive_transition_v1", + kTransitionNatives, + sizeof(kTransitionNatives) / sizeof(NativeSymbol)) && + wasm_runtime_register_natives( "rive_renderer_v1", kRendererNatives, sizeof(kRendererNatives) / sizeof(NativeSymbol));
diff --git a/src/wasm/wasm_natives_web_gen.hpp b/src/wasm/wasm_natives_web_gen.hpp index 5b27c5e..9c82424 100644 --- a/src/wasm/wasm_natives_web_gen.hpp +++ b/src/wasm/wasm_natives_web_gen.hpp
@@ -564,6 +564,12 @@ return artboardPointerEventImpl(vm, artboard, kind, pointerId, x, y); } EMSCRIPTEN_KEEPALIVE +uint32_t rive_web_artboard_gamepad_event(uint32_t vmHandle, uint32_t artboard, const uint8_t* payload, uint32_t byteCount) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + return artboardGamepadEventImpl(vm, artboard, payload, byteCount); +} +EMSCRIPTEN_KEEPALIVE uint32_t rive_web_artboard_animation(uint32_t vmHandle, uint32_t artboard, const char* name, uint32_t length) { auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; @@ -595,6 +601,12 @@ artboardAnimationSetTimeImpl(vm, animation, value, mode); } EMSCRIPTEN_KEEPALIVE +void rive_web_artboard_add_to_path(uint32_t vmHandle, uint32_t artboard, uint32_t path, float xx, float xy, float yx, float yy, float tx, float ty) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + artboardAddToPathImpl(vm, artboard, path, xx, xy, yx, yy, tx, ty); +} +EMSCRIPTEN_KEEPALIVE uint32_t rive_web_artboard_node(uint32_t vmHandle, uint32_t artboard, const char* name, uint32_t length) { auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; @@ -1310,6 +1322,12 @@ return gpuBindGroupLayoutFromShaderImpl(vm, shaderModule, groupIndex, dynamicUBOs, dynamicUBOCount); } EMSCRIPTEN_KEEPALIVE +uint32_t rive_web_gpu_bind_group_layout_from_shaders(uint32_t vmHandle, uint32_t vertexModule, uint32_t fragmentModule, uint32_t groupIndex, const uint32_t* dynamicUBOs, uint32_t dynamicUBOCount) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + return gpuBindGroupLayoutFromShadersImpl(vm, vertexModule, fragmentModule, groupIndex, dynamicUBOs, dynamicUBOCount); +} +EMSCRIPTEN_KEEPALIVE uint32_t rive_web_gpu_bind_group_new(uint32_t vmHandle, uint32_t layout, const rive_gpu_bind_group_ubo_v1* ubos, uint32_t uboByteCount, const rive_gpu_bind_group_texture_v1* textures, uint32_t textureByteCount, const rive_gpu_bind_group_sampler_v1* samplers, uint32_t samplerByteCount) { auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; @@ -1654,6 +1672,31 @@ shaderReleaseImpl(vm, shader); } EMSCRIPTEN_KEEPALIVE +uint32_t rive_web_test_blob(uint32_t vmHandle, const char* name, uint32_t nameLength, uint8_t* out, uint32_t outCount) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + WasmStringArg nameUtf8(vm, name, nameLength); + return testBlobImpl(vm, nameUtf8.data(), nameUtf8.size(), out, outCount); +} +EMSCRIPTEN_KEEPALIVE +void rive_web_transition_child_draw(uint32_t vmHandle, uint32_t child, uint32_t renderer) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + transitionChildDrawImpl(vm, child, renderer); +} +EMSCRIPTEN_KEEPALIVE +float rive_web_transition_child_width(uint32_t vmHandle, uint32_t child) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + return transitionChildWidthImpl(vm, child); +} +EMSCRIPTEN_KEEPALIVE +float rive_web_transition_child_height(uint32_t vmHandle, uint32_t child) +{ + auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle; + return transitionChildHeightImpl(vm, child); +} +EMSCRIPTEN_KEEPALIVE void rive_web_renderer_save(uint32_t vmHandle, uint32_t renderer) { auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle;
diff --git a/src/wasm/wasm_scripting_vm.cpp b/src/wasm/wasm_scripting_vm.cpp index d6d6731..2330689 100644 --- a/src/wasm/wasm_scripting_vm.cpp +++ b/src/wasm/wasm_scripting_vm.cpp
@@ -20,6 +20,7 @@ #include "rive/animation/linear_animation_instance.hpp" #include "rive/animation/listener_invocation.hpp" #include "rive/animation/state_machine_instance.hpp" +#include "rive/input/focus_manager.hpp" #include "rive/artboard.hpp" #include "rive/assets/blob_asset.hpp" #include "rive/custom_property_boolean.hpp" @@ -67,6 +68,7 @@ #include "rive/math/path_measure.hpp" #include "rive/renderer.hpp" #include "rive/scripted/scripted_object.hpp" +#include "rive/scripted/transition_child_ref.hpp" #include "rive/viewmodel/viewmodel.hpp" #include "rive/viewmodel/viewmodel_instance.hpp" #include "rive/viewmodel/viewmodel_instance_boolean.hpp" @@ -3215,6 +3217,47 @@ new HostGpuBindGroupLayout{std::move(layout)}); } +uint32_t gpuBindGroupLayoutFromShadersImpl(WasmScriptingVM* vm, + uint32_t vertexModule, + uint32_t fragmentModule, + uint32_t groupIndex, + const uint32_t* dynamicUBOs, + uint32_t dynamicUBOCount) +{ + ore::Context* oreContext = gpuOreContext(vm); + if (oreContext == nullptr) + { + return 0; + } + auto resolveModule = [vm](uint32_t handle) -> ore::ShaderModule* { + auto host = static_cast<HostGpuShaderModule*>(vm->handles().resolve( + handle, + WasmScriptingVM::HandleTable::Tag::gpuShaderModule)); + return host != nullptr ? host->shaderModule.get() : nullptr; + }; + ore::ShaderModule* vertex = resolveModule(vertexModule); + ore::ShaderModule* fragment = resolveModule(fragmentModule); + if (vertex == nullptr || (fragmentModule != 0 && fragment == nullptr)) + { + return 0; + } + auto layout = ore::makeBindGroupLayoutFromBindingMap( + *oreContext, + ore::bindingMapForStages(vertex, fragment), + groupIndex, + dynamicUBOs, + dynamicUBOCount, + vertex, + fragment); + if (layout == nullptr) + { + return 0; + } + return vm->handles().mint( + WasmScriptingVM::HandleTable::Tag::gpuBindGroupLayout, + new HostGpuBindGroupLayout{std::move(layout)}); +} + uint32_t gpuBindGroupNewImpl(WasmScriptingVM* vm, uint32_t layoutHandle, const rive_gpu_bind_group_ubo_v1* ubos, @@ -3675,6 +3718,15 @@ { return 0; } +uint32_t gpuBindGroupLayoutFromShadersImpl(WasmScriptingVM*, + uint32_t, + uint32_t, + uint32_t, + const uint32_t*, + uint32_t) +{ + return 0; +} uint32_t gpuBindGroupNewImpl(WasmScriptingVM*, uint32_t, const rive_gpu_bind_group_ubo_v1*, @@ -4073,6 +4125,59 @@ WasmScriptingVM::HandleTable::Tag::renderer)); } +uint32_t testBlobImpl(WasmScriptingVM* vm, + const char* name, + uint32_t nameLength, + uint8_t* out, + uint32_t outCount) +{ + if (vm == nullptr) + { + return 0; + } + return fillOut(vm->testBlob(std::string(name, nameLength)), out, outCount); +} + +// A transition child handle points at the caller's TransitionChildRef and +// is released when the call returns. +const ScriptBackend::TransitionChildRef* resolveTransitionChild( + WasmScriptingVM* vm, + uint32_t handle) +{ + if (vm == nullptr) + { + return nullptr; + } + return static_cast<const ScriptBackend::TransitionChildRef*>( + vm->handles().resolve( + handle, + WasmScriptingVM::HandleTable::Tag::transitionChild)); +} + +void transitionChildDrawImpl(WasmScriptingVM* vm, + uint32_t child, + uint32_t rendererHandle) +{ + auto ref = resolveTransitionChild(vm, child); + Renderer* renderer = resolveRenderer(vm, rendererHandle); + if (ref != nullptr && renderer != nullptr) + { + ref->draw(renderer); + } +} + +float transitionChildWidthImpl(WasmScriptingVM* vm, uint32_t child) +{ + auto ref = resolveTransitionChild(vm, child); + return ref != nullptr ? ref->width() : 0.0f; +} + +float transitionChildHeightImpl(WasmScriptingVM* vm, uint32_t child) +{ + auto ref = resolveTransitionChild(vm, child); + return ref != nullptr ? ref->height() : 0.0f; +} + void rendererSaveImpl(WasmScriptingVM* vm, uint32_t handle) { if (auto renderer = resolveRenderer(vm, handle)) @@ -5927,12 +6032,28 @@ } // Nodes pin their artboard; the component pointer lives inside its instance. +// Without an owner the node borrows a path effect's shape for that update. struct HostNode { rcp<HostArtboard> owner; TransformComponent* component; }; +uint32_t mintNode(WasmScriptingVM* vm, + rcp<HostArtboard> owner, + TransformComponent* component) +{ + bool borrowed = owner == nullptr; + uint32_t handle = + vm->handles().mint(WasmScriptingVM::HandleTable::Tag::node, + new HostNode{std::move(owner), component}); + if (borrowed) + { + vm->scopedNodes().push_back(handle); + } + return handle; +} + uint32_t artboardNodeImpl(WasmScriptingVM* vm, uint32_t handle, const char* name, @@ -5949,8 +6070,57 @@ { return 0; } - return vm->handles().mint(WasmScriptingVM::HandleTable::Tag::node, - new HostNode{ref_rcp(host), component}); + return mintNode(vm, ref_rcp(host), component); +} + +void artboardAddToPathImpl(WasmScriptingVM* vm, + uint32_t handle, + uint32_t pathHandle, + float xx, + float xy, + float yx, + float yy, + float tx, + float ty) +{ + auto host = resolveArtboard(vm, handle); + auto hostPath = resolvePath(vm, pathHandle); + if (host == nullptr || hostPath == nullptr) + { + return; + } + Mat2D transform(xx, xy, yx, yy, tx, ty); + host->artboard->addToRawPath(hostPath->raw, &transform); + hostPath->renderDirty = true; +} + +uint32_t artboardGamepadEventImpl(WasmScriptingVM* vm, + uint32_t handle, + const uint8_t* payload, + uint32_t byteCount) +{ + auto host = resolveArtboard(vm, handle); + GamepadWire wire; + if (host == nullptr || host->stateMachine == nullptr || + !decodeGamepadWire(payload, byteCount, wire)) + { + return 0; + } + auto invocation = decodeGamepadInvocation(wire, payload); + if (!invocation) + { + return 0; + } + // The Luau artboard:gamepad* path: focus first, then every scripted + // drawable that did not already take it. + ScriptedDrawable* dispatched = nullptr; + if (auto* focus = host->stateMachine->focusManager()) + { + (void)focus->gamepadDispatch(*invocation, &dispatched); + } + return (uint32_t)host->stateMachine->broadcastGamepadToScriptedDrawables( + *invocation, + dispatched); } HostNode* resolveNode(WasmScriptingVM* vm, uint32_t handle) @@ -5966,7 +6136,8 @@ void artboardNodeReleaseImpl(WasmScriptingVM* vm, uint32_t handle) { auto host = resolveNode(vm, handle); - if (vm == nullptr || host == nullptr) + // A borrowed node is released when its path effect update returns. + if (host == nullptr || host->owner == nullptr) { return; } @@ -5979,11 +6150,18 @@ float* out, uint32_t outCount) { - auto host = resolveNode(vm, handle); - if (host == nullptr || outCount < 5) + if (outCount < 5) { return; } + auto host = resolveNode(vm, handle); + // A stale node reads as the identity, the same on every lane. + if (host == nullptr) + { + const float identity[5] = {0, 0, 0, 1, 1}; + memcpy(out, identity, sizeof(identity)); + return; + } out[0] = host->component->x(); out[1] = host->component->y(); out[2] = host->component->rotation(); @@ -6061,12 +6239,12 @@ float* out, uint32_t outCount) { - auto host = resolveNode(vm, handle); - if (host == nullptr || outCount < 6) + if (outCount < 6) { return; } - const Mat2D& world = host->component->worldTransform(); + auto host = resolveNode(vm, handle); + Mat2D world = host != nullptr ? host->component->worldTransform() : Mat2D(); for (int i = 0; i < 6; i++) { out[i] = world[i]; @@ -6215,9 +6393,8 @@ { if (child->is<TransformComponent>()) { - out[index++] = vm->handles().mint( - WasmScriptingVM::HandleTable::Tag::node, - new HostNode{host->owner, child->as<TransformComponent>()}); + out[index++] = + mintNode(vm, host->owner, child->as<TransformComponent>()); } } return count; @@ -6235,9 +6412,7 @@ { return 0; } - return vm->handles().mint( - WasmScriptingVM::HandleTable::Tag::node, - new HostNode{host->owner, parent->as<TransformComponent>()}); + return mintNode(vm, host->owner, parent->as<TransformComponent>()); } // --- rive_data_v1 asset properties (image/font/blob) ------------------------ @@ -8731,6 +8906,8 @@ return "animation"; case Tag::node: return "node"; + case Tag::transitionChild: + return "transitionChild"; case Tag::audioSource: return "audioSource"; case Tag::audioSound: @@ -9268,19 +9445,49 @@ sourcePath.verbs().data(), verbCount); m_pathEffectOut = outPath; - uint32_t args[7] = {m_L, + // _v2 also gets a node over the shape, the Luau lane's NodeData; modules + // built before it get the paint alone. + bool withNode = + wasm_runtime_lookup_function(m_state->instance, + "host_obj_path_effect_v2") != nullptr; + size_t scopeStart = m_scopedNodes.size(); + uint32_t nodeHandle = 0; + TransformComponent* shape = shapePaint->parentTransformComponent(); + if (withNode && shape != nullptr) + { + nodeHandle = mintNode(this, nullptr, shape); + } + uint32_t args[8] = {m_L, (uint32_t)selfRef, dataPtr + (uint32_t)sizeof(paint) + pointBytes, verbCount, dataPtr + (uint32_t)sizeof(paint), floatCount, - dataPtr}; - uint32_t ok = callModule("host_obj_path_effect", 7, args); + dataPtr, + nodeHandle}; + uint32_t ok = withNode ? callModule("host_obj_path_effect_v2", 8, args) + : callModule("host_obj_path_effect", 7, args); + releaseScopedNodes(scopeStart); m_pathEffectOut = nullptr; guestFree(dataPtr); return ok != 0; } +void WasmScriptingVM::releaseScopedNodes(size_t from) +{ + for (size_t i = from; i < m_scopedNodes.size(); i++) + { + uint32_t handle = m_scopedNodes[i]; + if (auto node = static_cast<HostNode*>( + m_handles.resolve(handle, HandleTable::Tag::node))) + { + m_handles.release(handle, HandleTable::Tag::node); + delete node; + } + } + m_scopedNodes.resize(from); +} + bool WasmScriptingVM::callDataConvert(ScriptedObject* object, int selfRef, const char* method, @@ -9354,16 +9561,21 @@ float timeStamp, HitResult* outResult) { - // Not forwarded yet: host_obj_pointer_event's guest ABI has no slots. - (void)hitType; - (void)timeStamp; if (!valid()) { return false; } + // _v2 carries the listener type and time stamp; modules built before it + // export only the six-argument name, which must be called as such. wasm_function_inst_t f = wasm_runtime_lookup_function(m_state->instance, - "host_obj_pointer_event"); + "host_obj_pointer_event_v2"); + bool legacy = f == nullptr; + if (legacy) + { + f = wasm_runtime_lookup_function(m_state->instance, + "host_obj_pointer_event"); + } if (f == nullptr) { return false; @@ -9374,7 +9586,7 @@ { return false; } - wasm_val_t args[6]; + wasm_val_t args[8]; args[0].kind = WASM_I32; args[0].of.i32 = (int32_t)m_L; args[1].kind = WASM_I32; @@ -9387,10 +9599,19 @@ args[4].of.f64 = localPosition.x; args[5].kind = WASM_F64; args[5].of.f64 = localPosition.y; + args[6].kind = WASM_I32; + args[6].of.i32 = (int32_t)hitType; + args[7].kind = WASM_F64; + args[7].of.f64 = timeStamp; wasm_val_t results[1]; results[0].kind = WASM_I32; ScriptCallScope callScope(this); - if (!wasm_runtime_call_wasm_a(m_state->execEnv, f, 1, results, 6, args)) + if (!wasm_runtime_call_wasm_a(m_state->execEnv, + f, + 1, + results, + legacy ? 6 : 8, + args)) { reportTrap("host_obj_pointer_event"); return false; @@ -9451,76 +9672,6 @@ return result != 0; } -// Flattens the gamepad alternatives into the wire layout the module rebuilds -// snapshots from; false for non-gamepad kinds. Shared by callGamepadEvent and -// callListenerPerform. -static bool packGamepadWire(const ListenerInvocation& invocation, - GamepadWire& wire, - const GamepadSnapshot*& snapshot) -{ - if (const GamepadConnectedInvocation* c = invocation.asGamepadConnected()) - { - wire.kind = GamepadWire::kindConnected; - snapshot = &c->snapshot; - } - else if (const GamepadEventInvocation* e = invocation.asGamepadEvent()) - { - wire.kind = GamepadWire::kindEvent; - snapshot = &e->fullState; - wire.changeKind = (uint32_t)e->change.kind; - wire.changeIndex = e->change.index; - wire.changeValue = e->change.value; - wire.hasStandardButtonIntent = e->hasStandardButtonIntent ? 1 : 0; - wire.standardButton = (uint32_t)e->standardButton; - wire.hasStandardAxisIntent = e->hasStandardAxisIntent ? 1 : 0; - wire.standardAxis = (uint32_t)e->standardAxis; - } - else if (const GamepadDisconnectedInvocation* d = - invocation.asGamepadDisconnected()) - { - wire.kind = GamepadWire::kindDisconnected; - wire.deviceId = d->deviceId; - } - else - { - return false; - } - if (snapshot != nullptr) - { - wire.deviceId = snapshot->deviceId; - wire.mapping = (uint32_t)snapshot->mapping; - wire.buttonMaskLo = (uint32_t)snapshot->buttonMask; - wire.buttonMaskHi = (uint32_t)(snapshot->buttonMask >> 32); - wire.buttonCount = (uint32_t)snapshot->buttonValues.size(); - wire.axisCount = (uint32_t)snapshot->axes.size(); - } - return true; -} - -static void writeGamepadPayload(uint8_t* out, - const GamepadWire& wire, - const GamepadSnapshot* snapshot) -{ - memcpy(out, &wire, sizeof(wire)); - if (snapshot == nullptr) - { - return; - } - float* values = (float*)(out + sizeof(wire)); - if (wire.buttonCount != 0) - { - memcpy(values, - snapshot->buttonValues.data(), - wire.buttonCount * sizeof(float)); - } - if (wire.axisCount != 0) - { - memcpy(values + wire.buttonCount, - snapshot->axes.data(), - wire.axisCount * sizeof(float)); - } -} - bool WasmScriptingVM::callGamepadEvent(ScriptedObject* object, int selfRef, const char* method, @@ -9542,9 +9693,7 @@ { return false; } - uint32_t byteCount = - (uint32_t)(sizeof(wire) + - (wire.buttonCount + wire.axisCount) * sizeof(float)); + uint32_t byteCount = gamepadPayloadSize(wire); uint32_t sizeArgs[1] = {byteCount}; uint32_t dataPtr = callModule("malloc", 1, sizeArgs); if (dataPtr == 0) @@ -9616,13 +9765,9 @@ // append their own payload. hasGamepad = packGamepadWire(invocation, gamepad, snapshot); } - uint32_t tailBytes = - text != nullptr - ? wire.textLength - : (hasGamepad ? (uint32_t)(sizeof(gamepad) + (gamepad.buttonCount + - gamepad.axisCount) * - sizeof(float)) - : 0); + uint32_t tailBytes = text != nullptr + ? wire.textLength + : (hasGamepad ? gamepadPayloadSize(gamepad) : 0); uint32_t byteCount = (uint32_t)sizeof(wire) + tailBytes; uint32_t sizeArgs[1] = {byteCount}; uint32_t dataPtr = callModule("malloc", 1, sizeArgs); @@ -9645,26 +9790,82 @@ guestFree(dataPtr); } -// The transition protocol has no module-lane wire type for TransitionChild -// yet, so these hold the host-side defaults: the container composites nothing. -// Because callTransitionDraw below is a no-op, we must NOT claim to manage the -// incoming child -- otherwise it would be hidden from the normal draw loop for -// the duration of every transition and nothing at all would render. -bool WasmScriptingVM::transitionManagesTo(int selfRef) { return false; } +// A module predating Transition cannot draw the incoming child, so the +// runtime keeps drawing it. +bool WasmScriptingVM::transitionManagesTo(int selfRef) +{ + if (!valid() || wasm_runtime_lookup_function( + m_state->instance, + "host_obj_transition_manages_to") == nullptr) + { + return false; + } + uint32_t args[2] = {m_L, (uint32_t)selfRef}; + return callModule("host_obj_transition_manages_to", 2, args) != 0; +} + +uint32_t WasmScriptingVM::mintTransitionChild(const TransitionChildRef& ref) +{ + return ref.artboard == nullptr + ? 0 + : m_handles.mint(HandleTable::Tag::transitionChild, + const_cast<TransitionChildRef*>(&ref)); +} + +void WasmScriptingVM::releaseTransitionChild(uint32_t handle) +{ + if (handle != 0) + { + m_handles.release(handle, HandleTable::Tag::transitionChild); + } +} void WasmScriptingVM::callTransitionChanged(ScriptedObject* object, int selfRef, const TransitionChildRef& from, const TransitionChildRef& to, int direction) -{} +{ + if (!valid()) + { + return; + } + uint32_t fromHandle = mintTransitionChild(from); + uint32_t toHandle = mintTransitionChild(to); + uint32_t args[5] = {m_L, + (uint32_t)selfRef, + fromHandle, + toHandle, + (uint32_t)direction}; + callModule("host_obj_transition_changed", 5, args); + releaseTransitionChild(fromHandle); + releaseTransitionChild(toHandle); +} void WasmScriptingVM::callTransitionDraw(ScriptedObject* object, int selfRef, Renderer* renderer, const TransitionChildRef& from, const TransitionChildRef& to) -{} +{ + if (!valid()) + { + return; + } + uint32_t rendererHandle = + m_handles.mint(HandleTable::Tag::renderer, renderer); + uint32_t fromHandle = mintTransitionChild(from); + uint32_t toHandle = mintTransitionChild(to); + uint32_t args[5] = {m_L, + (uint32_t)selfRef, + rendererHandle, + fromHandle, + toHandle}; + callModule("host_obj_transition_draw", 5, args); + releaseTransitionChild(fromHandle); + releaseTransitionChild(toHandle); + m_handles.release(rendererHandle, HandleTable::Tag::renderer); +} void WasmScriptingVM::callLayoutResize(ScriptedObject* object, int selfRef,
diff --git a/src/wasm/web/rive_module_imports.mjs b/src/wasm/web/rive_module_imports.mjs index 3bd33e9..a45f6ee 100644 --- a/src/wasm/web/rive_module_imports.mjs +++ b/src/wasm/web/rive_module_imports.mjs
@@ -276,6 +276,12 @@ copyOut(out_p, out, outCount * 4); }, pointer_event: calls.rive_artboard_pointer_event, + gamepad_event: (artboard, payload, byteCount) => { + stackReset(); + const payload_p = stageIn(payload, byteCount); + const ret = calls.rive_artboard_gamepad_event(artboard, payload_p, byteCount); + return ret; + }, animation: (artboard, name, length) => { stackReset(); const name_p = stageIn(name, length); @@ -286,6 +292,7 @@ animation_duration: calls.rive_artboard_animation_duration, animation_advance: calls.rive_artboard_animation_advance, animation_set_time: calls.rive_artboard_animation_set_time, + add_to_path: calls.rive_artboard_add_to_path, node: (artboard, name, length) => { stackReset(); const name_p = stageIn(name, length); @@ -634,6 +641,12 @@ const ret = calls.rive_gpu_bind_group_layout_from_shader(shaderModule, groupIndex, dynamicUBOs_p, dynamicUBOCount); return ret; }, + bind_group_layout_from_shaders: (vertexModule, fragmentModule, groupIndex, dynamicUBOs, dynamicUBOCount) => { + stackReset(); + const dynamicUBOs_p = stageIn(dynamicUBOs, dynamicUBOCount * 4); + const ret = calls.rive_gpu_bind_group_layout_from_shaders(vertexModule, fragmentModule, groupIndex, dynamicUBOs_p, dynamicUBOCount); + return ret; + }, bind_group_new: (layout, ubos, uboByteCount, textures, textureByteCount, samplers, samplerByteCount) => { stackReset(); const ubos_p = stageIn(ubos, uboByteCount); @@ -841,6 +854,21 @@ radial: unsupported('rive_shader_radial'), release: calls.rive_shader_release, }, + rive_test_v1: { + blob: (name, nameLength, out, outCount) => { + stackReset(); + const name_p = stageIn(name, nameLength); + const out_p = stageIn(out, outCount); + const ret = calls.rive_test_blob(name_p, nameLength, out_p, outCount); + copyOut(out_p, out, outCount); + return ret; + }, + }, + rive_transition_v1: { + child_draw: calls.rive_transition_child_draw, + child_width: calls.rive_transition_child_width, + child_height: calls.rive_transition_child_height, + }, rive_renderer_v1: { save: calls.rive_renderer_save, restore: calls.rive_renderer_restore,
diff --git a/tests/unit_tests/runtime/color_test.cpp b/tests/unit_tests/runtime/color_test.cpp index 3105ec8..2ba829d 100644 --- a/tests/unit_tests/runtime/color_test.cpp +++ b/tests/unit_tests/runtime/color_test.cpp
@@ -1,3 +1,4 @@ +#include "rive/data_bind/data_values/data_value_color.hpp" #include "rive/shapes/paint/color.hpp" #include <catch.hpp> @@ -41,3 +42,14 @@ CHECK(rive::colorBlue(colorLerped) == 0); CHECK(rive::colorAlpha(colorLerped) == 0); } + +TEST_CASE("data value color channels keep their low byte", "[color]") +{ + rive::DataValueColor color(0x80102030); + color.red(-1); + CHECK((uint32_t)color.value() == 0x80FF2030); + color.blue(300); + CHECK((uint32_t)color.value() == 0x80FF202C); + color.alpha(0x1FF); + CHECK((uint32_t)color.value() == 0xFFFF202C); +}