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
.btfile 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 ascturtle/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 namegives the package an alias for qualified names.- Standard library packages have bare names (
io,files). Engine packages start withcturtle/(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:
| Include | Error |
|---|---|
A file name or relative path: ends in .bt, starts with ./ or ../, or is absolute | include '...' 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 package | included package '...' was not loaded |
A standard library name with a path after it, such as io/extra | include '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, andtypeof.
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:
- a local variable or parameter;
- the file's own package;
- 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; - 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 lastgeometryis 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:
| Package | What it is for |
|---|---|
io | Printing, and the standard input, output and error streams |
time | Waiting, measuring elapsed time, the current UTC time |
files | Reading and writing files; listing, creating and removing directories |
sync | Mutex, a lock for tasks that share data |
string | String methods and conversion between strings, numbers and buffers |
math | sqrt, trigonometry, powers, rounding and other float math |
buffer | Growable byte storage |
json | Reading JSON documents |
regex | Regular expressions |
socket | TCP network connections |
process | Running 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 standardiopackage. A name with a path after it, such asio/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"), ingame.json,bt.module.jsonor a dependency name. Loading it fails withBT module name 'io' is reserved: "io" is a standard library package; choose another module name. Give the module a name of its own, such asmy-game; folders inside it can still be calledio(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.
| Where | Entry point | Signature | Notes |
|---|---|---|---|
| cTurtle game | gameMain (change it with bt.entry in game.json) | a tree with no parameters | Optional. Runs once at start-up, before the first frame and before scripted UI starts. |
| cTurtle scripted UI | uiMain | fn uiMain(UiRegistry registry, int width, int height) -> int | In the script named by ui.entry in game.json. |
ctbt run, ctbt build, standalone executables | main | fn main() -> int or -> void, fn or infallible fn | No 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
- Formats:
bt.module.json,game.json - Tools:
ctbt - Guide: Functions and modules, Getting started