Console::CartLoader
console 0.2.0 · app/console/cart_loader.rb · 369 lines · view source
Console::CartLoader -- finds a cart, requires it, and starts the console.
A "cart" is a directory under carts/ that defines one module (or class) implementing the cart contract:
carts/space/ app/space.rb the cart: one module/class named after the directory sprites/ its own art sounds/ maps/ data/
Only the selected cart is ever required, so a cart is genuinely a standalone program: nothing else in carts/ is parsed at runtime, so a broken cart can never stop the console from booting, and two carts can both have a sprites/hero.png without seeing each other's.
The contract is intentionally tiny. All of these hooks are optional:
def self.assets # { sprites: {...}, sounds: {...}, music: {...} } def setup # once, at boot def update # every frame (when the cart defines no scenes) def render # every frame, always last: use it for a HUD
And it may define scenes instead of update:
scene :title, TitleScene
Every API method in Console::API is available as a bare call inside a cart. Paths a cart writes are relative to its own directory -- sprites/hero.png means carts/space/sprites/hero.png -- which Console::Assets resolves.
def initialize(args)- line 37
def cli- line 45
def pin(target)-
Pin the loader to exactly one cart.
This is how a published single-cart build boots: ./bin/publish-cart stages a directory holding one cart and an entry point that pins it, so the cart cannot be swapped at runtime.
The pin accepts a name or a path, exactly like --cart does, so a staged build pins 'carts/space' and keeps working if the cart is ever moved.
A pin outranks --cart and $CART. In a packaged build there is no terminal to type at, and the whole point is that what you shipped is what runs.
def pinned?- line 65
def pinned_name- line 69
discovery
def cart_dir(target)-
Turn whatever was asked for into a cart directory.
A bare name means the gallery: 'space' is carts/space. Anything with a path separator is taken as a path, which is what lets a cart live in games/ or demos/ instead of carts/ without the console caring.
def cart_name(dir)-
The cart's name is the last segment of its directory, which is also the module name resolve_cart_module looks for.
def cart_entry(dir)-
A cart's entry file.
app/main.rb is preferred -- "the entry point" means the same thing here as it does at the console root, and it drops the name stutter. app/<name>.rb is still accepted so a cart written the old way keeps working.
def cart?(dir)-
Is this a bootable cart?
def available-
Cart names in the default gallery, sorted. Only directories with an entry file count, so a half-created cart or a stray README in carts/ is simply not a cart.
def requested_name-
Explicit wins, then the env var, then a deterministic fallback.
A pin beats everything: a published build is pinned, and there is nothing sensible for it to fall back *to*.
The value is whatever was asked for -- a bare name or a path -- and it is left that way so callers can report exactly what the user typed. Use requested_dir to get the directory it resolves to.
def requested_dir-
The directory the requested target resolves to.
def selected_name- line 148
running
def run- line 154
def require_cart_files(cart, entry)-
Require the rest of the cart's own top-level code files.
A cart is allowed to be more than one file -- that is what app/ is for -- and the entry file is always required first, so a cart can define things its siblings reference at load time. Only this cart's directory is read, which is the property that keeps one broken cart from taking the console down with it. Sub-directories are the cart's to require itself.
def run_selftest-
Run the built-in suites.
The suites assert against asset files the selftest cart owns, so the asset scope moves to that cart even when a different one booted: a test that resolved a fixture against whichever cart happened to boot would quietly assert nothing useful.
def instantiate(cart)-
A cart file may define a Class (hooks as instance methods) or a Module (hooks as singleton methods). Classes are instantiated so that carts can hold per-run state.
def resolve_cart_module(name)-
A cart file may define either a module or a class named after itself.
def apply_switches- line 288
switches
def handle_list- line 315
def cart_title(dir)-
Read a cart's title without requiring it: grep the source.
def handle_test- line 338
def abort_boot(message)- line 344
def quit_now(ok = true)- line 350
def switch?(key)-
Is a switch present?
DR.cli_arguments stores a bare switch (
--list) as a key with a nil value, which is indistinguishable from an absent key by lookup alone -- so the key's presence is what counts, and only an explicit falsey value turns it off (--list=false).