Skip to content
Engine

Core Concepts

API Conventions

The patterns every Engine API follows. Naming, Async methods and Promises, Get versus Find, options tables, results with reasons, events, properties, errors and frozen tables.

Engine's modules are written to the same rules, so once you know one, the others feel familiar.

Naming

Engine follows Roblox's style:

  • Methods are PascalCase and called with a colon: DataService:CreateStore("Settings").
  • Package constructors use a dot: Signal.new(), Promise.resolve(value).
  • Properties and events are PascalCase too: ModerationSystem.SlowMode, EconomySystem.BalanceChanged.

Async methods return Promises

A method whose name ends in Async may take time (DataStores, web calls, other servers) and returns a Promise instead of yielding:

local Settings = DataService:CreateStore("Settings")

Settings:GetAsync("Global")
	:andThen(function(settings)
		apply(settings)
	end)
	:catch(function(err)
		warn("Could not load settings:", err)
	end)

Inside a thread that may yield, read the result directly with :await() (returns ok, ...) or :expect() (errors on rejection):

local ok, settings = Settings:GetAsync("Global"):await()

Many actions come in pairs. The plain method returns right away; the Async one resolves when the work is finished or saved:

local sanction = ModerationSystem:Ban(player, "Exploiting")         -- applied now
ModerationSystem:BanAsync(userId, "Exploiting"):andThen(onSaved)  -- resolves once saved

Get, Find and lists

PatternReturnsWhen it is missing
:GetX(name)The objectErrors with a message that says how to create it.
:FindX(name)The object or nilReturns nil.
:GetXs()A list of all of themReturns an empty list.

Use Get when the object must exist (a typo should fail loudly) and Find when it may not.

Create and Define make new objects (CreateShop, DefineRole), and objects you create have a :Destroy() method.

Options tables

Methods with more than one or two optional parameters take an options table, so calls read clearly and new options never break your code:

EconomySystem:CreateCurrency("Gems", { Symbol = "G", Max = 100000, Decimals = 0 })

Unknown keys are ignored. Wrong types (a string where a number belongs) error immediately.

Expected failures return a reason

Mistakes in your code (wrong argument types, unknown names) error. Things that can legitimately fail at runtime return a reason instead, so you can handle them without pcall:

local result = EconomySystem:Withdraw(player, "Coins", 500)
if not result.Ok then
	print(result.Reason) -- "Insufficient"
end

local sanction, denied = ModerationSystem:Kick(target, { By = moderator })
if not sanction then
	print(denied) -- "TargetOutranks"
end

Async methods reject their Promise with the same reason strings. Many modules publish their reasons as constants, such as ModerationSystem.ReportErrors or PasswordSystem.PolicyReasons.

Events are Signals

Every event is a Signal:

local connection = EconomySystem.BalanceChanged:Connect(function(player, currency, balance, delta)
	print(player.Name, currency, balance)
end)

connection:Disconnect()
EconomySystem.TradeCreated:Once(onFirstTrade)
local trade = EconomySystem.TradeCreated:Wait()

Handlers run in their own threads, so a handler that yields or errors does not affect others.

Properties

Modules expose state as properties. Read-only properties raise an error when assigned; settable ones apply the change right away:

print(ModerationSystem.LoadedRecordCount)
ModerationSystem.SlowMode = 5 -- same as :SetSlowMode(5)

Reading or writing a member that does not exist errors (Attempt to get ModerationSystem::SlowMod (not a valid member)), so typos are caught immediately instead of silently returning nil.

Errors

Engine's errors start with Engine: and name the module and method, so you can tell where they came from:

Engine: EconomySystem:CreateShop "Weapons" - Items must be a list of items

Frozen snapshots

Tables Engine hands back (records, results, descriptions) are often frozen snapshots. They do not change when the underlying data changes, and writing to them errors. Copy them with TableKit if you need a mutable version, and use the module's methods to make changes.

Types

Each module exports its types, listed in the Types section at the bottom of its page. Engine's getters return each module without a static type, so annotate your variables where it helps:

local Signal = Engine:GetPackage("Signal")
local onRoundEnd: typeof(Signal.new()) = Signal.new()