Skip to content

Quick Start

iKryptonic edited this page May 1, 2026 · 13 revisions

Quick Start

The fastest path to a working RBXStateMachine setup: one Entity module, one FSM module, one server bootstrap, one client bootstrap.

Important

This page is intentionally compact, but it still matches the current source. The framework module is ReplicatedStorage.Orchestrator in this repository's default Rojo mapping.


Goal

In one pass you will:

  • boot Orchestrator
  • create a demo part on the server
  • wrap it in an Entity
  • drive it with an FSM
  • start ServiceManager on the client
  • verify that the scheduler heartbeat is running

Minimal folder layout

ReplicatedStorage/
β”œβ”€β”€ Orchestrator/
β”œβ”€β”€ Entity/
β”‚   └── QuickStartEntity.luau
└── StateMachine/
    └── QuickStartFSM.luau

ServerScriptService/
└── Bootstrap.server.luau

StarterPlayerScripts/
└── Bootstrap.client.luau

1. Entity module

Create ReplicatedStorage/Entity/QuickStartEntity.luau:

local RunService = game:GetService("RunService")

return {
    Name = "QuickStartEntity",
    Mutable = true,
    Schema = {
        IsActive = { Type = "boolean", Default = false, Replicate = true },
        SpinRate = { Type = "number", Default = 180, Replicate = true },
        Tint = { Type = "Color3", Default = Color3.fromRGB(255, 255, 255), Replicate = true },
    },

    GetContext = function(self, params)
        local instance = params.Context and params.Context.Instance
        assert(instance and instance:IsA("BasePart"), "QuickStartEntity requires Context.Instance")
        return {
            Instance = instance,
        }
    end,

    ApplyChanges = function(self, changes)
        if not self.Instance then
            return
        end

        if changes.Tint ~= nil then
            self.Instance.Color = changes.Tint
        end

        if RunService:IsClient() and changes.IsActive ~= nil then
            self.Instance.Material = changes.IsActive and Enum.Material.Neon or Enum.Material.SmoothPlastic
        end
    end,
}

2. FSM module

Create ReplicatedStorage/StateMachine/QuickStartFSM.luau:

return {
    Name = "QuickStartFSM",
    ValidStates = { "Idle", "Spin" },
    InitialState = "Idle",

    RegisterStates = function(self)
        self:AddState("Idle", function(fsm)
            local entity = fsm.Entity
            entity.IsActive = false
            entity.Tint = Color3.fromRGB(255, 170, 0)
            entity:UpdateEntity()

            fsm:ScheduleTransition(1, "Spin")
        end, { "Spin" })

        self:AddState("Spin", {
            OnEnter = function(state, fsm)
                local entity = fsm.Entity
                entity.IsActive = true
                entity.Tint = Color3.fromRGB(0, 255, 127)
                entity:UpdateEntity()
            end,

            OnHeartbeat = function(state, fsm, dt)
                local entity = fsm.Entity
                local part = entity.Instance
                if part then
                    part.CFrame *= CFrame.Angles(0, math.rad(entity.SpinRate or 180) * dt, 0)
                end
            end,

            Timeout = 2,
            OnTimeout = "Idle",
        }, { "Idle" })
    end,
}

3. Server bootstrap

Create ServerScriptService/Bootstrap.server.luau:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator"))

Orchestrator:RegisterComponents()

local part = Instance.new("Part")
part.Name = "QuickStartPart"
part.Anchored = true
part.Size = Vector3.new(4, 4, 4)
part.Position = Vector3.new(0, 5, 0)
part.Parent = workspace

local entity = Orchestrator.CreateEntity({
    EntityClass = "QuickStartEntity",
    EntityId = "QuickStartPart",
    Context = {
        Instance = part,
    },
    InitialData = {
        SpinRate = 180,
    },
})
assert(entity, "Entity creation failed")

local fsm = Orchestrator.CreateStateMachine({
    StateMachineClass = "QuickStartFSM",
    StateMachineId = "QuickStartPart_FSM",
    Context = {
        Entity = entity,
        EntityId = "QuickStartPart",
    },
    InitialState = "Idle",
})
assert(fsm, "FSM creation failed")

print("[QuickStart] Server boot complete")

What this does

  • RegisterComponents() initializes Logger, Factory, Scheduler, NetworkManager, and replication callbacks
  • CreateEntity() registers a live entity instance in Factory.Registry
  • CreateStateMachine() registers a live FSM instance and auto-starts it
  • GlobalStateMachineHeartbeat runs through the scheduler and drives _Update(dt) for all active FSMs

4. Client bootstrap

Create StarterPlayerScripts/Bootstrap.client.luau:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator", 30))

Orchestrator:RegisterComponents()

if RunService:IsStudio() then
    local sm = Orchestrator:StartServiceManager()
    sm.SwitchSubsystem("TASKS")
    sm.SetContextMode("server")
end

print("[QuickStart] Client boot complete")

This launches ServiceManager only in Studio, which is the safest default for development.


5. What should happen when you press Play?

  Server              Factory             Scheduler              FSM               Client          ServiceManager
    β”‚                   β”‚                    β”‚                   β”‚                  β”‚                  β”‚
    │── CreateEntity(QuickStartPart) ─────→│                    β”‚                  β”‚                  β”‚
    │── CreateStateMachine(QuickStartPart_FSM) ─→│              β”‚                  β”‚                  β”‚
    β”‚                   │── Start({State="Idle"}) ────────────→│                  β”‚                  β”‚
    β”‚                   β”‚                    │── GlobalStateMachineHeartbeat ─────→│                  β”‚
    β”‚                   β”‚                    β”‚                   │── Idle β†’ Spin β†’ Idle
    │── entity snapshot + replicated fields ─────────────────────────────────────→│                  β”‚
    β”‚                   β”‚                    β”‚                   β”‚                  │── StartServiceManager() ─→│
    β”‚                   β”‚                    │←──────────────────────────────────── inspect tasks ─────│

Expected results:

  • the part appears in workspace
  • it idles for one second
  • it spins for two seconds
  • it repeats
  • the client dashboard opens in Studio

6. What to inspect in ServiceManager

Once the client dashboard opens:

TASKS tab

You should see at least:

  • GlobalStateMachineHeartbeat
  • ServiceManagerBrainTick

If GlobalStateMachineHeartbeat is missing, your runtime did not finish booting correctly.

FSM tab

You should see:

  • QuickStartPart_FSM
  • current state toggling between Idle and Spin
  • transition history growing over time

ENTITY tab

You should see:

  • QuickStartPart
  • IsActive
  • SpinRate
  • Tint
  • incrementing version data as commits happen

PROFILER tab

You should see:

  • current frame budget usage
  • hot tasks
  • hot FSMs
  • GC and thread pool diagnostics

7. A few important current-runtime details

CreateStateMachine() auto-starts

This is the single most important difference from many older snippets in the repo or wiki.

local fsm = Orchestrator.CreateStateMachine({
    StateMachineClass = "QuickStartFSM",
    StateMachineId = "QuickStartPart_FSM",
    Context = { Entity = entity },
})

The machine is already active unless you explicitly pass AutoStart = false.

WaitSpan still exists, but this quick start does not need it

For a fixed timed exit, use ScheduleTransition().

Use WaitSpan when you want a deliberate delayed re-entry into the same state, like the polling loop used in example/DoorStateMachineExample.luau.

The client mirrors entities; the server owns them

The server commits state with UpdateEntity(). The client receives replicated deltas and runs ApplyChanges(changes) locally.


8. Common copy-paste fixes

Error: attempt to index nil with 'WaitForChild' for Orchestrator

Make sure Rojo actually synced FSM/Orchestrator to ReplicatedStorage.Orchestrator.

Error: UpdateEntity failed: Attempt to call UpdateEntity on immutable entity

Add Mutable = true to the entity definition.

Dashboard opens but lists are empty

Switch to:

sm.SetContextMode("server")

so you are inspecting the authoritative server snapshot.

The part exists but never spins

Check the following in order:

  1. QuickStartFSM is inside a folder named StateMachine
  2. Entity = entity is passed in FSM context
  3. GlobalStateMachineHeartbeat is visible in TASKS
  4. no error is printed from ApplyChanges or ChangeState

9. Next step after the quick start

If this minimal setup works, move immediately to:

This page gives you the shortest working path; the rest of the wiki explains how to scale it.

🏠 Home


πŸš€ Getting Started


πŸ—οΈ Architecture


πŸ”§ API Reference


πŸ“š Guides


βš™οΈ Advanced


πŸ› οΈ Development


πŸ“– Reference

Clone this wiki locally