feature(scripting): serialize implemented methods (#12670) 8957983a44 feature(scripting): serialize implemented methods, detected by executing the generator in the editor Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 8cad039..c7a2a83 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -867dce9f736ed8ec73f271d9a034a30b65e385a4 +8957983a44b178d3746bf37ebfd27391bf7a1d3d
diff --git a/dev/defs/assets/script_asset.json b/dev/defs/assets/script_asset.json index 75b6e1a..3423237 100644 --- a/dev/defs/assets/script_asset.json +++ b/dev/defs/assets/script_asset.json
@@ -48,6 +48,18 @@ "description": "Topological order from compilation — scripts with lower values are depended on by scripts with higher values. Not exported to runtime; the order is inherent in the position the ScriptAsset is written into the .riv.", "runtime": false, "journal": false + }, + "serializedImplementedMethods": { + "type": "uint", + "initialValue": "2097151", + "key": { + "int": 1022, + "string": "serializedimplementedmethods" + }, + "description": "Bitfield of the optional script methods (init/draw/drawCanvas/advance/etc.) the editor detected by executing the generator. Bit layout matches OptionalScriptedMethods (bits 0-20). The runtime reads these directly instead of detecting at load. Defaults to all bits set ((1<<21)-1 = 2097151) so files exported before this property existed behave as 'implements everything' and rely on graceful dispatch (each callback no-ops when the method isn't actually present).", + "coop": false, + "exportsToRuntimeConditionally": true, + "journal": false } } -} +} \ No newline at end of file
diff --git a/include/rive/assets/script_asset.hpp b/include/rive/assets/script_asset.hpp index cff7db4..cb0a963 100644 --- a/include/rive/assets/script_asset.hpp +++ b/include/rive/assets/script_asset.hpp
@@ -94,12 +94,11 @@ int m_implementedMethods = 0; -protected: -#ifdef WITH_RIVE_SCRIPTING - bool verifyImplementation(ScriptedObject* object, lua_State* state); -#endif - public: + // Bits 0-20 hold the method flags (see ScriptAsset::serializedImplemented + // Methods, which the editor serializes and the runtime reads directly). + static const uint32_t methodMask = (1u << 21) - 1; + int implementedMethods() { return m_implementedMethods; } void implementedMethods(int implemented) {
diff --git a/include/rive/generated/assets/script_asset_base.hpp b/include/rive/generated/assets/script_asset_base.hpp index 31be61f..bfc334b 100644 --- a/include/rive/generated/assets/script_asset_base.hpp +++ b/include/rive/generated/assets/script_asset_base.hpp
@@ -33,10 +33,12 @@ static const uint16_t generatorFunctionRefPropertyKey = 893; static const uint16_t isModulePropertyKey = 914; + static const uint16_t serializedImplementedMethodsPropertyKey = 1022; protected: uint32_t m_GeneratorFunctionRef = 0; bool m_IsModule = false; + uint32_t m_SerializedImplementedMethods = 2097151; public: inline uint32_t generatorFunctionRef() const @@ -64,11 +66,26 @@ isModuleChanged(); } + inline uint32_t serializedImplementedMethods() const + { + return m_SerializedImplementedMethods; + } + void serializedImplementedMethods(uint32_t value) + { + if (m_SerializedImplementedMethods == value) + { + return; + } + m_SerializedImplementedMethods = value; + serializedImplementedMethodsChanged(); + } + Core* clone() const override; void copy(const ScriptAssetBase& object) { m_GeneratorFunctionRef = object.m_GeneratorFunctionRef; m_IsModule = object.m_IsModule; + m_SerializedImplementedMethods = object.m_SerializedImplementedMethods; TextAsset::copy(object); } @@ -82,6 +99,10 @@ case isModulePropertyKey: m_IsModule = CoreBoolType::deserialize(reader); return true; + case serializedImplementedMethodsPropertyKey: + m_SerializedImplementedMethods = + CoreUintType::deserialize(reader); + return true; } return TextAsset::deserialize(propertyKey, reader); } @@ -89,6 +110,7 @@ protected: virtual void generatorFunctionRefChanged() {} virtual void isModuleChanged() {} + virtual void serializedImplementedMethodsChanged() {} }; } // namespace rive
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index a51ebaf..920eb85 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -387,12 +387,18 @@ return new DataEnumSystem(); case ViewModelPropertyViewModelBase::typeKey: return new ViewModelPropertyViewModel(); - case ViewModelInstanceBase::typeKey: - return new ViewModelInstance(); - case ViewModelPropertyBooleanBase::typeKey: - return new ViewModelPropertyBoolean(); + case DataEnumValueBase::typeKey: + return new DataEnumValue(); + case ViewModelPropertyTriggerBase::typeKey: + return new ViewModelPropertyTrigger(); + case ViewModelPropertyStringBase::typeKey: + return new ViewModelPropertyString(); case ViewModelPropertyColorBase::typeKey: return new ViewModelPropertyColor(); + case ViewModelPropertyBooleanBase::typeKey: + return new ViewModelPropertyBoolean(); + case ViewModelInstanceBase::typeKey: + return new ViewModelInstance(); case ViewModelPropertyAssetImageBase::typeKey: return new ViewModelPropertyAssetImage(); case ViewModelInstanceBooleanBase::typeKey: @@ -405,18 +411,12 @@ return new ViewModelInstanceTrigger(); case ViewModelInstanceSymbolListIndexBase::typeKey: return new ViewModelInstanceSymbolListIndex(); - case ViewModelPropertyStringBase::typeKey: - return new ViewModelPropertyString(); case ViewModelInstanceViewModelBase::typeKey: return new ViewModelInstanceViewModel(); - case ViewModelPropertyTriggerBase::typeKey: - return new ViewModelPropertyTrigger(); case ViewModelInstanceAssetBase::typeKey: return new ViewModelInstanceAsset(); case ViewModelInstanceAssetImageBase::typeKey: return new ViewModelInstanceAssetImage(); - case DataEnumValueBase::typeKey: - return new DataEnumValue(); case CustomPropertyTriggerBase::typeKey: return new CustomPropertyTrigger(); case ScriptInputTriggerBase::typeKey: @@ -1669,6 +1669,10 @@ case ScriptAssetBase::generatorFunctionRefPropertyKey: object->as<ScriptAssetBase>()->generatorFunctionRef(value); break; + case ScriptAssetBase::serializedImplementedMethodsPropertyKey: + object->as<ScriptAssetBase>()->serializedImplementedMethods( + value); + break; case AudioEventBase::assetIdPropertyKey: object->as<AudioEventBase>()->assetId(value); break; @@ -3570,6 +3574,9 @@ return object->as<FileAssetBase>()->assetId(); case ScriptAssetBase::generatorFunctionRefPropertyKey: return object->as<ScriptAssetBase>()->generatorFunctionRef(); + case ScriptAssetBase::serializedImplementedMethodsPropertyKey: + return object->as<ScriptAssetBase>() + ->serializedImplementedMethods(); case AudioEventBase::assetIdPropertyKey: return object->as<AudioEventBase>()->assetId(); case GamepadInputBase::kindPropertyKey: @@ -4496,6 +4503,7 @@ case CustomPropertyEnumBase::enumIdPropertyKey: case FileAssetBase::assetIdPropertyKey: case ScriptAssetBase::generatorFunctionRefPropertyKey: + case ScriptAssetBase::serializedImplementedMethodsPropertyKey: case AudioEventBase::assetIdPropertyKey: case GamepadInputBase::kindPropertyKey: case GamepadInputBase::mappingPropertyKey: @@ -5319,6 +5327,8 @@ return object->is<FileAssetBase>(); case ScriptAssetBase::generatorFunctionRefPropertyKey: return object->is<ScriptAssetBase>(); + case ScriptAssetBase::serializedImplementedMethodsPropertyKey: + return object->is<ScriptAssetBase>(); case AudioEventBase::assetIdPropertyKey: return object->is<AudioEventBase>(); case GamepadInputBase::kindPropertyKey:
diff --git a/include/rive/lua/rive_lua_libs.hpp b/include/rive/lua/rive_lua_libs.hpp index d134944..bea58b4 100644 --- a/include/rive/lua/rive_lua_libs.hpp +++ b/include/rive/lua/rive_lua_libs.hpp
@@ -325,7 +325,7 @@ gpuCanvas, drawCanvas, features, - loadShader, + shader, format, preferredCanvasFormat, @@ -1470,6 +1470,13 @@ void setCanvasDrawingPhase(bool value) { m_canvasDrawingPhase = value; } bool canvasDrawingPhase() const { return m_canvasDrawingPhase; } + // When set, context:gpuCanvas() always returns a deferred (texture-less) + // canvas regardless of requested size, never calling makeRenderCanvas. + // Used by the editor's headless method-detection VM, which has no GPU + // device / RenderContext. Default false: normal runtimes allocate. + void setGpuCanvasDeferOnly(bool value) { m_gpuCanvasDeferOnly = value; } + bool gpuCanvasDeferOnly() const { return m_gpuCanvasDeferOnly; } + // WebGL/WASM only: GL context handle saved at riveGPUBeginFrame so // riveGPUEndFrame can restore the caller's context afterwards. void setPrevGLContext(intptr_t h) { m_prevGLContext = h; } @@ -1495,6 +1502,7 @@ uint64_t m_ownerId = 0; bool m_oreFrameOpen = false; bool m_canvasDrawingPhase = false; + bool m_gpuCanvasDeferOnly = false; intptr_t m_prevGLContext = 0; #ifdef __EMSCRIPTEN__ int m_glHandle = 0;
diff --git a/src/animation/scripted_listener_action.cpp b/src/animation/scripted_listener_action.cpp index 81e1b1c..a6a1a6c 100644 --- a/src/animation/scripted_listener_action.cpp +++ b/src/animation/scripted_listener_action.cpp
@@ -47,10 +47,13 @@ return; } rive_lua_pushRef(L, m_self); - if (performsAction()) + // Stack: [self] + // Probe the fields directly (rather than gating on performs()/ + // performsAction(), which are assumed-present for legacy files): + // performAction takes precedence over perform, matching prior behavior. + if (static_cast<lua_Type>(lua_getfield(L, -1, "performAction")) == + LUA_TFUNCTION) { - // Stack: [self] - lua_getfield(L, -1, "performAction"); // Stack: [self, performAction] lua_pushvalue(L, -2); rive_lua_push_scripted_invocation(L, invocation); @@ -64,23 +67,33 @@ // Stack: [self, status] lua_pop(L, 1); } + // Stack: [self] } - else if (performs()) + else { - lua_getfield(L, -1, "perform"); - // Stack: [self, perform] - lua_pushvalue(L, -2); - // Stack: [self, perform, self] - rive_lua_push_pointer_arg_for_perform(L, invocation); - // Stack: [self, perform, self, pointerEvent] - if (static_cast<lua_Status>(rive_lua_pcall_with_context( - L, - const_cast<ScriptedListenerAction*>(this), - 2, - 0)) != LUA_OK) + lua_pop(L, 1); // non-function performAction field -> [self] + if (static_cast<lua_Type>(lua_getfield(L, -1, "perform")) == + LUA_TFUNCTION) { - // Stack: [self, status] - lua_pop(L, 1); + // Stack: [self, perform] + lua_pushvalue(L, -2); + // Stack: [self, perform, self] + rive_lua_push_pointer_arg_for_perform(L, invocation); + // Stack: [self, perform, self, pointerEvent] + if (static_cast<lua_Status>(rive_lua_pcall_with_context( + L, + const_cast<ScriptedListenerAction*>(this), + 2, + 0)) != LUA_OK) + { + // Stack: [self, status] + lua_pop(L, 1); + } + // Stack: [self] + } + else + { + lua_pop(L, 1); // non-function perform field -> [self] } } // Stack: [self]
diff --git a/src/assets/script_asset.cpp b/src/assets/script_asset.cpp index 07733de..b1df3dc 100644 --- a/src/assets/script_asset.cpp +++ b/src/assets/script_asset.cpp
@@ -66,214 +66,6 @@ bool ScriptInput::validateHydrationPrerequisites() { return true; } #ifdef WITH_RIVE_SCRIPTING -bool OptionalScriptedMethods::verifyImplementation(ScriptedObject* object, - lua_State* state) -{ - // Log the stack-top type before pcall so we can see whether it's nil - // (meaning generator-ref resolved to nothing) vs a function that then - // errored internally. - int topType = static_cast<int>(lua_type(state, -1)); - - lua_pushvalue(state, -1); - if (static_cast<lua_Status>(rive_lua_pcall(state, 0, 1)) != LUA_OK) - { - const char* err = lua_tostring(state, -1); - fprintf(stderr, - "Verifying implementation pcall failed (protocol=%d, " - "top-type-before=%d): %s\n", - (int)object->scriptProtocol(), - topType, - err ? err : "(no error message)"); - rive_lua_pop(state, 1); - return false; - } - if (static_cast<lua_Type>(lua_type(state, -1)) != LUA_TTABLE) - { - fprintf(stderr, - "Verifying implementation not a table (protocol=%d)?\n", - (int)object->scriptProtocol()); - rive_lua_pop(state, 1); - return false; - } - m_implementedMethods = 0; - - auto scriptProtocol = object->scriptProtocol(); - if (scriptProtocol == ScriptProtocol::node || - scriptProtocol == ScriptProtocol::layout || - scriptProtocol == ScriptProtocol::converter || - scriptProtocol == ScriptProtocol::pathEffect || - scriptProtocol == ScriptProtocol::listenerAction || - scriptProtocol == ScriptProtocol::transitionCondition || - scriptProtocol == ScriptProtocol::interpolator) - { - if (static_cast<lua_Type>(lua_getfield(state, -1, "update")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_updatesBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "advance")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_advancesBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "pointerDown")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsPointerDownBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "pointerUp")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsPointerUpBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "pointerMove")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsPointerMoveBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "pointerCanceled")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsPointerCancelBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "pointerExit")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsPointerExitBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>( - lua_getfield(state, -1, "gamepadConnected")) == LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsGamepadConnect; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "gamepadEvent")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsGamepadEvent; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>( - lua_getfield(state, -1, "gamepadDisconnected")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsGamepadDisconnect; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "init")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_initsBit; - } - rive_lua_pop(state, 1); - } - if (scriptProtocol == ScriptProtocol::layout) - { - if (static_cast<lua_Type>(lua_getfield(state, -1, "measure")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_measuresBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "resize")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_resizesBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "draw")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_drawsBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "drawCanvas")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_drawsCanvasBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "keyboardEvent")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsKeyboardInputBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "textEvent")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsTextInputBit; - } - rive_lua_pop(state, 1); - } - else if (scriptProtocol == ScriptProtocol::node) - { - if (static_cast<lua_Type>(lua_getfield(state, -1, "draw")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_drawsBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "drawCanvas")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_drawsCanvasBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "keyboardEvent")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsKeyboardInputBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "textEvent")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_wantsTextInputBit; - } - rive_lua_pop(state, 1); - } - else if (scriptProtocol == ScriptProtocol::converter) - { - if (static_cast<lua_Type>(lua_getfield(state, -1, "convert")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_dataConvertsBit; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "reverseConvert")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_dataReverseConvertsBit; - } - rive_lua_pop(state, 1); - } - else if (scriptProtocol == ScriptProtocol::listenerAction) - { - if (static_cast<lua_Type>(lua_getfield(state, -1, "perform")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_listenerPerforms; - } - rive_lua_pop(state, 1); - if (static_cast<lua_Type>(lua_getfield(state, -1, "performAction")) == - LUA_TFUNCTION) - { - m_implementedMethods |= m_listenerPerformsAction; - } - rive_lua_pop(state, 1); - } - rive_lua_pop(state, 1); - return true; -} - ScriptingVM* ScriptAsset::scriptingVM() { if (m_file == nullptr) @@ -355,11 +147,14 @@ if (!m_initted) { - if (!verifyImplementation(object, state)) - { - rive_lua_pop(state, 1); - return false; - } + // The editor detected the implemented methods (by executing the + // generator in its workspace) and serialized the bitfield; the runtime + // never detects. Files predating this property decode to the all-bits + // default (2097151), so they behave as "implements everything" and rely + // on graceful dispatch (each callback no-ops when the method isn't + // actually a function on the returned table). + OptionalScriptedMethods::implementedMethods( + serializedImplementedMethods() & methodMask); m_initted = true; } object->implementedMethods(implementedMethods());
diff --git a/src/lua/lua_scripted_context.cpp b/src/lua/lua_scripted_context.cpp index 212c9a7..1cd4d11 100644 --- a/src/lua/lua_scripted_context.cpp +++ b/src/lua/lua_scripted_context.cpp
@@ -453,6 +453,18 @@ #else auto* gpuScriptingCtx = static_cast<ScriptingContext*>(lua_getthreaddata(L)); + if (gpuScriptingCtx->gpuCanvasDeferOnly()) + { + // Headless detection (editor method-detection VM): there is + // no real RenderContext/GPU device. Hand back a deferred + // canvas with no backing texture regardless of requested + // size, so a generator that creates a sized canvas at + // construction runs without reaching makeRenderCanvas. + auto* handle = lua_newrive<ScriptedGPUCanvas>(L); + handle->m_L = L; + handle->renderCtx = nullptr; + return 1; + } auto* gpuRenderCtx = static_cast<gpu::RenderContext*>( gpuScriptingCtx->renderContext()); if (gpuRenderCtx == nullptr) @@ -515,7 +527,7 @@ case (int)LuaAtoms::preferredCanvasFormat: return lua_push_preferred_canvas_format(L); - case (int)LuaAtoms::loadShader: + case (int)LuaAtoms::shader: { #if defined(RIVE_CANVAS) && defined(RIVE_ORE) const char* shaderName = luaL_checkstring(L, 2);
diff --git a/src/lua/renderer/lua_gpu.cpp b/src/lua/renderer/lua_gpu.cpp index 87270d1..c028942 100644 --- a/src/lua/renderer/lua_gpu.cpp +++ b/src/lua/renderer/lua_gpu.cpp
@@ -863,7 +863,7 @@ { auto* context = static_cast<ScriptingContext*>(lua_getthreaddata(L)); - // Resolve the file-side ShaderAsset by name. Without this, Shader.new() + // Resolve the file-side ShaderAsset by name. Without this, the lookup // falls back to the editor-only RSTB cache which is empty at runtime. ShaderAsset* fileAsset = nullptr; if (context != nullptr) @@ -900,20 +900,6 @@ return 1; } -static int shader_construct(lua_State* L) -{ - // Argument 1 is the name of a pre-compiled WGSL shader asset that was - // bundled with the Rive file (set via the "wgslAssetName" field in the - // editor). Raw WGSL source strings are NOT accepted at runtime; shaders - // must be pre-compiled and embedded as assets. - const char* name = luaL_checkstring(L, 1); - if (lua_gpu_push_shader_by_name(L, name) == 0) - { - luaL_error(L, "Shader.new: no shader asset named '%s' found", name); - } - return 1; -} - // ============================================================================ // GPUBuffer // ============================================================================ @@ -3396,8 +3382,8 @@ int luaopen_rive_gpu(lua_State* L) { - static const luaL_Reg shaderStatics[] = {{"new", shader_construct}, - {nullptr, nullptr}}; + // Shader has no constructor; shaders are obtained via context:shader(name). + static const luaL_Reg shaderStatics[] = {{nullptr, nullptr}}; static const luaL_Reg gpuBufferStatics[] = {{"new", gpubuffer_construct}, {nullptr, nullptr}}; static const luaL_Reg gpuTextureStatics[] = {{"new", gputexture_construct},
diff --git a/src/lua/rive_lua_libs.cpp b/src/lua/rive_lua_libs.cpp index 64aa082..678451b 100644 --- a/src/lua/rive_lua_libs.cpp +++ b/src/lua/rive_lua_libs.cpp
@@ -277,7 +277,7 @@ {"gpuCanvas", (int16_t)LuaAtoms::gpuCanvas}, {"features", (int16_t)LuaAtoms::features}, {"drawCanvas", (int16_t)LuaAtoms::drawCanvas}, - {"loadShader", (int16_t)LuaAtoms::loadShader}, + {"shader", (int16_t)LuaAtoms::shader}, {"format", (int16_t)LuaAtoms::format}, {"preferredCanvasFormat", (int16_t)LuaAtoms::preferredCanvasFormat}, {"andThen", (int16_t)LuaAtoms::andThen},
diff --git a/src/scripted/scripted_data_converter.cpp b/src/scripted/scripted_data_converter.cpp index 3cc0e90..381fd35 100644 --- a/src/scripted/scripted_data_converter.cpp +++ b/src/scripted/scripted_data_converter.cpp
@@ -97,8 +97,14 @@ // Stack: [] rive_lua_pushRef(L, m_self); // Stack: [self] - lua_getfield(L, -1, method.c_str()); - + if (static_cast<lua_Type>(lua_getfield(L, -1, method.c_str())) != + LUA_TFUNCTION) + { + // Assumed for legacy files but not implemented; pass the value through + // unchanged (same as !dataConverts()). + rive_lua_pop(L, 2); // non-function field + self + return value; + } // Stack: [self, field] lua_pushvalue(L, -2); // Stack: [self, field, self]
diff --git a/src/scripted/scripted_drawable.cpp b/src/scripted/scripted_drawable.cpp index 01923a4..61d6fdc 100644 --- a/src/scripted/scripted_drawable.cpp +++ b/src/scripted/scripted_drawable.cpp
@@ -42,17 +42,25 @@ // Stack: [scriptedRenderer] rive_lua_pushRef(L, m_self); // Stack: [scriptedRenderer, self] - lua_getfield(L, -1, "draw"); - // Stack: [scriptedRenderer, self, "draw"] - lua_pushvalue(L, -2); - // Stack: [scriptedRenderer, self, "draw", self] - lua_pushvalue(L, -4); - // Stack: [scriptedRenderer, self, "draw", self, scriptedRenderer] - if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, this, 2, 0)) != - LUA_OK) + if (static_cast<lua_Type>(lua_getfield(L, -1, "draw")) == LUA_TFUNCTION) { - // Stack: [scriptedRenderer, self, status] - rive_lua_pop(L, 1); + // Stack: [scriptedRenderer, self, "draw"] + lua_pushvalue(L, -2); + // Stack: [scriptedRenderer, self, "draw", self] + lua_pushvalue(L, -4); + // Stack: [scriptedRenderer, self, "draw", self, scriptedRenderer] + if (static_cast<lua_Status>( + rive_lua_pcall_with_context(L, this, 2, 0)) != LUA_OK) + { + // Stack: [scriptedRenderer, self, status] + rive_lua_pop(L, 1); + } + } + else + { + // draw is assumed for legacy files but not implemented; no-op (the + // save/transform above stay balanced with the restore below). + rive_lua_pop(L, 1); // non-function field } scriptedRenderer->end(); // Stack: [scriptedRenderer, self] @@ -157,7 +165,9 @@ if (static_cast<lua_Type>(lua_getfield(state, -1, mName.c_str())) != LUA_TFUNCTION) { - fprintf(stderr, "expected %s to be a function\n", mName.c_str()); + // The pointer handler is assumed present for legacy files (all-bits + // default) but isn't actually implemented: report "not hit" so the + // state machine keeps walking other hit targets. rive_lua_pop(state, 1); } else @@ -208,7 +218,13 @@ // Stack: [] rive_lua_pushRef(L, self()); // Stack: [self] - lua_getfield(L, -1, "keyboardEvent"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "keyboardEvent")) != + LUA_TFUNCTION) + { + // Assumed for legacy files but not implemented; no-op. + rive_lua_pop(L, 2); // non-function field + self + return shouldStopPropagation; + } // Stack: [self, field] lua_pushvalue(L, -2); // Stack: [self, field, self] @@ -255,7 +271,13 @@ // Stack: [] rive_lua_pushRef(L, self()); // Stack: [self] - lua_getfield(L, -1, "textEvent"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "textEvent")) != + LUA_TFUNCTION) + { + // Assumed for legacy files but not implemented; no-op. + rive_lua_pop(L, 2); // non-function field + self + return shouldStopPropagation; + } // Stack: [self, field] lua_pushvalue(L, -2); // Stack: [self, field, self]
diff --git a/src/scripted/scripted_layout.cpp b/src/scripted/scripted_layout.cpp index 05e8ef0..20bb6b7 100644 --- a/src/scripted/scripted_layout.cpp +++ b/src/scripted/scripted_layout.cpp
@@ -28,7 +28,12 @@ // Stack: [] rive_lua_pushRef(L, m_self); // Stack: [self] - lua_getfield(L, -1, "resize"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "resize")) != LUA_TFUNCTION) + { + // Assumed for legacy files but not implemented; no-op. + rive_lua_pop(L, 2); // non-function field + self + return; + } // Stack: [self, function] lua_pushvalue(L, -2); // Stack: [self, function, self] @@ -60,7 +65,13 @@ // Stack: [] rive_lua_pushRef(L, m_self); // Stack: [self] - lua_getfield(L, -1, "measure"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "measure")) != LUA_TFUNCTION) + { + // Assumed for legacy files but not implemented; report no measurement + // (same as !measures()). + rive_lua_pop(L, 2); // non-function field + self + return Vec2D(0, 0); + } // Stack: [self, field] lua_pushvalue(L, -2); // Stack: [self, field, self]
diff --git a/src/scripted/scripted_object.cpp b/src/scripted/scripted_object.cpp index e2c6362..2eff633 100644 --- a/src/scripted/scripted_object.cpp +++ b/src/scripted/scripted_object.cpp
@@ -183,7 +183,13 @@ return false; } rive_lua_pushRef(L, m_self); - lua_getfield(L, -1, "advance"); + // implementedMethods may be assumed for legacy files (all-bits default); if + // the field isn't actually a function, treat it as not implemented. + if (static_cast<lua_Type>(lua_getfield(L, -1, "advance")) != LUA_TFUNCTION) + { + rive_lua_pop(L, 2); // non-function field + self + return false; + } lua_pushvalue(L, -2); lua_pushnumber(L, elapsedSeconds); if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, this, 2, 1)) != @@ -205,7 +211,12 @@ return; } rive_lua_pushRef(L, m_self); - lua_getfield(L, -1, "drawCanvas"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "drawCanvas")) != + LUA_TFUNCTION) + { + rive_lua_pop(L, 2); // non-function field + self + return; + } lua_pushvalue(L, -2); if (static_cast<lua_Status>(rive_lua_pcall(L, 1, 0)) != LUA_OK) { @@ -222,11 +233,18 @@ { return; } - m_inUpdatePhase = true; // Stack: [] rive_lua_pushRef(L, m_self); // Stack: [self] - lua_getfield(L, -1, "update"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "update")) != LUA_TFUNCTION) + { + // Not actually implemented (assumed for legacy files); no-op. The + // update phase never started, so there's no flag to reset. + rive_lua_pop(L, 2); // non-function field + self + return; + } + // Only inside the update phase while the callback actually runs. + m_inUpdatePhase = true; // Stack: [self, field] Swap self and field lua_insert(L, -2); // Stack: [field, self] @@ -242,7 +260,13 @@ { rive_lua_pushRef(L, m_self); // Stack: [self] - lua_getfield(L, -1, "init"); + if (static_cast<lua_Type>(lua_getfield(L, -1, "init")) != LUA_TFUNCTION) + { + // init is optional and not implemented (assumed for legacy files); + // nothing to run — the object is considered initialized. + rive_lua_pop(L, 2); // non-function field + self + return true; + } // Stack: [self, field] lua_pushvalue(L, -2); // Stack: [self, field, self]
diff --git a/tests/unit_tests/silvers/script_input_color_trigger.sriv b/tests/unit_tests/silvers/script_input_color_trigger.sriv index 1864827..aed4bab 100644 --- a/tests/unit_tests/silvers/script_input_color_trigger.sriv +++ b/tests/unit_tests/silvers/script_input_color_trigger.sriv Binary files differ