-
Notifications
You must be signed in to change notification settings - Fork 0
Registration Guide
How Orchestrator:RegisterComponents() discovers, compiles, and loads your Entity and StateMachine modules in the current runtime.
Important
This page is based on FSM/Orchestrator/init.luau, FSM/Orchestrator/Core/Factory/init.luau, BaseEntity.luau, and BaseStateMachine.luau.
In the current framework, registration is not a manual list of classes.
local Orchestrator = require(game:GetService("ReplicatedStorage"):WaitForChild("Orchestrator"))
Orchestrator:RegisterComponents()That single call is responsible for booting the runtime and preparing all discovered components.
Internally, it performs the following high-level sequence:
- initializes logger/shared globals/network/factory/scheduler
- starts the shared scheduler
- disables the base FSM internal loop and routes updates through
GlobalStateMachineHeartbeat - scans the game for folders whose names match Entity/FSM aliases
- requires each discovered
ModuleScript - compiles the module's returned table into a subclass of
BaseEntityorBaseStateMachine - stores the compiled class for later
CreateEntity()/CreateStateMachine()calls - registers server snapshot callbacks and client sync hooks
The current factory does not require one exact path. It scans all descendants of game and compiles any folder whose name matches one of the supported aliases.
EntityEntities
StateMachineStateMachinesSMFSM
So all of these are valid discovery locations:
ReplicatedStorage/Entity/
ReplicatedStorage/StateMachine/
ServerStorage/Entities/
ReplicatedStorage/Combat/FSM/
Workspace/NPCs/StateMachines/
Note
The recommended layout is still ReplicatedStorage/Entity and ReplicatedStorage/StateMachine, because it is the easiest layout for Rojo projects and keeps shared code in one obvious place.
ReplicatedStorage/
βββ Orchestrator/
βββ Entity/
β βββ SpinnerEntity.luau
β βββ DoorEntity.luau
βββ StateMachine/
β βββ SpinnerFSM.luau
β βββ DoorStateMachine.luau
Each compiled module should return a table definition, not a pre-instantiated object.
local SpinnerEntity = {
Name = "SpinnerEntity",
Mutable = true,
Schema = {
IsSpinning = { Type = "boolean", Default = false, Replicate = true },
SpinRate = { Type = "number", Default = 180, Replicate = true },
},
}
function SpinnerEntity:GetContext(params)
local instance = params.Context and params.Context.Instance
assert(instance and instance:IsA("BasePart"), "SpinnerEntity requires Context.Instance")
return {
Instance = instance,
}
end
function SpinnerEntity:ApplyChanges(changes)
if changes.IsSpinning ~= nil then
self.Instance.Material = changes.IsSpinning and Enum.Material.Neon or Enum.Material.SmoothPlastic
end
end
return SpinnerEntitylocal SpinnerFSM = {
Name = "SpinnerFSM",
ValidStates = { "Idle", "Spin" },
InitialState = "Idle",
TerminalStates = {},
}
function SpinnerFSM:RegisterStates()
self:AddState("Idle", function(fsm)
fsm:ScheduleTransition(2, "Spin")
end, { "Spin" })
self:AddState("Spin", {
OnEnter = function(state, fsm)
fsm.Context.Entity.IsSpinning = true
fsm.Context.Entity:UpdateEntity()
end,
Timeout = 2,
OnTimeout = "Idle",
}, { "Idle" })
end
return SpinnerFSMDuring compilation, the runtime:
- clones the module definition (or
Definitionfield, if present) - ensures
Nameexists - ensures
classNameexists for FSM subclass creation - calls
BaseEntity.Extend(...)orBaseStateMachine.Extend(...) - mixes in your custom methods onto the resulting subclass
That means the module you write is the class definition, not the live instance.
CompileModule() currently supports an optional module-level initializer.
Preferred order:
StaticInit(Utility)- fallback to
Initialize(Utility)for backward compatibility
Example:
local MyFSM = {
Name = "MyFSM",
ValidStates = { "Idle" },
InitialState = "Idle",
}
function MyFSM.StaticInit(Utility)
print("Factory utility available:", Utility.Logger)
end
function MyFSM:RegisterStates()
self:AddState("Idle", function() end)
end
return MyFSMUse StaticInit when a module needs one-time setup at compile time rather than per-instance construction.
Caution
StaticInit is wrapped in pcall. If it throws, the factory logs the error and continues booting other modules.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator"))
Orchestrator:RegisterComponents()
local entity = Orchestrator.CreateEntity({
EntityClass = "SpinnerEntity",
EntityId = "Spinner_1",
Context = {
Instance = workspace:WaitForChild("SpinnerPart"),
},
})
local fsm = Orchestrator.CreateStateMachine({
StateMachineClass = "SpinnerFSM",
StateMachineId = "SpinnerFSM_1",
Context = {
Entity = entity,
},
})local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator", 30))
Orchestrator:RegisterComponents()
if RunService:IsStudio() then
Orchestrator:StartServiceManager()
endThe current public boot path is simply:
Orchestrator:RegisterComponents()The older βregister explicit folders or registriesβ style is not the current source-of-truth flow.
If you insert or generate modules after initial boot, you can compile an eligible folder manually.
local customFolder = ReplicatedStorage:WaitForChild("BossPack"):WaitForChild("FSM")
Orchestrator.Factory.CompileFolder(customFolder)This is useful for:
- feature packs loaded later in the session
- plug-in style content
- tests that install fixtures dynamically
CompileFolder() recurses nested folders and compiles every ModuleScript it finds.
Compilation only makes classes available. Live objects appear when you instantiate them.
[ModuleScript discovered] βββ [require module] βββ [StaticInit / Initialize]
βββ [Extend BaseEntity / BaseStateMachine] βββ [Store compiled class]
βββ [CreateEntity / CreateStateMachine] βββ [Register live instance in Registry]
CreateEntity():
- resolves the class by name or table
- creates a unique ID if you do not provide one
- returns the existing entity when the ID already exists
- optionally reuses pooled entities
- applies
InitialData - registers the entity in the live registry
- wires replication if running on the server
CreateStateMachine():
- resolves the class by name or table
- returns the existing FSM when the ID already exists
- injects
Context.FSM = newSM - registers the live machine
- auto-starts it unless
AutoStart = false
Current repo default:
require(ReplicatedStorage:WaitForChild("Orchestrator"))not:
require(ReplicatedStorage.RBXStateMachine)ReplicatedStorage/AI/ will not be discovered unless the actual folder name matches one of the supported aliases.
CompileModule() logs an error if require(module) does not return a table definition.
It does not. You still need CreateEntity() and CreateStateMachine().
It does not. The factory warns and returns the existing object.
Registration succeeds, but later UpdateEntity() fails on immutable entities.
- framework is synced to
ReplicatedStorage.Orchestrator - folders use supported alias names
- each module returns a table definition
-
Namematches the class name you intend to instantiate - entities that commit changes declare
Mutable = true - server and client both call
RegisterComponents() - runtime-created content calls
CompileFolder()when needed
Quick Links: Home Β· Quick Start Β· API Reference Β· Architecture Β· Examples Β· Glossary
Copyright: Β© 2026 RBXStateMachine contributors Β· Repository Β· License information