chore: Pass Lua VM from editor when decoding runtime File (#11458) f57124001d
The C++ runtime requires a ScriptingVM and lua_State to run scripts. Previously when a runtime File was built, we would always instance a ScriptingVM and lua_State. At runtime, this is required, however, when building the runtime in the editor, this resulted in additional objects being created that weren't needed. This PR passes the lua_State into File::import so that the file will only create the ScriptingVM once and either use the passed in lua_State or instance a new one if none is passed.

Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head
index c880cd3..76080af 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-f3a89390cb428a5ea841d21de91f9cb2adc312df
+f57124001d4ee388c32a3ec0cd08e292fafc268d
diff --git a/include/rive/file.hpp b/include/rive/file.hpp
index 3d9bba3..a14d5db 100644
--- a/include/rive/file.hpp
+++ b/include/rive/file.hpp
@@ -86,15 +86,17 @@
     static rcp<File> import(Span<const uint8_t> data,
                             Factory* factory,
                             ImportResult* result = nullptr,
-                            FileAssetLoader* assetLoader = nullptr)
+                            FileAssetLoader* assetLoader = nullptr,
+                            void* vm = nullptr)
     {
-        return import(data, factory, result, ref_rcp(assetLoader));
+        return import(data, factory, result, ref_rcp(assetLoader), vm);
     }
 
     static rcp<File> import(Span<const uint8_t> data,
                             Factory*,
                             ImportResult* result,
-                            rcp<FileAssetLoader> assetLoader);
+                            rcp<FileAssetLoader> assetLoader,
+                            void* vm = nullptr);
 
     /// @returns the file's backboard. All files have exactly one backboard.
     Backboard* backboard() const { return m_backboard; }
@@ -180,21 +182,8 @@
     // 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(lua_State* vm)
-    {
-        cleanupScriptingVM();
-        m_luaState = vm;
-    }
-    lua_State* 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;
-    }
+    void scriptingState(lua_State* vm) { m_luaState = vm; }
+    lua_State* scriptingState() { return m_luaState; }
 #ifdef WITH_RIVE_TOOLS
     void clearScriptingVM() { cleanupScriptingVM(); }
     bool hasVM() { return m_luaState != nullptr; }
@@ -280,7 +269,7 @@
     lua_State* m_luaState = nullptr;
     std::unique_ptr<CPPRuntimeScriptingContext> m_scriptingContext;
     std::unique_ptr<ScriptingVM> m_scriptingVM;
-    void makeScriptingVM();
+    void makeScriptingVM(lua_State* existingState = nullptr);
     void cleanupScriptingVM();
     void registerScripts();
 #endif
diff --git a/include/rive/lua/rive_lua_libs.hpp b/include/rive/lua/rive_lua_libs.hpp
index 45eb3f9..8c5b645 100644
--- a/include/rive/lua/rive_lua_libs.hpp
+++ b/include/rive/lua/rive_lua_libs.hpp
@@ -860,6 +860,7 @@
 {
 public:
     ScriptingVM(ScriptingContext* context);
+    ScriptingVM(ScriptingContext* context, lua_State* existingState);
     ~ScriptingVM();
 
     // ScriptingContext& context() { return m_context; }
@@ -895,6 +896,7 @@
 private:
     lua_State* m_state;
     ScriptingContext* m_context;
+    bool m_ownsState;
 };
 
 class ScriptedDataValue
diff --git a/src/assets/script_asset.cpp b/src/assets/script_asset.cpp
index 1542c46..9642b5b 100644
--- a/src/assets/script_asset.cpp
+++ b/src/assets/script_asset.cpp
@@ -169,7 +169,7 @@
     }
     // 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();
+    return m_file->scriptingState();
 }
 #endif
 
diff --git a/src/file.cpp b/src/file.cpp
index 0b7dfac..a7ef9a0 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -234,7 +234,8 @@
 rcp<File> File::import(Span<const uint8_t> bytes,
                        Factory* factory,
                        ImportResult* result,
-                       rcp<FileAssetLoader> assetLoader)
+                       rcp<FileAssetLoader> assetLoader,
+                       void* vm)
 {
     BinaryReader reader(bytes);
     RuntimeHeader header;
@@ -262,6 +263,12 @@
         return nullptr;
     }
     auto file = make_rcp<File>(factory, std::move(assetLoader));
+#ifdef WITH_RIVE_SCRIPTING
+    if (vm != nullptr)
+    {
+        file->scriptingState(static_cast<lua_State*>(vm));
+    }
+#endif
 
     auto readResult = file->read(reader, header);
     if (result)
@@ -609,39 +616,54 @@
 #ifdef WITH_RIVE_SCRIPTING
 void File::registerScripts()
 {
-    if (m_scriptingVM == nullptr)
-    {
-        makeScriptingVM();
-    }
-
-    // Add all scripts to the VM for registration
+    // Check if we have any script assets in the file
+    std::vector<ScriptAsset*> scripts;
     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.
+            scripts.push_back(asset->as<ScriptAsset>());
+        }
+    }
+    // Only make the ScriptingVM if we have any script assets
+    if (!scripts.empty())
+    {
+        makeScriptingVM(scriptingState());
+        for (auto scriptAsset : scripts)
+        {
+            // At runtime, if the script is verified, add it to be
+            // registered with the VM. At edit time, the script will
+            // have already been registered, so this won't run
             if (scriptAsset->verified())
             {
                 m_scriptingVM->addModule(scriptAsset);
             }
         }
+        // Perform registration - ScriptingContext will handle dependencies and
+        // retries
+        m_scriptingVM->performRegistration();
     }
-
-    // Perform registration - ScriptingContext will handle dependencies and
-    // retries
-    m_scriptingVM->performRegistration();
 }
 
-void File::makeScriptingVM()
+void File::makeScriptingVM(lua_State* existingState)
 {
     cleanupScriptingVM();
     m_scriptingContext =
         rivestd::make_unique<CPPRuntimeScriptingContext>(m_factory);
-    m_scriptingVM = rivestd::make_unique<ScriptingVM>(m_scriptingContext.get());
-    m_luaState = m_scriptingVM->state();
-    initializeLuaData(m_luaState, m_ViewModels);
+    if (existingState != nullptr)
+    {
+        m_scriptingVM =
+            rivestd::make_unique<ScriptingVM>(m_scriptingContext.get(),
+                                              existingState);
+        m_luaState = existingState;
+    }
+    else
+    {
+        m_scriptingVM =
+            rivestd::make_unique<ScriptingVM>(m_scriptingContext.get());
+        m_luaState = m_scriptingVM->state();
+        initializeLuaData(m_luaState, m_ViewModels);
+    }
 }
 
 void File::cleanupScriptingVM()
diff --git a/src/lua/rive_lua_libs.cpp b/src/lua/rive_lua_libs.cpp
index 0b7d466..66f5565 100644
--- a/src/lua/rive_lua_libs.cpp
+++ b/src/lua/rive_lua_libs.cpp
@@ -342,13 +342,24 @@
     luaL_sandboxthread(state);
 }
 
-ScriptingVM::ScriptingVM(ScriptingContext* context) : m_context(context)
+ScriptingVM::ScriptingVM(ScriptingContext* context) :
+    m_context(context), m_ownsState(true)
 {
     m_state = lua_newstate(l_alloc, nullptr);
     init(m_state, m_context);
 }
 
-ScriptingVM::~ScriptingVM() { lua_close(m_state); }
+ScriptingVM::ScriptingVM(ScriptingContext* context, lua_State* existingState) :
+    m_state(existingState), m_context(context), m_ownsState(false)
+{}
+
+ScriptingVM::~ScriptingVM()
+{
+    if (m_ownsState)
+    {
+        lua_close(m_state);
+    }
+}
 
 static int register_module(lua_State* L)
 {