-
Notifications
You must be signed in to change notification settings - Fork 0
API Orchestrator
Orchestrator is the top-level runtime facade for RBXStateMachine. It bootstraps core modules, exposes Factory and Scheduler services, provides high-level create/delete/cancel delegates, wraps networking APIs, and manages client/server replication startup.
Important
This page documents FSM/Orchestrator/init.luau and the delegate behavior it exposes over Factory, NetworkManager, and Scheduler.
In most games, Orchestrator is the entry point you actually require and call.
| Member | Meaning |
|---|---|
Orchestrator.Factory |
Compiled class creation / registry / persistence pipeline |
Orchestrator.NetworkManager |
Remotes, server request handlers, entity command routing, event buses |
Orchestrator.Scheduler |
Runtime scheduler instance created during initialization |
Orchestrator.Settings |
Shared framework settings module |
Orchestrator.History |
Legacy/service-manager-facing history table |
Orchestrator._serviceManager |
Cached ServiceManager module, if started/registered |
ββββββββββββββββββββββ
β RegisterComponents β
βββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββ
β Initialize Logger β
βββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Set shared.fsm proxy β
ββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Initialize NetworkManager / Factory / Scheduler β
β modules β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββ
β Create Scheduler instance β
ββββββββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Disable BaseStateMachine internal loop β
βββββββββββββββββββββ¬ββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββ
β Schedule GlobalStateMachineHeartbeat β
βββββββββββββββββββββ¬ββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββ
β NetworkManager.Initialize β
ββββββββββββββββ¬βββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Register server snapshot callbacks or client listeners β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββ
β Optional ServiceManager wiring β
ββββββββββββββββββββββββββββββββββ
Orchestrator:RegisterComponents() -> booleanMain framework bootstrap. This is the call most games make during startup.
- calls internal
Initialize() - on success, if running on the server and
_serviceManageris stillnil, requiresscript.ServiceManager - calls
serviceManager.RegisterServerHandlers(self) - caches that module in
_serviceManager - returns the
Initialize()result
-
truewhen initialization succeeds -
falsewhile an initialization is already in progress - an error object / falsy result if internal initialization fails unexpectedly
-
@reads: runtime context (RunService:IsServer/IsClient), service manager module presence -
@writes:Factory,NetworkManager,Scheduler, shared globals, callbacks,_serviceManager
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents()- Idempotent: once initialized, later calls return immediately.
-
Re-entrance guarded: while initialization is in progress, concurrent calls return
falseinstead of racing. - Server side: registers entity snapshot request handlers and ServiceManager server callbacks.
- Client side: creates the pending update buffer, hooks entity remotes, and requests the initial entity snapshot.
Orchestrator:StartServiceManager() -> anyLazy-launches the ServiceManager UI/module.
- existing
_serviceManagerif already available - otherwise the required / initialized ServiceManager module
-
@reads:script.ServiceManager -
@writes:_serviceManager, ServiceManager UI visibility state
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents()
if game:GetService("RunService"):IsStudio() then
FSM.Orchestrator:StartServiceManager()
endOrchestrator:GetServiceManager() -> any?Returns the cached ServiceManager module reference, or nil if it has not been started / registered yet.
Orchestrator.CreateEntity(params: {
EntityClass: any,
EntityId: string,
Context: {[any]: any}?,
Persistent: boolean?,
PersistenceKey: string?,
}) -> any?Shared-context wrapper over Factory.CreateEntity(...).
-
@reads: initialization state -
@writes: delegated to Factory
local door = FSM.Orchestrator.CreateEntity({
EntityClass = "DoorEntity",
EntityId = "door:front",
Context = { Model = workspace.FrontDoor },
Persistent = true,
})Orchestrator.GetEntity(entityId: string) -> any?Returns the live entity from Factory.Registry.
Orchestrator.GetEntities() -> {[string]: any}Returns Registry.GetAllEntities().
Orchestrator.DeleteEntity(entityId: string) -> booleanFinds the entity and calls entity:Destroy(). Factory's destroy wiring then handles registry cleanup and network notifications.
-
trueif an entity existed and destroy was requested -
falseif not found
Orchestrator.DeleteAllEntities() -> booleanDestroys every live entity.
- snapshots the registry before iterating so destroy-time unregisters do not corrupt the loop
- wraps each destroy in
pcall - always returns
trueonce initialized
Orchestrator.PoolEntity(entityId: string) -> booleanMoves a live entity into the registry's class pool.
- looks up the live entity
- marks
entity._privateProperties.IsValid = false - unregisters it from the live entity registry
- if running on the server, broadcasts
OnEntityPooled - pushes it into
Registry.PoolEntity(className, entity)
-
trueif an entity was pooled -
falseif not found
The broadcast is server-only by design. Clients receive OnEntityPooled and call PoolEntity(...) locally; rebroadcasting from the client would recurse into an invalid server-only path.
Orchestrator.CreateStateMachine(params: {
StateMachineClass: any,
StateMachineId: string,
Context: {[any]: any}?,
}) -> any?Shared wrapper over Factory.CreateStateMachine(...).
Orchestrator.GetStateMachine(stateMachineId: string) -> any?Returns the live FSM from Factory.Registry.
Orchestrator.GetStateMachines() -> {[string]: any}Returns Registry.GetAllStateMachines().
Orchestrator.CancelStateMachine(stateMachineId: string) -> booleanFinds the FSM and calls sm:Cancel().
-
trueif found -
falseif not found
Orchestrator.RetryStateMachine(stateMachineId: string) -> boolean?Destroys a live FSM and recreates it from its compiled class and shallow-cloned context.
- read old FSM from registry
- shallow clone
oldSM.Context - resolve the compiled class using
Factory.Get("StateMachine", oldSM.Name) - unregister the old FSM
- destroy the old FSM
- create a new FSM with the same ID and cloned context
-
trueif retry was requested successfully -
nil/ falsy if the FSM or compiled class cannot be found
The context clone is shallow. Nested tables, instances, and other reference types remain shared.
Orchestrator.CancelAll() -> booleanCancels every live FSM after snapshotting the registry table.
Orchestrator re-exposes the core networking helpers from NetworkManager.
| Method | Allowed caller | Notes |
|---|---|---|
RegisterEntityCommandCallback |
shared | client or server |
ServerCommandEntity |
client only | sends EntityCommandEvent:FireServer(...)
|
RegisterServerRequestCallback |
server only | installs sync or async server request handlers |
ServerRequest |
client only | synchronous RemoteFunction request |
ServerRequestAsync |
client only | async RemoteEvent request |
BroadcastEntityCommand |
server only | broadcasts to all clients |
Orchestrator.RegisterEntityCommandCallback(
entityId: string,
command: string,
handler: (Player, ...any) -> ()
) -> booleanRegisters a command handler under NetworkManager.EntityCommandCallbacks[entityId][command].
Orchestrator.ServerCommandEntity(entityId: string, command: string, ...: any) -> ()Client-side fire-and-forget entity command.
FSM.Orchestrator.ServerCommandEntity("door:front", "Toggle", true)Orchestrator.RegisterServerRequestCallback(
requestName: string,
handler: (Player, ...any) -> any,
aSync: boolean
) -> booleanRegisters a named server request handler.
-
aSync = falseor omitted -> synchronousServerRequest -
aSync = true-> asyncServerRequestAsync
Orchestrator.ServerRequest(requestName: string, ...: any) -> anyClient-side synchronous request/response path using RemoteFunction:InvokeServer(...).
Alias of Orchestrator.ServerRequest.
Orchestrator.ServerRequestAsync(requestName: string, ...: any) -> ()Client-side async request path using RemoteEvent:FireServer(...).
Orchestrator.BroadcastEntityCommand(requestType: string, entityId: string, ...: any) -> booleanServer-side broadcast helper.
FSM.Orchestrator.BroadcastEntityCommand("SyncClient", "door:front", "ApplyState", "Open")Event buses are local Signal objects, not network replicated buses.
Orchestrator.RegisterEventBus(name: string) -> anyCreates the named bus if it does not exist and returns it.
Orchestrator.GetEventBus(name: string) -> any?Returns an existing bus, or nil.
Orchestrator.FireEventBus(name: string, ...: any) -> ()Fires the named bus if it exists. Missing buses are ignored.
Orchestrator.AwaitEventBus(name: string, timeout: number?) -> any?Waits in a polling loop until the bus exists or the timeout elapses.
Orchestrator.UnregisterEventBus(name: string) -> ()Destroys the signal if it has a Destroy method, then removes it from the registry.
local bus = FSM.Orchestrator.RegisterEventBus("QuestUpdated")
bus:Connect(function(questId, state)
print("Quest changed", questId, state)
end)
FSM.Orchestrator.FireEventBus("QuestUpdated", "daily-1", "Completed")[RegisterEventBus] βββ [Signal stored in EventBuses] βββ [GetEventBus / AwaitEventBus]
β
ββββ [UnregisterEventBus]
[GetEventBus / AwaitEventBus] βββ [FireEventBus] βββ [Signal listeners run]
During initialization, Orchestrator registers a server request called RequestEntitySnapshot.
That handler returns an array of sanitized entity snapshots containing:
IdClassNameInstance-
Data(only schema fields withReplicate = true, plus_v) Version
It also registers __DebugRemoteStats, which exposes NetworkManager._remoteStats for debugging dashboards.
When running on the client, initialization:
- creates
_pendingUpdatesbuffer - connects
EntityUpdateEvent - buffers updates that arrive before the entity exists locally
- validates
_vversion ordering - applies replicated data directly into entity committed
Data - calls
ApplyChanges(...)with_vstripped out - connects
EntityCommandEvent - reacts to
OnEntityCreated,OnEntityPooled, andOnEntityDestroyed - calls
SyncEntitiesFromServer()to fetch the initial snapshot
[Server entity UpdateEntity]
ββββ [Factory StateUpdated listener]
ββββ [BroadcastEntityUpdate]
ββββ [Client EntityUpdateEvent]
ββββ {Entity exists locally?}
βββ no β [Buffer in _pendingUpdates]
β β [OnEntityCreated / snapshot create entity]
β β [Drain buffered updates in version order]
βββ yes β [Validate version and schema]
β [Write committed Data]
β [ApplyChanges]
During initialization, Orchestrator creates a scheduler instance and starts it. It then disables the internal BaseStateMachine heartbeat and schedules a recurring scheduler task:
TaskName = "GlobalStateMachineHeartbeat"
Priority = 1
Event = "Heartbeat"
IsRecurringTask = true
TaskExecutionDelay = 0That task computes dt from wall clock, clamps it to 0.1, and calls:
Core.Factory.BaseModules.BaseStateMachine.Step(dt)This is the reason FSM updates run through Scheduler instead of through BaseStateMachine's own RunService.Heartbeat connection once Orchestrator is active.
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents()local RunService = game:GetService("RunService")
local FSM = require(game:GetService("ReplicatedStorage").RBXStateMachine)
FSM.Orchestrator:RegisterComponents()
if RunService:IsStudio() then
FSM.Orchestrator:StartServiceManager()
end-- Server
FSM.Orchestrator.RegisterServerRequestCallback("GetDoorState", function(player, entityId)
local door = FSM.Orchestrator.GetEntity(entityId)
return door and door.IsOpen or nil
end, false)
-- Client
local isOpen = FSM.Orchestrator.ServerRequest("GetDoorState", "door:front")-
RegisterComponents()is the one true bootstrap. Calling lower-level pieces manually can leave shared state half initialized. -
StartServiceManager()is lazy UI startup, not core boot. Core systems already came up duringRegisterComponents(). -
Requestis just an alias ofServerRequest. It is still client-only. -
RetryStateMachine()usesoldSM.Nameto resolve the compiled class. Renamed or dynamically wrapped FSMs can fail lookup. -
Orchestrator.Entities/Orchestrator.StateMachinesare snapshots captured during init. UseGetEntities()/GetStateMachines()for fresh clones. - Event buses are local only. They do not cross the network.
-
Client replication writes directly into committed
Data. That path bypassesUpdateEntity()and only callsApplyChanges(...)after validation. -
Server-only methods assert in the wrong context.
BroadcastEntityCommandandRegisterServerRequestCallbackmust not be called from clients. -
Snapshot sync is version-aware but shallow. Existing entity context objects such as
Instancereferences still come from creation-time context, not from replicated deltas. -
Initialization can return
falsewhile already booting. Guard startup code accordingly if multiple scripts may race to initialize.
Quick Links: Home Β· Quick Start Β· API Reference Β· Architecture Β· Examples Β· Glossary
Copyright: Β© 2026 RBXStateMachine contributors Β· Repository Β· License information