Start order

Say which start runs first with function Start() after Wallet.Start, and shot check checks the order of the calls in the start script.

What it is

A game starts its systems from a script, one call per line (Init/Server.shot: Wallet.Start(), Market.Start(), ...). When one Start uses what another Start sets up, swapping their two lines breaks the game at run time, often without an error: a list copied empty, a handler that runs before the state it needs exists. A clause on the Start's own header line declares that order where its reader sees it:

function Start() after Wallet.Start {it prices gems with the rates Wallet loads}

shot check then finds the scripts that call the Start and checks the order of the calls. It costs nothing at run time: the clause is not code and is left out of the compiled Luau.

The whole syntax

Market/Market.shot

types script.Parent.Types

use @shot.Wallet

local prices : {[string] : number} = {}

rootdoc {
    The shop's prices in coins.
}
module Market = {
    doc { Opens the shop }
    function Start() after Wallet.Start {it prices gems with the rates Wallet loads}
        prices.gem_pack = 5 * (Wallet.Rates().Gems or 0)
    end
}

Wallet/Wallet.shot

types script.Parent.Types

local Players = game:GetService("Players")

local rates : {[string] : number} = {}
local coins : {[Player] : number} = {}

rootdoc {
    Every player's coins, and what a gem is worth in coins.
}
module Wallet = {
    doc { Loads the exchange rates and gives every player who joins a wallet }
    function Start() before Prizes.Start {Prizes pays a join prize into the wallet this makes}
        rates.Gems = 10
        Players.PlayerAdded:Connect(function(player : Player)
            coins[player] = 0
        end)
    end

    doc { The exchange rates, currency to coins }
    function Rates() : {[string] : number}
        return table.clone(rates)
    end

    doc { Adds coins to a player's wallet }
    function Give(player : Player, amount : number)
        coins[player] = (coins[player] or 0) + amount
    end
}

Init/Server.shot

@server enabled

use Wallet
use Market
use Prizes

Wallet.Start()
Market.Start()
Prizes.Start()
  • after X: X's call must come above this function's call in the script that calls both. before Y: Y's call must come below it.
  • The clause states what the Start needs, not a rule to keep: a change that needs another order removes that need, then drops the clause (the error's last fix).
  • Write each name as the start script calls it (Wallet.Start, Economy.Server.Start, ProfileStore.Start), not as a path. Several names take commas: after Wallet.Start, Bank.Start. after and before can both appear on one line.
  • A reason in braces may follow a name: {it prices gems with the rates Wallet loads}. Say what breaks at run time if the order is lost; the error quotes it. It stays on the header line.
  • The clause goes on the header line of a module function (or a class method) after its parameters and return type. The function is usually Start, but any function a script calls on its own line can declare one.
  • Declare an order only when it is true: you swapped the two calls and something changed. Put it on the side that breaks (Market needs Wallet first: Market says after Wallet.Start; Wallet's handler must run first: Wallet says before Prizes.Start).

What is checked

  • The scripts that call the declaring function are found wherever they are (any @server or @client script, not only Init), and in each the declared names are looked up among the calls that start a line, by the text before the parentheses.
  • The order of the two calls' lines. A wrong order is an error at the call that must move. When the declaration has a reason, the error also says how to keep the order a change asks for: make the Start that runs first do that work without the other (when it is first needed, not at start), then drop the clause.
  • A declared name no script calls is an error with the nearest call; a declaring function no script calls is an error, since its order can't be checked.
  • Two declarations that can't both hold (Market after Wallet.Start and Wallet after Market.Start) are one error naming both.

What it doesn't check

  • Couplings nobody declared. shot check doesn't look for them: find them by reading what each Start reads that another Start sets up, and prove each by swapping the two calls on a copy and running the scenarios.
  • Calls that aren't on a line of their own (inside an if, a function or a loop are still lines of their own; print(Wallet.Start()) is not), and starts called from two different scripts: two scripts have no order between them, so that declaration is an error.
  • Longer cycles (A after B, B after C, C after A): each declaration is checked against the lines, so one of them is reported as a wrong order.

Errors

Init/Server.shot:7:1   Market.Start() runs before Wallet.Start() (line 8), but Market.Start must run after it (Market/Market.shot:12: after Wallet.Start {it prices gems with the rates Wallet loads}): move this line below line 8; or, to keep this order, make Market.Start do that without Wallet.Start (when it is first needed, not at start), then drop after Wallet.Start there; or if Market no longer needs that order, drop after Wallet.Start there
Init/Server.shot:8:1   Wallet.Start() runs after Prizes.Start() (line 7), but Wallet.Start must run before it (Wallet/Wallet.shot:13: before Prizes.Start {Prizes pays a join prize into the wallet this makes}): move this line above line 7; or, to keep this order, make Prizes.Start do that without Wallet.Start (when it is first needed, not at start), then drop before Prizes.Start there; or if Wallet no longer needs that order, drop before Prizes.Start there
Market/Market.shot:12:25   after Walet.Start {it prices gems with the rates Wallet loads}: no script calls Walet.Start() (nearest: Wallet.Start): write the name as Init/Server.shot calls it
Wallet/Wallet.shot:13:25   after Market.Start and Market/Market.shot:12's after Wallet.Start {it prices gems with the rates Wallet loads} can't both hold: each wants its Start first. Keep the one that is still true and drop the other
Market/Market.shot:12:25   no script calls Start of Market/Market.shot, so its after can't be checked: call it from the script that starts the game (Init/Server.shot), or drop the clause
Market/Market.shot:12:19   a comma goes between two names: after Wallet.Start, Bank.Start