-
Notifications
You must be signed in to change notification settings - Fork 0
API ServiceManager
ServiceManager is the framework's developer dashboard: a lazy-started client UI backed by a server snapshot API, a behavior-tree tick loop (ServiceBrain / βNexusBrainβ), and nine subsystem controllers.
Important
Docs audit note (2026-05): This page was re-audited against FSM/Orchestrator/ServiceManager/init.luau, ServiceBrain.luau, and the subsystem modules.
There are two distinct phases:
-
server registration β
Orchestrator:RegisterComponents()requires the module and callsServiceManager.RegisterServerHandlers(self)so clients can requestServiceManager_ServerSnapshot -
client launch β
Orchestrator:StartServiceManager()requires the same module, callsInitialize(self), thenShow()
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents() -- server snapshot endpoint is registered on the server
-- client only
local dashboard = FSM.Orchestrator:StartServiceManager()
dashboard.SwitchSubsystem("NETWORK")Initialize() instantiates exactly nine root subsystem modules:
| Key | Module | Purpose |
|---|---|---|
TASKS |
TasksModule |
scheduler list, per-task perf, FPS strip |
FSM |
FSMModule |
live + completed FSMs, graph visualizer, transition history |
ENTITY |
EntityModule |
entities, schema keys, version history, data map |
NETWORK |
NetworkModule |
remotes, server callbacks, EventBuses, call history |
DATASTORE |
DataStoreModule |
DataStoreSystem snapshot + operation visibility |
INSIGHTS |
InsightsModule |
analytics summaries / recommendations |
CONSOLE |
ConsoleModule |
console buffer and debug helpers |
LOGS |
LogsModule |
aggregated logger history |
PROFILER |
ProfilerModule |
profiler-facing metrics and drill-downs |
Root layers are registered into ZAxisNavigator in this same order.
Client Orchestrator ServiceManager Server handlers
β β β β
ββ StartServiceManager() βββ β β
β ββ Initialize(self) βββ β
β β β build store, GUI, navigator, ServiceBrain, subsystems
β β β schedule ServiceManagerBrainTick
β β β start polling loop
β ββ Show() ββββββββββββ β
β β ββ Request("ServiceManager_ServerSnapshot") βββ
β β ββββββββββββββββ snapshot βββββββββββββββββββββ
β β β cache _serverData + render active subsystem
ServiceManager is explicitly context-aware.
| Mode | Data source | Notes |
|---|---|---|
"client" |
live client-side Orchestrator objects |
_SyncClientContext() is currently a no-op because data is already local |
"server" |
cached _serverData returned by ServiceManager_ServerSnapshot
|
server mode forces an immediate sync request on mode switch |
Reactive store keys seeded during Initialize():
nexus.mode = "client"nexus.connected = truenexus.activeSubsystem = nil
When the mode changes, ServiceManager intentionally discards drill-down layers because their captured Context tables point at stale client-only or server-only objects.
ServiceBrain.Build() returns the top-level behavior tree used by _TickBrain().
ββββββββββββββ
β _TickBrain β
βββββββ¬βββββββ
βΌ
ββββββββββββββββββββββββββ
β initialized + visible? β
βββββββ¬ββββββββββββββ¬βββββ
β no β yes
βΌ βΌ
ββββββββββ βββββββββββββββββββ
β return β β SyncContextData β
ββββββββββ ββββββββββ¬βββββββββ
βΌ
βββββββββββββββββββββ
β NeedsDataRefresh? β
βββββββ¬βββββββββ¬βββββ
β yes β no
βΌ βΌ
ββββββββββββββββββββββ βββββββββββββββ
β FetchSubsystemData β β HasDirtyUI? β
βββββββββββ¬βββββββββββ ββββββ¬βββββ¬ββββ
βΌ βyes βno
ββββββββββββββββββββββ βΌ βΌ
β RunInsightAnalysis β ββββββββββββββββββββ ββββββββββββββββββββ
βββββββββββ¬βββββββββββ βRenderActiveSubsystemβ β UpdateAnimations β
βΌ βββββββββββ¬βββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββ β
β RenderActiveSubsystemββββββββββββ
ββββββββββββ¬ββββββββββββ
βΌ
ββββββββββββββββββββ
β UpdateAnimations β
ββββββββββββββββββββ
Key behavior:
-
_TickBrain()exits immediately if not initialized -
_TickBrain()also exits while the GUI exists but is hidden - root layers always rebuild on refresh; drill-down layers rebuild only when
LiveUpdate == true - the active subsystem's
FetchDatais called before render whenever the refresh interval elapses
RegisterServerHandlers() installs one sync request handler:
Orchestrator.RegisterServerRequestCallback("ServiceManager_ServerSnapshot", function(_player)
return snapshot
end, false)Snapshot payload assembled on the server:
| Key | Shape | Source |
|---|---|---|
Tasks |
{ List = {...}, Performance = {...} } |
Orchestrator.Scheduler |
FSM |
{ StateMachines = _fsmShadow } |
live FSM registry + bounded shadow cache |
Entities |
flat entity map | Factory.Registry.GetAllEntities() |
Network |
remotes / commands / buses / callbacks | Orchestrator.NetworkManager |
DataStore |
{ Stores = {}, Operations = {} } |
DataStoreSystem entity |
Memory |
collectgarbage("count") KB |
current server VM |
Timestamp |
os.clock() |
snapshot time |
Logs |
most recent 200 aggregated log entries | orchestrator + scheduler + network + factory loggers |
RegisterServerHandlers() maintains a bounded shadow cache so completed / failed FSMs remain inspectable after the Factory unregisters them.
SHADOW_CAP = 200-
_fsmShadow[id]stores serializable FSM snapshots -
_fsmShadowOrdertracks touch order for FIFO eviction -
_fsmLiveRefs[id]temporarily keeps the live FSM reference so finalcompletedvsfailedstatus can be inferred after the registry entry disappears -
StateChangedconnections append transition-history copies into the shadow so the graph view survives live FSM destruction
On the client, Initialize() watches ReplicatedStorage:GetAttribute("ServiceManagerCmd") and dispatches commands whenever the attribute changes.
Supported commands in the current source:
| Command | Effect |
|---|---|
context:client / context:server
|
calls SetContextMode(...)
|
subsystem:TASKS (or any other root key) |
calls SwitchSubsystem(...)
|
fsmDetail:<fsmId> |
opens the FSM detail drill-down |
fsmViz:<fsmId> |
opens the FSM graph visualizer |
step:<N> |
sets FSM visualizer playback step |
step:live |
switches FSM visualizer back to live mode |
netDetail:<itemName> |
opens a NETWORK detail drill-down |
netCalls:<itemName> |
opens a NETWORK calls-history drill-down |
consolePush:<text> |
injects a synthetic console line |
consoleEnter:<text> |
simulates entering text in the console input and writes a result string to ServiceManagerResult
|
dumpCalls:<itemName> |
dumps the latest ArgsStruct for a NETWORK row into ServiceManagerResult
|
Example:
local rs = game:GetService("ReplicatedStorage")
rs:SetAttribute("ServiceManagerCmd", "context:server")
rs:SetAttribute("ServiceManagerCmd", "subsystem:NETWORK")
rs:SetAttribute("ServiceManagerCmd", "netCalls:Remote:EntityUpdateEvent")One-time client-side setup entry point. Idempotent.
What it creates / schedules
ServiceBrain.Initialize(Orchestrator)ReactiveStore.new({ ... })AnimationController.new()AnalyticsEngine.new()- the 9 subsystem module instances
-
GlassGUIforPlayers.LocalPlayer.PlayerGuiwhen on the client -
ZAxisNavigatorwhen a GUI body container exists _brainTree = ServiceBrain.Build()Orchestrator.Scheduler:Schedule({ TaskName = "ServiceManagerBrainTick", Priority = 15, Event = "Heartbeat", IsRecurringTask = true, TaskExecutionDelay = 0, ... })- a
RenderSteppedFPS sampler on the client - the background server-poll loop on the client
- the
ServiceManagerCmdattribute bridge on the client
Side effects
- sets
_initialized = true - caches
_orchestrator - seeds the reactive store
- writes
ServiceManager._initialized = true - logs
"ServiceManager initialized"
Edge cases
- second call returns immediately
- on the server, GUI-specific steps are skipped
- if
Orchestrator.Scheduleris unavailable, no brain tick task is scheduled
Shows the GUI if it exists.
Side effects
- calls
_gui:Show() - if no subsystem is active yet, immediately switches to
TASKS
Hides the window without destroying it.
Important
- once hidden,
_TickBrain()early-returns before any BT work or rendering
Toggles visibility by delegating to Show() / Hide().
Activates one of the nine root subsystems.
Accepted values:
TASKSFSMENTITYNETWORKDATASTOREINSIGHTSCONSOLELOGSPROFILER
Side effects
- ignores unknown names
- sets
_activeSubsystem = name - writes
nexus.activeSubsystemin the store - pre-fetches subsystem data via
module.FetchData(...)when present - navigates the Z-axis root layer
- updates GUI sidebar selection with callback suppression
- forces immediate refresh by setting
_lastDataRefresh = 0
local sm = FSM.Orchestrator:StartServiceManager()
sm.SwitchSubsystem("FSM")Switches between live client data and cached server snapshot data.
Behavior
- invalid modes are ignored
- same-mode calls are ignored
- updates
_contextModeandnexus.mode - updates GUI context widgets and status bar
- stops active animation pulses before tearing down stale drill-downs
- when entering
server, immediately performs_orchestrator.Request("ServiceManager_ServerSnapshot") - navigates back to the active subsystem root
- pushes a context divider line into the console buffer
- resets the TASKS perf history / smoothing buffers
- forces the next tick to refetch with
_lastDataRefresh = 0 - logs the context switch
Failure path
- failed immediate server poll sets
nexus.connected = falseand logs a warning
sm.SetContextMode("server")Returns the underlying ReactiveStore instance.
This is the supported escape hatch for advanced subscribers that need to watch nexus.mode, nexus.connected, or nexus.activeSubsystem.
Tears down the dashboard.
Side effects
- disconnects
_serverPollConnectionwhen set - destroys
ZAxisNavigator - stops all animation pulses
- destroys
AnalyticsEngine - destroys
ReactiveStore - destroys
GlassGUI - calls
Destroy()on every subsystem module that exposes it - clears
_subsystemModulesand_logs - sets
_initialized = false
Important
- the polling loop exits because it is gated by
while _initialized do
Server-only snapshot registration hook. Called automatically by Orchestrator:RegisterComponents() on the server.
Behavior
- returns immediately on the client
- guarded by
ServiceManager._serverHandlersRegisteredso repeated registration is a no-op with a warning log - seeds the FSM shadow cache from already-registered live FSMs
- subscribes to
Factory.Registry.StateMachineRegistered - mirrors state changes into shadow transition history
- registers sync request
ServiceManager_ServerSnapshot - logs
"ServiceManager server handlers registered"
_StartServerPolling() runs only on the client.
- requests
ServiceManager_ServerSnapshotcontinuously while_initialized == true - sleeps 2 seconds in
servermode - sleeps 5 seconds in
clientmode - always keeps
_serverDatawarm, even in client mode, so drills can switch contexts quickly
_TickBrain() refresh rules:
- hidden GUI => no BT work
- refresh interval defaults to
_dataRefreshInterval = 2.0 - dirty root views rebuild on each refresh
- drill-down layers rebuild only if they opted into
Context.LiveUpdate = true
The current implementation delivers this in a few separate ways rather than one global kill-switch:
-
lazy start β client GUI, polling, navigator, and brain tick do not exist until
Orchestrator:StartServiceManager()is called -
hidden-window short-circuit β once initialized,
_TickBrain()returns immediately while the GUI is hidden -
server-side FSM replication gate β the extra
StateChangedβBroadcastEntityCommand(...)hook inCore/Factory/init.luauis wrapped inif RunService:IsServer() and Utility.Settings.ServiceManager.Enabled then ... end
Note
Settings.ServiceManager.Enabled = false does not currently prevent a direct manual StartServiceManager() call. It only disables code paths that explicitly consult that setting.
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents()
local sm = FSM.Orchestrator:StartServiceManager()
sm.Show()
sm.SwitchSubsystem("NETWORK")
sm.SetContextMode("server")-- client
local snapshot = FSM.Orchestrator:ServerRequest("ServiceManager_ServerSnapshot")
if snapshot then
print(snapshot.Timestamp)
print(snapshot.Network and snapshot.Network.Remotes and snapshot.Network.Remotes.EntityUpdateEvent)
endlocal rs = game:GetService("ReplicatedStorage")
rs:SetAttribute("ServiceManagerCmd", "context:server")
rs:SetAttribute("ServiceManagerCmd", "subsystem:FSM")
rs:SetAttribute("ServiceManagerCmd", "fsmViz:EnemyAI_42")
rs:SetAttribute("ServiceManagerCmd", "step:3")-
Initialize()is idempotent, not re-entrant. If you need a fresh instance, callDestroy()first. -
Unknown subsystem names are silently ignored.
SwitchSubsystem("foo")does nothing. -
Invalid context modes are silently ignored. Only
"client"and"server"are accepted. -
Server snapshot registration is intentionally one-shot. A second
RegisterServerHandlers()call only logs a warning. -
Completed FSMs are shadowed, not live. Drill-downs in server mode may show preserved historical snapshots, not active runtime objects.
-
Logs are capped. Local
_logskeeps 500 entries; server snapshots trim aggregatedLogsto 200 entries. -
The dashboard is a dev tool, not gameplay UI. It assumes studio/debug-style access to replicated internals and telemetry.
Quick Links: Home Β· Quick Start Β· API Reference Β· Architecture Β· Examples Β· Glossary
Copyright: Β© 2026 RBXStateMachine contributors Β· Repository Β· License information