| #ifdef WITH_RIVE_SCRIPTING | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #endif | |
| #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) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid() || scriptAsset() == nullptr) | |
| { | |
| return; | |
| } | |
| m_vm->setInputArtboard(m_self, name.c_str(), this, artboard); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::setBooleanInput(std::string name, bool value) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->setInputBoolean(m_self, name.c_str(), value); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::setNumberInput(std::string name, float value) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->setInputNumber(m_self, name.c_str(), value); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::setIntegerInput(std::string name, int value) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->setInputUnsigned(m_self, name.c_str(), value); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::setStringInput(std::string name, std::string value) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->setInputString(m_self, name.c_str(), value.c_str()); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::setViewModelInput(std::string name, | |
| ViewModelInstanceValue* value) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->setInputViewModel(m_self, name.c_str(), value); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| void ScriptedObject::trigger(std::string name) | |
| { | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->callTrigger(this, m_self, name.c_str()); | |
| addScriptedDirt(ComponentDirt::ScriptUpdate); | |
| } | |
| bool ScriptedObject::scriptAdvance(float elapsedSeconds) | |
| { | |
| if (!advances() || m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return false; | |
| } | |
| return m_vm->callAdvance(this, m_self, elapsedSeconds); | |
| } | |
| void ScriptedObject::scriptUpdate() | |
| { | |
| if (!updates() || m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| m_vm->callUpdate(this, m_self); | |
| } | |
| bool ScriptedObject::tryUserInit() | |
| { | |
| switch (m_vm->callUserInit(this, m_self, m_context)) | |
| { | |
| case ScriptBackend::InitResult::notImplemented: | |
| case ScriptBackend::InitResult::succeeded: | |
| return true; | |
| case ScriptBackend::InitResult::failed: | |
| break; | |
| } | |
| m_vm->releaseRef(m_self); | |
| disposeScriptedContext(); | |
| m_vm->unregisterScriptedObject(this); | |
| m_vm = nullptr; | |
| m_self = 0; | |
| return false; | |
| } | |
| bool ScriptedObject::ensureScriptInitialized(ScriptBackend* vm, | |
| int generatorRef) | |
| { | |
| if (vm == nullptr || !vm->valid()) | |
| { | |
| return false; | |
| } | |
| if (m_self != 0 && m_vm == vm) | |
| { | |
| return true; | |
| } | |
| if (m_vm != nullptr) | |
| { | |
| if (m_self != 0) | |
| { | |
| m_vm->releaseRef(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()) | |
| { | |
| return false; | |
| } | |
| } | |
| // m_vm is assigned only after a successful instantiate, so the failure | |
| // path leaves the object fully detached. | |
| m_self = vm->instantiate(generatorRef, this, &m_context, &m_contextPtr); | |
| if (m_self == 0) | |
| { | |
| m_context = 0; | |
| m_contextPtr = nullptr; | |
| return false; | |
| } | |
| m_vm = vm; | |
| vm->registerScriptedObject(this); | |
| return true; | |
| } | |
| bool ScriptedObject::hydrateScriptInputs() | |
| { | |
| if (m_vm == nullptr || !m_vm->valid() || 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 (!tryUserInit()) | |
| { | |
| return false; | |
| } | |
| m_userLuaInitDone = true; | |
| } | |
| didHydrateScriptInputs(); | |
| return true; | |
| } | |
| void ScriptedObject::disposeTrackedProperties() | |
| { | |
| // Tracked properties only exist on the Luau backend; the wasm module keeps | |
| // its property wrappers module side. | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| auto trackedProperties = m_trackedScriptedProperties; | |
| for (auto* property : trackedProperties) | |
| { | |
| if (property != nullptr) | |
| { | |
| property->dispose(); | |
| } | |
| } | |
| #endif | |
| } | |
| void ScriptedObject::disposeScriptedContext() | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| if (m_contextPtr != nullptr) | |
| { | |
| m_contextPtr->clearScriptedObject(); | |
| m_contextPtr = nullptr; | |
| } | |
| #endif | |
| if (m_context != 0) | |
| { | |
| if (m_vm != nullptr && m_vm->valid()) | |
| { | |
| m_vm->releaseRef(m_context); | |
| } | |
| m_context = 0; | |
| } | |
| } | |
| void ScriptedObject::scriptDispose() | |
| { | |
| disposeScriptInputs(); | |
| disposeTrackedProperties(); | |
| m_trackedScriptedProperties.clear(); | |
| if (m_vm != nullptr && m_vm->valid()) | |
| { | |
| m_vm->releaseRef(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::scriptUpdate() {} | |
| // Inputs hold a back-pointer to their scripted object; without disposal | |
| // they dangle at teardown even when scripting is compiled out. | |
| void ScriptedObject::scriptDispose() { disposeScriptInputs(); } | |
| #endif | |
| // Shared by both scripting flavors. | |
| void ScriptedObject::disposeScriptInputs() | |
| { | |
| for (auto prop : m_customProperties) | |
| { | |
| auto scriptInput = ScriptInput::from(prop); | |
| if (scriptInput != nullptr) | |
| { | |
| scriptInput->scriptedObject(nullptr); | |
| } | |
| } | |
| m_customProperties.clear(); | |
| } | |
| void ScriptedObject::reinit() | |
| { | |
| if (scriptAsset() != nullptr) | |
| { | |
| scriptAsset()->initScriptedObject(this); | |
| #ifdef WITH_RIVE_SCRIPTING | |
| hydrateScriptInputs(); | |
| didReinit(); | |
| #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); | |
| } | |
| } | |
| } | |
| } | |
| } |