blob: 2eff6334b9e408c2919529e88cefd32718763e6f [file]
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#endif
#include "rive/assets/script_asset.hpp"
#include "rive/artboard.hpp"
#include "rive/file.hpp"
#include "rive/script_input_artboard.hpp"
#include "rive/animation/scripted_listener_action.hpp"
#include "rive/animation/scripted_transition_condition.hpp"
#include "rive/scripted/scripted_data_converter.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/scripted/scripted_interpolator.hpp"
#include "rive/scripted/scripted_layout.hpp"
#include "rive/scripted/scripted_path_effect.hpp"
#include "rive/scripted/scripted_object.hpp"
#include "rive/data_bind/data_bind.hpp"
using namespace rive;
ScriptedObject* ScriptedObject::from(Core* object)
{
switch (object->coreType())
{
case ScriptedDataConverter::typeKey:
return object->as<ScriptedDataConverter>();
case ScriptedDrawable::typeKey:
return object->as<ScriptedDrawable>();
case ScriptedLayout::typeKey:
return object->as<ScriptedLayout>();
case ScriptedPathEffect::typeKey:
return object->as<ScriptedPathEffect>();
case ScriptedListenerAction::typeKey:
return object->as<ScriptedListenerAction>();
case ScriptedTransitionCondition::typeKey:
return object->as<ScriptedTransitionCondition>();
case ScriptedInterpolator::typeKey:
return object->as<ScriptedInterpolator>();
}
return nullptr;
}
#ifdef WITH_RIVE_SCRIPTING
void ScriptedObject::setArtboardInput(std::string name, Artboard* artboard)
{
lua_State* L = state();
if (L == nullptr || scriptAsset() == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
auto artboardInstance = artboard->instance();
artboardInstance->frameOrigin(false);
lua_newrive<ScriptedArtboard>(L,
L,
scriptAsset()->file(),
std::move(artboardInstance),
nullptr,
dataContext());
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::setBooleanInput(std::string name, bool value)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
lua_pushboolean(L, value);
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::setNumberInput(std::string name, float value)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
lua_pushnumber(L, value);
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::setIntegerInput(std::string name, int value)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
lua_pushunsigned(L, value);
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::setStringInput(std::string name, std::string value)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
lua_pushstring(L, value.c_str());
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::setViewModelInput(std::string name,
ViewModelInstanceValue* value)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
switch (value->coreType())
{
case ViewModelInstanceViewModelBase::typeKey:
{
auto viewModel = value->as<ViewModelInstanceViewModel>();
auto vmi = viewModel->referenceViewModelInstance();
if (vmi == nullptr)
{
fprintf(stderr,
"riveLuaPushViewModelInstanceValue - passed in a "
"ViewModelInstanceViewModel with no associated "
"ViewModelInstance.\n");
return;
}
lua_newrive<ScriptedViewModel>(L,
L,
ref_rcp(vmi->viewModel()),
vmi);
break;
}
default:
break;
}
lua_setfield(L, -2, name.c_str());
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
void ScriptedObject::trigger(std::string name)
{
lua_State* L = state();
if (L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
if (static_cast<lua_Type>(lua_getfield(L, -1, name.c_str())) !=
LUA_TFUNCTION)
{
rive_lua_pop(L, 2);
return;
}
lua_pushvalue(L, -2);
rive_lua_pcall_with_context(L, this, 1, 0);
rive_lua_pop(L, 1);
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
bool ScriptedObject::scriptAdvance(float elapsedSeconds)
{
lua_State* L = state();
if (!advances() || L == nullptr)
{
return false;
}
rive_lua_pushRef(L, m_self);
// 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)) !=
LUA_OK)
{
rive_lua_pop(L, 2);
return false;
}
bool result = lua_toboolean(L, -1);
rive_lua_pop(L, 2);
return result;
}
void ScriptedObject::scriptDrawCanvas()
{
lua_State* L = state();
if (!drawsCanvas() || L == nullptr)
{
return;
}
rive_lua_pushRef(L, m_self);
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)
{
rive_lua_pop(L, 1);
return;
}
rive_lua_pop(L, 1);
}
void ScriptedObject::scriptUpdate()
{
lua_State* L = state();
if (!updates() || L == nullptr)
{
return;
}
// Stack: []
rive_lua_pushRef(L, m_self);
// 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.
m_inUpdatePhase = 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, this, 1, 0)) !=
LUA_OK)
{
rive_lua_pop(L, 1);
}
m_inUpdatePhase = false;
}
bool ScriptedObject::tryLuaUserInit(lua_State* L)
{
rive_lua_pushRef(L, m_self);
// 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 true;
}
// Stack: [self, field]
lua_pushvalue(L, -2);
// Stack: [self, field, self]
// Reuse the ScriptedContext created during ensureScriptInitialized so
// the generator and init() both see the same Context instance.
rive_lua_pushRef(L, m_context);
// Stack: [self, field, self, ScriptedContext]
auto pCallResult = rive_lua_pcall_with_context(L, this, 2, 1);
if (static_cast<lua_Status>(pCallResult) != LUA_OK)
{
lua_unref(L, m_self);
disposeScriptedContext();
// Stack: [self, status]
rive_lua_pop(L, 2);
if (m_vm != nullptr)
{
m_vm->unregisterScriptedObject(this);
}
m_vm = nullptr;
m_self = 0;
return false;
}
if (!lua_toboolean(L, -1) || m_contextPtr->missingRequestedData())
{
lua_unref(L, m_self);
disposeScriptedContext();
rive_lua_pop(L, 2);
if (m_vm != nullptr)
{
m_vm->unregisterScriptedObject(this);
}
m_vm = nullptr;
m_self = 0;
return false;
}
// 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 true;
}
bool ScriptedObject::ensureScriptInitialized(ScriptingVM* vm)
{
lua_State* L = vm ? vm->state() : nullptr;
if (L == nullptr)
{
return false;
}
if (m_self != 0 && m_vm == vm)
{
rive_lua_pop(L, 1);
return true;
}
if (m_vm != nullptr)
{
lua_State* oldState = state();
if (m_self != 0)
{
lua_unref(oldState, m_self);
m_self = 0;
}
disposeTrackedProperties();
disposeScriptedContext();
m_vm->unregisterScriptedObject(this);
}
m_userLuaInitDone = false;
for (auto prop : m_customProperties)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput && !scriptInput->validateForColdScriptInit())
{
rive_lua_pop(L, 1);
return false;
}
}
// 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 in tryLuaUserInit when init(self, context) is called, so a
// script only ever sees one Context per scripted-object lifetime.
// Stack: [generator]
m_contextPtr = lua_newrive<ScriptedContext>(L, this);
// Stack: [generator, context]
m_context = lua_ref(L, -1);
// Stack: [generator, context] (lua_ref does not pop)
// m_vm is not yet assigned here, so disposeScriptedContext() cannot
// resolve a lua_State via state(). Unref/clear directly on failure.
auto disposeContextDirect = [&]() {
if (m_contextPtr != nullptr)
{
m_contextPtr->clearScriptedObject();
m_contextPtr = nullptr;
}
if (m_context != 0)
{
lua_unref(L, m_context);
m_context = 0;
}
};
if (static_cast<lua_Status>(rive_lua_pcall_with_context(L, this, 1, 1)) !=
LUA_OK)
{
disposeContextDirect();
rive_lua_pop(L, 1);
return false;
}
if (static_cast<lua_Type>(lua_type(L, -1)) != LUA_TTABLE)
{
disposeContextDirect();
rive_lua_pop(L, 1);
return false;
}
m_self = lua_ref(L, -1);
m_vm = vm;
if (vm != nullptr)
{
vm->registerScriptedObject(this);
}
rive_lua_pop(L, 1);
return true;
}
bool ScriptedObject::hydrateScriptInputs()
{
lua_State* L = state();
if (L == nullptr || m_self == 0)
{
return false;
}
// First validate that all properties are hydratable.
// This ensures we don't partially hydrate props and early out on the next
// step
for (auto prop : m_customProperties)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr &&
!scriptInput->validateHydrationPrerequisites())
{
return false;
}
}
// Next hydrate all properties
for (auto prop : m_customProperties)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr && !scriptInput->hydrateScriptInput())
{
return false;
}
}
// Finally initialize the script if it hasn't been initialized before.
if (inits() && !m_userLuaInitDone)
{
if (!tryLuaUserInit(L))
{
return false;
}
m_userLuaInitDone = true;
}
didHydrateScriptInputs();
return true;
}
void ScriptedObject::disposeScriptInputs()
{
for (auto prop : m_customProperties)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(nullptr);
}
}
m_customProperties.clear();
}
void ScriptedObject::disposeTrackedProperties()
{
auto trackedProperties = m_trackedScriptedProperties;
for (auto* property : trackedProperties)
{
if (property != nullptr)
{
property->dispose();
}
}
}
void ScriptedObject::disposeScriptedContext()
{
if (m_contextPtr != nullptr)
{
m_contextPtr->clearScriptedObject();
m_contextPtr = nullptr;
}
if (m_context != 0)
{
lua_State* L = state();
if (L != nullptr)
{
lua_unref(L, m_context);
}
m_context = 0;
}
}
void ScriptedObject::scriptDispose()
{
disposeScriptInputs();
disposeTrackedProperties();
m_trackedScriptedProperties.clear();
lua_State* L = state();
if (L != nullptr)
{
lua_unref(L, m_self);
disposeScriptedContext();
}
if (m_vm != nullptr)
{
m_vm->unregisterScriptedObject(this);
m_vm = nullptr;
}
m_self = 0;
m_userLuaInitDone = false;
}
#else
void ScriptedObject::setArtboardInput(std::string name, Artboard* artboard) {}
void ScriptedObject::setBooleanInput(std::string name, bool value) {}
void ScriptedObject::setIntegerInput(std::string name, int value) {}
void ScriptedObject::setNumberInput(std::string name, float value) {}
void ScriptedObject::setStringInput(std::string name, std::string value) {}
void ScriptedObject::setViewModelInput(std::string name,
ViewModelInstanceValue* value)
{}
void ScriptedObject::trigger(std::string name) {}
bool ScriptedObject::scriptAdvance(float elapsedSeconds) { return false; }
void ScriptedObject::scriptDrawCanvas() {}
void ScriptedObject::scriptUpdate() {}
void ScriptedObject::scriptDispose() {}
void ScriptedObject::disposeScriptInputs() {}
#endif
void ScriptedObject::reinit()
{
if (scriptAsset() != nullptr)
{
scriptAsset()->initScriptedObject(this);
#ifdef WITH_RIVE_SCRIPTING
hydrateScriptInputs();
#endif
}
}
ScriptAsset* ScriptedObject::scriptAsset() const
{
return (ScriptAsset*)m_fileAsset.get();
}
void ScriptedObject::setAsset(rcp<FileAsset> asset)
{
if (asset != nullptr && asset->is<ScriptAsset>())
{
FileAssetReferencer::setAsset(asset);
}
}
void ScriptedObject::markNeedsUpdate() {}
void ScriptedObject::cloneProperties(CustomPropertyContainer* twin,
DataBindContainer* dataBindContainer) const
{
for (auto prop : m_customProperties)
{
auto clonedValue = prop->clone()->as<CustomProperty>();
twin->addProperty(clonedValue);
auto input = ScriptInput::from(prop);
if (input != nullptr)
{
auto dataBind = input->dataBind();
if (dataBind)
{
auto dataBindClone = static_cast<DataBind*>(dataBind->clone());
dataBindClone->file(dataBind->file());
if (dataBind->converter() != nullptr)
{
dataBindClone->converter(
dataBind->converter()->clone()->as<DataConverter>());
}
dataBindClone->target(clonedValue);
dataBindContainer->addDataBind(dataBindClone);
if (auto* clonedInput = ScriptInput::from(clonedValue))
{
clonedInput->dataBind(dataBindClone,
/*ownsDataBind=*/false);
}
}
}
}
}