| #ifdef WITH_RIVE_SCRIPTING |
| // ScriptingVM's implementation of the ScriptBackend seam: the Lua mechanics |
| // that used to live inline in the scripted core objects. |
| #include "rive/lua/rive_lua_libs.hpp" |
| |
| #include "rive/animation/listener_invocation.hpp" |
| #include "rive/artboard.hpp" |
| #include "rive/assets/script_asset.hpp" |
| #include "rive/file.hpp" |
| #include "rive/scripted/scripted_object.hpp" |
| #include "rive/shapes/paint/shape_paint.hpp" |
| |
| using namespace rive; |
| |
| void ScriptingVM::releaseRef(int ref) |
| { |
| if (m_state != nullptr && ref != 0) |
| { |
| lua_unref(m_state, ref); |
| } |
| } |
| |
| int ScriptingVM::resolveGeneratorRef(int ref) const |
| { |
| #ifdef WITH_RIVE_TOOLS |
| if (m_state != nullptr) |
| { |
| auto context = |
| static_cast<ScriptingContext*>(lua_getthreaddata(m_state)); |
| if (context != nullptr && context->hasGeneratorRef(ref)) |
| { |
| return context->getGeneratorRef(ref); |
| } |
| } |
| #endif |
| return ref; |
| } |
| |
| int ScriptingVM::instantiate(int generatorRef, |
| ScriptedObject* object, |
| int* outContextRef, |
| ScriptedContext** outContextPtr) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, generatorRef); |
| |
| // Create the Context userdata before calling the generator so scripts |
| // can request resources at construction time (e.g. |
| // `return { canvas = context:gpuCanvas() }`). The same Context is reused |
| // when init(self, context) is called, so a script only ever sees one |
| // Context per scripted-object lifetime. |
| // Stack: [generator] |
| ScriptedContext* contextPtr = lua_newrive<ScriptedContext>(L, object); |
| // Stack: [generator, context] |
| int contextRef = lua_ref(L, -1); |
| // Stack: [generator, context] (lua_ref does not pop) |
| |
| auto disposeContext = [&]() { |
| contextPtr->clearScriptedObject(); |
| lua_unref(L, contextRef); |
| }; |
| |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 1, 1)) != |
| LUA_OK) |
| { |
| disposeContext(); |
| rive_lua_pop(L, 1); |
| return 0; |
| } |
| if (static_cast<lua_Type>(lua_type(L, -1)) != LUA_TTABLE) |
| { |
| disposeContext(); |
| rive_lua_pop(L, 1); |
| return 0; |
| } |
| int selfRef = lua_ref(L, -1); |
| rive_lua_pop(L, 1); |
| *outContextRef = contextRef; |
| *outContextPtr = contextPtr; |
| return selfRef; |
| } |
| |
| ScriptBackend::InitResult ScriptingVM::callUserInit(ScriptedObject* object, |
| int selfRef, |
| int contextRef) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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 InitResult::notImplemented; |
| } |
| // Stack: [self, field] |
| lua_pushvalue(L, -2); |
| // Stack: [self, field, self] |
| rive_lua_pushRef(L, contextRef); |
| // Stack: [self, field, self, ScriptedContext] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 1)) != |
| LUA_OK) |
| { |
| // Stack: [self, status] |
| rive_lua_pop(L, 2); |
| return InitResult::failed; |
| } |
| if (!lua_toboolean(L, -1) || object->m_contextPtr->missingRequestedData()) |
| { |
| rive_lua_pop(L, 2); |
| return InitResult::failed; |
| } |
| // Stack: [self, result] |
| rive_lua_pop(L, 1); |
| assert(static_cast<lua_Type>(lua_type(L, -1)) == LUA_TTABLE); |
| // Stack: [self] |
| rive_lua_pop(L, 1); |
| return InitResult::succeeded; |
| } |
| |
| bool ScriptingVM::callAdvance(ScriptedObject* object, |
| int selfRef, |
| float elapsedSeconds) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| // 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, object, 2, 1)) != |
| LUA_OK) |
| { |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| bool result = lua_toboolean(L, -1); |
| rive_lua_pop(L, 2); |
| return result; |
| } |
| |
| void ScriptingVM::callUpdate(ScriptedObject* object, int selfRef) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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. |
| object->setInUpdatePhase(true); |
| // Stack: [self, field] Swap self and field |
| lua_insert(L, -2); |
| // Stack: [field, self] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 1, 0)) != |
| LUA_OK) |
| { |
| rive_lua_pop(L, 1); |
| } |
| object->setInUpdatePhase(false); |
| } |
| |
| void ScriptingVM::callTrigger(ScriptedObject* object, |
| int selfRef, |
| const char* name) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| if (static_cast<lua_Type>(lua_getfield(L, -1, name)) != LUA_TFUNCTION) |
| { |
| rive_lua_pop(L, 2); |
| return; |
| } |
| lua_pushvalue(L, -2); |
| rive_lua_pcall_with_context(L, object, 1, 0); |
| rive_lua_pop(L, 1); |
| } |
| |
| bool ScriptingVM::callNumberMethod(ScriptedObject* object, |
| int selfRef, |
| const char* name, |
| const float* args, |
| size_t argCount, |
| float* outResult) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, name)) != LUA_TFUNCTION) |
| { |
| rive_lua_pop(L, 2); // non-function field + self |
| return false; |
| } |
| // Stack: [self, method] |
| lua_pushvalue(L, -2); |
| // Stack: [self, method, self] |
| for (size_t i = 0; i < argCount; i++) |
| { |
| lua_pushnumber(L, args[i]); |
| } |
| if (static_cast<lua_Status>( |
| rive_lua_pcall_with_context(L, object, int(argCount) + 1, 1)) != |
| LUA_OK) |
| { |
| // Stack: [self, errorMessage] |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| // Stack: [self, result] |
| *outResult = static_cast<float>(lua_tonumber(L, -1)); |
| rive_lua_pop(L, 2); |
| return true; |
| } |
| |
| bool ScriptingVM::callBooleanMethod(ScriptedObject* object, |
| int selfRef, |
| const char* name) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| lua_getfield(L, -1, name); |
| // Stack: [self, field] |
| lua_insert(L, -2); // Swap self and field |
| // Stack: [field, self] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 1, 1)) != |
| LUA_OK) |
| { |
| // Stack: [status] |
| rive_lua_pop(L, 1); |
| return false; |
| } |
| bool result = lua_isboolean(L, -1) && lua_toboolean(L, -1); |
| // Stack: [result] |
| rive_lua_pop(L, 1); |
| return result; |
| } |
| |
| bool ScriptingVM::callPathEffectUpdate(ScriptedObject* object, |
| int selfRef, |
| const RawPath& sourcePath, |
| const ShapePaint* shapePaint, |
| RawPath* outPath) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| lua_getfield(L, -1, "update"); |
| // Stack: [self, "update"] |
| lua_pushvalue(L, -2); |
| // Stack: [self, "update", self] |
| lua_newrive<ScriptedPathData>(L, &sourcePath); |
| // Stack: [self, "update", self, pathData] |
| lua_newrive<ScriptedNode>(L, |
| nullptr, |
| shapePaint->parentTransformComponent()); |
| auto scriptedNode = lua_torive<ScriptedNode>(L, -1); |
| scriptedNode->shapePaint(shapePaint); |
| // Stack: [self, "update", self, pathData, node] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 3, 1)) != |
| LUA_OK) |
| { |
| // Stack: [self, status] |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| // Stack: [self, outputPathData] |
| // update() is script-authored, so it can return anything; a non-PathData |
| // result means there's no geometry to contribute. |
| auto scriptedPath = lua_topathdata(L, -1); |
| if (scriptedPath == nullptr) |
| { |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| outPath->addPath(scriptedPath->rawPath); |
| rive_lua_pop(L, 2); |
| return true; |
| } |
| |
| bool ScriptingVM::callDataConvert(ScriptedObject* object, |
| int selfRef, |
| const char* method, |
| DataValue* input, |
| ScriptDataResult* outResult) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, method)) != LUA_TFUNCTION) |
| { |
| // Assumed for legacy files but not implemented; the caller passes the |
| // value through unchanged (same as !dataConverts()). |
| rive_lua_pop(L, 2); // non-function field + self |
| return false; |
| } |
| // Stack: [self, field] |
| lua_pushvalue(L, -2); |
| // Stack: [self, field, self] |
| if (input->is<DataValueNumber>()) |
| { |
| lua_newrive<ScriptedDataValueNumber>( |
| L, |
| L, |
| input->as<DataValueNumber>()->value()); |
| } |
| else if (input->is<DataValueString>()) |
| { |
| lua_newrive<ScriptedDataValueString>( |
| L, |
| L, |
| input->as<DataValueString>()->value()); |
| } |
| else if (input->is<DataValueBoolean>()) |
| { |
| lua_newrive<ScriptedDataValueBoolean>( |
| L, |
| L, |
| input->as<DataValueBoolean>()->value()); |
| } |
| else if (input->is<DataValueColor>()) |
| { |
| lua_newrive<ScriptedDataValueColor>( |
| L, |
| L, |
| input->as<DataValueColor>()->value()); |
| } |
| else |
| { |
| // Stack: [self, field, self] |
| rive_lua_pop(L, 3); |
| return true; |
| } |
| // Stack: [self, field, self, ScriptedData] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 1)) == |
| LUA_OK) |
| { |
| // The script decides what convert() hands back. Anything that isn't |
| // a DataValue (nil from a missing return, a raw Lua number, a table) |
| // leaves the result kind as none, so the caller sees an empty value |
| // instead of dereferencing null. |
| auto result = lua_todatavalue(L, -1); |
| if (result == nullptr) |
| { |
| // Stack: [self, result] |
| rive_lua_pop(L, 2); |
| return true; |
| } |
| if (result->isNumber()) |
| { |
| outResult->kind = ScriptDataResult::Kind::number; |
| outResult->number = |
| result->dataValue()->as<DataValueNumber>()->value(); |
| } |
| else if (result->isString()) |
| { |
| outResult->kind = ScriptDataResult::Kind::string; |
| outResult->string = |
| result->dataValue()->as<DataValueString>()->value(); |
| } |
| else if (result->isBoolean()) |
| { |
| outResult->kind = ScriptDataResult::Kind::boolean; |
| outResult->boolean = |
| result->dataValue()->as<DataValueBoolean>()->value(); |
| } |
| else if (result->isColor()) |
| { |
| outResult->kind = ScriptDataResult::Kind::color; |
| outResult->color = |
| result->dataValue()->as<DataValueColor>()->value(); |
| } |
| } |
| // Stack: [self, status] or [self, result] |
| rive_lua_pop(L, 2); |
| return true; |
| } |
| |
| void ScriptingVM::callListenerPerform(ScriptedObject* object, |
| int selfRef, |
| const ListenerInvocation& invocation) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| // 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, performAction] |
| lua_pushvalue(L, -2); |
| rive_lua_push_scripted_invocation(L, invocation); |
| // Stack: [self, performAction, self, invocation] |
| if (static_cast<lua_Status>( |
| rive_lua_pcall_with_context(L, object, 2, 0)) != LUA_OK) |
| { |
| // Stack: [self, status] |
| lua_pop(L, 1); |
| } |
| // Stack: [self] |
| } |
| else |
| { |
| lua_pop(L, 1); // non-function performAction field -> [self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, "perform")) == |
| LUA_TFUNCTION) |
| { |
| // 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, object, 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] |
| lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::callDraw(ScriptedObject* object, |
| int selfRef, |
| Renderer* renderer) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| auto scriptedRenderer = lua_newrive<ScriptedRenderer>(L, renderer); |
| // Stack: [scriptedRenderer] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [scriptedRenderer, self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, "draw")) == LUA_TFUNCTION) |
| { |
| // 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, object, 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. |
| rive_lua_pop(L, 1); // non-function field |
| } |
| scriptedRenderer->end(); |
| // Stack: [scriptedRenderer, self] |
| rive_lua_pop(L, 2); |
| } |
| |
| bool ScriptingVM::callGamepadEvent(ScriptedObject* object, |
| int selfRef, |
| const char* method, |
| const ListenerInvocation& invocation) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| lua_getfield(L, -1, method); |
| if (static_cast<lua_Type>(lua_type(L, -1)) != LUA_TFUNCTION) |
| { |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| lua_pushvalue(L, -2); |
| if (const GamepadConnectedInvocation* c = invocation.asGamepadConnected()) |
| { |
| lua_newrive<ScriptedGamepadConnected>(L, c->snapshot); |
| } |
| else if (const GamepadEventInvocation* e = invocation.asGamepadEvent()) |
| { |
| lua_newrive<ScriptedGamepadEvent>(L, *e); |
| } |
| else if (const GamepadDisconnectedInvocation* d = |
| invocation.asGamepadDisconnected()) |
| { |
| lua_newrive<ScriptedGamepadDisconnected>(L, d->deviceId); |
| } |
| else |
| { |
| rive_lua_pop(L, 3); |
| return false; |
| } |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 0)) != |
| LUA_OK) |
| { |
| rive_lua_pop(L, 1); |
| } |
| rive_lua_pop(L, 1); |
| return true; |
| } |
| |
| bool ScriptingVM::callPointerEvent(ScriptedObject* object, |
| int selfRef, |
| const char* method, |
| int pointerId, |
| Vec2D localPosition, |
| HitResult* outResult) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| if (static_cast<lua_Type>(lua_getfield(L, -1, method)) != LUA_TFUNCTION) |
| { |
| // The pointer handler is assumed present for legacy files (all-bits |
| // default) but isn't actually implemented: the caller reports "not |
| // hit" so the state machine keeps walking other hit targets. |
| rive_lua_pop(L, 2); |
| return false; |
| } |
| lua_pushvalue(L, -2); |
| auto pointerEvent = |
| lua_newrive<ScriptedPointerEvent>(L, pointerId, localPosition); |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 0)) != |
| LUA_OK) |
| { |
| fprintf(stderr, "%s failed\n", method); |
| rive_lua_pop(L, 1); |
| } |
| *outResult = pointerEvent->m_hitResult; |
| rive_lua_pop(L, 1); |
| return true; |
| } |
| |
| bool ScriptingVM::callKeyboardEvent(ScriptedObject* object, |
| int selfRef, |
| Key key, |
| KeyModifiers modifiers, |
| bool isPressed, |
| bool isRepeat) |
| { |
| lua_State* L = m_state; |
| bool shouldStopPropagation = false; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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] |
| lua_newrive<ScriptedKeyboardInvocation>(L, |
| key, |
| modifiers, |
| isPressed, |
| isRepeat); |
| // Stack: [self, field, self, keyEvent] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 1)) != |
| LUA_OK) |
| { |
| fprintf(stderr, "%s failed\n", "keyboardEvent"); |
| // Stack: [self, status] |
| rive_lua_pop(L, 1); |
| } |
| else |
| { |
| if (lua_isboolean(L, -1)) |
| { |
| shouldStopPropagation = lua_toboolean(L, -1); |
| } |
| // Stack: [self, result] |
| rive_lua_pop(L, 1); |
| } |
| // Stack: [self] |
| rive_lua_pop(L, 1); |
| return shouldStopPropagation; |
| } |
| |
| bool ScriptingVM::callTextEvent(ScriptedObject* object, |
| int selfRef, |
| const std::string& text) |
| { |
| lua_State* L = m_state; |
| bool shouldStopPropagation = false; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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] |
| lua_newrive<ScriptedTextInputInvocation>(L, text); |
| // Stack: [self, field, self, textInvocation] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 2, 1)) != |
| LUA_OK) |
| { |
| fprintf(stderr, "%s failed\n", "textEvent"); |
| // Stack: [self, status] |
| rive_lua_pop(L, 1); |
| } |
| else |
| { |
| if (lua_isboolean(L, -1)) |
| { |
| shouldStopPropagation = lua_toboolean(L, -1); |
| } |
| // Stack: [self, result] |
| rive_lua_pop(L, 1); |
| } |
| // Stack: [self] |
| rive_lua_pop(L, 1); |
| return shouldStopPropagation; |
| } |
| |
| bool ScriptingVM::transitionManagesTo(int selfRef) |
| { |
| lua_State* L = m_state; |
| if (L == nullptr || selfRef == 0) |
| { |
| return true; |
| } |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| lua_Type fieldType = |
| static_cast<lua_Type>(lua_getfield(L, -1, "managesTo")); |
| // Stack: [self, field] |
| // Opt out only on an explicit boolean false; absent/nil/any other value |
| // keeps the default (script composites both children). |
| bool managesTo = !(fieldType == LUA_TBOOLEAN && !lua_toboolean(L, -1)); |
| rive_lua_pop(L, 2); // field + self |
| return managesTo; |
| } |
| |
| // Pushes a TransitionChild userdata for `child`, or nil when it carries no |
| // artboard. Returns the handle so the caller can invalidate it once the call |
| // returns, keeping the wrapper from outliving the frame's transforms. |
| static TransitionChild* pushTransitionChild( |
| lua_State* L, |
| const ScriptBackend::TransitionChildRef& child) |
| { |
| if (child.artboard == nullptr) |
| { |
| lua_pushnil(L); |
| return nullptr; |
| } |
| return lua_newrive<TransitionChild>(L, child.artboard, child.transform); |
| } |
| |
| void ScriptingVM::callTransitionChanged(ScriptedObject* object, |
| int selfRef, |
| const TransitionChildRef& from, |
| const TransitionChildRef& to, |
| int direction) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, "changed")) != LUA_TFUNCTION) |
| { |
| // changed is optional; nothing to notify. |
| rive_lua_pop(L, 2); // non-function field + self |
| return; |
| } |
| // Stack: [self, changed] |
| lua_pushvalue(L, -2); // self (arg 1) |
| // Stack: [self, changed, self] |
| TransitionChild* fromHandle = pushTransitionChild(L, from); |
| TransitionChild* toHandle = pushTransitionChild(L, to); |
| lua_pushnumber(L, direction); |
| // Stack: [self, changed, self, from, to, direction] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 4, 0)) != |
| LUA_OK) |
| { |
| // Stack: [self, status] |
| rive_lua_pop(L, 1); |
| } |
| if (fromHandle != nullptr) |
| { |
| fromHandle->invalidate(); |
| } |
| if (toHandle != nullptr) |
| { |
| toHandle->invalidate(); |
| } |
| // Stack: [self] |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::callTransitionDraw(ScriptedObject* object, |
| int selfRef, |
| Renderer* renderer, |
| const TransitionChildRef& from, |
| const TransitionChildRef& to) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| auto scriptedRenderer = lua_newrive<ScriptedRenderer>(L, renderer); |
| // Stack: [scriptedRenderer] |
| TransitionChild* fromHandle = nullptr; |
| TransitionChild* toHandle = nullptr; |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [scriptedRenderer, self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, "draw")) == LUA_TFUNCTION) |
| { |
| // Stack: [scriptedRenderer, self, "draw"] |
| lua_pushvalue(L, -2); // self (arg 1) |
| // Stack: [scriptedRenderer, self, "draw", self] |
| lua_pushvalue(L, -4); // scriptedRenderer (arg 2) |
| // Stack: [scriptedRenderer, self, "draw", self, scriptedRenderer] |
| fromHandle = pushTransitionChild(L, from); |
| toHandle = pushTransitionChild(L, to); |
| // Stack: [scriptedRenderer, self, "draw", self, sr, from, to] |
| if (static_cast<lua_Status>( |
| rive_lua_pcall_with_context(L, object, 4, 0)) != LUA_OK) |
| { |
| // Stack: [scriptedRenderer, self, status] |
| rive_lua_pop(L, 1); |
| } |
| } |
| else |
| { |
| // draw is assumed for the transition protocol but not implemented. |
| rive_lua_pop(L, 1); // non-function field |
| } |
| if (fromHandle != nullptr) |
| { |
| fromHandle->invalidate(); |
| } |
| if (toHandle != nullptr) |
| { |
| toHandle->invalidate(); |
| } |
| scriptedRenderer->end(); |
| // Stack: [scriptedRenderer, self] |
| rive_lua_pop(L, 2); |
| } |
| |
| void ScriptingVM::callLayoutResize(ScriptedObject* object, |
| int selfRef, |
| Vec2D size) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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] |
| lua_pushvec2d(L, size); |
| lua_pushnumber(L, displayScale()); |
| // Stack: [self, function, self, size, scale] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 3, 0)) != |
| LUA_OK) |
| { |
| // Stack: [self, status] |
| fprintf(stderr, |
| "resize failed: %s\n", |
| lua_tostring(L, -1) ? lua_tostring(L, -1) : "?"); |
| rive_lua_pop(L, 1); |
| } |
| // Stack: [self] |
| rive_lua_pop(L, 1); |
| } |
| |
| bool ScriptingVM::callLayoutMeasure(ScriptedObject* object, |
| int selfRef, |
| Vec2D* outSize) |
| { |
| lua_State* L = m_state; |
| // Stack: [] |
| rive_lua_pushRef(L, selfRef); |
| // Stack: [self] |
| 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 false; |
| } |
| // Stack: [self, field] |
| lua_pushvalue(L, -2); |
| // Stack: [self, field, self] |
| if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, object, 1, 1)) != |
| LUA_OK) |
| { |
| fprintf(stderr, "measure failed\n"); |
| } |
| else if (static_cast<lua_Type>(lua_type(L, -1)) != LUA_TVECTOR) |
| { |
| fprintf(stderr, "expected measure to return a Vec2D\n"); |
| } |
| else |
| { |
| *outSize = *lua_tovec2d(L, -1); |
| } |
| // Stack: [self, status] or Stack: [self, result] |
| rive_lua_pop(L, 2); |
| return true; |
| } |
| |
| void ScriptingVM::setInputBoolean(int selfRef, const char* name, bool value) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| lua_pushboolean(L, value); |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::setInputNumber(int selfRef, const char* name, float value) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| lua_pushnumber(L, value); |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::setInputUnsigned(int selfRef, |
| const char* name, |
| uint32_t value) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| lua_pushunsigned(L, value); |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::setInputString(int selfRef, |
| const char* name, |
| const char* value) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| lua_pushstring(L, value); |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::setInputArtboard(int selfRef, |
| const char* name, |
| ScriptedObject* object, |
| Artboard* artboard) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| auto artboardInstance = artboard->instance(); |
| artboardInstance->frameOrigin(false); |
| lua_newrive<ScriptedArtboard>(L, |
| L, |
| object->scriptAsset()->file(), |
| std::move(artboardInstance), |
| nullptr, |
| object->dataContext()); |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| void ScriptingVM::setInputViewModel(int selfRef, |
| const char* name, |
| ViewModelInstanceValue* value) |
| { |
| lua_State* L = m_state; |
| rive_lua_pushRef(L, selfRef); |
| switch (value->coreType()) |
| { |
| case ViewModelInstanceViewModelBase::typeKey: |
| { |
| auto viewModel = value->as<ViewModelInstanceViewModel>(); |
| auto vmi = viewModel->referenceViewModelInstance(); |
| if (vmi == nullptr) |
| { |
| fprintf(stderr, |
| "setInputViewModel - passed in a " |
| "ViewModelInstanceViewModel with no associated " |
| "ViewModelInstance.\n"); |
| rive_lua_pop(L, 1); |
| return; |
| } |
| |
| lua_newrive<ScriptedViewModel>(L, |
| L, |
| ref_rcp(vmi->viewModel()), |
| vmi); |
| break; |
| } |
| default: |
| // Nothing pushed; leave self untouched. |
| rive_lua_pop(L, 1); |
| return; |
| } |
| lua_setfield(L, -2, name); |
| rive_lua_pop(L, 1); |
| } |
| |
| #endif |