Skip to main content

Loop

The Loop class handles scheduling and looping (who would have guessed) over all of your game systems.

Yielding

Yielding is not allowed in systems. Doing so will result in the system thread being closed early, but it will not affect other systems.

Types

System

type System = SystemTable | (...any) → ()

Either a plain function or a table defining the system.

SystemTable

interface SystemTable {
system(...any) → ()--

The system function

event?string--

The event the system runs on. A string, a key from the table you pass to Loop:begin.

priority?number--

Priority influences the position in the frame the system is scheduled to run at.

after?{System}--

A list of systems that this system must run after.

}

A table defining a system with possible options.

Systems are scheduled in order of priority, meaning lower priority runs first. The default priority is 0.

Functions

new

Loop.new(
......any--

Values that will be passed to all of your systems

) → Loop

Creates a new loop. Loop.new accepts as arguments the values that will be passed to all of your systems.

So typically, you want to pass the World in here, as well as maybe a table of global game state.

local world = World.new()
local gameState = {}

local loop = Loop.new(world, gameState)

scheduleSystems

Loop:scheduleSystems(
systems{System}--

Array of systems to schedule.

) → ()

Schedules a set of systems based on the constraints they define.

Systems may optionally declare:

  • The name of the event they run on (e.g., RenderStepped, Stepped, Heartbeat)
  • A numerical priority value
  • Other systems that they must run after

If systems do not specify an event, they will run on the default event.

Systems that share an event will run in order of their priority, which means that systems with a lower priority value run first. The default priority is 0.

Systems that have defined what systems they run after can only be scheduled after all systems they depend on have already been scheduled.

All else being equal, the order in which systems run is stable, meaning if you don't change your code, your systems will always run in the same order across machines.

info

It is possible for your systems to be in an unresolvable state. In which case, scheduleSystems will error. This can happen when your systems have circular or unresolvable dependency chains.

If a system has both a priority and defines systems it runs after, the system can only be scheduled if all of the systems it depends on have a lower or equal priority.

Systems can never depend on systems that run on other events, because it is not guaranteed or required that events will fire every frame or will always fire in the same order.

caution

scheduleSystems has to perform nontrivial sorting work each time it's called, so you should avoid calling it multiple times if possible.

scheduleSystem

Loop:scheduleSystem(systemSystem) → ()

Schedules a single system. This is an expensive function to call multiple times. Instead, try batch scheduling systems with Loop:scheduleSystems if possible.

evictSystem

Loop:evictSystem(systemSystem) → ()

Removes a previously-scheduled system from the Loop. Evicting a system also cleans up any storage from hooks. This is intended to be used for hot reloading. Dynamically loading and unloading systems for gameplay logic is not recommended.

replaceSystem

Loop:replaceSystem(
oldSystem,
newSystem
) → ()

Replaces an older version of a system with a newer version of the system. Internal system storage (which is used by hooks) will be moved to be associated with the new system. This is intended to be used for hot reloading.

setWorlds

Loop:setWorlds(
worlds{World} | {[string]World}--

An array or dictionary of Worlds to be used by the Loop. If a dictionary is passed, the keys are used as the names of the Worlds for the Debugger.

) → ()

Sets the Worlds to be used by the Loop for deferring commands, and the Debugger for profiling.

begin

Loop:begin(
events{[string]RBXScriptSignal}--

A map from event name to event objects.

) → {[string]RBXScriptConnection}--

A map from your event names to connection objects.

Connects to frame events and starts invoking your systems.

Pass a table of events you want to be able to run systems on, a map of name to event. Systems can use these names to define what event they run on. By default, systems run on an event named "default". Custom events may be used if they have a Connect function.

loop:begin({
	default = RunService.Heartbeat,
	Heartbeat = RunService.Heartbeat,
	RenderStepped = RunService.RenderStepped,
	Stepped = RunService.Stepped,
})

 

Returns a table similar to the one you passed in, but the values are RBXScriptConnection values (or whatever is returned by :Connect if you passed in a synthetic event).

addMiddleware

Loop:addMiddleware(middleware(
nextFn() → (),
eventNamestring
) → () → ()) → ()

Adds a user-defined middleware function that is called during each frame.

This allows you to run code before and after each frame, to perform initialization and cleanup work.

loop:addMiddleware(function(nextFn)
	return function()
		Plasma.start(plasmaNode, nextFn)
	end
end)

You must pass addMiddleware a function that itself returns a function that invokes nextFn at some point.

The outer function is invoked only once. The inner function is invoked during each frame event.

info

Middleware added later "wraps" middleware that was added earlier. The innermost middleware function is the internal function that actually calls your systems.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a new loop. `Loop.new` accepts as arguments the values that will be passed to all of your systems.\n\nSo typically, you want to pass the World in here, as well as maybe a table of global game state.\n\n```lua\nlocal world = World.new()\nlocal gameState = {}\n\nlocal loop = Loop.new(world, gameState)\n```",
            "params": [
                {
                    "name": "...",
                    "desc": "Values that will be passed to all of your systems",
                    "lua_type": "...any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Loop"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 51,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "scheduleSystems",
            "desc": "Schedules a set of systems based on the constraints they define.\n\nSystems may optionally declare:\n- The name of the event they run on (e.g., RenderStepped, Stepped, Heartbeat)\n- A numerical priority value\n- Other systems that they must run *after*\n\nIf systems do not specify an event, they will run on the `default` event.\n\nSystems that share an event will run in order of their priority, which means that systems with a lower `priority`\nvalue run first. The default priority is `0`.\n\nSystems that have defined what systems they run `after` can only be scheduled after all systems they depend on have\nalready been scheduled.\n\nAll else being equal, the order in which systems run is stable, meaning if you don't change your code, your systems\nwill always run in the same order across machines.\n\n:::info\nIt is possible for your systems to be in an unresolvable state. In which case, `scheduleSystems` will error.\nThis can happen when your systems have circular or unresolvable dependency chains.\n\nIf a system has both a `priority` and defines systems it runs `after`, the system can only be scheduled if all of\nthe systems it depends on have a lower or equal priority.\n\nSystems can never depend on systems that run on other events, because it is not guaranteed or required that events\nwill fire every frame or will always fire in the same order.\n:::\n\n:::caution\n`scheduleSystems` has to perform nontrivial sorting work each time it's called, so you should avoid calling it multiple\ntimes if possible.\n:::",
            "params": [
                {
                    "name": "systems",
                    "desc": "Array of systems to schedule.",
                    "lua_type": "{ System }"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 129,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "scheduleSystem",
            "desc": "Schedules a single system. This is an expensive function to call multiple times. Instead, try batch scheduling\nsystems with [Loop:scheduleSystems] if possible.",
            "params": [
                {
                    "name": "system",
                    "desc": "",
                    "lua_type": "System"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 150,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "evictSystem",
            "desc": "Removes a previously-scheduled system from the Loop. Evicting a system also cleans up any storage from hooks.\nThis is intended to be used for hot reloading. Dynamically loading and unloading systems for gameplay logic\nis not recommended.",
            "params": [
                {
                    "name": "system",
                    "desc": "",
                    "lua_type": "System"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 161,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "replaceSystem",
            "desc": "Replaces an older version of a system with a newer version of the system. Internal system storage (which is used\nby hooks) will be moved to be associated with the new system. This is intended to be used for hot reloading.",
            "params": [
                {
                    "name": "old",
                    "desc": "",
                    "lua_type": "System"
                },
                {
                    "name": "new",
                    "desc": "",
                    "lua_type": "System"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 189,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "setWorlds",
            "desc": "Sets the Worlds to be used by the Loop for deferring commands, and the Debugger for profiling.",
            "params": [
                {
                    "name": "worlds",
                    "desc": "An array or dictionary of Worlds to be used by the Loop. If a dictionary is passed, the keys are used as the names of the Worlds for the Debugger.",
                    "lua_type": "{World} | {[string]: World}"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 333,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "begin",
            "desc": "Connects to frame events and starts invoking your systems.\n\nPass a table of events you want to be able to run systems on, a map of name to event. Systems can use these names\nto define what event they run on. By default, systems run on an event named `\"default\"`. Custom events may be used\nif they have a `Connect` function.\n\n```lua\nloop:begin({\n\tdefault = RunService.Heartbeat,\n\tHeartbeat = RunService.Heartbeat,\n\tRenderStepped = RunService.RenderStepped,\n\tStepped = RunService.Stepped,\n})\n```\n\n \n\nReturns a table similar to the one you passed in, but the values are `RBXScriptConnection` values (or whatever is\nreturned by `:Connect` if you passed in a synthetic event).",
            "params": [
                {
                    "name": "events",
                    "desc": "A map from event name to event objects.",
                    "lua_type": "{[string]: RBXScriptSignal}"
                }
            ],
            "returns": [
                {
                    "desc": "A map from your event names to connection objects.",
                    "lua_type": "{[string]: RBXScriptConnection}"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 361,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "addMiddleware",
            "desc": "Adds a user-defined middleware function that is called during each frame.\n\nThis allows you to run code before and after each frame, to perform initialization and cleanup work.\n\n```lua\nloop:addMiddleware(function(nextFn)\n\treturn function()\n\t\tPlasma.start(plasmaNode, nextFn)\n\tend\nend)\n```\n\nYou must pass `addMiddleware` a function that itself returns a function that invokes `nextFn` at some point.\n\nThe outer function is invoked only once. The inner function is invoked during each frame event.\n\n:::info\nMiddleware added later \"wraps\" middleware that was added earlier. The innermost middleware function is the internal\nfunction that actually calls your systems.\n:::",
            "params": [
                {
                    "name": "middleware",
                    "desc": "",
                    "lua_type": "(nextFn: () -> (), eventName: string) -> () -> ()"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 554,
                "path": "lib/Loop.luau"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "System",
            "desc": "Either a plain function or a table defining the system.",
            "lua_type": "SystemTable | (...any) -> ()",
            "source": {
                "line": 75,
                "path": "lib/Loop.luau"
            }
        },
        {
            "name": "SystemTable",
            "desc": "A table defining a system with possible options.\n\nSystems are scheduled in order of `priority`, meaning lower `priority` runs first.\nThe default priority is `0`.",
            "fields": [
                {
                    "name": "system",
                    "lua_type": "(...any) -> ()",
                    "desc": "The system function"
                },
                {
                    "name": "event?",
                    "lua_type": "string",
                    "desc": "The event the system runs on. A string, a key from the table you pass to `Loop:begin`."
                },
                {
                    "name": "priority?",
                    "lua_type": "number",
                    "desc": "Priority influences the position in the frame the system is scheduled to run at."
                },
                {
                    "name": "after?",
                    "lua_type": "{System}",
                    "desc": "A list of systems that this system must run after."
                }
            ],
            "source": {
                "line": 89,
                "path": "lib/Loop.luau"
            }
        }
    ],
    "name": "Loop",
    "desc": "The Loop class handles scheduling and *looping* (who would have guessed) over all of your game systems.\n\n:::caution Yielding\nYielding is not allowed in systems. Doing so will result in the system thread being closed early, but it will not\naffect other systems.\n:::",
    "source": {
        "line": 33,
        "path": "lib/Loop.luau"
    }
}