Skip to main content

Packages

How BT code is split across folders, how include works, what each file can see, and which functions the engine starts.

my-game/ game.json declares module "example/game"
scripts/main.bt package "example/game/scripts"
scripts/combat/actions.bt package "example/game/scripts/combat"
scripts/combat/damage.bt package "example/game/scripts/combat"
// scripts/combat/actions.bt
include "io";

fn combat_init() { println("combat ready"); }
// scripts/main.bt
include "example/game/scripts/combat";

tree gameMain() -> void {
combat_init()?; // declared in scripts/combat/actions.bt
}

Packages​

  • A package is a folder. Every .bt file directly inside it belongs to the package. A subfolder is a separate package.
  • Files in the same package see each other's declarations without any include, and share ~ private declarations.
  • The order of files in a folder doesn't matter.
  • Standard library packages (such as io) and engine packages (such as cturtle/world) are built in and have no folder in your project.

Modules​

A module gives a folder tree a name. A package's name is the module name followed by the folder's path inside the module:

/src/game/ module "example/game"
combat/actions.bt package "example/game/combat"
combat/ai/planner.bt package "example/game/combat/ai"

Declare a module with the module object in your game.json (or .ctproject), or with a bt.module.json file in its root folder. Those pages describe module names and dependencies on other modules. If several definitions apply to a file, the one in the nearest enclosing folder wins.

Includes​

include "io";
include "example/game/combat";
include "example/game/ui/widgets" as w;
  • The string is a package name, never a file name.
  • as name gives the package an alias for qualified names.
  • Standard library packages have bare names (io, files). Engine packages start with cturtle/ (cturtle/world). Your packages are named by module name plus folder path.
  • Includes can appear anywhere at the top level. Packages may include each other in a cycle.
  • Including a package doesn't give you the packages it includes; include each package you use.

Errors:

IncludeError
A file name or relative path: ends in .bt, starts with ./ or ../, or is absoluteinclude '...' names a file; include a package by its module path
A path that climbs out of its module with ..include '...' escapes its BT module root
A name that matches no packageincluded package '...' was not loaded
A standard library name with a path after it, such as io/extrainclude 'io/extra': "io" is a standard library package and has no subpackages

Which code is loaded​

The engine starts from the scripts listed in bt.sources in game.json (see game.json), loads each of their whole packages, then every package those include, and so on. Everything loaded becomes one program. Each package keeps its own names. Any duplicate name, ambiguous name, missing include or type error stops the program from loading at all; nothing runs until every error is fixed.

Visibility​

What a file can use:

  • everything in its own package;
  • public declarations of another package, only if this file includes that package. Otherwise: symbol 'f' requires include "pkg/path" in this file;
  • engine functions, types and constants, only if this file includes their package;
  • core language features without any include: built-in types, error, arrays, maps, channels, tasks, their methods, and typeof.

Some common features need an include: sqrt needs math, buffer needs buffer, and string methods need string.

~ declarations and fields are private to their package and can't be used from other packages at all (see Private declarations). Everything else is public to any file that includes the package.

Names and namespaces​

Each package has its own namespace. Two packages may declare the same function, object, interface, enum, constant, variable, query or access name. Within one package a name is declared once: duplicate exported name 'f' (first declared in ...).

An unqualified name is looked up in this order:

  1. a local variable or parameter;
  2. the file's own package;
  3. the packages the file includes. If two of them declare the name, it is the error 'x' is ambiguous: declared in packages a/geo and b/shapes; qualify it as geo.x or shapes.x;
  4. built-in and engine names.

A name that only one included package declares works unqualified.

A qualified name q.name picks the package: q is the include's as alias, or else the last segment of its path (include "cturtle/world"; gives world, and include "math"; gives math). Qualified names work everywhere a top-level name does:

include "example/geometry";
include "example/shapes" as sh;
include "math";

infallible fn demo() -> float {
geometry.Vec2 p = geometry.Vec2 { x: 3.0, y: 4.0 }; // types and literals
array<sh.Vec2> boxes = [sh.Vec2 { x: 1.0 }];
infallible fn() -> float f = sh.helper; // function values
return math.sqrt(p.x * p.x + p.y * p.y) + f(); // calls
}

Enum members, enum conversions, constants and top-level variables qualify the same way: sh.Tone.Soft, sh.Tone(2)?, geometry.LIMIT and sh.counter. A qualified variable can be assigned, compound-assigned and incremented (sh.counter += 1;, sh.counter++;), and a qualified constant can appear in another constant's value (const int BOTH = geometry.LIMIT * 2;). Interpolating an enum value shows the member name alone: "${sh.Tone.Soft}" is Soft.

Engine constants work unqualified (Action.ScanSector) and qualified by their package (pkg.Action.ScanSector).

Rules:

  • A local or parameter named like a qualifier hides it: in geometry.Vec2 geometry = ...; geometry.x, the last geometry is the local.
  • An alias names one include. Reusing it is qualifier 'g' is already used by include "..."; choose another alias with 'as'.
  • Two includes whose paths end in the same segment are fine until that segment qualifies a name: qualifier 'util' is ambiguous: includes "a/util" and "b/util" both use it; give one an alias with 'as'.
  • A qualified name the package does not declare is package "..." has no 'name'.
  • ~ private declarations stay private to their package, qualified or not.

Names hosts and tools see​

Outside your code (in compiler messages, the debugger, and when a host starts a function by name), a declaration goes by its plain name if only one package declares it. A name that several packages declare is written package::name instead, for example example/geometry::Vec2 and example/shapes::Vec2. To have the host start such a function, give it that full name.

Standard library packages​

BT's standard library is available to every BT program, in games and tools alike:

PackageWhat it is for
ioPrinting, and the standard input, output and error streams
timeWaiting, measuring elapsed time, the current UTC time
filesReading and writing files; listing, creating and removing directories
syncMutex, a lock for tasks that share data
stringString methods and conversion between strings, numbers and buffers
mathsqrt, trigonometry, powers, rounding and other float math
bufferGrowable byte storage
jsonReading JSON documents
regexRegular expressions
socketTCP network connections
processRunning programs, command-line arguments, environment variables, paths

Each package is documented in the BT standard library reference. Engine packages such as cturtle/world are available in cTurtle games and are documented in the cTurtle API reference.

These names are reserved for the standard library:

  • include "io"; always means the standard io package. A name with a path after it, such as io/extra, is an error.
  • A module can't be named after a standard package or start with one followed by / ("name": "io" or "name": "json/tools"), in game.json, bt.module.json or a dependency name. Loading it fails with BT module name 'io' is reserved: "io" is a standard library package; choose another module name. Give the module a name of its own, such as my-game; folders inside it can still be called io (my-game/io).

Entry points​

An entry point is a function the host starts by name. Entry points must be public: entry point 'main' cannot be private.

WhereEntry pointSignatureNotes
cTurtle gamegameMain (change it with bt.entry in game.json)a tree with no parametersOptional. Runs once at start-up, before the first frame and before scripted UI starts.
cTurtle scripted UIuiMainfn uiMain(UiRegistry registry, int width, int height) -> intIn the script named by ui.entry in game.json.
ctbt run, ctbt build, standalone executablesmainfn main() -> int or -> void, fn or infallible fnNo parameters. An int result is the exit status. An uncaught failure or trap prints a message and exits with status 1. Read command-line arguments with process_arg_count() and process_arg(i) from process.

The engine can also start other trees by name; keep those public too.

main errors: BT native entry 'main' was not found, must take no parameters, must return void or int. ctbt reports them before building or running.

See also​