fix(unreal): Unreal layout alignment fix and other small things (#13028) b20256f368
* fixed other align types

* merge build changes

* now copy over gm headers as well

* fixed gm crash with new canvas gms

* Fixed nested view models not getting the owning artboard set. Started getting nested artboard defaults. Fixed getting obj ptr values.

* typo

* always add artboard default

* list index now doesnt create a variable. instead you get the value via an async task

* better display name

* undef

* command server now supports passing luaua output through

* now get the artboard name for artboard defaults

* new getting index property correctly

* downgraded unsettle log

* revert no longer needed changes

* missed ptr

* Potential fix for pull request finding

* Potential fix for pull request finding

* removed uneeded elseif, removed commented code

* removed duplicate call

* several issues found in PR

* Potential fix for pull request finding

* Potential fix for pull request finding

* some more small cleanup

* uneeded change

* missed some files

* removed uneeded comments

* guard function input

* better guard

* better set owning artboard model

* dont subscrive when its not needed.

* removed uneeded comment and added copyright

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
diff --git a/.rive_head b/.rive_head
index 892e121..786a329 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-25b5033ad80758aeb3ad9acdf160a72ac35ef96b
+b20256f36800e04af741647860cb9013d148c76e
diff --git a/include/rive/command_queue.hpp b/include/rive/command_queue.hpp
index 8abc809..dee88b7 100644
--- a/include/rive/command_queue.hpp
+++ b/include/rive/command_queue.hpp
@@ -14,6 +14,7 @@
 #include <condition_variable>
 #include <cstdint>
 #include <functional>
+#include <memory>
 #include <mutex>
 #include <string>
 #include <vector>
@@ -41,6 +42,7 @@
 class ArtboardInstance;
 class StateMachineInstance;
 class CommandServer;
+class ScriptingContext;
 
 RIVE_DEFINE_HANDLE(FontHandle);
 RIVE_DEFINE_HANDLE(FileHandle);
@@ -55,6 +57,15 @@
 using CommandServerCallback = std::function<void(CommandServer*)>;
 using CommandServerDrawCallback = std::function<void(DrawKey, CommandServer*)>;
 
+// Creates the ScriptingContext used for a loaded file's Lua VM. Invoked on the
+// command server thread with the server's Factory so the context is built with
+// the correct factory on the correct thread. Return nullptr (or leave the
+// factory empty) to fall back to the default CPPRuntimeScriptingContext. Used
+// by hosts (e.g. Unreal) to redirect Lua console/error output to their own
+// logging.
+using ScriptingContextFactory =
+    std::function<std::unique_ptr<ScriptingContext>(Factory*)>;
+
 struct ViewModelEnum
 {
     std::string name;
@@ -351,9 +362,15 @@
     CommandQueue();
     ~CommandQueue();
 
-    FileHandle loadFile(std::vector<uint8_t> rivBytes,
-                        FileListener* listener = nullptr,
-                        uint64_t requestId = 0);
+    FileHandle loadFile(
+        std::vector<uint8_t> rivBytes,
+        FileListener* listener = nullptr,
+        uint64_t requestId = 0
+#ifdef WITH_RIVE_SCRIPTING
+        ,
+        ScriptingContextFactory scriptingContextFactory = nullptr
+#endif
+    );
 
     void deleteFile(FileHandle, uint64_t requestId = 0);
 
@@ -1008,6 +1025,9 @@
     ObjectStream<rcp<AudioSource>> m_externalAudioSources;
     ObjectStream<rcp<Font>> m_externalFonts;
     ObjectStream<std::vector<uint8_t>> m_byteVectors;
+#ifdef WITH_RIVE_SCRIPTING
+    ObjectStream<ScriptingContextFactory> m_scriptingContextFactories;
+#endif
     ObjectStream<PointerEvent> m_pointerEvents;
     ObjectStream<std::string> m_names;
     ObjectStream<CommandServerCallback> m_callbacks;
diff --git a/include/rive/logging_scripting_context.hpp b/include/rive/logging_scripting_context.hpp
new file mode 100644
index 0000000..e8a5d9e
--- /dev/null
+++ b/include/rive/logging_scripting_context.hpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 Rive
+ */
+
+#ifndef _RIVE_LOGGING_SCRIPTING_CONTEXT_HPP_
+#define _RIVE_LOGGING_SCRIPTING_CONTEXT_HPP_
+
+#include "rive/command_queue.hpp"
+
+#include <cstddef>
+#include <functional>
+
+// A lua-free bridge for routing a script's console/error output to a host
+// logger.
+
+namespace rive
+{
+enum class ScriptingLogLevel
+{
+    info,
+    warn,
+    error,
+};
+
+// Receives a single fully-assembled UTF-8 log line from a script. The line has
+// no trailing newline. Invoked on the command server thread. `data` is only
+// valid for the duration of the call (copy it if you need to retain it).
+using ScriptingLogSink = std::function<
+    void(ScriptingLogLevel level, const char* data, size_t length)>;
+
+// Builds a ScriptingContextFactory whose contexts forward all script console
+// output as ScriptingLogLevel::info lines and runtime/Lua errors as
+// ScriptingLogLevel::error lines to `sink`.
+ScriptingContextFactory makeLoggingScriptingContextFactory(
+    ScriptingLogSink sink);
+} // namespace rive
+
+#endif
diff --git a/src/command_queue.cpp b/src/command_queue.cpp
index 25d2148..c9df381 100644
--- a/src/command_queue.cpp
+++ b/src/command_queue.cpp
@@ -35,9 +35,15 @@
 
 CommandQueue::~CommandQueue() {}
 
-FileHandle CommandQueue::loadFile(std::vector<uint8_t> rivBytes,
-                                  FileListener* listener,
-                                  uint64_t requestId)
+FileHandle CommandQueue::loadFile(
+    std::vector<uint8_t> rivBytes,
+    FileListener* listener,
+    uint64_t requestId
+#ifdef WITH_RIVE_SCRIPTING
+    ,
+    ScriptingContextFactory scriptingContextFactory
+#endif
+)
 {
     auto handle = reinterpret_cast<FileHandle>(++m_currentFileHandleIdx);
 
@@ -54,6 +60,9 @@
     m_commandStream << handle;
     m_commandStream << requestId;
     m_byteVectors << std::move(rivBytes);
+#ifdef WITH_RIVE_SCRIPTING
+    m_scriptingContextFactories << std::move(scriptingContextFactory);
+#endif
 
     return handle;
 }
diff --git a/src/command_server.cpp b/src/command_server.cpp
index cd88272..3903520 100644
--- a/src/command_server.cpp
+++ b/src/command_server.cpp
@@ -676,11 +676,25 @@
                 commandStream >> handle;
                 commandStream >> requestId;
                 m_commandQueue->m_byteVectors >> rivBytes;
+#ifdef WITH_RIVE_SCRIPTING
+                ScriptingContextFactory scriptingContextFactory;
+                m_commandQueue->m_scriptingContextFactories >>
+                    scriptingContextFactory;
+#endif
                 lock.unlock();
 #ifdef WITH_RIVE_SCRIPTING
                 std::cout << "Rive: Command Server Scripting Enabled.\n";
-                auto scriptingContext =
-                    std::make_unique<CPPRuntimeScriptingContext>(m_factory);
+                // Use the host-provided scripting context when supplied (e.g.
+                // Unreal routes console/error output to UE_LOG); otherwise fall
+                // back to the default CPP runtime context.
+                std::unique_ptr<ScriptingContext> scriptingContext =
+                    scriptingContextFactory ? scriptingContextFactory(m_factory)
+                                            : nullptr;
+                if (scriptingContext == nullptr)
+                {
+                    scriptingContext =
+                        std::make_unique<CPPRuntimeScriptingContext>(m_factory);
+                }
                 scriptingContext->setRenderContext(m_factory);
                 auto vm = make_rcp<ScriptingVM>(std::move(scriptingContext));
                 rcp<rive::File> file = rive::File::import(rivBytes,
diff --git a/src/lua/logging_scripting_context.cpp b/src/lua/logging_scripting_context.cpp
new file mode 100644
index 0000000..41d6d36
--- /dev/null
+++ b/src/lua/logging_scripting_context.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 Rive
+ */
+
+#include "rive/logging_scripting_context.hpp"
+
+#ifdef WITH_RIVE_SCRIPTING
+#include "rive/lua/rive_lua_libs.hpp"
+
+#include <cstring>
+#include <string>
+#include <utility>
+
+namespace rive
+{
+namespace
+{
+// Routes a script's console output and errors to a host-provided sink instead
+// of stdout/stderr. print() may be called several times per line, so text is
+// accumulated between printBeginLine()/printEndLine() and flushed as one line.
+class LoggingScriptingContext : public CPPRuntimeScriptingContext
+{
+public:
+    LoggingScriptingContext(Factory* factory, ScriptingLogSink sink) :
+        CPPRuntimeScriptingContext(factory), m_sink(std::move(sink))
+    {}
+
+    void printBeginLine(lua_State*) override { m_line.clear(); }
+
+    void print(Span<const char> data) override
+    {
+        m_line.append(data.data(), data.size());
+    }
+
+    void printEndLine() override
+    {
+        m_sink(ScriptingLogLevel::info, m_line.c_str(), m_line.size());
+        m_line.clear();
+    }
+
+    void printError(lua_State* state) override
+    {
+        const char* error = lua_tostring(state, -1);
+        if (error != nullptr)
+        {
+            m_sink(ScriptingLogLevel::error, error, std::strlen(error));
+        }
+    }
+
+private:
+    ScriptingLogSink m_sink;
+    std::string m_line;
+};
+} // namespace
+
+ScriptingContextFactory makeLoggingScriptingContextFactory(
+    ScriptingLogSink sink)
+{
+    if (!sink)
+    {
+        return nullptr;
+    }
+    return [sink = std::move(sink)](
+               Factory* factory) -> std::unique_ptr<ScriptingContext> {
+        return std::make_unique<LoggingScriptingContext>(factory, sink);
+    };
+}
+} // namespace rive
+
+#else // !WITH_RIVE_SCRIPTING
+
+namespace rive
+{
+ScriptingContextFactory makeLoggingScriptingContextFactory(ScriptingLogSink)
+{
+    return nullptr;
+}
+} // namespace rive
+
+#endif