Segment Lua runtime authority - #4
Conversation
📝 WalkthroughWalkthroughSplits the single ChangesDual Authoritative/Client Lua Runtime Architecture
Sequence Diagram(s)sequenceDiagram
participant Engine
participant AuthLuaMgr as Authoritative LuaManager
participant ClientLuaMgr as Client LuaManager
participant LuaEventRouter as MWNet::LuaEventRouter
participant NetworkServer as mwnet::Server
rect rgba(70, 130, 180, 0.5)
note over Engine: prepareEngine (Host role)
Engine->>AuthLuaMgr: new LuaManager(RuntimeMode::AuthoritativeServer)
Engine->>ClientLuaMgr: new LuaManager(RuntimeMode::Client)
Engine->>LuaEventRouter: setLuaEventRouter(networkManager.getLuaEventRouter())
end
rect rgba(60, 179, 113, 0.5)
note over ClientLuaMgr: Client script calls sendGlobalEvent
ClientLuaMgr->>LuaEventRouter: sendGlobalEventToAuthoritativeRuntime(name, data)
alt same process (Host)
LuaEventRouter->>AuthLuaMgr: queueNetworkedGlobalEvent(name, data)
else remote (Client→DedicatedServer)
LuaEventRouter->>NetworkServer: queueMessage(GlobalEventDataMessageEntry)
NetworkServer->>LuaEventRouter: receiveNetworkedGlobalEvent(clientIdx, name, data)
LuaEventRouter->>AuthLuaMgr: queueNetworkedGlobalEvent(name, data)
end
end
rect rgba(188, 143, 143, 0.5)
note over Engine: frame()
Engine->>AuthLuaMgr: synchronizedUpdate()
Engine->>ClientLuaMgr: synchronizedUpdate()
Engine->>AuthLuaMgr: update()
Engine->>ClientLuaMgr: allowUpdate() via LuaWorker thread
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/openmw/mwbase/environment.hpp`:
- Around line 157-158: The getLuaManager() method has a return type of
Misc::NotNullPtr<LuaManager> which guarantees a non-null return value, but the
current implementation can return nullptr when mLuaManager is null or falsy in
the ternary expression. Fix this by throwing an exception when mLuaManager is
unset, similar to how getAuthoritativeLuaManager() and getClientLuaManager() are
implemented. Replace the ternary operator with a check that throws when the
value is not set, ensuring the method's non-null contract is upheld.
In `@apps/openmw/mwlua/engineevents.cpp`:
- Around line 23-25: The Visitor constructor currently only receives the
localScriptsEnabled flag, but the OnActive handler relies on
NetworkManager::isDedicatedServer() to route player-active events to global
scripts, which fails in host mode where the authoritative Lua runtime is not a
dedicated server. Modify the Visitor constructor to accept an additional
parameter indicating Lua authority (whether this runtime is authoritative for
global scripts), store it as a member variable alongside mGlobalScripts and
mLocalScriptsEnabled, and then update the OnActive handler to use this authority
flag instead of isDedicatedServer() when determining whether to call
mGlobalScripts.playerAdded(...).
In `@apps/openmw/mwlua/luaevents.cpp`:
- Around line 38-54: The local event batches (mLocalEventBatch and
mNewLocalEventBatch) in the LuaEvents class are not being serialized when the
game saves, causing pending local events to be lost. You need to add
serialization and deserialization handling for these local event batches,
similar to how global scripts are handled with
getLuaManagerForGlobalScripts()->write(). Add write() and read() methods (or
update existing save/load mechanisms) in the LuaEvents class to persist
mLocalEventBatch and mNewLocalEventBatch to the save file and restore them on
load, ensuring local events queued just before saving are preserved across game
sessions.
In `@apps/openmw/mwlua/luamanagerimp.cpp`:
- Around line 376-380: The reloadAllScripts() function is calling
reloadAllScriptsImpl() immediately, which causes reentrancy issues when
Lua-facing reload paths execute during the authoritative Lua runtime. Instead of
directly invoking reloadAllScriptsImpl(), set a flag to defer the reload
operation and service it later in the authoritative server's control flow.
Specifically, set the reload flag unconditionally in reloadAllScripts() (for
both runtimes), and then check and execute the actual reload in the
isAuthoritativeServer() branch after calling applyDelayedActions() to ensure it
happens at the safe point after synchronizedUpdate completes. Apply this same
pattern to the additional locations mentioned at lines 924-929.
In `@apps/openmw/mwlua/objectbindings.cpp`:
- Around line 418-421: The hasScript lambda function returns false
unconditionally for global objects, but this is inconsistent with the
removeScript lambda which throws an exception explaining the authority boundary
issue. Update the hasScript lambda (at objectT["hasScript"]) to throw the same
std::runtime_error exception with an appropriate message explaining that global
object scripts cannot be accessed due to authority boundary restrictions, rather
than silently returning false, so that Lua code receives clear feedback about
why the operation cannot be performed.
In `@apps/openmw/mwnet/luaeventrouter.cpp`:
- Around line 17-23: The issue is that the LuaEventRouter is dropping valid
networked global events during the transitional Host state when the
authoritative Lua manager has not yet been registered (the early return in the
environment.hasAuthoritativeLuaManager() check). Instead of immediately
returning and discarding the event, implement a buffering mechanism to queue
these events and flush them once the authoritative Lua manager becomes
available, or alternatively implement an explicit NACK or retry mechanism to
ensure events are redelivered. Modify the logic around the
environment.hasAuthoritativeLuaManager() check to cache incoming events in a
queue during the bootstrap phase, then process and clear that queue once the
authoritative manager registers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d0be1e4d-ef15-4a54-a355-dcdaba5f35e2
📒 Files selected for processing (44)
apps/components_tests/lua/testconfiguration.cppapps/openmw/CMakeLists.txtapps/openmw/engine.cppapps/openmw/engine.hppapps/openmw/mwbase/environment.cppapps/openmw/mwbase/environment.hppapps/openmw/mwbase/luaeventrouter.hppapps/openmw/mwbase/luamanager.hppapps/openmw/mwdialogue/dialoguemanagerimp.cppapps/openmw/mwgui/console.cppapps/openmw/mwgui/debugwindow.cppapps/openmw/mwgui/inventorywindow.cppapps/openmw/mwgui/settingswindow.cppapps/openmw/mwgui/windowmanagerimp.cppapps/openmw/mwinput/actionmanager.cppapps/openmw/mwinput/controllermanager.cppapps/openmw/mwinput/keyboardmanager.cppapps/openmw/mwinput/mousemanager.cppapps/openmw/mwlua/context.hppapps/openmw/mwlua/corebindings.cppapps/openmw/mwlua/debugbindings.cppapps/openmw/mwlua/engineevents.cppapps/openmw/mwlua/engineevents.hppapps/openmw/mwlua/luaevents.cppapps/openmw/mwlua/luaevents.hppapps/openmw/mwlua/luamanagerimp.cppapps/openmw/mwlua/luamanagerimp.hppapps/openmw/mwlua/objectbindings.cppapps/openmw/mwlua/types/player.cppapps/openmw/mwnet/client.hppapps/openmw/mwnet/luaeventrouter.cppapps/openmw/mwnet/luaeventrouter.hppapps/openmw/mwnet/networkmanager.hppapps/openmw/mwnet/server.hppapps/openmw/mwscript/miscextensions.cppapps/openmw/mwscript/transformationextensions.cppapps/openmw/mwstate/statemanagerimp.cppapps/openmw/mwworld/actionteleport.cppapps/openmw/mwworld/livecellref.cppapps/openmw/mwworld/scene.cppapps/openmw/mwworld/worldimp.cppapps/openmw/mwworld/worldmodel.cppcomponents/lua/configuration.cppcomponents/lua/configuration.hpp
| Misc::NotNullPtr<LuaManager> getLuaManager() const { return mLuaManager ? &mLuaManager->get() : nullptr; } | ||
|
|
There was a problem hiding this comment.
getLuaManager() violates its non-null return contract.
getLuaManager() can return nullptr even though its return type is Misc::NotNullPtr<LuaManager>, which can trigger assertions/crashes in callers expecting a guaranteed value. Align this accessor with getAuthoritativeLuaManager()/getClientLuaManager() and throw when unset (or make the return type nullable).
Suggested fix
- Misc::NotNullPtr<LuaManager> getLuaManager() const { return mLuaManager ? &mLuaManager->get() : nullptr; }
+ Misc::NotNullPtr<LuaManager> getLuaManager() const
+ {
+ if (!mLuaManager)
+ throw std::logic_error("Lua manager is not registered");
+ return &mLuaManager->get();
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Misc::NotNullPtr<LuaManager> getLuaManager() const { return mLuaManager ? &mLuaManager->get() : nullptr; } | |
| Misc::NotNullPtr<LuaManager> getLuaManager() const | |
| { | |
| if (!mLuaManager) | |
| throw std::logic_error("Lua manager is not registered"); | |
| return &mLuaManager->get(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwbase/environment.hpp` around lines 157 - 158, The
getLuaManager() method has a return type of Misc::NotNullPtr<LuaManager> which
guarantees a non-null return value, but the current implementation can return
nullptr when mLuaManager is null or falsy in the ternary expression. Fix this by
throwing an exception when mLuaManager is unset, similar to how
getAuthoritativeLuaManager() and getClientLuaManager() are implemented. Replace
the ternary operator with a check that throws when the value is not set,
ensuring the method's non-null contract is upheld.
| explicit Visitor(GlobalScripts& globalScripts, bool localScriptsEnabled) | ||
| : mGlobalScripts(globalScripts) | ||
| , mLocalScriptsEnabled(localScriptsEnabled) |
There was a problem hiding this comment.
Route player-active global handlers by Lua authority, not dedicated-server mode.
The visitor only receives the local-script flag, so OnActive still uses NetworkManager::isDedicatedServer() for the player branch. In host mode the authoritative Lua manager owns GLOBAL scripts but is not a dedicated server, so mGlobalScripts.playerAdded(...) is skipped. Call the global handler for the authoritative/global runtime instead; client runtimes have no global scripts selected.
Suggested fix
if (ptr.getCellRef().getRefId() == "player")
{
- const auto& netMan = MWBase::Environment::get().getNetworkManager();
- if (!netMan->isDedicatedServer())
- {
- }
- else
- {
- mGlobalScripts.playerAdded(GObject(ptr));
- }
+ mGlobalScripts.playerAdded(GObject(ptr));
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwlua/engineevents.cpp` around lines 23 - 25, The Visitor
constructor currently only receives the localScriptsEnabled flag, but the
OnActive handler relies on NetworkManager::isDedicatedServer() to route
player-active events to global scripts, which fails in host mode where the
authoritative Lua runtime is not a dedicated server. Modify the Visitor
constructor to accept an additional parameter indicating Lua authority (whether
this runtime is authoritative for global scripts), store it as a member variable
alongside mGlobalScripts and mLocalScriptsEnabled, and then update the OnActive
handler to use this authority flag instead of isDedicatedServer() when
determining whether to call mGlobalScripts.playerAdded(...).
| void LuaEvents::addLocalEvent(Local event) | ||
| { | ||
| if (!mLocalEventsEnabled) | ||
| return; | ||
| mNewLocalEventBatch.push_back(std::move(event)); | ||
| } | ||
|
|
||
| void LuaEvents::callEventHandlers() | ||
| { | ||
| for (const Global& e : mGlobalEventBatch) | ||
| mGlobalScripts.receiveEvent(e.mEventName, e.mEventData); | ||
| mGlobalEventBatch.clear(); | ||
| if (!mLocalEventsEnabled) | ||
| { | ||
| mLocalEventBatch.clear(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Preserve client-owned local event batches across saves.
With this guard, local event batches now live only in the client Lua manager, but the provided save path serializes only getLuaManagerForGlobalScripts()->write(). Pending mLocalEventBatch / mNewLocalEventBatch entries sent just before saving are no longer written or restored. Please add client Lua event serialization/load handling, or intentionally migrate local queued events into the local-script save path.
🧰 Tools
🪛 Cppcheck (2.21.0)
[style] 39-39: The function 'onUseItem' is never used.
(unusedFunction)
[style] 43-43: The function 'onNewExterior' is never used.
(unusedFunction)
[style] 38-38: The function 'addLocalEvent' is never used.
(unusedFunction)
[style] 45-45: The function 'callEventHandlers' is never used.
(unusedFunction)
[style] 50-50: The function 'addGlobalEvent' is never used.
(unusedFunction)
[style] 51-51: The function 'addMenuEvent' is never used.
(unusedFunction)
[style] 38-38: The function 'stateChanged' is never used.
(unusedFunction)
[style] 40-40: The function 'consoleCommand' is never used.
(unusedFunction)
[style] 46-46: The function 'onViewportResized' is never used.
(unusedFunction)
[style] 48-48: The function 'uiModeChanged' is never used.
(unusedFunction)
[style] 51-51: The function 'isLObject' is never used.
(unusedFunction)
[style] 47-47: The function 'setVarByInt' is never used.
(unusedFunction)
[style] 50-50: The function 'getId' is never used.
(unusedFunction)
[style] 38-38: The function 'getGridX' is never used.
(unusedFunction)
[style] 39-39: The function 'getGridY' is never used.
(unusedFunction)
[style] 40-40: The function 'isExterior' is never used.
(unusedFunction)
[style] 41-41: The function 'isQuasiExterior' is never used.
(unusedFunction)
[style] 42-42: The function 'hasWater' is never used.
(unusedFunction)
[style] 43-43: The function 'noSleep' is never used.
(unusedFunction)
[style] 44-44: The function 'getRegion' is never used.
(unusedFunction)
[style] 45-45: The function 'getNameId' is never used.
(unusedFunction)
[style] 46-46: The function 'getDisplayName' is never used.
(unusedFunction)
[style] 47-47: The function 'getDescription' is never used.
(unusedFunction)
[style] 48-48: The function 'getMood' is never used.
(unusedFunction)
[style] 49-49: The function 'getWaterHeight' is never used.
(unusedFunction)
[style] 53-53: The function 'getExteriorCellLocation' is never used.
(unusedFunction)
[style] 39-39: The function 'unsetRefNum' is never used.
(unusedFunction)
[style] 47-47: The function 'getClass' is never used.
(unusedFunction)
[style] 46-46: The function 'remove' is never used.
(unusedFunction)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwlua/luaevents.cpp` around lines 38 - 54, The local event
batches (mLocalEventBatch and mNewLocalEventBatch) in the LuaEvents class are
not being serialized when the game saves, causing pending local events to be
lost. You need to add serialization and deserialization handling for these local
event batches, similar to how global scripts are handled with
getLuaManagerForGlobalScripts()->write(). Add write() and read() methods (or
update existing save/load mechanisms) in the LuaEvents class to persist
mLocalEventBatch and mNewLocalEventBatch to the save file and restore them on
load, ensuring local events queued just before saving are preserved across game
sessions.
| if (isAuthoritativeServer()) | ||
| { | ||
| applyDelayedActions(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Defer authoritative reloads instead of reloading inline.
reloadAllScripts() can be reached from Lua-facing reload paths while the authoritative Lua runtime is executing. Calling reloadAllScriptsImpl() immediately mutates script cache/containers reentrantly and also violates the “end of next synchronizedUpdate” contract. Set the flag for both runtimes and service it in the authoritative branch after delayed actions.
Suggested fix
if (isAuthoritativeServer())
{
applyDelayedActions();
+ if (mReloadAllScriptsRequested)
+ {
+ reloadAllScriptsImpl();
+ mReloadAllScriptsRequested = false;
+ }
return;
} void LuaManager::reloadAllScripts()
{
- if (ownsClientScriptContexts())
- mReloadAllScriptsRequested = true;
- else
- reloadAllScriptsImpl();
+ mReloadAllScriptsRequested = true;
}Also applies to: 924-929
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwlua/luamanagerimp.cpp` around lines 376 - 380, The
reloadAllScripts() function is calling reloadAllScriptsImpl() immediately, which
causes reentrancy issues when Lua-facing reload paths execute during the
authoritative Lua runtime. Instead of directly invoking reloadAllScriptsImpl(),
set a flag to defer the reload operation and service it later in the
authoritative server's control flow. Specifically, set the reload flag
unconditionally in reloadAllScripts() (for both runtimes), and then check and
execute the actual reload in the isAuthoritativeServer() branch after calling
applyDelayedActions() to ensure it happens at the safe point after
synchronizedUpdate completes. Apply this same pattern to the additional
locations mentioned at lines 924-929.
| objectT["hasScript"] = [](const GObject&, std::string_view) { return false; }; | ||
| objectT["removeScript"] = [](const GObject&, std::string_view) { | ||
| // TODO: global-to-local script removal needs an explicit authority boundary. | ||
| throw std::runtime_error("Global object scripts do not own local script containers"); |
There was a problem hiding this comment.
hasScript returning false is misleading; consider throwing or returning a distinct indicator.
The asymmetry between hasScript (silently returns false) and removeScript (throws with explanation) could cause subtle bugs. A Lua script checking if object:hasScript("myScript") will get false even when the script exists, leading to incorrect control flow.
Consider either:
- Throwing with the same authority-boundary explanation as
removeScript - Returning
nilto indicate "unknown/inaccessible" rather than definitivelyfalse
Option 1: Throw like removeScript
-objectT["hasScript"] = [](const GObject&, std::string_view) { return false; };
+objectT["hasScript"] = [](const GObject&, std::string_view) -> bool {
+ throw std::runtime_error("Global object scripts do not own local script containers");
+};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwlua/objectbindings.cpp` around lines 418 - 421, The hasScript
lambda function returns false unconditionally for global objects, but this is
inconsistent with the removeScript lambda which throws an exception explaining
the authority boundary issue. Update the hasScript lambda (at
objectT["hasScript"]) to throw the same std::runtime_error exception with an
appropriate message explaining that global object scripts cannot be accessed due
to authority boundary restrictions, rather than silently returning false, so
that Lua code receives clear feedback about why the operation cannot be
performed.
| if (!environment.hasAuthoritativeLuaManager()) | ||
| { | ||
| Log(Debug::Error) << "LuaEventRouter: dropping networked global event '" << eventName << "' from client " | ||
| << clientIndex | ||
| << " because authoritative Lua manager is not registered yet (transitional Host state)"; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Avoid lossy handling before authoritative runtime registration.
Line 17–23 drops valid networked global events during host bootstrap; those events are never replayed, so authoritative/client state can diverge. Buffer-and-flush (or explicitly NACK/retry) instead of dropping.
🧰 Tools
🪛 Cppcheck (2.21.0)
[style] 17-17: The function 'getId' is never used.
(unusedFunction)
[style] 18-18: The function 'OnServerClientDisconnected' is never used.
(unusedFunction)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/openmw/mwnet/luaeventrouter.cpp` around lines 17 - 23, The issue is that
the LuaEventRouter is dropping valid networked global events during the
transitional Host state when the authoritative Lua manager has not yet been
registered (the early return in the environment.hasAuthoritativeLuaManager()
check). Instead of immediately returning and discarding the event, implement a
buffering mechanism to queue these events and flush them once the authoritative
Lua manager becomes available, or alternatively implement an explicit NACK or
retry mechanism to ensure events are redelivered. Modify the logic around the
environment.hasAuthoritativeLuaManager() check to cache incoming events in a
queue during the bootstrap phase, then process and clear that queue once the
authoritative manager registers.
In-game smoke test notesI did a quick in-game smoke test of the Lua authority split using my wacky portable Linux build system. Startup / host-client pathThe game now reaches playable state as Host. The in-process client connects to the authoritative server: So the basic host runtime + local client endpoint path is alive. Nice. Confirmed working: client/menu -> authoritative GLOBAL eventsI temporarily added a test DREAMWEAVE_TEST_GLOBAL_EVENT = function(firstArg)
print('DREAMWEAVE_TEST_GLOBAL_EVENT firstArg=' .. tostring(firstArg))
end,Then I sent events from both player and menu Lua console contexts: core.sendGlobalEvent('DREAMWEAVE_TEST_GLOBAL_EVENT', 'hello from client')
core.sendGlobalEvent('DREAMWEAVE_TEST_GLOBAL_EVENT', 'hello from menu')The authoritative This confirms the important path we actually cared about here: Known broken: client access to global storageSome built-in client/menu/local scripts still assume Observed errors: and: Root cause: client-side packages still expose This should not be fixed by simply making client global storage active, because that would recreate fake client-owned global state. That would be lying, and we already have enough of that. The remaining work is explicit synchronization/mirroring. Known broken / invalidated:
|
Dreamweave Lua Runtime Authority Segmentation
Summary
This PR establishes explicit Lua runtime authority boundaries for Dreamweave networking work.
The important split is:
GLOBALandLOADscripts.PLAYER,LOCAL,MENU, andCUSTOMscripts.CUSTOMscripts are client-local explicit-attachment scripts. They are not authority-neutral.Host mode now creates both an authoritative Lua runtime and a client Lua runtime. Dedicated server creates only the authoritative runtime. Client mode creates only the client runtime.
This is intentionally not a Lua thread-pool branch. Server/authoritative Lua remains single-threaded. Host and Client keep the existing client Lua worker behavior for the client runtime only.
What Changed
Runtime identity
AuthoritativeServerClientScript filtering
GLOBALandLOADscripts.PLAYER,LOCAL,MENU, andCUSTOMscripts.Package and storage ownership
LOADnow belongs to the authoritative runtime and uses authoritative/global storage.Event authority boundary
openmw.core.sendGlobalEventnow routes by Lua context authority:LuaEventRouterto the authoritative runtime or network path.Host dual runtime
Local script ownership
RefData::getLuaScripts()/LocalScripts/PlayerScriptsare client-local ownership seams.Explicitly Deferred
Script-id mapping / script registry transfer
The full script-id mapping story is deferred.
Local script save/load still depends on script registry metadata that historically lived in the single
LUAMpath. With split runtimes, the authoritative runtime owns globalLUAMdata while the client runtime owns local script containers.The next branch must define how authoritative script registry/config metadata is delivered to clients before local script state is deserialized. The likely model is:
This PR does not implement that network transfer.
Global-to-local script mutation APIs
APIs such as global-context local script attach/remove should eventually route through an explicit authority/client-local boundary.
They should not directly mutate
RefData::getLuaScripts()from authoritative/global contexts.Deferred future shape:
This PR blocks direct local container mutation where it would violate runtime ownership, but does not implement the future boundary-routed semantics.
Client global storage replication
Client local/player/menu scripts do not get a real replicated global storage view in this PR.
If client-side APIs attempt to access global storage before a replication model exists, failure is preferable to silently writing fake client-local global state.
Future work must define global storage serialization/replication semantics.
LuaManager decomposition
This PR exposes that
LuaManageris too broad. It currently owns runtime state, script configuration, packages, events, storage, serialization, local script factories, and engine lifecycle handling.Future work should split responsibilities into smaller components, likely along these lines:
Not In Scope
Testing Checklist
Before merging, test at minimum:
GLOBAL,LOAD,PLAYER,LOCAL,MENU, andCUSTOMscripts present.GLOBALandLOADscripts only.sendGlobalEventfrom:GLOBAL;LOAD;MENU;PLAYER;LOCAL.reloadluafrom MWScript console.reloadLuafrom a client/local context.Known Risk
This branch changes Lua ownership semantics substantially. It is intended as authority-boundary foundation work, not as a final multiplayer scripting model.
The branch should be reviewed with special attention to call sites that still use transitional
getLuaManager()access. Where authority matters, call sites should use explicit authoritative/client accessors or a named boundary.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests