Skip to content

LDGerrits/Bootstrapper

Repository files navigation

Bootstrapper

A lightweight, agnostic module loader and scheduler for Roblox. Eliminate load-order bugs and race conditions by organizing your modules into a deterministic pipeline. Define exactly what runs, and in what order, every single frame with zero ambiguity.

Why use this?

As Roblox projects scale, developers often fall into the trap of decentralized execution. Every module, controller or service connects to RunService independently, and, before long, you have dozens of scripts updating independently every frame. Without strict control over whether your InputController runs before or after your CharacterController, you inevitably run into state bugs, race conditions, and one-frame delays.

Bootstrapper solves this by treating execution flow as a single, managed pipeline. By organizing your modules into simple arrays and updating them in a strict sequence, you completely eliminate unpredictable load orders and execution desyncs. Bootstrapper makes sure things happen exactly when you tell them to and doesn't care if you write OOP, functional, or procedural code.

Features

  • Deterministic Execution: Load modules alphabetically or provide a strict manual sequence. Your game will start exactly the same way every single time.
  • Centralized Scheduler: Iterate over your modules and fire their updates synchronously.
  • Luau Method Syntax: Use standard dot or colon notation (. or :) in your string arguments to explicitly call functions or inject self for object methods.
  • Flexible Event Binding: Hook a sequence of modules directly to RunService events, RBXScriptSignals, or custom Signal objects.
  • Automatic Memory Profiling: Automatically assigns debug.setmemorycategory to every module's thread and RunService connection so your MicroProfiler is readable.
  • Lazy Require: Pass raw ModuleScript instances directly as argument. Bootstrapper will automatically require them.

Installation

Wally

Add this to your wally.toml:

ldgerrits/bootstrapper@^1.2.1

Quick Start

local Bootstrapper = require(packages.Bootstrapper)

-- Strict boot sequence (manual order for core systems)
local bootSequence = { path.to.DataService, path.to.PlayerService, path.to.ZoneService }
Bootstrapper.run(bootSequence, ':init') -- injects self
Bootstrapper.runAsync(bootSequence, ':start') -- injects self

-- Automatic discovery (A-Z sorted)
local systems = Bootstrapper.loadChildren(path.to.Systems, Bootstrapper.byName('System$'))

-- Use '.run' for a strict A-Z sequence, or '.runConcurrent()' if not
Bootstrapper.run(systems, '.init') -- does NOT inject self
Bootstrapper.runConcurrent(systems, '.start') -- does NOT inject self

-- Maintains alphabetical execution every frame with auto-memory profiling.
Bootstrapper.bindToHeartbeat(systems, '.onUpdate') -- does NOT inject self

-- Run every second without drift.
Bootstrapper.bindToInterval(systems, '.onTick', 1.0) -- does NOT inject self

Usage

1. Finding & Loading Modules

Load modules from a folder. Bootstrapper automatically requires them and returns a deterministically sorted array (alphabetical by Name), ensuring your game starts the same way every time.

local Bootstrapper = require(path.to.Bootstrapper)

local services, errors = Bootstrapper.loadDescendants(path.to.Services, Bootstrapper.byName('Service$'))

2. Manual Sequences

You can use .loadSequence() to define a manual order using an array ModuleScript instances. This sequence can be stored and reused for all lifecycle stages.

local services, errors = Bootstrapper.loadSequence({
    path.to.DataService,
    path.to.PlayerService,
    path.to.AssetService,
})

-- Use the resulting services array for everything else
Bootstrapper.run(services, '.init')

3. Dot vs. Colon Notation

When passing a method name to any Bootstrapper function, you can dictate how the function is executed:

  • 'methodName' or ':methodName': Calls as an object method. module:methodName()

  • '.methodName': Calls as a function. module.methodName()

-- Injects 'self' into every module's '.onUpdate()' method
Bootstrapper.bindToHeartbeat(services, ':onUpdate') -- injects self
Bootstrapper.bindToHeartbeat(services, 'onUpdate') -- functionally the same as ':onUpdate'


-- Calls '.tick()' statically, without passing the module table
Bootstrapper.bindToInterval(services, '.tick', 1.0) -- does NOT inject self

4. Lazy Require

You don't have to call a loader function. Pass an array of ModuleScripts directly to any function, and Bootstrapper handles the require() automatically.

-- This works even if the modules haven't been required yet
Bootstrapper.bindToHeartbeat({
    path.to.CombatService,
    path.to.VFXService,
}, '.onUpdate')

5. Execution Flow Control

  • .run(): Sequential & Yielding. Yields until every module finishes in order. Returns successful modules and an error map.

  • .runAsync(): Sequential & Non-yielding. Executes the sequence in a background thread while maintaining order.

  • .runConcurrent(): Concurrent & Non-yielding. Fires all methods simultaneously. Best for independent tasks where order doesn't matter.

-- Sequential & Yielding
local services, errors = Bootstrapper.run(services, '.init')

-- Sequential & Non-yielding
Bootstrapper.runAsync(services, '.postInit')

-- Concurrent & Non-yielding
Bootstrapper.runConcurrent(services, '.start', gameState)

6. Event Binding

Route RunService events or signals directly to module methods. All binding functions follow the same signature: (modules, methodName, [context]).

local cleanupRenderStep = Bootstrapper.bindToRenderStep(services, '.onRender', Enum.RenderPriority.Last.Value)
local cleanupInterval = Bootstrapper.bindToInterval(systems, '.onTick', 1.0)
local cleanupGameState = Bootstrapper.bindTo(services, '.onGameState', GameState.Changed)

-- Disconnect everything when they are no longer needed
cleanupRenderStep()
cleanupInterval()
cleanupGameState()

About

A lightweight, agnostic module loader and scheduler for Roblox. Define exactly what runs, and in what order, every single frame with zero ambiguity.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages