Scripting in Core Runtime (#11235) 0a5325e474

rev to luau 0.702 (#11259) b50983c49d

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
Co-authored-by: Philip Chung <philterdesign@gmail.com>
This commit is contained in:
philter
2025-12-11 23:09:23 +00:00
parent 85be3fe2d7
commit 76eae106ad
58 changed files with 1119 additions and 109 deletions

View File

@@ -1 +1 @@
08a3949a0d8d393b113be39d80e278c8eac10d0e
0a5325e474d689a7f18d4ea260513ed86b4bf1cc

View File

@@ -37,8 +37,17 @@
},
"description": "The ref of the generator function (the function returned by the compiled code).",
"coop": false,
"withRiveToolsOnly": true,
"exportsToRuntimeConditionally": true,
"journal": false
},
"isModule": {
"type": "bool",
"initialValue": "false",
"key": {
"int": 914,
"string": "ismodule"
},
"description": "Whether the script is a module (doesn't implement a protocol like Node, Layout, etc."
}
}
}

View File

@@ -12,7 +12,6 @@ namespace rive
class Artboard;
class Component;
class DataBind;
class File;
class ScriptedObject;
enum ScriptProtocol
@@ -35,6 +34,7 @@ protected:
DataBind* m_dataBind = nullptr;
public:
virtual ~ScriptInput() {};
virtual void initScriptedValue();
virtual bool validateForScriptInit() = 0;
static ScriptInput* from(Core* component);
@@ -61,8 +61,7 @@ private:
int m_implementedMethods = 0;
protected:
bool verifyImplementation(ScriptProtocol scriptProtocol,
LuaState* luaState);
bool verifyImplementation(ScriptedObject* object, LuaState* luaState);
public:
int implementedMethods() { return m_implementedMethods; }
@@ -104,7 +103,19 @@ public:
bool inits() { return (m_implementedMethods & m_initsBit) != 0; }
};
class ScriptAsset : public ScriptAssetBase, public OptionalScriptedMethods
class ModuleDetails
{
public:
virtual ~ModuleDetails() = default;
virtual std::string moduleName() = 0;
virtual void registrationComplete(int ref) {}
virtual Span<uint8_t> moduleBytecode() { return Span<uint8_t>(); }
virtual bool isProtocolScript() = 0;
};
class ScriptAsset : public ScriptAssetBase,
public OptionalScriptedMethods,
public ModuleDetails
{
public:
@@ -112,7 +123,7 @@ public:
friend class ScriptAssetImporter;
bool verified() const { return m_verified; }
Span<uint8_t> bytecode() { return m_bytecode; }
Span<uint8_t> moduleBytecode() override { return m_bytecode; }
#endif
bool initScriptedObject(ScriptedObject* object);
@@ -129,23 +140,19 @@ public:
void file(File* value) { m_file = value; }
File* file() const { return m_file; }
#ifdef WITH_RIVE_SCRIPTING
LuaState* vm()
{
if (m_file == nullptr)
{
return nullptr;
}
return m_file->scriptingVM();
}
LuaState* vm();
void registrationComplete(int ref) override;
#endif
std::string moduleName() override { return name(); }
bool isProtocolScript() override { return !isModule(); }
private:
File* m_file = nullptr;
#ifdef WITH_RIVE_SCRIPTING
bool m_scriptRegistered = false;
bool m_verified = false;
SimpleArray<uint8_t> m_bytecode;
bool m_initted = false;
ScriptProtocol m_scriptProtocol = ScriptProtocol::utility;
#endif
bool initScriptedObjectWith(ScriptedObject* object);

View File

@@ -15,6 +15,7 @@ protected:
public:
virtual void addProperty(CustomProperty* prop);
virtual void removeProperty(CustomProperty* prop);
virtual const std::vector<Component*>& containerChildren() const
{
static const std::vector<Component*> emptyVec;

View File

@@ -31,6 +31,10 @@ class Factory;
class ScrollPhysics;
class ViewModelRuntime;
class BindableArtboard;
#ifdef WITH_RIVE_SCRIPTING
class CPPRuntimeScriptingContext;
class ScriptingVM;
#endif
///
/// Tracks the success/failure result when importing a Rive file.
@@ -162,8 +166,27 @@ public:
std::vector<Artboard*> artboards() { return m_artboards; };
void scriptingVM(LuaState* vm) { m_luaState = vm; }
LuaState* scriptingVM() { return m_luaState; }
// When the runtime is hosted in the editor, we get a pointer
// to the VM that we can use. If this is nullptr, we can assume
// we are running in the runtime and should instance our own VMs
// and pass them down to the root
#ifdef WITH_RIVE_SCRIPTING
void scriptingVM(LuaState* vm)
{
cleanupScriptingVM();
m_luaState = vm;
}
LuaState* scriptingVM()
{
// For now, if we don't have a vm, create one. In the future, we
// may need a way to create multiple vms in parallel
if (m_luaState == nullptr)
{
makeScriptingVM();
}
return m_luaState;
}
#endif
#ifdef WITH_RIVE_TOOLS
/// Strips FileAssetContents for FileAssets of given typeKeys.
@@ -221,7 +244,14 @@ private:
/// with the file.
rcp<FileAssetLoader> m_assetLoader;
#ifdef WITH_RIVE_SCRIPTING
LuaState* m_luaState = nullptr;
std::unique_ptr<CPPRuntimeScriptingContext> m_scriptingContext;
std::unique_ptr<ScriptingVM> m_scriptingVM;
void makeScriptingVM();
void cleanupScriptingVM();
void registerScripts();
#endif
rcp<ViewModelInstance> copyViewModelInstance(
ViewModelInstance* viewModelInstance,

View File

@@ -1,6 +1,7 @@
#ifndef _RIVE_SCRIPT_ASSET_BASE_HPP_
#define _RIVE_SCRIPT_ASSET_BASE_HPP_
#include "rive/assets/file_asset.hpp"
#include "rive/core/field_types/core_bool_type.hpp"
#include "rive/core/field_types/core_uint_type.hpp"
namespace rive
{
@@ -29,17 +30,14 @@ public:
uint16_t coreType() const override { return typeKey; }
#ifdef WITH_RIVE_TOOLS
static const uint16_t generatorFunctionRefPropertyKey = 893;
#endif
static const uint16_t isModulePropertyKey = 914;
protected:
#ifdef WITH_RIVE_TOOLS
uint32_t m_GeneratorFunctionRef = 0;
#endif
bool m_IsModule = false;
public:
#ifdef WITH_RIVE_TOOLS
inline uint32_t generatorFunctionRef() const
{
return m_GeneratorFunctionRef;
@@ -53,14 +51,23 @@ public:
m_GeneratorFunctionRef = value;
generatorFunctionRefChanged();
}
#endif
inline bool isModule() const { return m_IsModule; }
void isModule(bool value)
{
if (m_IsModule == value)
{
return;
}
m_IsModule = value;
isModuleChanged();
}
Core* clone() const override;
void copy(const ScriptAssetBase& object)
{
#ifdef WITH_RIVE_TOOLS
m_GeneratorFunctionRef = object.m_GeneratorFunctionRef;
#endif
m_IsModule = object.m_IsModule;
FileAsset::copy(object);
}
@@ -68,19 +75,19 @@ public:
{
switch (propertyKey)
{
#ifdef WITH_RIVE_TOOLS
case generatorFunctionRefPropertyKey:
m_GeneratorFunctionRef = CoreUintType::deserialize(reader);
return true;
#endif
case isModulePropertyKey:
m_IsModule = CoreBoolType::deserialize(reader);
return true;
}
return FileAsset::deserialize(propertyKey, reader);
}
protected:
#ifdef WITH_RIVE_TOOLS
virtual void generatorFunctionRefChanged() {}
#endif
virtual void isModuleChanged() {}
};
} // namespace rive

View File

@@ -1508,11 +1508,9 @@ public:
case FileAssetBase::assetIdPropertyKey:
object->as<FileAssetBase>()->assetId(value);
break;
#ifdef WITH_RIVE_TOOLS
case ScriptAssetBase::generatorFunctionRefPropertyKey:
object->as<ScriptAssetBase>()->generatorFunctionRef(value);
break;
#endif
case AudioEventBase::assetIdPropertyKey:
object->as<AudioEventBase>()->assetId(value);
break;
@@ -1754,6 +1752,9 @@ public:
case TextBase::fitFromBaselinePropertyKey:
object->as<TextBase>()->fitFromBaseline(value);
break;
case ScriptAssetBase::isModulePropertyKey:
object->as<ScriptAssetBase>()->isModule(value);
break;
}
}
static void setDouble(Core* object, int propertyKey, float value)
@@ -2940,10 +2941,8 @@ public:
return object->as<CustomPropertyEnumBase>()->enumId();
case FileAssetBase::assetIdPropertyKey:
return object->as<FileAssetBase>()->assetId();
#ifdef WITH_RIVE_TOOLS
case ScriptAssetBase::generatorFunctionRefPropertyKey:
return object->as<ScriptAssetBase>()->generatorFunctionRef();
#endif
case AudioEventBase::assetIdPropertyKey:
return object->as<AudioEventBase>()->assetId();
case ScriptInputArtboardBase::artboardIdPropertyKey:
@@ -3123,6 +3122,8 @@ public:
return object->as<TextFollowPathModifierBase>()->orient();
case TextBase::fitFromBaselinePropertyKey:
return object->as<TextBase>()->fitFromBaseline();
case ScriptAssetBase::isModulePropertyKey:
return object->as<ScriptAssetBase>()->isModule();
}
return false;
}
@@ -3804,9 +3805,7 @@ public:
case CustomPropertyEnumBase::propertyValuePropertyKey:
case CustomPropertyEnumBase::enumIdPropertyKey:
case FileAssetBase::assetIdPropertyKey:
#ifdef WITH_RIVE_TOOLS
case ScriptAssetBase::generatorFunctionRefPropertyKey:
#endif
case AudioEventBase::assetIdPropertyKey:
case ScriptInputArtboardBase::artboardIdPropertyKey:
return CoreUintType::id;
@@ -3883,6 +3882,7 @@ public:
case TextFollowPathModifierBase::radialPropertyKey:
case TextFollowPathModifierBase::orientPropertyKey:
case TextBase::fitFromBaselinePropertyKey:
case ScriptAssetBase::isModulePropertyKey:
return CoreBoolType::id;
case ViewModelInstanceNumberBase::propertyValuePropertyKey:
case CustomPropertyNumberBase::propertyValuePropertyKey:
@@ -4559,10 +4559,8 @@ public:
return object->is<CustomPropertyEnumBase>();
case FileAssetBase::assetIdPropertyKey:
return object->is<FileAssetBase>();
#ifdef WITH_RIVE_TOOLS
case ScriptAssetBase::generatorFunctionRefPropertyKey:
return object->is<ScriptAssetBase>();
#endif
case AudioEventBase::assetIdPropertyKey:
return object->is<AudioEventBase>();
case ScriptInputArtboardBase::artboardIdPropertyKey:
@@ -4709,6 +4707,8 @@ public:
return object->is<TextFollowPathModifierBase>();
case TextBase::fitFromBaselinePropertyKey:
return object->is<TextBase>();
case ScriptAssetBase::isModulePropertyKey:
return object->is<ScriptAssetBase>();
case ViewModelInstanceNumberBase::propertyValuePropertyKey:
return object->is<ViewModelInstanceNumberBase>();
case CustomPropertyNumberBase::propertyValuePropertyKey:

View File

@@ -3,6 +3,7 @@
#define _RIVE_LUA_LIBS_HPP_
#include "lua.h"
#include "lualib.h"
#include "rive/assets/script_asset.hpp"
#include "rive/lua/lua_state.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/renderer.hpp"
@@ -26,13 +27,14 @@
#include "rive/data_bind/data_values/data_value_number.hpp"
#include "rive/data_bind/data_values/data_value_string.hpp"
#include "rive/viewmodel/viewmodel.hpp"
#include "rive/artboard.hpp"
#include "rive/file.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/hit_result.hpp"
#include "rive/refcnt.hpp"
#include <chrono>
#include <unordered_map>
#include <functional>
#include <string>
#include <vector>
static const int maxCStack = 8000;
static const int luaGlobalsIndex = -maxCStack - 2002;
@@ -40,7 +42,14 @@ static const int luaRegistryIndex = -maxCStack - 2000;
namespace rive
{
class Artboard;
class ArtboardInstance;
class Factory;
class File;
class ModuleDetails;
class ScriptedObject;
class StateMachineInstance;
class TransformComponent;
enum class LuaAtoms : int16_t
{
// Vector
@@ -423,9 +432,9 @@ public:
std::unique_ptr<ArtboardInstance>&& artboardInstance);
~ScriptReffedArtboard();
rive::rcp<rive::File> file() { return m_file; }
Artboard* artboard() { return m_artboard.get(); }
StateMachineInstance* stateMachine() { return m_stateMachine.get(); }
rive::rcp<rive::File> file();
Artboard* artboard();
StateMachineInstance* stateMachine();
rcp<ViewModelInstance> viewModelInstance() { return m_viewModelInstance; }
private:
@@ -440,6 +449,7 @@ class ScriptedArtboard
public:
ScriptedArtboard(rcp<File> file,
std::unique_ptr<ArtboardInstance>&& artboardInstance);
~ScriptedArtboard();
static constexpr uint8_t luaTag = LUA_T_COUNT + 10;
static constexpr const char* luaName = "Artboard";
@@ -465,6 +475,8 @@ public:
bool advance(float seconds);
void cleanupDataRef(lua_State* L);
private:
rcp<ScriptReffedArtboard> m_scriptReffedArtboard;
int m_dataRef = 0;
@@ -716,8 +728,27 @@ public:
virtual void printEndLine() = 0;
virtual int pCall(lua_State* state, int nargs, int nresults) = 0;
void queuePendingModule(ModuleDetails* moduleDetails);
void clearPendingModule(const std::string& name);
// Add a module to be registered later via performRegistration()
void addModule(ModuleDetails* moduleDetails);
// Perform registration of all added modules, handling dependencies and
// retries
void performRegistration(lua_State* state);
private:
bool tryRegisterModule(lua_State* state,
ModuleDetails* moduleDetails,
int& functionRef);
void retryPendingModules(lua_State* state);
private:
Factory* m_factory;
std::vector<ModuleDetails*> m_pendingModules;
std::vector<ModuleDetails*> m_modulesToRegister;
};
class ScriptingVM
@@ -730,6 +761,17 @@ public:
lua_State* state() { return m_state; }
static void init(lua_State* state, ScriptingContext* context);
// Add a module to be registered later via performRegistration()
void addModule(ModuleDetails* moduleDetails)
{
m_context->addModule(moduleDetails);
}
// Perform registration of all added modules, handling dependencies and
// retries
void performRegistration() { m_context->performRegistration(m_state); }
static bool registerModule(lua_State* state,
const char* name,
Span<uint8_t> bytecode);
@@ -742,6 +784,7 @@ public:
Span<uint8_t> bytecode);
bool registerScript(const char* name, Span<uint8_t> bytecode);
static void dumpStack(lua_State* state);
private:
@@ -909,6 +952,7 @@ class CPPRuntimeScriptingContext : public ScriptingContext
{
public:
CPPRuntimeScriptingContext(Factory* factory) : ScriptingContext(factory) {}
virtual ~CPPRuntimeScriptingContext() = default;
std::chrono::time_point<std::chrono::steady_clock> executionTime;

View File

@@ -11,9 +11,10 @@ class Artboard;
class ScriptInputArtboard : public ScriptInputArtboardBase, public ScriptInput
{
private:
Artboard* m_artboard;
Artboard* m_artboard = nullptr;
public:
~ScriptInputArtboard();
void artboard(Artboard* artboard) { m_artboard = artboard; }
void initScriptedValue() override
{
@@ -31,6 +32,7 @@ public:
bool validateForScriptInit() override { return m_artboard != nullptr; }
StatusCode import(ImportStack& importStack) override;
Core* clone() const override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -8,7 +8,11 @@ namespace rive
{
class ScriptInputBoolean : public ScriptInputBooleanBase, public ScriptInput
{
protected:
void propertyValueChanged() override;
public:
~ScriptInputBoolean();
void initScriptedValue() override
{
ScriptInput::initScriptedValue();
@@ -20,6 +24,7 @@ public:
}
bool validateForScriptInit() override { return true; }
StatusCode import(ImportStack& importStack) override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -8,7 +8,11 @@ namespace rive
{
class ScriptInputColor : public ScriptInputColorBase, public ScriptInput
{
protected:
void propertyValueChanged() override;
public:
~ScriptInputColor();
void initScriptedValue() override
{
ScriptInput::initScriptedValue();
@@ -20,6 +24,7 @@ public:
}
bool validateForScriptInit() override { return true; }
StatusCode import(ImportStack& importStack) override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -8,7 +8,11 @@ namespace rive
{
class ScriptInputNumber : public ScriptInputNumberBase, public ScriptInput
{
protected:
void propertyValueChanged() override;
public:
~ScriptInputNumber();
void initScriptedValue() override
{
ScriptInput::initScriptedValue();
@@ -18,6 +22,7 @@ public:
obj->setNumberInput(name(), propertyValue());
}
}
StatusCode onAddedClean(CoreContext* context) override;
bool validateForScriptInit() override { return true; }
StatusCode import(ImportStack& importStack) override;
};

View File

@@ -8,7 +8,11 @@ namespace rive
{
class ScriptInputString : public ScriptInputStringBase, public ScriptInput
{
protected:
void propertyValueChanged() override;
public:
~ScriptInputString();
void initScriptedValue() override
{
ScriptInput::initScriptedValue();
@@ -20,6 +24,7 @@ public:
}
bool validateForScriptInit() override { return true; }
StatusCode import(ImportStack& importStack) override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -8,14 +8,14 @@ namespace rive
{
class ScriptInputTrigger : public ScriptInputTriggerBase, public ScriptInput
{
protected:
void propertyValueChanged() override;
public:
~ScriptInputTrigger();
bool validateForScriptInit() override { return true; }
void fire(const CallbackData& value) override
{
Super::fire(value);
scriptedObject()->trigger(name());
}
StatusCode import(ImportStack& importStack) override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -18,6 +18,7 @@ protected:
std::vector<uint32_t> m_DataBindPathIdsBuffer;
public:
~ScriptInputViewModelProperty();
void decodeDataBindPathIds(Span<const uint8_t> value) override;
void copyDataBindPathIds(
const ScriptInputViewModelPropertyBase& object) override;
@@ -25,6 +26,7 @@ public:
void initScriptedValue() override;
bool validateForScriptInit() override;
StatusCode import(ImportStack& importStack) override;
StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive

View File

@@ -32,6 +32,7 @@ private:
}
m_dataValue->as<T>()->value(input->as<T>()->value());
};
virtual void disposeScriptInputs() override;
#ifdef WITH_RIVE_SCRIPTING
DataValue* applyConversion(DataValue* value, const std::string& method);
#endif
@@ -51,6 +52,7 @@ public:
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
StatusCode import(ImportStack& importStack) override;
void addProperty(CustomProperty* prop) override;
Core* clone() const override;
bool addScriptedDirt(ComponentDirt value, bool recurse = false) override
{

View File

@@ -40,6 +40,7 @@ public:
{
return children();
}
void addProperty(CustomProperty* prop) override;
bool addScriptedDirt(ComponentDirt value, bool recurse = false) override;
DataContext* dataContext() override
{

View File

@@ -30,6 +30,7 @@ public:
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction) override;
void addProperty(CustomProperty* prop) override;
Core* clone() const override;
ScriptProtocol scriptProtocol() override { return ScriptProtocol::layout; }
};

View File

@@ -24,11 +24,13 @@ class ScriptedObject : public FileAssetReferencer,
protected:
int m_self = 0;
int m_context = 0;
virtual void disposeScriptInputs();
#ifdef WITH_RIVE_SCRIPTING
LuaState* m_state = nullptr;
#endif
public:
virtual ~ScriptedObject() { scriptDispose(); }
ScriptAsset* scriptAsset() const;
void setArtboardInput(std::string name, Artboard* artboard);
void setBooleanInput(std::string name, bool value);

View File

@@ -17,6 +17,7 @@ public:
#ifdef WITH_RIVE_SCRIPTING
bool scriptInit(LuaState* state) override;
#endif
void addProperty(CustomProperty* prop) override;
StatusCode onAddedClean(CoreContext* context) override;
StatusCode onAddedDirty(CoreContext* context) override;
uint32_t assetId() override { return scriptAssetId(); }

View File

@@ -1,5 +1,5 @@
local dependency = require('dependency')
local luau = dependency.github('luigi-rosso/luau', 'rive_0_28')
local luau = dependency.github('luigi-rosso/luau', 'rive_0_29')
local libhydrogen = dependency.github('luigi-rosso/libhydrogen', 'rive_0_1')
dofile('rive_build_config.lua')

View File

@@ -42,9 +42,8 @@ ScriptInput* ScriptInput::from(Core* component)
void ScriptInput::initScriptedValue() {}
bool OptionalScriptedMethods::verifyImplementation(
ScriptProtocol scriptProtocol,
LuaState* luaState)
bool OptionalScriptedMethods::verifyImplementation(ScriptedObject* object,
LuaState* luaState)
{
#ifdef WITH_RIVE_SCRIPTING
auto state = luaState->state;
@@ -63,6 +62,7 @@ bool OptionalScriptedMethods::verifyImplementation(
}
m_implementedMethods = 0;
auto scriptProtocol = object->scriptProtocol();
if (scriptProtocol == ScriptProtocol::node ||
scriptProtocol == ScriptProtocol::layout ||
scriptProtocol == ScriptProtocol::converter ||
@@ -148,6 +148,19 @@ bool OptionalScriptedMethods::verifyImplementation(
#endif
}
#ifdef WITH_RIVE_SCRIPTING
LuaState* ScriptAsset::vm()
{
if (m_file == nullptr)
{
return nullptr;
}
// We get the scripting VM from File for now, however,
// this will need to change if/when we support multiple VMs
return m_file->scriptingVM();
}
#endif
bool ScriptAsset::initScriptedObject(ScriptedObject* object)
{
#ifdef WITH_RIVE_SCRIPTING
@@ -161,26 +174,47 @@ bool ScriptAsset::initScriptedObject(ScriptedObject* object)
#endif
}
#if defined(WITH_RIVE_SCRIPTING)
void ScriptAsset::registrationComplete(int ref)
{
if (isModule())
{
m_scriptRegistered = true;
}
else
{
generatorFunctionRef(ref);
}
}
#endif
bool ScriptAsset::initScriptedObjectWith(ScriptedObject* object)
{
#if defined(WITH_RIVE_SCRIPTING) && defined(WITH_RIVE_TOOLS)
if (vm() == nullptr || generatorFunctionRef() == 0)
#if defined(WITH_RIVE_SCRIPTING)
if (vm() == nullptr)
{
return false;
}
auto vmState = vm()->state;
auto pushedType = rive_lua_pushRef(vmState, generatorFunctionRef());
if (static_cast<lua_Type>(pushedType) != LUA_TFUNCTION)
if (generatorFunctionRef() == 0)
{
fprintf(stderr,
"ScriptAsset::initScriptedObjectWith: did not push a function "
"at generatorFunctionRef, instead it pushed a %d\n ",
pushedType);
"ScriptAsset doesn't have a generator function %s\n",
name().c_str());
return false;
}
auto vmState = vm()->state;
rive_lua_pushRef(vmState, generatorFunctionRef());
if (!m_initted)
{
m_scriptProtocol = object->scriptProtocol();
verifyImplementation(m_scriptProtocol, vm());
if (!verifyImplementation(object, vm()))
{
fprintf(stderr,
"ScriptAsset failed to verify method implementation %s\n",
name().c_str());
rive_lua_pop(vmState, 1);
return false;
}
m_initted = true;
}
object->implementedMethods(implementedMethods());

View File

@@ -24,4 +24,14 @@ void CustomPropertyContainer::addProperty(CustomProperty* prop)
{
m_customProperties.push_back(prop);
}
}
void CustomPropertyContainer::removeProperty(CustomProperty* prop)
{
auto it =
std::find(m_customProperties.begin(), m_customProperties.end(), prop);
if (it != m_customProperties.end())
{
m_customProperties.erase(it);
}
}

View File

@@ -17,6 +17,9 @@
#include "rive/importers/file_asset_importer.hpp"
#include "rive/importers/script_asset_importer.hpp"
#include "rive/importers/import_stack.hpp"
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#endif
#include "rive/importers/keyed_object_importer.hpp"
#include "rive/importers/keyed_property_importer.hpp"
#include "rive/importers/linear_animation_importer.hpp"
@@ -220,6 +223,9 @@ File::~File()
delete physics;
}
delete m_backboard;
#ifdef WITH_RIVE_SCRIPTING
cleanupScriptingVM();
#endif
}
rcp<File> File::import(Span<const uint8_t> bytes,
@@ -569,11 +575,64 @@ ImportResult File::read(BinaryReader& reader, const RuntimeHeader& header)
}
}
return !reader.hasError() && importStack.resolve() == StatusCode::Ok
auto resolved = importStack.resolve();
#ifdef WITH_RIVE_SCRIPTING
registerScripts();
#endif
return !reader.hasError() && resolved == StatusCode::Ok
? ImportResult::success
: ImportResult::malformed;
}
#ifdef WITH_RIVE_SCRIPTING
void File::registerScripts()
{
if (m_scriptingVM == nullptr)
{
makeScriptingVM();
}
// Add all scripts to the VM for registration
for (auto asset : m_fileAssets)
{
if (asset->is<ScriptAsset>())
{
ScriptAsset* scriptAsset = asset->as<ScriptAsset>();
// At runtime, generatorFunctionRef should be 0, meaning
// it hasn't been registered yet with a VM.
if (scriptAsset->verified())
{
m_scriptingVM->addModule(scriptAsset);
}
}
}
// Perform registration - ScriptingContext will handle dependencies and
// retries
m_scriptingVM->performRegistration();
}
void File::makeScriptingVM()
{
cleanupScriptingVM();
m_scriptingContext =
rivestd::make_unique<CPPRuntimeScriptingContext>(m_factory);
m_scriptingVM = rivestd::make_unique<ScriptingVM>(m_scriptingContext.get());
m_luaState = new LuaState(m_scriptingVM->state());
}
void File::cleanupScriptingVM()
{
if (m_scriptingVM != nullptr)
{
delete m_luaState;
m_luaState = nullptr;
m_scriptingVM.reset();
m_scriptingContext.reset();
}
}
#endif
Artboard* File::artboard(std::string name) const
{
for (const auto& artboard : m_artboards)

View File

@@ -16,7 +16,6 @@ void ScriptedObjectImporter::addInput(CustomProperty* value)
if (input != nullptr)
{
m_scriptedObject->addProperty(value);
input->scriptedObject(m_scriptedObject);
}
}

View File

@@ -1,5 +1,8 @@
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/file.hpp"
#include "rive/artboard.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/viewmodel/viewmodel_property_number.hpp"
#include "rive/viewmodel/viewmodel_property_trigger.hpp"
#include "rive/node.hpp"
@@ -33,6 +36,15 @@ ScriptReffedArtboard::~ScriptReffedArtboard()
m_file = nullptr;
}
rive::rcp<rive::File> ScriptReffedArtboard::file() { return m_file; }
Artboard* ScriptReffedArtboard::artboard() { return m_artboard.get(); }
StateMachineInstance* ScriptReffedArtboard::stateMachine()
{
return m_stateMachine.get();
}
static int artboard_draw(lua_State* L)
{
auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1);
@@ -299,6 +311,17 @@ ScriptedArtboard::ScriptedArtboard(
make_rcp<ScriptReffedArtboard>(file, std::move(artboardInstance)))
{}
ScriptedArtboard::~ScriptedArtboard() { m_scriptReffedArtboard = nullptr; }
void ScriptedArtboard::cleanupDataRef(lua_State* L)
{
if (m_dataRef != 0)
{
lua_unref(L, m_dataRef);
m_dataRef = 0;
}
}
static int node_index(lua_State* L)
{
int atom;
@@ -539,9 +562,22 @@ static int node_namecall(lua_State* L)
return 0;
}
static void scripted_artboard_dtor(lua_State* L, void* data)
{
ScriptedArtboard* artboard = static_cast<ScriptedArtboard*>(data);
// Clean up m_dataRef before calling the C++ destructor
// (we can't do this in ~ScriptedArtboard() because it doesn't have access
// to lua_State*)
artboard->cleanupDataRef(L);
// Call the C++ destructor
artboard->~ScriptedArtboard();
}
int luaopen_rive_artboards(lua_State* L)
{
lua_register_rive<ScriptedArtboard>(L);
// Override the default destructor to clean up m_dataRef first
lua_setuserdatadtor(L, ScriptedArtboard::luaTag, scripted_artboard_dtor);
lua_pushcfunction(L, artboard_index, nullptr);
lua_setfield(L, -2, "__index");

View File

@@ -1,7 +1,9 @@
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/assets/script_asset.hpp"
#include "lualib.h"
#include <unordered_map>
#include <unordered_set>
#include <string>
using namespace rive;
@@ -446,10 +448,148 @@ static void dump_stack(lua_State* state)
void ScriptingVM::dumpStack(lua_State* state) { dump_stack(state); }
void ScriptingContext::addModule(ModuleDetails* moduleDetails)
{
m_modulesToRegister.push_back(moduleDetails);
}
bool ScriptingContext::tryRegisterModule(lua_State* state,
ModuleDetails* moduleDetails,
int& functionRef)
{
const std::string& name = moduleDetails->moduleName();
functionRef = 0;
if (moduleDetails->isProtocolScript())
{
if (ScriptingVM::registerScript(state,
name.c_str(),
moduleDetails->moduleBytecode()))
{
// registerScript leaves the function on the stack
if (static_cast<lua_Type>(lua_type(state, -1)) == LUA_TFUNCTION)
{
functionRef = lua_ref(state, -1);
}
lua_pop(state, 1);
return true;
}
}
else
{
if (ScriptingVM::registerModule(state,
name.c_str(),
moduleDetails->moduleBytecode()))
{
return true;
}
}
return false;
}
void ScriptingContext::retryPendingModules(lua_State* state)
{
bool anyRetried = true;
while (anyRetried)
{
anyRetried = false;
// Track which modules we've tried in this iteration to avoid infinite
// loops
std::unordered_set<std::string> triedThisCycle;
auto currentPending = m_pendingModules;
for (ModuleDetails* moduleDetails : currentPending)
{
const std::string& name = moduleDetails->moduleName();
// Skip if we've already tried this module in this iteration
if (triedThisCycle.find(name) != triedThisCycle.end())
{
continue;
}
triedThisCycle.insert(name);
int functionRef = 0;
if (tryRegisterModule(state, moduleDetails, functionRef))
{
// Successfully registered, remove from pending list
clearPendingModule(name);
moduleDetails->registrationComplete(
moduleDetails->isProtocolScript() ? functionRef : 0);
anyRetried = true;
break;
}
}
}
}
void ScriptingContext::performRegistration(lua_State* state)
{
// Try to register all modules
std::unordered_set<std::string> tried;
bool anyRegistered = true;
while (anyRegistered)
{
anyRegistered = false;
for (ModuleDetails* moduleDetails : m_modulesToRegister)
{
const std::string& name = moduleDetails->moduleName();
// Skip if already registered
if (checkRegisteredModules(state, name.c_str()) == 1)
{
continue;
}
// Skip if we've already tried this module
if (tried.find(name) != tried.end())
{
continue;
}
tried.insert(name);
// Try to register the module
int functionRef = 0;
if (tryRegisterModule(state, moduleDetails, functionRef))
{
// Successfully registered
anyRegistered = true;
moduleDetails->registrationComplete(
moduleDetails->isProtocolScript() ? functionRef : 0);
this->retryPendingModules(state);
break;
}
else
{
// Registration failed - likely missing dependencies
// Queue for retry
queuePendingModule(moduleDetails);
}
}
}
// Clear the modules list after registration attempt
m_modulesToRegister.clear();
}
bool ScriptingVM::registerScript(lua_State* state,
const char* name,
Span<uint8_t> bytecode)
{
// Check if already registered
if (checkRegisteredModules(state, name) == 1)
{
return true;
}
if (!push_module(state, name, bytecode))
{
return false;
@@ -462,6 +602,12 @@ bool ScriptingVM::registerModule(lua_State* state,
const char* name,
Span<uint8_t> bytecode)
{
// Check if already registered
if (checkRegisteredModules(state, name) == 1)
{
return true;
}
lua_pushcfunction(state, register_module, nullptr);
lua_pushstring(state, name);
if (!push_module(state, name, bytecode))
@@ -498,6 +644,22 @@ bool ScriptingVM::registerScript(const char* name, Span<uint8_t> bytecode)
return registerScript(m_state, name, bytecode);
}
void ScriptingContext::queuePendingModule(ModuleDetails* moduleDetails)
{
m_pendingModules.push_back(moduleDetails);
}
void ScriptingContext::clearPendingModule(const std::string& name)
{
m_pendingModules.erase(std::remove_if(m_pendingModules.begin(),
m_pendingModules.end(),
[&name](ModuleDetails* module) {
return module->moduleName() ==
name;
}),
m_pendingModules.end());
}
int CPPRuntimeScriptingContext::pCall(lua_State* state, int nargs, int nresults)
{
// calculate stack position for message handler

View File

@@ -3,9 +3,20 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_artboard.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputArtboard::~ScriptInputArtboard()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
m_artboard = nullptr;
}
StatusCode ScriptInputArtboard::import(ImportStack& importStack)
{
auto backboardImporter =
@@ -34,6 +45,27 @@ StatusCode ScriptInputArtboard::import(ImportStack& importStack)
return StatusCode::Ok;
}
StatusCode ScriptInputArtboard::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
Core* ScriptInputArtboard::clone() const
{
ScriptInputArtboard* twin =

View File

@@ -3,9 +3,19 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_boolean.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputBoolean::~ScriptInputBoolean()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
StatusCode ScriptInputBoolean::import(ImportStack& importStack)
{
auto importer =
@@ -24,4 +34,34 @@ StatusCode ScriptInputBoolean::import(ImportStack& importStack)
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputBoolean::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
void ScriptInputBoolean::propertyValueChanged()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->setBooleanInput(name(), propertyValue());
}
}

View File

@@ -3,9 +3,19 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_color.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputColor::~ScriptInputColor()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
StatusCode ScriptInputColor::import(ImportStack& importStack)
{
auto importer =
@@ -24,4 +34,34 @@ StatusCode ScriptInputColor::import(ImportStack& importStack)
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputColor::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
void ScriptInputColor::propertyValueChanged()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->setIntegerInput(name(), propertyValue());
}
}

View File

@@ -3,9 +3,19 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_number.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputNumber::~ScriptInputNumber()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
StatusCode ScriptInputNumber::import(ImportStack& importStack)
{
auto importer =
@@ -24,4 +34,34 @@ StatusCode ScriptInputNumber::import(ImportStack& importStack)
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputNumber::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
void ScriptInputNumber::propertyValueChanged()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->setNumberInput(name(), propertyValue());
}
}

View File

@@ -3,9 +3,19 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_string.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputString::~ScriptInputString()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
StatusCode ScriptInputString::import(ImportStack& importStack)
{
auto importer =
@@ -24,4 +34,34 @@ StatusCode ScriptInputString::import(ImportStack& importStack)
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputString::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
void ScriptInputString::propertyValueChanged()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->setStringInput(name(), propertyValue());
}
}

View File

@@ -3,9 +3,19 @@
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/script_input_trigger.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputTrigger::~ScriptInputTrigger()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
StatusCode ScriptInputTrigger::import(ImportStack& importStack)
{
auto importer =
@@ -24,4 +34,33 @@ StatusCode ScriptInputTrigger::import(ImportStack& importStack)
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputTrigger::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}
void ScriptInputTrigger::propertyValueChanged()
{
if (propertyValue() != 0)
{
scriptedObject()->trigger(name());
}
}

View File

@@ -4,9 +4,19 @@
#include "rive/viewmodel/viewmodel_instance_value.hpp"
#include "rive/viewmodel/viewmodel_property_enum_custom.hpp"
#include "rive/viewmodel/viewmodel_property_viewmodel.hpp"
#include "rive/custom_property_container.hpp"
using namespace rive;
ScriptInputViewModelProperty::~ScriptInputViewModelProperty()
{
auto obj = scriptedObject();
if (obj != nullptr)
{
obj->removeProperty(this);
}
}
void ScriptInputViewModelProperty::decodeDataBindPathIds(
Span<const uint8_t> value)
{
@@ -83,5 +93,26 @@ StatusCode ScriptInputViewModelProperty::import(ImportStack& importStack)
// to add it as a Component, otherwise, return Ok
return Super::import(importStack);
}
return StatusCode::Ok;
}
StatusCode ScriptInputViewModelProperty::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
auto p = parent();
if (p != nullptr)
{
auto scriptedObj = ScriptedObject::from(p);
if (scriptedObj != nullptr)
{
scriptedObj->addProperty(this);
}
}
return StatusCode::Ok;
}

View File

@@ -17,12 +17,29 @@ using namespace rive;
ScriptedDataConverter::~ScriptedDataConverter()
{
disposeScriptInputs();
if (m_dataValue)
{
delete m_dataValue;
}
}
void ScriptedDataConverter::disposeScriptInputs()
{
auto props = m_customProperties;
ScriptedObject::disposeScriptInputs();
for (auto prop : props)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
// ScriptedDataConverters need to delete their own inputs
// because they are not components
delete scriptInput;
}
}
}
#ifdef WITH_RIVE_SCRIPTING
bool ScriptedDataConverter::scriptInit(LuaState* state)
{
@@ -158,6 +175,16 @@ bool ScriptedDataConverter::advanceComponent(float elapsedSeconds,
return scriptAdvance(elapsedSeconds);
}
void ScriptedDataConverter::addProperty(CustomProperty* prop)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(this);
}
CustomPropertyContainer::addProperty(prop);
}
StatusCode ScriptedDataConverter::import(ImportStack& importStack)
{
auto result = registerReferencer(importStack);
@@ -180,19 +207,6 @@ Core* ScriptedDataConverter::clone() const
{
auto clonedValue = prop->clone()->as<CustomProperty>();
twin->addProperty(clonedValue);
auto scriptInput = ScriptInput::from(clonedValue);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(twin);
if (scriptInput->dataBind() != nullptr)
{
auto clonedDataBind =
scriptInput->dataBind()->clone()->as<DataBind>();
clonedDataBind->target(clonedValue);
scriptInput->dataBind(clonedDataBind);
twin->addDirtyDataBind(clonedDataBind);
}
}
}
return twin;
}

View File

@@ -171,6 +171,16 @@ bool ScriptedDrawable::addScriptedDirt(ComponentDirt value, bool recurse)
return Drawable::addDirt(value, recurse);
}
void ScriptedDrawable::addProperty(CustomProperty* prop)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(this);
}
CustomPropertyContainer::addProperty(prop);
}
StatusCode ScriptedDrawable::import(ImportStack& importStack)
{
auto result = registerReferencer(importStack);
@@ -189,16 +199,6 @@ Core* ScriptedDrawable::clone() const
{
twin->setAsset(m_fileAsset);
}
for (auto prop : m_customProperties)
{
auto clonedValue = prop->clone()->as<CustomProperty>();
twin->addProperty(clonedValue);
auto scriptInput = ScriptInput::from(clonedValue);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(twin);
}
}
return twin;
}

View File

@@ -129,6 +129,16 @@ void ScriptedLayout::controlSize(Vec2D size,
}
#endif
void ScriptedLayout::addProperty(CustomProperty* prop)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(this);
}
CustomPropertyContainer::addProperty(prop);
}
Core* ScriptedLayout::clone() const
{
ScriptedLayout* twin = ScriptedLayoutBase::clone()->as<ScriptedLayout>();
@@ -136,15 +146,5 @@ Core* ScriptedLayout::clone() const
{
twin->setAsset(m_fileAsset);
}
for (auto prop : m_customProperties)
{
auto clonedValue = prop->clone()->as<CustomProperty>();
twin->addProperty(clonedValue);
auto scriptInput = ScriptInput::from(clonedValue);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(twin);
}
}
return twin;
}

View File

@@ -4,6 +4,7 @@
#include "rive/assets/script_asset.hpp"
#include "rive/artboard.hpp"
#include "rive/file.hpp"
#include "rive/script_input_artboard.hpp"
#include "rive/scripted/scripted_data_converter.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/scripted/scripted_layout.hpp"
@@ -35,6 +36,7 @@ void ScriptedObject::setArtboardInput(std::string name, Artboard* artboard)
{
return;
}
auto state = m_state->state;
rive_lua_pushRef(state, m_self);
lua_newrive<ScriptedArtboard>(state,
@@ -298,12 +300,32 @@ bool ScriptedObject::scriptInit(LuaState* luaState)
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::scriptDispose()
{
disposeScriptInputs();
if (m_state != nullptr)
{
lua_unref(m_state->state, m_self);
lua_unref(m_state->state, m_context);
#ifdef TESTING
// Force GC to collect any ScriptedArtboard instances created via
// instance()
lua_gc(m_state->state, LUA_GCCOLLECT, 0);
#endif
}
m_state = nullptr;
m_self = 0;
@@ -330,6 +352,8 @@ bool ScriptedObject::scriptAdvance(float elapsedSeconds) { return false; }
void ScriptedObject::scriptUpdate() {}
void ScriptedObject::scriptDispose() {}
void ScriptedObject::disposeScriptInputs() {}
#endif
void ScriptedObject::reinit()

View File

@@ -107,6 +107,16 @@ bool ScriptedPathEffect::addScriptedDirt(ComponentDirt value, bool recurse)
return Component::addDirt(value, recurse);
}
void ScriptedPathEffect::addProperty(CustomProperty* prop)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(this);
}
CustomPropertyContainer::addProperty(prop);
}
StatusCode ScriptedPathEffect::import(ImportStack& importStack)
{
auto result = registerReferencer(importStack);
@@ -127,16 +137,6 @@ Core* ScriptedPathEffect::clone() const
{
twin->setAsset(m_fileAsset);
}
for (auto prop : m_customProperties)
{
auto clonedValue = prop->clone()->as<CustomProperty>();
twin->addProperty(clonedValue);
auto scriptInput = ScriptInput::from(clonedValue);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(twin);
}
}
return twin;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +1,11 @@
#include "catch.hpp"
#include "scripting_test_utilities.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/math/path_types.hpp"
#include "rive_file_reader.hpp"
#include "rive_testing.hpp"
using namespace rive;
@@ -367,3 +370,57 @@ TEST_CASE("path measure extract across multiple contours", "[scripting]")
// Should extract from both contours
CHECK(destPath->rawPath.verbs().count() > 0);
}
TEST_CASE("path drawing examples", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_paths_test.riv", &silver);
auto artboard = file->artboardNamed("PathsScript");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = 60;
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("script_paths"));
}
TEST_CASE("path effects examples", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_path_effects_test.riv", &silver);
auto artboard = file->artboardNamed("PathEffects");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = 60;
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("script_path_effects"));
}

View File

@@ -1,6 +1,7 @@
#include "catch.hpp"
#include "scripting_test_utilities.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#include "rive_file_reader.hpp"
@@ -282,4 +283,31 @@ TEST_CASE("can add artboard to path", "[scripting]")
lua_getglobal(L, "addToPath");
lua_pushvalue(L, -2);
CHECK(lua_pcall(L, 1, 1, 0) == LUA_OK);
}
TEST_CASE("script instances Artboard input", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_artboard_test.riv", &silver);
auto artboard = file->artboardNamed("Artboard");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = 60;
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("script_artboards"));
}

View File

@@ -1,7 +1,11 @@
#include "catch.hpp"
#include "scripting_test_utilities.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/viewmodel/viewmodel_instance_number.hpp"
#include "rive/viewmodel/viewmodel_instance_string.hpp"
#include "rive_file_reader.hpp"
using namespace rive;
@@ -342,3 +346,101 @@ end
CHECK(top == lua_gettop(L));
}
}
TEST_CASE("scripted string converter", "[silver]")
{
rive::SerializingFactory silver;
auto file =
ReadRiveFile("assets/script_string_converter_test.riv", &silver);
auto artboard = file->artboardNamed("Converter");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
auto field1 =
vmi->propertyValue("Field1")->as<rive::ViewModelInstanceString>();
REQUIRE(field1 != nullptr);
field1->propertyValue("H#e%l&l*o");
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
auto field2 =
vmi->propertyValue("Field2")->as<rive::ViewModelInstanceString>();
REQUIRE(field2 != nullptr);
field2->propertyValue("____one two three___");
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
auto field3 =
vmi->propertyValue("Field3")->as<rive::ViewModelInstanceString>();
REQUIRE(field3 != nullptr);
field3->propertyValue(" **This uses a string converter@@. ");
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
auto field4 =
vmi->propertyValue("Field4")->as<rive::ViewModelInstanceString>();
REQUIRE(field4 != nullptr);
field4->propertyValue("It strips special characters like *&^%$#@!)()");
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
CHECK(silver.matches("script_string_converter"));
}
TEST_CASE("scripted data converter using multi chain requires", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_dependency_test.riv", &silver);
auto artboard = file->artboardNamed("Artboard");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
rive::ViewModelInstanceNumber* num =
vmi->propertyValue("InputValue1")->as<rive::ViewModelInstanceNumber>();
REQUIRE(num != nullptr);
int counter = 0;
int frames = 30;
for (int i = 0; i < frames; i++)
{
num->propertyValue(counter);
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
counter += 5;
}
CHECK(silver.matches("script_converter_with_dependency"));
}

View File

@@ -8,6 +8,9 @@
#include "utils/no_op_renderer.hpp"
#include "rive/layout/layout_enums.hpp"
#include "rive/data_bind/data_values/data_value.hpp"
#include "rive/viewmodel/viewmodel_instance_color.hpp"
#include "rive/viewmodel/viewmodel_instance_trigger.hpp"
#include "rive_file_reader.hpp"
#include <memory>
using namespace rive;
@@ -758,3 +761,39 @@ end
// Cleanup - must happen before luaState goes out of scope
converter->scriptDispose();
}
TEST_CASE("scripted input color and trigger test", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_inputs_test_1.riv", &silver);
auto artboard = file->artboardNamed("Artboard");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
rive::ViewModelInstanceTrigger* trigger =
vmi->propertyValue("Trigger")->as<rive::ViewModelInstanceTrigger>();
REQUIRE(trigger != nullptr);
int frames = 30;
for (int i = 0; i < frames; i++)
{
trigger->trigger();
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("script_input_color_trigger"));
}

View File

@@ -4,10 +4,13 @@
#include "catch.hpp"
#include "scripting_test_utilities.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/scripted/scripted_layout.hpp"
#include "rive/layout/layout_enums.hpp"
#include "rive/math/vec2d.hpp"
#include "rive/viewmodel/viewmodel_instance_number.hpp"
#include "rive_file_reader.hpp"
#include "utils/no_op_renderer.hpp"
using namespace rive;
@@ -287,3 +290,55 @@ end
CHECK(top == lua_gettop(L));
}
}
TEST_CASE("layout grid script", "[silver]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/script_layout_test.riv", &silver);
auto artboard = file->artboardNamed("LayoutScript");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
auto rows = vmi->propertyValue("Rows")->as<rive::ViewModelInstanceNumber>();
REQUIRE(rows != nullptr);
auto rowsValue = rows->propertyValue();
REQUIRE(rowsValue == 5);
auto cols =
vmi->propertyValue("Columns")->as<rive::ViewModelInstanceNumber>();
REQUIRE(cols != nullptr);
auto colsValue = cols->propertyValue();
REQUIRE(colsValue == 5);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = 20;
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
rows->propertyValue(8);
rowsValue = rows->propertyValue();
REQUIRE(rowsValue == 8);
cols->propertyValue(7);
colsValue = cols->propertyValue();
REQUIRE(colsValue == 7);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("script_layout_grid"));
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.