blob: 29a00f4217f65a8f3b246f27eaf0df60ddfb87c9 [file] [edit]
#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