fix(runtime): ensure lua data is initialized (#13010) e27bf13d74 Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index 950e8f5..9be0836 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -afd68206e676b8b4d360a720a86068094740d330 +e27bf13d74952314871275f26d0ed7228b97b4dc
diff --git a/include/rive/lua/rive_lua_libs.hpp b/include/rive/lua/rive_lua_libs.hpp index e13a885..90943f6 100644 --- a/include/rive/lua/rive_lua_libs.hpp +++ b/include/rive/lua/rive_lua_libs.hpp
@@ -1482,6 +1482,10 @@ virtual void printEndLine() = 0; virtual int pCall(lua_State* state, int nargs, int nresults) = 0; + // When true, the VM's owner sets up the Lua `Data` global itself (the + // editor builds it in Dart), so File should not call initializeLuaData. + virtual bool initializesDataGlobalExternally() const { return false; } + // Add a module to be registered later via performRegistration() void addModule(ModuleDetails* moduleDetails); // Perform registration of all added modules, handling dependencies and
diff --git a/src/file.cpp b/src/file.cpp index 138465c..9daec55 100644 --- a/src/file.cpp +++ b/src/file.cpp
@@ -693,6 +693,14 @@ ScriptingVM* vm = m_scriptingVM.get(); if (vm != nullptr) { + // Set up the Data global (view model constructors) on the active + // VM, whether it was created here or supplied externally (e.g. by + // the CommandServer). Skip it when the VM's owner builds Data + // itself, as the editor does in Dart. + if (!vm->context()->initializesDataGlobalExternally()) + { + initializeLuaData(vm->state(), m_ViewModels); + } for (auto scriptAsset : scripts) { // At runtime, if the script is verified, add it to be @@ -729,7 +737,6 @@ cleanupScriptingVM(); auto context = std::make_unique<CPPRuntimeScriptingContext>(m_factory); m_scriptingVM = make_rcp<ScriptingVM>(std::move(context)); - initializeLuaData(m_scriptingVM->state(), m_ViewModels); } lua_State* File::scriptingState()
diff --git a/tests/unit_tests/assets/data_global_repro.riv b/tests/unit_tests/assets/data_global_repro.riv new file mode 100644 index 0000000..2ee24f5 --- /dev/null +++ b/tests/unit_tests/assets/data_global_repro.riv Binary files differ
diff --git a/tests/unit_tests/runtime/scripting/scripting_context_test.cpp b/tests/unit_tests/runtime/scripting/scripting_context_test.cpp index dd5ecd0..1f20a7f 100644 --- a/tests/unit_tests/runtime/scripting/scripting_context_test.cpp +++ b/tests/unit_tests/runtime/scripting/scripting_context_test.cpp
@@ -237,6 +237,44 @@ CHECK(silver.matches("script_create_viewmodel_instance")); } +// Regression test for rive-ios#454: when a ScriptingVM is supplied to +// File::import (as the CommandServer does on iOS), the Lua `Data` global — +// which exposes view model constructors like Data.ProbeChipVM.new() — must +// still be initialized. Before the fix, initializeLuaData only ran inside +// makeScriptingVM(), which is skipped when an external VM is provided, so +// `Data` was nil in scripts. +TEST_CASE("Data global is initialized when a ScriptingVM is provided to import", + "[scripting]") +{ + // Mirror CommandServer::processCommands: create the VM up front and pass + // it into File::import. + auto context = + std::make_unique<rive::CPPRuntimeScriptingContext>(&gNoOpFactory); + auto vm = rive::make_rcp<rive::ScriptingVM>(std::move(context)); + + auto bytes = ReadFile("assets/data_global_repro.riv"); + rive::ImportResult result; + auto file = + rive::File::import(bytes, &gNoOpFactory, &result, nullptr, vm.get()); + REQUIRE(result == rive::ImportResult::success); + REQUIRE(file != nullptr); + + // The file adopts the VM we supplied... + REQUIRE(file->scriptingVM() == vm.get()); + + // ...and its `Data` global is a populated table (was nil before the fix), + // exposing the file's view model as a constructor. + lua_State* L = vm->state(); + REQUIRE(L != nullptr); + lua_getglobal(L, "Data"); + REQUIRE(lua_istable(L, -1)); + lua_getfield(L, -1, "ProbeChipVM"); + REQUIRE(lua_istable(L, -1)); + lua_getfield(L, -1, "new"); + CHECK(lua_isfunction(L, -1)); + lua_pop(L, 3); +} + TEST_CASE("script has access to the data bound view model", "[silver]") { rive::SerializingFactory silver;