MrxState
Module: mrxstate.lua
Overview
The MrxState module manages the global state transitions and lifecycle events in the game. It handles fading effects, player input suppression, and coordination of various states such as cinematic sequences, streaming, tethering, and game readiness. This module is crucial for ensuring smooth transitions between different gameplay phases.
Inheritance
- Inherits from:
none(base/utility module) - Imports:
MrxUtil,MrxGui,MrxSoundCategories,MrxVoSequence,MrxMunitionsPickup,MrxGuiInterface
Instance pattern
This is a stateless singleton module — no Create/setmetatable/tInstance/uGuid. All state lives in module-level globals: the _States table (one entry per state, each holding its own nRefCount, bLocked, Enter/Exit closures, and callback queues), plus the global-lock/fade flags (_bGloballyLocked, _bGloballyFading, _bStateComplete) and the two global callback queues (_tGlobalEnterCallbacks, _tGlobalExitCallbacks).
Module constants
Confirmed at the top of the source:
State enum (the nState argument every Enter/Exit/SafeEnter/etc. takes):
| Constant | Value | sName in _States | Enter behavior |
|---|---|---|---|
STATE_NONE | 0 | — (no _States entry) | not a real state; sentinel only |
STATE_CINEMATIC | 1 | "STATE_CINEMATIC" | completes immediately (_StateComplete) |
STATE_WAITFORSTREAMING | 2 | "STATE_WAITFORSTREAMING" | Sys.RequestGameState("WaitForStreaming"), then waits for the "WaitForStreaming" "exit" Event.GameStateChange |
STATE_WAITFORTETHER | 3 | "STATE_WAITFORTETHER" | Sys.RequestGameState("WaitForTether"), waits for the "WaitForTether" "exit" game-state change |
STATE_WAITFORGAME | 4 | "STATE_WAITFORGAME" | completes immediately (_StateComplete) |
STATE_NONE = 0has no entry in_States, soEnter(STATE_NONE, ...)/GetStateName(STATE_NONE)returnfalse/nil— it’s a sentinel, not a usable state. The four usable states are1–4.STATE_WAITFORSTREAMING(2) is the one the mission-load path enters/exits:WifMissionFlow.UnlockMissioncallsMrxState.Enter(MrxState.STATE_WAITFORSTREAMING, oMission.Activate, ...)and the mission’sfOnAssetsLoadedcallsMrxState.Exit(STATE_WAITFORSTREAMING)+Exit(STATE_WAITFORGAME).
Fade timing constants (seconds; used by _GlobalEnter/_GlobalExit): _nQuickFadeOutTime = 0.1, _nQuickFadeInTime = 0.5, _nLongFadeOutTime = 1.1, _nLongFadeInTime = 1.1. _bEnableFade (default true) and _bUseQuickFade (default false) gate whether a fade happens and which timing pair is used.
Log markers (load-probe / diagnostics)
This module emits the world-load / state-transition markers that log-analysis tooling keys on. Exact strings, confirmed from source:
- Global transition (prefix
###!):"###! GlobalEnter - Begin"(start of_GlobalEnter),"###! GlobalEnter - Complete"(end of_CompleteEnterand end of_GlobalExit’s fade-in timer — note this string is printed in two places),"###! GlobalExit - Begin"(start of_GlobalExit),"###! GlobalExit - Complete"(end of_GlobalExit’s fade-in timer). - State/refcount (prefix
@@@@@@@@@@ MrxState.): each line embedsGetStateName(nState)and the currentnRefCount:Enter:"@@@@@@@@@@ MrxState.Enter: state <NAME> (refcount=<n>)"— printed after the increment.Exit:"@@@@@@@@@@ MrxState.Exit: state <NAME> (refcount=<n>)"— printed after the decrement; the unpaired case instead prints"@@@@@@@@@@ MrxState.Exit: UNPAIRED EXIT to state <NAME>"._StateComplete:"@@@@@@@@@@ MrxState._StateComplete: state <NAME>, about to _AttemptGlobalExit"._AttemptGlobalExit:"@@@@@@@@@@ MrxState._AttemptGlobalExit", then one of"... not globally locked, bailing out","... state <NAME> still active .. (refcount=<n>,bLocked=<b>)", or"... all states exited; success".
- Both
EnterandExitalso callDebug.Printf(Debug.GetCallstack())on the line immediately after their refcount line — see the per-function notes below.
Other tooling (the
loadprobelog analyzer) matches these exact strings to decide how far the world-load got. Do not change the marker text if you hook or wrapEnter/Exit/_GlobalEnter/_GlobalExit, and avoid suppressing them — downstream analysis depends on them appearing verbatim.
Functions
_GlobalEnter(fComplete, tData)
Handles the global enter transition by fading out, ducking audio, suppressing player input, and setting characters invincible. Logs ###! GlobalEnter - Begin and ###! GlobalEnter - Complete.
_GlobalExit()
Handles the global exit transition by fading in, restoring player input, and running global-exit callbacks. Logs ###! GlobalExit - Begin and ###! GlobalExit - Complete.
Reset()
Resets various internal flags and callback tables to their initial states.
_StateComplete(nState)
Completes a state transition by calling the state’s exit function and attempting a global exit if all states are exited. Logs detailed debug information about the state completion process.
Enter(nState, fEnterCompleteCallback, tEnterCompleteCallbackData, fReadyToExitCallback, tReadyToExitCallbackData)
Increments _States[nState].nRefCount and enters/re-enters the state. Returns false for an unknown nState. The four callback args each accept the literal string "nil" as a stand-in for nil (so they can survive being requeued through _tGlobalEnterCallbacks while a fade is in progress — if _bGloballyFading is set, the whole call is deferred into that queue and returns false). On the first state entered (when neither _bGloballyLocked nor _bGloballyFading is set) it takes the global lock, sets the fading flag, and kicks off _GlobalEnter(_CompleteEnter, ...) (the fade-out). Subsequent Enter calls while already locked just queue their callbacks and bump the refcount.
- Confirmed: calls
Debug.Printf(Debug.GetCallstack())unconditionally on every non-deferred invocation (the line right after the refcount log), in addition to the state-name/refcount marker line. Worth knowing before wrapping this function heavily or calling it in a tight loop — it’s a real, if minor, per-call cost, and it’s the highest-frequency function in this file to log through if you’re also bracketing it with your own diagnostic wrapper for an unrelated investigation (stacking enough hooks nearEnter/Exitwas one contributing factor in a real crash encountered while debugging a separate briefing-flow issue — see the Custom Contract deep dive).
_CompleteEnter(tStateData)
Completes the enter transition by calling the state’s enter function and processing any queued enter callbacks. Logs ###! GlobalEnter - Complete.
Exit(nState, fCallback, tCallbackData)
Decrements _States[nState].nRefCount. Unpaired-exit guard: if the refcount is already <= 0, it logs "UNPAIRED EXIT to state <NAME>", calls fCallback immediately, and returns false without decrementing — so an over-Exit is a no-op with a warning, not an underflow. Queues fCallback into _tGlobalExitCallbacks (to run after the eventual _GlobalExit fade-in), and only when the refcount hits exactly 0 does it call that state’s Exit() closure, then _AttemptGlobalExit(). Like Enter, if _bGloballyFading is set the call is deferred into _tGlobalEnterCallbacks instead. _AttemptGlobalExit runs the real global exit (fade-in + _tGlobalExitCallbacks) only once every state’s nRefCount is 0 and no state is bLocked.
- Confirmed: also calls
Debug.Printf(Debug.GetCallstack())unconditionally on every call, both the normal case and the “unpaired exit” warning case — same caution asEnterabove.
_AttemptGlobalExit()
Attempts to perform a global exit if no states are active and the game is not globally locked. Logs detailed debug information about the attempt.
_GetTotalRefCount()
Returns the total reference count across all states.
IsLocked()
Checks if the game is globally locked.
SetQuickFade(bEnable)
Enables or disables quick fading effects.
EnableFade(bEnable)
Enables or disables global fade effects.
PrintStatus()
Logs the current status of all states, indicating which are active.
GetStateName(nState)
Returns the name of a specified state.
_StateComplete(nState)
The “ready to exit” trigger for a state — runs its tReadyToExitCallbacks and then _AttemptGlobalExit(). Clears the state’s bLocked flag. STATE_WAITFORTETHER is special-cased: its refcount is forced to 0 here (it can be entered multiple times but a single tether resolution clears them all at once).
SafeEnter(nState) / SafeEnterCallback(nState) / SafeExit(nState)
A deferred-exit wrapper for the case where an Exit might arrive before its matching Enter has finished. SafeEnter calls Enter(nState, SafeEnterCallback, {nState}) and increments the state’s safeEnterCount when the enter completes. SafeExit either consumes one pending safe-enter (decrement safeEnterCount + real Exit) or, if none is pending yet, increments forceExitCount — a “you owe me an exit” tally that SafeEnterCallback pays down as safe-enters complete. Use this pair instead of raw Enter/Exit when the enter/exit ordering isn’t guaranteed.
AddGlobalExitCallback(fCallback, tCallbackArgs)
Registers a callback to fire on the next global exit. If the game is not currently _bGloballyLocked, it runs the callback immediately instead of queuing it — confirmed used by MrxTaskContract.AssetsLoaded to run a contract’s Activated right after its assets finish loading, whether or not a load transition is in flight.
Events
- No module-level
Event.*subscription. The onlyEvent.Create(Event.GameStateChange, ...)calls live inside theEnterclosures forSTATE_WAITFORSTREAMINGandSTATE_WAITFORTETHER, each wired to_StateCompletefor that state — they exist only while those states are active, not as a persistent listener. (The previous version of this page implied a standingGameStateChangesubscription; corrected.) Event.TimerRelativeis used by_GlobalEnter/_GlobalExitto time the fades (using the fade-timing constants above) — these are one-shot scheduled timers, not subscriptions.
Notes for modders
- Do not alter or suppress the log markers listed above — the
loadprobetool matches them verbatim to classify how far a world-load got. This is the single most important thing to preserve in this file. - The refcount is per-state and additive: N
Enters on the same state need NExits before that state clears. A global exit (fade back in) only happens once all four states are at refcount 0 — a single stuckEnteranywhere holds the whole game in the faded/locked state. If a load appears to hang faded-out, a state left at refcount > 0 is the first thing to check (PrintStatus()dumps every active state). SetQuickFade(true)before anEnterswaps the 1.1s long fade for the 0.1s/0.5s quick fade for that transition;EnableFade(false)skips the visual fade entirely (still honors the timing for sequencing).- Prefer
SafeEnter/SafeExitover rawEnter/Exitwhen an exit can race ahead of its enter.