# The Goose standard library
Table of contents
- [std](#std)
- [exception](#exception)
- [dictionary](#dictionary)
- [vec](#vec)
- [math](#math)
- [os](#os)
- [ffi](#ffi)
- [binary](#binary)
- [base64](#base64)
- [csv](#csv)
- [json](#json)
- [regex](#regex)
- [gfx](#gfx)
- [audio](#audio)
- [physics](#physics)
- [ui](#ui)
The standard library has sixteen modules under `stdlib/`: `std`,
`dictionary`, `vec`, `math`, `os`, `ffi`, `binary`, `base64`, `csv`, `json`,
`regex`, `audio`, `gfx`, `physics`, `ui`, and `exception`. Import each module by name,
for example `import std;`. The compiler locates the library in its source
tree; use `--stdlib
` or `GOOSE_STDLIB` to select another location.
Everything is written in Goose except the following parts, all reached
through `extern fn` (spec §7.10):
* the C behind `os` (`src/runtime/runtime_os.h`);
* the byte search behind `std`'s `find_any` and `find_pair`
(`src/runtime/runtime_impl.h`);
* `std`'s `popcount`, `clz` and `ctz`, and `ffi`'s two copies
(`src/runtime/runtime_ext.h`);
* libm, behind `math`;
* the graphics layer behind `gfx`, the ui layer behind `ui` and the PCM
mixer behind `audio`, which are in the
[goose_gfx](https://github.com/aardappel/goose_gfx) package;
* the physics layer behind `physics`, which is in the
[goose_physics](https://github.com/aardappel/goose_physics) package.
Both packages are repositories of their own. The design and its rationale
are described in `design/stdlib_design.md` and in the packages' own
[docs/gfx.md](https://github.com/aardappel/goose_gfx/blob/main/docs/gfx.md),
[docs/ui.md](https://github.com/aardappel/goose_gfx/blob/main/docs/ui.md), [docs/audio.md](https://github.com/aardappel/goose_gfx/blob/main/docs/audio.md) and
[docs/physics.md](https://github.com/aardappel/goose_physics/blob/main/docs/physics.md).
This document is the reference.
For **text rendering, fonts and game HUDs**, start with
[`ui`](#text-rendering-and-game-huds), which renders over `gfx`. For external
binary file formats, use [`binary`](#binary); `from_bytes` reads Goose's own
serialization format. Text formats have modules of their own:
[`json`](#json), [`csv`](#csv) and [`base64`](#base64), and
[`regex`](#regex) searches text with regular expressions.
The library uses these conventions:
* A function that reads or mutates elements in place takes `xs: T[:]`; every
array kind and every slice coerces to it: `sort(arr)`, `sort(arr[1..])`.
* A function that changes the length takes `xs: A&`, the whole array by
reference, and instantiates for whatever array kind it is given, provided
that kind has the operations used. A grow-only array shrinks through the
reference wherever nothing in the caller points into it (spec §5.1), so
`remove_at(xs, i)` works on a `[>..]` local; the checker rejects the
call if a live reference or slice could be invalidated.
* Nothing non-fixed is taken by value (spec §4.1): `f(xs)` binds by
reference, and a function that wants its own copy calls `copy(xs)`.
* Fresh arrays come back as `T[>..]`, built directly in the caller's
destination: `let ys = filter(xs) { it > 0 };`, `out.append(map(xs) { … })`.
* "Maybe an element" is a `T?`: a reference into the input, or null for
none. Narrow it with `if`/`guard`. Positions are `i64`, `-1` for none.
Two-outcome scalars return a trailing `bool`: `let v, ok = parse_int(s);`.
* Blocks: `xs.find() { it > 3 }`, `sort(xs) { a, b => a.age < b.age }`. A
comparator is always "a goes before b". Sorting, heap and extremum
functions come with and without a block; without a block they compare
with `<`.
* Text output goes into a `u8[>..]&` builder the caller passes; `str(…)` is
the fresh-string form.
* UFCS applies: `xs.sort()`, `d.insert(k, v)`, `r.rand_int(6)`.
* A function taking an element *by value* (`push_n`, `insert_at`, `fill`,
`heap_push`) cannot take one that contains self-relative references, because
those values cannot be copied (spec §3.9). Construct them in place.
* The functions that move elements around (`swap_at`, `reverse`, `shuffle`,
`sort`, `stable_sort`, `fill`, `copy_into`, `insert_at`, `remove_at`,
`swap_remove`, `retain`, `dedup`, the heap functions) move the elements
themselves, including references: `reverse(refs)` reorders an array of
references and leaves what they point to unchanged, and an array of
references to values that cannot be copied (spec §3.9) can be sorted too.
They store whole elements with `.=`, as generic code may (spec §3.8). An
array of relative references (self-relative or `in pool`) is reordered
the same way. A `T` local or parameter holds the plain reference, and
each element the reference is stored back into encodes it again. For a
self-relative link, this requires the link to point within that
element's root (spec §3.9), which a link moved within its own array does.
`swap(a, b)` exchanges the values `a` and `b` refer to. `remove_at`,
`swap_remove` and `heap_pop` return a copy of the removed value; for a
reference element, they return a copy of its pointee. A reference element
passed by value (`insert_at`, `heap_push`) is written `&x`, which makes
`T` the reference type. `stable_sort` sorts an array of `in pool` links,
but its temporary array cannot hold copies of self-relative links (spec
§3.9).
* The `std`, `dictionary`, `vec`, `math`, and `os` names are global;
`ffi`, `binary`, `base64`, `csv`, `json`, `regex`, `audio`, `gfx`,
`physics`, `ui` and `exception` use their own namespaces, and so do the
helpers `std` and `json` read and write float text with, in `float_text`.
A local named `fill` or `count` shadows the global function of that name,
so a call to that function is an error. A global variable of such a name does not
shadow it, because the name in a call resolves past global variables to
the functions (spec §11.1).
## std
### Scalars and bits
```goose
fn min(a: T, b: T) -> T fn max(a: T, b: T) -> T
fn clamp(x: T, lo: T, hi: T) -> T
fn abs(x: T) -> T // integers; abs(f32) and abs(f64) overloads
fn rem(a: T, b: T) -> T // integers; pairs with `/`
fn div_euclid(a: T, b: T) -> T // integers; pairs with `%`
fn sign(x: T) -> T // -1, 0, 1 at x's type; f32/f64 overloads
fn lerp(a: f64, b: f64, t: f64) -> f64 // and f32
fn next_pow2(x: i64) -> i64 // smallest power of two >= max(x, 1)
fn popcount(x: u64) -> i64
fn clz(x: u64) -> i64 // 64 for 0
fn ctz(x: u64) -> i64 // 64 for 0
fn swap(a: T&, b: T&) // swap(x, y)
```
`popcount`, `clz` and `ctz` are implemented in the runtime. Each is a single
instruction where the C compiler has one (MSVC's `popcount` needs
`/arch:AVX` or higher for that), and a loop under TinyCC.
`rem` is the remainder of truncating division: `rem(-7, 3) == -1`, paired
with `-7 / 3 == -2`. `div_euclid` is the quotient paired with Euclidean `%`:
`div_euclid(-7, 3) == -3` and `-7 % 3 == 2`. It rounds down for positive
divisors and up for negative ones (`div_euclid(-7, -3) == 3`). Thus a
coordinate's tile and offset are `div_euclid(x, width)` and `x % width`.
Both functions return the operands' integer type and abort on a zero
divisor. `rem(MIN, -1)` is zero, including at `i64`; `div_euclid` follows
`/`'s overflow rules (always abort at `i64.min, -1`; at narrower signed
widths, debug abort and release wrap). Unsigned `rem` and `div_euclid` are
ordinary `%` and `/`. Neither function takes floats; float `%` is already
the truncating remainder (`fmod`). The identities
`b * (a / b) + rem(a, b) == a` and
`b * div_euclid(a, b) + a % b == a` hold mathematically when the quotient
is representable, but evaluating their intermediate products can overflow.
### Hashing
```goose
fn hash(x: i8) -> u64 // ... one overload per integer type
fn hash(x: bool) -> u64 fn hash(x: f32) -> u64 fn hash(x: f64) -> u64
fn hash(s: u8[:]) -> u64 // 8 bytes per multiply; any u8 array coerces
fn hash_combine(seed: u64, h: u64) -> u64
```
These hashes mix all of their input into the low 32 bits. `dictionary`
masks those bits to find the home slot and keeps them as the slot's tag.
A float hashes as its bits, `-0.0` as `0.0`, and an `f32` as the `f64` of
its value.
`dictionary` also picks up a `hash` overload for a user key type, declared
globally or in the key type's namespace (it calls `key.hash()`, §11.1), and
that overload should do the same, as `hash_combine` of the fields' hashes
does:
```goose
struct key { a: i64, b: i64 }
fn hash(k: key) -> u64 { hash_combine(hash(k.a), hash(k.b)) }
```
### Random numbers
```goose
struct rng { s: u64 } // splitmix64; the seed is the state
fn rand_u64(r: rng&) -> u64
fn rand_int(r: rng&, n: i64) -> i64 // uniform in [0, n), without modulo bias; asserts n > 0
fn rand_flt(r: rng&) -> f64 // uniform in [0, 1)
fn shuffle(xs: T[:], r: rng&) // Fisher-Yates
```
```goose
var r = rng { os.random_seed() }; // or any fixed seed
let d = r.rand_int(6);
```
### Arrays: reading and searching
```goose
fn each_rev(xs: T[:]) // F(x), last to first
fn each_chunk(xs: T[:], n: i64) // F(chunk: T[:], i); the last chunk may be short
fn find(xs: T[:]) -> T? // first x with F(x), as a reference
fn find_index(xs: T[:]) -> i64
fn position(xs: T[:], v: T) -> i64 // first i with xs[i] == v
fn contains(xs: T[:], v: T) -> bool
fn any(xs: T[:]) -> bool fn all(xs: T[:]) -> bool
fn count(xs: T[:]) -> i64 // number of x with F(x)
fn min(xs: T[:]) -> T // asserts xs.len > 0; min(xs) { a, b => ... } too
fn max(xs: T[:]) -> T
fn min_by(xs: T[:]) -> i64 // index of the smallest F(x); -1 if empty
fn max_by(xs: T[:]) -> i64
fn last(xs: T[:]) -> T& // asserts xs.len > 0
fn lower_bound(xs: T[:]) -> i64 // first i with !F(xs[i]); F true on a prefix
fn binary_search(xs: T[:], v: T) -> i64, bool // sorted by <: position (or insertion point), found
fn find(xs: T[:], sub: T[:]) -> i64 // first occurrence of a subsequence, -1 if none
fn rfind(xs: T[:], sub: T[:]) -> i64
fn starts_with(xs: T[:], p: T[:]) -> bool fn ends_with(xs: T[:], p: T[:]) -> bool
```
`find`/`rfind`/`starts_with`/`ends_with` are generic over the element, so
they are the string functions too: `find(line, "://")`. `find` and `rfind`
compare a whole subsequence only where its first element matches.
(The function is named `position` rather than `index_of` because
`index_of` is the builtin that turns an element reference into its index.)
```goose
let big = xs.find() { it > 100 };
if big { print(big); }
var row = 0;
each_chunk(pixels, width) { line, y => row += line.len; };
```
### Byte sets
```goose
struct ByteSet { members: bool[256], ... } // a set of bytes, prepared for searching
fn byte_set(members: bool[256]) -> ByteSet
fn byte_set(bytes: u8[:]) -> ByteSet // the bytes listed: byte_set(",\"\n")
fn find_any(s: const u8[:], set: const ByteSet&) -> i64
// first i with s[i] in the set, -1 if none
fn find_pair(s: u8[:], a: const ByteSet&, d: i64, b: const ByteSet&) -> i64
// first i with s[i] in a and s[i + d] in b
// (i + d < s.len), -1 if none; d >= 0
```
These functions scan in the runtime. On x86-64 they test 16 bytes at a time
with SSE2, which every such CPU has. A set that is not at most three bytes,
one range, or the complement of either is looked up in nibble tables, which
needs SSSE3; the runtime checks for it when the program starts. Elsewhere,
and in a JIT run, they test one byte at a time, and a set of a single byte
is searched with the C library's `memchr`. `byte_set` works out how to test
a set by going over all 256 bytes, so make the set once, in a variable,
outside the loop that searches. `members` is the set, and the other fields
are used by the search. `find_pair` is the test for a prefilter that knows
two bytes of a match that are `d` apart; [`regex`](#regex) uses it this way
with the two rarest of a pattern's leading bytes.
```goose
let special = byte_set(",\"\n");
var pos = 0;
loop {
let k = find_any(text[pos..], special);
if k < 0 { break; }
pos += k;
handle(text[pos]);
pos += 1;
}
```
### Arrays: transforming
```goose
fn map(xs: T[:]) // -> U[>..], U the block's result type
fn filter(xs: T[:]) -> T[>..]
fn fold(xs: T[:], acc: A) -> A // acc = F(acc, x)
fn sum(xs: T[:]) -> T // from default()
fn concat(a: T[:], b: T[:]) -> T[>..]
```
```goose
let squares = xs.map() { it * it };
let evens = xs.filter() { it % 2 == 0 };
let total = fold(xs, 0.0) { a, x => a + x };
```
### Arrays: in place
```goose
fn fill(xs: T[:], v: T)
fn copy_into(dst: T[:], src: T[:]) // equal lengths; first to last
fn reverse(xs: T[:])
fn sort(xs: T[:]) // by <; sort(xs) { a, b => ... } by the block
fn stable_sort(xs: T[:]) // natural merge sort; one temporary of xs.len elements
fn to_lower(s: u8[:]) // ASCII, in place
fn to_upper(s: u8[:])
```
`sort` is a pattern-defeating quicksort based on pdqsort and Rust's
ipnsort. It is unstable (the order it leaves equal elements in is
unspecified), works in place and does not allocate. Its partition does not
branch on the comparisons, and a run of equal keys takes a linear pass
rather than n log n. Heapsort takes over a range that partitioning keeps
splitting badly, so the worst case is O(n log n).
`stable_sort` is a
natural merge sort: it finds the runs already in order (or in strictly
descending order, which it reverses) and merges them pass by pass between
`xs` and a temporary of `xs.len` elements on a fresh data stack.
Both make a
single pass over input that is already in order or strictly descending,
and neither allocates for such input or for 20 elements or fewer. A
comparator that is not a strict weak ordering (`<=`, or `<` over floats
with NaNs) leaves the elements in an unspecified order, but the result is
always a permutation of the input, and neither function aborts.
A `let` array can be sorted because `let` prevents rebinding, not element
writes. A `const` array or read-only slice cannot be sorted (spec §9.5).
### Arrays: changing the length
```goose
fn push_n(xs: A&, v: T, n: i64) // n pushes
fn insert_at(xs: A&, i: i64, v: T) // shifts [i..) up; needs push
fn remove_at(xs: A&, i: i64) -> T // shifts down; needs pop
fn swap_remove(xs: A&, i: i64) -> T // O(1), reorders
fn retain(xs: A&) // keeps x with F(x), in order; needs resize
fn dedup(xs: A&) // drops adjacent duplicates
fn heap_push(xs: A&, v: T) // min-heap by <; block forms of all three
fn heap_pop(xs: A&) -> T
fn make_heap(xs: A&) // O(n)
```
A `[>..<]` is the natural heap and queue container; a limited `[..k]` field
works as well.
The heap is binary, with the children of `i` at `2i + 1` and `2i + 2`. Its
sifts move a hole instead of swapping elements. The element being placed
waits in a local while each level moves one element into the hole, and it
is stored once, where the hole stops. `heap_pop` walks the hole from the
root down to a leaf, along the child that goes first, and then sifts the
last element up from there, as libstdc++ and Rust do. This takes about one
comparison per level, where sifting the element down from the root takes
two. For a comparator that is a strict weak order, every operation leaves
the same layout as the textbook swapping sifts, including for ties, so
elements that tie come out in the same order. A comparator that reads the
heap while it runs sees one slot holding a copy of its neighbour rather
than the element waiting outside.
```goose
var q: i64[>..<] = [];
q.heap_push(5); q.heap_push(1);
let smallest = q.heap_pop();
```
### Strings
```goose
fn format_int(out: B&, v: i64, base: i64, width: i64, padc: u8) // base 2..36, right-aligned
fn format_uint(out: B&, v: u64, base: i64, width: i64, padc: u8) // B: u8[>..] or u8[..k]
fn format_flt(out: u8[>..]&, v: f64, decimals: i64) // fixed decimals, rounded half up; inf/-inf/nan as print writes them
fn parse_int(s: u8[:], base: i64 = 10) -> i64, bool // optional sign, whole string; false outside i64
fn parse_flt(s: u8[:]) -> f64, bool // sign, fraction, exponent; correctly rounded
fn format_uleb(out: u8[>..]&, v: i64) // LEB128 (3.6), 1-10 bytes
fn parse_uleb(s: u8[:]) -> i64, i64 // value and byte count; 0, 0 if malformed
fn compare(a: u8[:], b: u8[:]) -> i64 // bytewise: -1, 0, 1
fn trim(s: u8[:]) -> u8[:] // ASCII whitespace; a sub-slice
fn trim_start(s: u8[:]) -> u8[:] fn trim_end(s: u8[:]) -> u8[:]
fn split(s: u8[:], sep: u8) -> const u8[:][>..] // read-only slices into s; empty parts kept
fn split(s: u8[:], sep: u8[:]) -> const u8[:][>..] // matched from the left; an empty sep: [s]
fn each_split(s: u8[:], sep: u8) // F(part); no array built
fn each_split(s: u8[:], sep: u8[:])
fn words(s: u8[:]) -> const u8[:][>..] // split at runs of ASCII whitespace; no empty words
fn each_word(s: u8[:]) // F(word)
fn join(out: u8[>..]&, parts: T[:], sep: u8[:]) // T any u8 array/slice type
fn format_replaced(out: u8[>..]&, s: u8[:], old: u8[:], with: u8[:])
fn each_utf8(s: u8[:]) // F(codepoint); malformed bytes give 0xFFFD
fn push_utf8(out: u8[>..]&, cp: i64) // 0xFFFD's bytes for cp outside 0..0x10FFFF
```
The builtins `format`/`str`/`print` do the plain rendering of scalars and
strings; the functions here add control over the output, and parsing. In
base 10, `format_int` and `format_uint` write the same text as `format`
(after any padding) and run as fast. `parse_flt` gives the double nearest to
the decimal, as C's `strtod` does, so a float's text from `print` or
`format` reads back as the same value. Strings are `u8` arrays: input is a
`u8[:]`, output goes to a `u8[>..]&` builder, and storage is a `u8[]` or
`u8[..k]`.
`each_utf8` decodes as the WHATWG decoder does. A sequence cut short, an
overlong form, a surrogate or a code point past 0x10FFFF gives one 0xFFFD
for each maximal subpart (Unicode §3.9), and the byte that broke a sequence
starts the next one: `[0xC3, 'A']` gives 0xFFFD and then `A`. `push_utf8`
writes a surrogate as the three bytes of its own code, which is how `json`
keeps a lone `\u` escape.
```goose
var line: u8[>..] = [];
format(line, "x=");
format_int(line, x, 16, 8, '0');
each_split(text, '\n') { handle(trim(it)); };
let n, ok = parse_int(trim(field));
let cmd = words(input); // "take the sword": ["take", "the", "sword"]
if cmd.len > 1 && cmd[0] == "take" { take(cmd[cmd.len - 1]); }
```
`format_uleb` and `parse_uleb` write and read the length prefix used by
serialized images (`design/serialization.md`). Writing the prefix separately
lets a program save a `bytes_of` view without first copying it into an image.
Reading the prefix tells a stream reader how many payload bytes to expect.
```goose
let payload = pool.bytes_of(); // a view, nothing copied
var head: u8[>..] = [];
format_uleb(head, payload.len);
write_file(path, head); append_file(path, payload);
```
## exception
Typed exceptions implemented in Goose with `return ... from` (spec §7.9),
inspired by [Lobster's exception module](https://github.com/aardappel/lobster/blob/master/modules/exception.lobster).
Import `exception`; its functions are in namespace `exception`.
```goose
fn try() // Body(); on an error, Catch(error)
fn try() // Body(); discard an error
fn try() // as the first, then Finally() on every way out
fn throw(value) // exit the nearest enclosing handler
```
```goose
import exception;
fn load() {
// This can be any number of calls below try.
exception::throw(str("cannot load item ", 42));
}
exception::try(load) { error => print(error); };
```
`E` is the error type, supplied explicitly at `try`: `u8[]` for owned text,
an integer, or an application's struct or enum (`Error..` for a
variable-mode enum). The thrown value must fit `E`, which must be usable
as an array element. The body and catch are static function values;
Goose permits one trailing block, so name at least one of the callbacks.
The body's results are discarded; it can update captured output variables.
The handler returns an empty `E[]` on success and a singleton containing
the error on failure. This uses the ordinary data stacks, needs no default
`E`, and allows `0`, `false`, and `""` as errors. The catch runs after that
handler has returned. Calling `throw` in the catch therefore throws to an
outer `try`; use `exception::throw(copy(error))` to rethrow an owned
variable-size error. An existing non-fixed value likewise needs `copy`
when first thrown; fresh constructors and string slices can be supplied
directly. Owned text is copied before the frames containing it exit.
The language's checks and limitations still apply:
* Every throw must have a handler on every compile-time call path, including
throws from a catch. An incompatible error type is a compile error.
* References carried by a long-distance return must point to globals or
static data. Use owned `u8[]`, not a slice into a local string, and owned
fields in error records.
* Recursive code can throw to an outer handler. A `try` cannot itself be
re-entered recursively: its non-fixed handler result would hold a data
stack across the recursive call (§7.8).
* The checker propagates calls that never resume their caller. A function
declared to return a value may end with `exception::throw(error);`, and a
throwing branch needs no result. This does not give `throw` a value type:
use it as a statement or branch, rather than `return throw(error);` or as
an argument that requires a value. Recursive inference is conservative
(spec §7.3).
* Only calls to `throw` are caught. Runtime failures, `abort` and `exit`
remain uncatchable. There is no stack-trace facility.
`try(body, catch, finally)` runs `finally` after the body completes, after
a caught throw, and when a throw or a lexical `return` aimed at another
function leaves through the call. It is `defer finally();` ahead of the try
(reference, "defer"), so `finally` follows the rules of a defer body: it
cannot leave, and it may not throw, catch, or call anything that does.
## dictionary
```goose
struct dictionary { count: i64, slots: dictionary_slot[>..<] }
fn get(d: dictionary&, key: K) -> V? // reference to the value, or null
fn contains(d: dictionary&, key: K) -> bool
fn insert(d: dictionary&, key: K, val: V) -> bool // overwrites; true if new
fn get_or_insert(d: dictionary&, key: K, v0: V) -> V&
fn update(d: dictionary&, key: K, v0: V) // F(val&); inserts v0 first if absent
fn remove(d: dictionary&, key: K) -> bool
fn clear(d: dictionary&)
fn reserve(d: dictionary&, n: i64) // room for n entries without a rehash
fn each(d: dictionary&) // F(key, val&); order unspecified
```
The table uses open addressing with linear probing, a power-of-two capacity
and backward-shift deletion. Keys and values are fixed-size; a key type
needs `hash` and `==`. Each slot keeps 31 bits of its key's hash as a tag,
so a lookup compares keys only where the tags agree, and growth and removal
do not hash keys again (in tables of up to 2^31 slots). `insert`, `update`
and `get_or_insert` look a key up once, whether or not it is there. The
table doubles in place at 2/3 load; `reserve(n)` sizes it for `n` entries
at that load.
Strings are keyed as `u8[:]` slices into text the caller keeps (`const u8[:]`
for literals or views of `const` data, §9.5), or as inline
`u8[..k]`. A set is `dictionary`.
The slot array is grow-shrink, so a reference into it (a `get` or
`get_or_insert` result, or an `each` binder) must have its last use before
the next `insert`, `update`, `remove` or `clear`. Otherwise the checker
reports an error at the mutation, naming the reference and where it was
bound (spec §5.2). Finish with the reference first, or use `update`, which
does the lookup and the change in one call:
```goose
var counts = dictionary {};
each_split(text, ' ') { counts.update(it, 0) { it += 1; } };
counts.each() { w, n => if n > 100 { print(w, " ", n); } };
let n = counts.get("the");
if n { print(n); } // the last use of n; counts is free again after it
```
The checker looks at liveness, not scope: a reference whose last use comes
before the mutation needs no block around it, and a reference still used
after the mutation is an error wherever it was declared. The key type is
`const u8[:]` because literals and the read-only views returned by `split`
require it (§9.5). A dictionary holding writable slices can use
`dictionary`, whether the underlying buffer is bound with `let`
or `var`.
A value can be a reference if it is optional: `Cell?`, or a link into a
pool such as `Cell&?`. The unused slots hold `default()`,
which a non-optional reference does not have. `insert` over an existing key
stores the new reference rather than writing through the old one. `get` and
`get_or_insert` return the stored reference itself, and the blocks of
`update` and `each` receive it (`V?` and `V&` of a reference are that
reference, §3.8), loaded if it is relative. Writing through it writes the
pointee; replacing it requires an `insert`. `get` returns null both for a
null value and for an absent key; `contains` tells them apart. Such a
reference points at its pointee rather than into the slot array, so it may
stay live across the next mutation:
```goose
var owner = dictionary {};
owner.insert(7, cells[3]);
let c = owner.get(7);
owner.insert(7, cells[4]); // cells[3] is unchanged, and c still refers to it
if c { c.v += 1; }
```
## vec
```goose
struct vec2 { x: T, y: T } struct vec3 { x: T, y: T, z: T }
struct vec4 { x: T, y: T, z: T, w: T }
type float2/float3/float4 = vec* double2/3/4 = vec* int2/3/4 = vec*
let float3_0 = float3 { 0.0, 0.0, 0.0 }; // _0 and _1 for all nine aliases
fn dot(a: vec3, b: vec3) -> T // all sizes
fn cross(a: vec3, b: vec3) -> vec3
fn length_sq(v: vec3) -> T
fn length(v: vec3) -> T // float vectors
fn normalize(v: vec3) -> vec3
fn distance(a: vec3, b: vec3) -> T
fn lerp(a: vec3, b: vec3, t: T) -> vec3
fn xy(v: vec3) -> vec2 // also xy(vec4), xyz(vec4)
```
Elementwise `+ - * /` are built into the language (`a + b`, `p - q`), and
so is unary minus on a float or signed integer vector (`-v`). `*` and `/`
also broadcast a scalar in either order: `v * 2.0`, `v / length(v)`,
`1.0 / v`; `v *= scale` and `v /= scale` update a vector in place. The
scalar converts to the component type, so an `f64` variable scaling a
`float3` needs an explicit `as f32`. Per-component
`min`/`max` is written out (`min(a.x, b.x)`).
## math
`math` provides libm at both widths (`sin(x)` picks `sin` or `sinf` by the
argument's type): `sqrt sin cos tan asin acos atan atan2 exp log log2 log10
pow floor ceil round trunc`. These return floats as C does (`as i64`
converts).
`sqrt` is implemented in the runtime (`gs_sqrt`, `gs_sqrtf`). It gives the
same correctly rounded root as C, and NaN below zero, but without C's
errno, so it is the bare square root instruction where the C compiler
allows. The other functions for which C sets errno (`sin` to `pow`) are
libm's own functions under runtime names (`gs_exp`, `gs_expf`, ...), which
gcc and clang are told depend only on their arguments. Since Goose has no
errno, a call does not make the C compiler reload values that a loop
around the call holds in registers. An integer argument converts to both
widths equally well, so the call has to name one (`sqrt(n as f64)`);
`sqrt(n * 0.5)` calls the `f64` version, as `sqrt(0.5)` does.
`mul_add(x, y, z)`, at both widths, is `x * y + z` rounded once where the
code is built for FMA and twice where it is not. Which one a call gets is
up to the implementation (spec §7.12). The C backend rounds once on arm64,
on x86-64 built for AVX2 or later (`-march=x86-64-v3`, clang-cl's
`/arch:AVX2`), and in a `simd fn`'s AVX2 and AVX-512 versions. It rounds
twice elsewhere, under MSVC and under the JIT. `x * y + z` written out always rounds twice.
The two roundings can give very different results, not just a different
last bit:
- **Cancellation.** Where `x * y` and `z` nearly cancel, the fused result
keeps the product's low bits and the unfused one loses them, so one can
be zero where the other isn't. `mul_add(x, x, -(1.0 + 2.0 * (x - 1.0)))`
with `x = 1 + 2^-30` is `2^-60` fused and `0.0` unfused, and a branch on
it goes either way.
- **Overflow.** `mul_add(1.0e308, 2.0, -1.0e308)` is `1.0e308` fused, but
`inf` unfused, because the product overflows before the add.
Inside a `simd fn`, only the calls Goose emits in that function's own body
fuse: its own code, and the functions Goose inlines into it. A call in a
plain helper that the `simd fn` calls keeps the helper's rounding, even if
the C compiler inlines the helper later. Whether Goose inlines a small
helper depends on the optimization level, so moving a `mul_add` into a
helper can change both the result and the speed between `-O0` and `-O2`.
Marking the helper `inline fn` keeps its calls fused in `simd` callers at
every level except `-O0`, where Goose inlines nothing. A function value is resolved at compile time, so a `simd fn`
given `mul_add` as one (`F(x, y, z)` with `F` = `mul_add`) fuses like a
direct call.
The module also has:
```goose
let PI = 3.141592653589793; let TAU = 6.283185307179586;
fn radians(deg: f64) -> f64 fn degrees(rad: f64) -> f64 // and f32
fn is_nan(x: f64) -> bool fn is_inf(x: f64) -> bool // and f32
fn is_finite(x: f64) -> bool // not inf or NaN; and f32
```
## os
A small interface to `src/runtime/runtime_os.h`:
```goose
fn read_file(path: const u8[:], out: u8[>..]&) -> bool // appends the whole file
fn write_file(path: const u8[:], data: const u8[:]) -> bool
fn write_file_atomic(path: const u8[:], data: const u8[:]) -> bool // the old file or all of the new
fn append_file(path: const u8[:], data: const u8[:]) -> bool
fn file_exists(path: const u8[:]) -> bool // a file, not a directory
fn delete_file(path: const u8[:]) -> bool
fn rename_file(from: const u8[:], to: const u8[:]) -> bool // replaces `to` atomically
fn make_dir(path: const u8[:]) -> bool // true if one is there afterwards
fn is_dir(path: const u8[:]) -> bool
fn delete_dir(path: const u8[:]) -> bool // an empty one
fn list_dir(path: const u8[:], out: u8[>..]&) -> bool // appends each name and '\n', sorted
fn read_line(out: u8[>..]&) -> bool // stdin, newline stripped; false at end
fn read_stdin(out: u8[>..]&) // everything until end of input
fn write_stdout(s: const u8[:]) fn write_stderr(s: const u8[:]) fn flush_stdout()
fn arg_count() -> i64 fn arg(i: i64, out: u8[>..]&)
fn args() -> u8[:][>..] // indexable; argument 0 is the program
fn env(name: const u8[:], out: u8[>..]&) -> bool
fn trim_memory() -> bool // return unused data-stack pages to the OS
fn resource_dir() -> u8[>..] // absolute directory, trailing separator
fn resource_dir(out: u8[>..]&) -> bool // appends; unchanged on failure
fn time() -> f64 // seconds since the epoch
fn clock() -> f64 // monotonic, high resolution
fn time_ns() -> i64 fn clock_ns() -> i64
fn sleep(seconds: f64) fn sleep_ms(ms: i64)
fn random_seed() -> u64 // entropy, for rng
```
`trim_memory` asks the OS to discard complete pages beyond every current
data-stack top in the calling thread program, including its globals and
scratch from scopes that have ended. It preserves live values, borrowed
views, limited-array capacity and virtual-address reservations; subsequent
growth works normally. It affects no other worker. Use it after a large
workload, such as replacing a document, rather than after every temporary
value. Ordinary scope exit keeps pages available for cheap reuse, while a
trimmed page causes a page fault when the stack grows into it again.
The pages are released immediately. Windows decommits them, which also
returns their commit charge, and Linux drops them from the resident set
at once. macOS replaces the unused suffix of each reservation with fresh
anonymous memory at the same addresses. On all three platforms, a stack
that grows again gets fresh zeroed pages. macOS trimming covers the full
unused reservation so ordinary growth needs no runtime tracking or signal
handling. `false` means at least one OS request failed.
Paths are UTF-8 on every platform; on Windows the runtime converts them to
UTF-16 for the system's wide APIs. Environment values and the program's
arguments are UTF-8 too. On Windows the arguments pass through the ANSI
code page first, so they keep only the characters that code page has. A
path is refused, and the call returns `false`, if it is longer than 4095
bytes, contains a NUL byte, or (on Windows) is not valid UTF-8, because a
path that was cut short or decoded another way could name a different
file.
`read_file` appends nothing when it fails. `read_stdin` appends the bytes
unchanged, on Windows too, where the C library's text mode would turn each
`\r\n` into a `\n` and stop at a Ctrl-Z. `read_line` strips a `\r` before a
line's newline, along with the newline.
`write_file_atomic` is for files that must never be seen half-written, such
as saved games. It writes the data to a new file in the same directory as
`path`, named after `path` with a random tag and `.tmp` added. It then
flushes that file to disk and renames it over `path`, keeping the
permissions of `path` on POSIX. A crash or a failure at any point leaves
`path` with either its old contents or all of the new ones. On a failure
the new file is removed; after a crash it can remain, with a name ending in
`.tmp`.
`rename_file` does the same one-step replacement for a file the program
wrote itself, within one volume. On Windows, a file cannot be replaced
while another program has it open, and virus scanners briefly open files
that were just written, so both functions wait up to about a second for
such a file before returning `false`.
`list_dir` leaves out `.` and `..` and sorts the names bytewise, so a
listing does not depend on the file system. A name that cannot be written
as one line of UTF-8 (a POSIX name containing a newline, or a Windows name
that is not valid UTF-16) is left out too. On a failure nothing is
appended.
```goose
var names: u8[>..] = [];
if list_dir("saves", names) {
each_split(names, '\n') { if it.len > 0 { load_save(str("saves/", it)); } };
}
if !write_file_atomic("saves/slot1.sav", image) { write_stderr("not saved\n"); }
```
### Application resources
Ordinary relative paths resolve against the process working directory, as
in C/C++. Goose does not change that directory when it starts a program.
For data distributed with the application, `resource_dir()` returns an
absolute UTF-8 directory with a trailing separator, ready to concatenate:
```goose
var wad: u8[>..] = [];
if !read_file(str(resource_dir(), "data/map.wad"), wad) {
abort("cannot read map.wad");
}
```
In JIT runs the root is the entry source's directory, regardless of which
imported module calls the helper. In AOT programs it is the running
executable's directory, discovered at runtime rather than recorded at
compilation. In a conventional macOS `.app` bundle it is `Contents/Resources/`.
Deploy the data relative to that root; moving the executable and its data
together preserves resource lookup. An AOT program does not depend on its
original source tree or on the launcher's working directory. A shared
library that an `extern "..." { }` block names with a path separator, such
as `"bin/steam_api64"`, is opened relative to the same root (spec §7.10),
so a library shipped beside the program is found the same way as its data.
The string-returning form aborts if the directory cannot be discovered;
the builder overload returns false and appends nothing. Native discovery
supports Windows, Linux and macOS. This directory is for shipped assets;
it is not guaranteed to be writable. User-supplied paths, outputs and saves
can continue to use ordinary file operations and their chosen locations.
`exit(code)` and `abort(msg)` are builtins, so that the checker knows they
diverge. Subprocesses and networking are not in v1; they arrive as
`extern fn`s when a program needs them.
## ffi
```goose
fn cstr(p: u64, out: u8[>..]&) -> bool // the C string at p, its NUL left out; false for null
fn cstr(p: u64) -> u8[>..] // the same, fresh; empty for null
fn bytes(p: u64, n: i64, out: u8[>..]&) -> bool // the n bytes at p; false for null
fn bytes(p: u64, n: i64) -> u8[>..]
```
These functions read memory that a C library owns. A function bound from a
shared library
(spec §7.10, [reference.md §24](reference.md#24-calling-c-and-being-called-from-c))
returns a `const char *`, or an array that the library keeps, as a `u64`,
which Goose cannot read through. These functions copy the data into Goose
storage, so foreign memory never becomes a Goose value without a copy.
They trust the pointer and the length, as every extern call trusts C: a
wrong pointer or length is the same kind of error as in C, and it is not
caught. A null pointer appends nothing and gives the `false` result.
```goose
import ffi;
extern "SDL2" {
fn SDL_Init(flags: u32) -> i32;
fn SDL_GetError() -> u64;
}
if SDL_Init(0x20) < 0 { abort(str("SDL: ", ffi::cstr(SDL_GetError()))); }
```
## binary
Checked, little-endian reads of external formats, written entirely in Goose.
`import binary;` puts the API in namespace `binary`. Input is a `const u8[:]`;
returned byte slices and fixed-width names borrow that input without copying
or allocating. Their lifetimes and read-only access are checked normally.
This API does not interpret Goose serialization images (`from_bytes`).
### Reads at offsets
```goose
fn range_ok(data: const u8[:], at: i64, size: i64) -> bool
fn read_u16le(data: const u8[:], at: i64) -> u16, bool
fn read_i16le(data: const u8[:], at: i64) -> i16, bool
fn read_u32le(data: const u8[:], at: i64) -> u32, bool
fn read_bytes(data: const u8[:], at: i64, size: i64) -> const u8[:], bool
fn read_name(data: const u8[:], at: i64, width: i64) -> const u8[:], bool
```
Offsets and sizes are bytes. Negative values, truncated fields and offsets
past the end fail with zero or an empty slice and `false`; range checking
does not overflow on extreme offsets or sizes. An empty range at the end
is valid. Reads work at unaligned offsets on either host byte order.
`read_name` validates the entire fixed-width field and returns the bytes
before its first NUL, or all `width` bytes if there is no NUL. It does not
validate padding or interpret the bytes as a particular text encoding.
### Cursor and bounded subreaders
```goose
struct Reader { data: const u8[:], pos: i64 }
fn reader(data: const u8[:]) -> Reader
fn read_u16le(r: Reader&) -> u16, bool
fn read_i16le(r: Reader&) -> i16, bool
fn read_u32le(r: Reader&) -> u32, bool
fn read_bytes(r: Reader&, size: i64) -> const u8[:], bool
fn read_name(r: Reader&, width: i64) -> const u8[:], bool
fn skip(r: Reader&, size: i64) -> bool
fn subreader(r: Reader&, size: i64) -> Reader, bool
```
The cursor holds only its input and byte position. Every read returns its
own success flag. Success advances `pos` (by the full width for a name);
failure returns zero/empty and leaves `pos` unchanged, so a shorter read
can be tried next. Set `pos` to seek; a negative or out-of-range position
makes reads fail. Callers keep any file offsets or diagnostic context they
need. There is no sticky error state.
`subreader` consumes a bounded range from its parent and gives it a new
cursor starting at zero. Failure returns an empty reader and `false`,
leaving the parent in place. Keep the parent in scope while using its
child or returned slices; the current reference-result analysis can tie
their lifetime to the parent.
```goose
// data owns the file bytes. The first four bytes give a payload length.
var r = binary::reader(data);
let size, size_ok = binary::read_u32le(r);
if !size_ok { abort("missing payload length"); }
var payload, payload_ok = binary::subreader(r, size);
if !payload_ok { abort("truncated payload"); }
let version, version_ok = binary::read_u16le(payload);
let name, name_ok = binary::read_name(payload, 8);
if !version_ok || !name_ok { abort("truncated payload header"); }
// name borrows data. payload cannot read into the next record.
```
Use offset reads for random access and cursors for sequential records.
Applications still validate signatures, counts, indices and format-specific
limits after checking the read. The WAD loader in
[`31_mini_doom.goose`](../samples/31_mini_doom.goose) shows both styles.
## base64
Base64 as RFC 4648 §4 defines it: the standard alphabet (`A`-`Z`, `a`-`z`,
`0`-`9`, `+`, `/`) and `=` padding. `import base64;` puts it in namespace
`base64`.
```goose
fn encode(out: u8[>..]&, src: const u8[:]) // appends 4 characters per 3 bytes, padded
fn decode(out: u8[>..]&, src: const u8[:]) -> bool // appends the bytes; false if src is invalid
```
`decode` accepts only whole groups of four characters, the last of which
may end in `=` or `==`. It rejects line breaks, white space, unpadded text
and other alphabets (the `-` and `_` of the URL-safe alphabet are invalid).
It does not require the bits left over before the padding to be zero, so
`Zh==` decodes the same as `Zg==`; most decoders allow this. On invalid
input it returns `false` and leaves `out` as it was.
```goose
var text: u8[>..] = [];
base64::encode(text, "foobar"); // "Zm9vYmFy"
var bytes: u8[>..] = [];
if !base64::decode(bytes, text) { abort("not base64"); }
```
The codec computes the alphabet rather than looking it up, so the C
compiler vectorizes both loops. Both are also `simd fn`s (spec §7.12): when
clang builds for x86-64, they run as AVX2 or AVX-512 code on a CPU that
supports it, several times faster than with the baseline SSE2.
## csv
Comma-separated values as RFC 4180 defines them. `import csv;` puts the
reader and the writer's quoting in namespace `csv`.
```goose
fn each_record(text: const u8[:], sep: u8 = ',') -> bool // F(fields: const u8[:][:]) per record
fn format_field(out: u8[>..]&, s: u8[:], sep: u8 = ',') // s, quoted where it needs to be
```
`each_record` passes each record's fields to its block, in order, as slices
of the text. Nothing is copied, and the slice of fields belongs to the
reader and is reused for the next record. A field may be quoted, and a
quoted field may contain separators, line breaks and doubled quotes, each
of which stands for one quote. A quoted field is passed without its
surrounding quotes, with its doubled quotes left as they are. Since a field
that is not quoted cannot contain a quote,
`format_replaced(out, field, "\"\"", "\"")` appends the text of any field.
Records end at LF or CRLF, except that the last record does not need to.
An empty line is a record of one empty field, and a header is a record like
any other. Spaces around a field are part of it. `sep` may be any byte
except a quote or a line break, such as `';'`, or `'\t'` for tab-separated
values.
`each_record` returns `false` at the first malformed record, after passing
every earlier record to its block; the block does not see the malformed
record. A record is malformed if it has a quote in a field that is not
quoted, a quoted field with no closing quote, or anything other than a
separator or a line break after a closing quote. A block's `return` leaves
the function that called `each_record`, as with every block.
```goose
var total = 0.0;
let ok = csv::each_record(text) { fields =>
let price, valid = parse_flt(fields[2]);
if valid { total += price; }
};
if !ok { abort("malformed CSV"); }
```
`format_field` appends `s` as it is or, if `s` contains the separator, a
quote or a line break, quoted with its quotes doubled. The caller writes the
separators between fields and the line break after a record.
```goose
for row in rows {
csv::format_field(out, row.name);
out.push(',');
format(out, row.count, "\n");
}
```
## json
JSON as RFC 8259 defines it, three ways: a writer that appends values to a
builder, a parser that builds a document tree, and a reader that maps the
text directly onto the caller's own types, as a serde derive does.
`import json;` puts them in namespace `json`.
### Writing
```goose
fn format_string(out: u8[>..]&, s: u8[:]) // quoted, with " \ and control characters escaped
fn format_key(out: u8[>..]&, key: u8[:]) // a quoted key and its colon: "key":
fn format_number(out: u8[>..]&, v: f64) // the shortest form that reads back as v
fn format_int(out: u8[>..]&, v: i64)
fn format_bool(out: u8[>..]&, b: bool)
```
The caller writes the brackets and the commas between values. A string's
bytes from 0x80 up are written unchanged, so UTF-8 text stays UTF-8.
`format_number` writes the fewest significant digits that read back as `v`
exactly, in the same layout as serde_json (`1.0`, `0.1`, `1e-7`,
`1.5e300`, `0.30000000000000004`). It writes `null` for NaN and the
infinities, which JSON has no numbers for.
```goose
out.push('{');
json::format_key(out, "name");
json::format_string(out, player.name);
out.push(',');
json::format_key(out, "pos");
out.push('[');
json::format_number(out, player.x);
out.push(',');
json::format_number(out, player.y);
out.append("]}");
```
### The document tree
```goose
struct Node { next: Node&?, first: Node&?, key: const u8[:], val: Value }
enum Value { Null, Bool { v: bool }, Int { v: i64 }, Num { v: f64 }, Str { s: const u8[:] },
Arr { count: i64 }, Obj { count: i64 } }
fn parse(text: u8[:], doc: Node[>..]&, decoded: u8[>..]&) -> Node?, u8[>..]
// the root, or null and the error
fn member(n: Node?, key: u8[:]) -> Node? // the first member named key; null if none
fn each(n: Node&) // F(child) for each element or member, in order
fn as_f64(n: Node?) -> f64, bool // an Int or a Num
fn as_i64(n: Node?) -> i64, bool // an Int
fn as_bool(n: Node?) -> bool, bool
fn as_string(n: Node?) -> const u8[:], bool
```
`parse` pushes a node for every value onto `doc`, in text order. Each node
holds a link to its next sibling, a link to its first child (for a
container), its key (empty in an array) and its value. Keys and strings are
slices of `text`. Those that contained escapes are instead slices of their
decoded text, which is pushed onto `decoded`; nothing else is copied. A
number without a fraction or an exponent that fits an `i64` becomes an
`Int`. Any other number becomes a `Num`, correctly rounded, and infinite if
it is too large for an `f64`. Escapes decode to UTF-8: a surrogate pair
becomes one code point, and a lone surrogate becomes the three bytes that
encode its own code point. Strings are not checked to be valid UTF-8.
On
malformed text, `parse` returns null and a message naming the byte at which
it found the text malformed, such as
`json: expected ',' or '}' at byte 41`. The nodes before that point have
already been pushed.
The parser is a single loop
that keeps the open containers on a stack of its own, so nesting of any
depth can be parsed. `member` and the accessors accept a null node, so
lookups can be chained. Duplicate keys stay in the tree, and `member` finds
the first.
```goose
var doc: json::Node[>..] = [];
var decoded: u8[>..] = [];
let root, err = json::parse(text, doc, decoded);
guard root else { abort(err); }
let w, ok = json::as_i64(json::member(json::member(root, "window"), "width"));
let items = json::member(root, "items");
if items {
json::each(items) { item =>
let name, named = json::as_string(json::member(item, "name"));
if named { print(name); }
};
}
```
### Reading into your own types
```goose
struct Reader { text: const u8[:], pos: i64, error: const u8[:], error_at: i64 }
fn reader(text: const u8[:]) -> Reader
fn each_member(r: Reader&) // F(key) for each member of the object at the cursor
fn each_element(r: Reader&) // F() for each element of the array at the cursor
fn read_number(r: Reader&) -> f64
fn read_int(r: Reader&) -> i64 // no fraction or exponent, and fits an i64
fn read_bool(r: Reader&) -> bool
fn read_string(r: Reader&, scratch: u8[>..]&) -> const u8[:]
fn skip(r: Reader&) // passes over a value of any kind, checking it
fn peek(r: Reader&) -> u8 // the next byte past white space; 0 at the end
fn finish(r: Reader&) -> bool // no error, and nothing but white space left
fn message(r: Reader&) -> u8[>..] // "json: at byte "
```
The reader parses as it goes and builds nothing. Each block reads the value
it is given with the function for the type it expects, and uses `skip` to
pass over values it does not need. `read_string` returns a slice of the
text, or of `scratch` for a string that contained escapes. Keys are passed
to the block as written, including any escapes, so a key written with an
escape never equals the same key written without one. Errors are sticky: a
read that finds text it does not expect records what it expected and
where, and moves the cursor to the end, so every later read fails as well
and every loop ends. The caller checks once, with `finish`, which also
rejects anything but white space after the value. To handle a `null` where
a value is optional, check `peek(r) == 'n'` and call `skip`.
```goose
struct Item { id: i64, price: f64 }
fn read_items(text: u8[:]) -> Item[>..] {
var r = json::reader(text);
var items: Item[>..] = [];
json::each_element(r) {
var it = Item { id: 0, price: 0.0 };
json::each_member(r) { key =>
if key == "id" { it.id = json::read_int(r); }
else if key == "price" { it.price = json::read_number(r); }
else { json::skip(r); }
};
items.push(it);
};
if !json::finish(r) { abort(json::message(r)); }
return items;
}
```
## regex
Regular expressions with the syntax and the matching rules of Rust's
`regex` crate, on bytes. `import regex;` puts them in namespace `regex`.
```goose
fn compile(pattern: u8[:]) -> Regex // aborts on a malformed pattern
fn compile(pattern: u8[:], err: u8[>..]&) -> Regex // appends the error to err instead
fn find(re: Regex&, text: u8[:]) -> i64, i64 // the first match's start and end; -1, -1
fn matches(re: Regex&, text: u8[:]) -> bool // whether there is a match
fn count(re: Regex&, text: u8[:]) -> i64 // the matches each_match finds
fn each_match(re: Regex&, text: u8[:]) // F(start, end) for each match
fn each_captures(re: Regex&, text: u8[:]) // F(caps: Captures) for each match
fn format_replaced(out: u8[>..]&, re: Regex&, text: u8[:])
// text, each match replaced by F(caps)
struct Captures { text: const u8[:], slots: const i64[:] }
fn group(c: Captures, k: i64) -> const u8[:] // group k's text; empty if it took no part
```
Matching is leftmost-first, as in the crate, Perl and most engines. The
match that starts earliest is chosen, and among the matches starting there,
the one the pattern prefers: alternatives are tried in order, and
repetitions are greedy unless they are lazy. Searches are unanchored. The
iterating functions find matches that do not overlap, from left to right,
and never find an empty match where the previous match ended: `a*` on
`baaa` matches at 0..0 and 1..4. Group 0 is the whole match, and the other
groups are numbered by their opening parentheses. Group k spans
`slots[2k]..slots[2k + 1]` of the text; both slots are -1 for a group that
took no part. A repeated group captures its last iteration. The slice that
`group` returns is rooted at the `Captures` the block was given. A view of
a group that has to outlive the block, such as a dictionary key, should
slice the text by the slots instead.
The supported syntax is:
* literal bytes;
* `.` (which matches a newline only under `s`);
* classes `[a-z]` and `[^...]`, where a `]` first or a `-` first or last is
literal;
* `\d \w \s` and their complements `\D \W \S`, also inside classes;
* escapes `\n \t \r \f \v \xHH` and any punctuation (`\.`, `\\`);
* groups `(...)`, `(?:...)` and named groups `(?P...)` or
`(?...)`, whose names are accepted but not used;
* flags `i` (letters match either case) and `s`, written `(?i)` to apply to
the end of the group or `(?i:...)` to apply within, and `(?-i)` to turn
one off;
* alternation `|`;
* `* + ? {n} {n,} {n,m}`, each made lazy by a `?` after it.
The classes and the case folding are ASCII, as the crate's are with Unicode
off. Anchors and word boundaries (`^ $ \A \z \b \B`), class set operations
(`&& -- ~~`), nested classes, Unicode classes and the other flags are not
supported, and `compile` reports them as errors:
`regex: anchors are not supported at offset 0 in ^a`. The module's header
describes two known differences from the crate: an alternation whose
branches share a repeating prefix is matched as written, as in Perl and
RE2, and groups at the end of a pattern under a `{0}` are kept.
`compile` builds complete DFAs up front: one that finds where a match ends,
and one over the reversed pattern that finds where it starts. A search
therefore takes one table lookup per byte of text it looks at. When every
match has rare leading bytes, or a rare byte after a part that cannot
contain that byte, the search skips through the text with `find_pair` or
`find_any` (above) and only looks where a match can be. Where the pattern
gives a group a fixed place in the match, the group is taken from that
place; otherwise it comes from a backtracker that runs over the match
alone. `find`, `matches` and `count` use neither.
A DFA can need exponentially many states, and a pattern whose program or
DFAs would grow too large (`[ab]*a[ab]{20}`, or counted repetitions in the
thousands) is reported as an error rather than compiled. Make the `Regex`
once, outside the loop that searches with it.
```goose
let re = regex::compile("(\\w+)=(\\d+)");
regex::each_captures(re, line) { c =>
let n, ok = parse_int(regex::group(c, 2));
if ok { settings.insert(line[c.slots[2]..c.slots[3]], n); } // a key that outlives c
};
var err: u8[>..] = [];
let user = regex::compile(pattern_from_user, err);
if err.len > 0 { print(err); }
var out: u8[>..] = [];
let field = regex::compile("""\{\{\s*(\w+)\s*\}\}""");
regex::format_replaced(out, field, template) { caps =>
let value = vars.get(regex::group(caps, 1));
if value { value } else { "" }
};
```
## gfx
Graphics on SDL3's GPU API, which draws through Direct3D 12, Vulkan or Metal:
a window and its input, buffers, textures, samplers, pipelines, render and
compute passes, and reading results back. The module is optional: it needs a
compiler built with the `third_party/SDL` submodule, and a program using it
links what `goose --gfx-link msvc|cc` prints (a response file:
`cl game.c @`, `cc game.c -o game @`). A compiler built without it
still typechecks and generates C for such a program; only running the
program in-process fails. Everything is in namespace `gfx`;
`samples/27_gfx_cube.goose` is a small complete program, and the package's
[docs/gfx.md](https://github.com/aardappel/goose_gfx/blob/main/docs/gfx.md) says how it works.
**Text rendering is provided by [`ui`](#text-rendering-and-game-huds)**,
including fonts, positioned text and noninteractive game HUDs. Use
`ui::label` or `ui::draw_text`, then `ui::render` over the gfx scene. Only
specialized artwork or rendering requirements need a separate text renderer.
### Shaders
```goose
// Code shaders share, in a global each names as a part.
let scene = """
layout(set = 1, binding = 0) uniform Scene { mat4 view_proj; };
""";
// A shader's source after its stage, "vert", "frag" or "comp", or a file,
// .vert, .frag or .comp, relative to this one.
let vs = embed_shader("vert", scene, """
layout(location = 0) in vec3 a_pos;
void main() { gl_Position = view_proj * vec4(a_pos, 1.0); }
""");
let fs = embed_shader("lit.frag");
```
`embed_shader` is a builtin. The GLSL 450 shader is compiled when the
program is, into SPIR-V, MSL and HLSL at once, and the result is a
`const u8[:]` of static data to pass to `pipeline` or `compute_pipeline`.
Its source is usually a `"""` string (spec §2) written at the call. It may
also come in several parts, each a string literal or a `let` or `const`
global initialized with one; the parts are joined as lines, in order. A
shader that does not compile is a compile error at the call. Where there is
an offending line of GLSL, the error points at it, in the program or in the
shader's file. `#include "x.glsl"` resolves relative to the shader's file,
or, for source written in the program, relative to the program's file. The
dialect is that of cute_spirv (`third_party/cute_spirv`): there are no
doubles and no geometry or tessellation stages, uniform blocks have no
instance names, and each storage buffer block holds one runtime array
(`buffer B { vec4 items[]; };`).
Resources go where SDL_GPU expects them, which the compiler checks:
| Stage | `set` | holds, each set's bindings numbered from 0 in this order |
|---|---|---|
| vertex | 0 | samplers, then storage textures, then storage buffers (read-only) |
| vertex | 1 | uniform blocks |
| fragment | 2 | samplers, then storage textures, then storage buffers (read-only) |
| fragment | 3 | uniform blocks |
| compute | 0 | samplers, then read-only storage textures, then read-only storage buffers |
| compute | 1 | read-write storage textures, then read-write storage buffers |
| compute | 2 | uniform blocks |
The `slot` of a `bind_*` call counts within one kind: the first storage buffer
is slot 0 no matter how many samplers precede it. A compute shader's
read-write resources are instead passed to `begin_compute` in binding order.
### Device, window and frames
```goose
fn open(title: const u8[:], width: i64, height: i64) -> bool // and (..., flags)
fn open_headless(width: i64, height: i64) -> bool // no window; and (..., flags)
fn close()
fn frame() -> bool // ends the frame drawn since the last call; false once asked to close
fn quit() // frame() returns false next
fn flush() // submit everything and wait for the GPU
fn error() -> u8[>..] // why the last failing call failed
fn check() // abort if the program has misused gfx
fn driver() -> u8[>..] // "direct3d12", "vulkan" or "metal"
fn set_title(title: const u8[:])
fn screen() -> Texture fn screen_depth() -> Texture fn screen_size() -> int2
fn time() -> f64 fn delta_time() -> f64 fn frame_count() -> i64
```
Flags for `open`: `WINDOW_RESIZABLE`, `WINDOW_HIDDEN`, `WINDOW_FULLSCREEN`,
`NO_VSYNC`, `DEBUG` (validation layers where installed; on in a debug build of
the layer). With `GOOSE_GFX_HEADLESS=1` in the environment, `open` opens no
window; the test runners use this. The screen is an RGBA8 texture the size
of the window in pixels, with a depth texture. A frame draws into it, and
`frame()` shows it. A window has the width and height that `open` gives, in
the display's own coordinates, and it has every pixel the display has in
that area. On a high-density display, that is more pixels than its width
and height; `screen_size()` counts these pixels, and the mouse position is
reported in them.
```goose
guard gfx::open("demo", 1280, 720) else { abort(str("gfx: ", gfx::error())); }
while gfx::frame() {
gfx::begin_screen(float4 { 0.1, 0.1, 0.1, 1.0 });
// bind, uniforms, draw ...
gfx::end_pass();
}
gfx::close();
```
Errors: a call that can fail for reasons outside the program returns false or
a zero handle, and `error()` says why. A call that the program should not
have made is printed when it happens and skipped, and the program aborts at
the next `frame()`, `check()`, read back or `close()`. Examples are a draw
with no pipeline bound, a texture the shader samples left unbound, a
released handle, and a uniform struct of the wrong size.
### Input
```goose
fn key_down(name: const u8[:]) -> bool // SDL's key names: "A", "Space", "Left", "Escape"
fn key_pressed(name: const u8[:]) -> bool // went down since the last frame()
fn key_released(name: const u8[:]) -> bool
fn mouse_down(button: i64) -> bool // MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT
fn mouse_pressed(button: i64) -> bool fn mouse_released(button: i64) -> bool
fn mouse_pos() -> float2 fn mouse_delta() -> float2 // in the screen's pixels
fn mouse_wheel() -> f32 fn mouse_in_window() -> bool // over the window, or dragging from it
fn focused() -> bool // keyboard focus at the last frame()
fn set_mouse_relative(on: bool) -> bool fn mouse_relative() -> bool
fn set_cursor_visible(visible: bool) -> bool fn cursor_visible() -> bool
fn inject_key(name: const u8[:], down: bool) -> bool // as if typed, seen at the next frame()
fn inject_mouse(x: f32, y: f32, button: i64, down: bool) // to pixel x, y; button 0 only moves
fn inject_text(text: const u8[:]) -> bool // an EVENT_TEXT per character
fn inject_focus(focused: bool) -> bool // simulate focus at the next frame()
fn events() -> Event[>..] // the last frame's input, in the order it came
fn text_input(on: bool) // typed text as EVENT_TEXT: off by default
fn scancode(name: const u8[:]) -> i64 fn key_name(scancode: i64) -> u8[>..]
fn clipboard() -> u8[>..] fn set_clipboard(text: const u8[:]) -> bool
fn open_count() -> i64 // times gfx was opened: older handles are gone
```
The state functions report the state at a frame; `events()` reports what
happened between two frames, which text editing needs. The events are:
* key presses and releases, with their scancode, the character their key
types (`keycode`) and the `MOD_*` keys held;
* typed characters;
* mouse motion;
* buttons (`MOUSE_*`, with X1 and X2), with their click count;
* the wheel.
When headless, gfx uses a clipboard of its own rather than the system's.
`mouse_pos` reports the mouse's position from the moment the window opens,
even before the mouse first moves. `mouse_in_window` is true while the
mouse is over the window, and while it holds a button that was pressed
there, as SDL reports it. For a window that opened under the mouse, SDL
reports this from the mouse's first move at the latest. Once the mouse
leaves, `mouse_pos` stays at the point where it left, off the screen, so
edge scrolling checks both. When headless, the mouse is in the window when
the last injected position is on the screen.
`set_mouse_relative(true)` confines the mouse to the window and hides its
cursor. `mouse_delta()` continues to report movement past the window edges,
in screen pixels: use it for first-person camera turns, rather than
`mouse_pos()`. Setting a different mode discards queued motion and clears
the current delta so a previously accumulated move cannot turn the camera.
Calling it again with the same mode preserves motion. It returns false on
failure, including a window without keyboard focus; `error()` gives the reason.
`set_cursor_visible(false)` hides the cursor for programs drawing their own.
Its preference applies outside relative mode: relative mode always hides the
cursor, and `cursor_visible()` reports that effective state inside the window.
The preference is restored when relative mode is disabled. Hiding a cursor
without focus fails; showing one always works while gfx is open.
`focused()` reports keyboard focus, which differs from `mouse_in_window()`.
`events()` includes `EVENT_FOCUS_GAINED` and `EVENT_FOCUS_LOST` in order with
other input; these kinds have no payload. Losing focus clears held keys and
buttons and mouse/wheel deltas, disables relative mode, and resets the cursor
to visible. Input received while unfocused is ignored. Focus gain leaves
relative mode disabled so a game can pause on focus loss and wait for a click
before recapturing. Held keys/buttons also get released edges and synthetic
up events before `EVENT_FOCUS_LOST`, so event-driven UI cannot retain a held
key or a drag across an application switch. `close()` also releases the
mouse and restores the cursor.
Headless gfx starts focused, with a visible cursor and relative mode disabled.
The setters simulate their state without touching the system mouse.
`inject_focus` queues the same transitions for testing; it does not change
operating-system focus. Closing and reopening resets all these states.
### Buffers
```goose
struct Buffer { id: u32 }
fn buffer(usage: i64, data: const u8[:]) -> Buffer // and (usage, size, data): zeros after
fn vertex_buffer(data: const u8[:]) -> Buffer // data from bytes_of(vertices)
fn index_buffer(data: const u8[:]) -> Buffer
fn update_buffer(b: Buffer, data: const u8[:]) -> bool // and (b, offset, data)
fn read_buffer(b: Buffer, out: T[>..]&, count: i64) -> bool // appends count elements of T
fn read_buffer(b: Buffer, offset: i64, out: u8[:]) -> bool
fn buffer_size(b: Buffer) -> i64
fn release(b: Buffer)
```
Usage flags: `BUFFER_VERTEX`, `BUFFER_INDEX`, `BUFFER_INDIRECT`,
`BUFFER_STORAGE` (read by vertex and fragment shaders), `BUFFER_COMPUTE_READ`,
`BUFFER_COMPUTE_WRITE`. Updates and read backs happen between passes, in
order with the draws around them; a read back waits for the GPU.
### Textures and samplers
```goose
struct Texture { id: u32 }
struct TextureDesc { kind: i32 = 0, format: i32 = 1, usage: i32 = 1, width: i32, height: i32,
depth: i32 = 1, mips: i32 = 1, samples: i32 = 1 } // TEXTURE_2D, RGBA8, SAMPLED
struct Region { mip: i32 = 0, layer: i32 = 0, x: i32 = 0, y: i32 = 0, z: i32 = 0,
w: i32 = 0, h: i32 = 0, d: i32 = 0 } // w == 0: the whole mip
fn texture(desc: TextureDesc) -> Texture // mips 0: the full chain
fn texture2d(width: i64, height: i64) -> Texture // and (..., format, usage)
fn render_target(width: i64, height: i64, format: i32) -> Texture // COLOR_TARGET | SAMPLED
fn depth_target(width: i64, height: i64) -> Texture // DEPTH, and SAMPLED
fn texture3d(width, height, depth, format, usage) fn texture_cube(size, format, usage)
fn texture_array(width, height, layers, format, usage)
fn load_texture(path: const u8[:], flags: i64) -> Texture // PNG or BMP; LOAD_MIPS, LOAD_SRGB
fn update_texture(t: Texture, data: const u8[:]) -> bool // and (t, region, data)
fn read_texture(t: Texture, out: T[>..]&) -> bool // appends mip 0 as T: u8, float4, ...
fn read_texture(t: Texture, region: Region, out: u8[:]) -> bool
fn read_pixels(t: Texture) -> u8[>..] // mip 0's bytes, top row first
fn save_png(t: Texture, path: const u8[:]) -> bool // RGBA8 or BGRA8
fn screenshot(path: const u8[:]) -> bool // the screen, as shown
fn generate_mips(t: Texture)
fn texture_info(t: Texture) -> TextureDesc
fn release(t: Texture)
struct Sampler { id: u32 }
struct SamplerDesc { min_filter: i32 = 1, mag_filter: i32 = 1, mip_filter: i32 = 1,
wrap_u: i32 = 0, wrap_v: i32 = 0, wrap_w: i32 = 0,
max_anisotropy: f32 = 1.0, compare: i32 = 0 } // LINEAR, REPEAT
fn sampler(desc: SamplerDesc) -> Sampler fn sampler(filter: i32, wrap: i32) -> Sampler
fn shadow_sampler() -> Sampler // for sampler2DShadow
fn release(s: Sampler)
```
Kinds `TEXTURE_2D`, `TEXTURE_2D_ARRAY`, `TEXTURE_3D`, `TEXTURE_CUBE` (faces
+x, -x, +y, -y, +z, -z as layers 0 to 5). Usage `SAMPLED`, `COLOR_TARGET`,
`DEPTH_TARGET`, `STORAGE_READ`, `COMPUTE_READ`, `COMPUTE_WRITE`,
`COMPUTE_READ_WRITE`; a multisampled texture is a target only. Formats
`RGBA8`, `BGRA8`, `RGBA8_SRGB`, `R8`, `RG8`, `RGBA16F`, `RGBA32F`, `R16F`,
`RG16F`, `R32F`, `RG32F`, `R32UI`, `RGBA8UI`, `RGB10A2`, `RG11B10F`, and
`DEPTH16`, `DEPTH24`, `DEPTH32F`, `DEPTH24_STENCIL8`, `DEPTH` (32-bit float
where it can also be sampled). Filters `NEAREST`, `LINEAR`; wraps `REPEAT`,
`MIRROR`, `CLAMP`; compares `COMPARE_LESS` and the like.
### Pipelines and drawing
```goose
struct Pipeline { id: u32 }
struct PipelineDesc { primitive: i32 = 0, cull: i32 = 0, clockwise: bool = false,
wireframe: bool = false, depth_test: bool = false,
depth_write: bool = false, depth_compare: i32 = 2, blend: i32 = 0,
instance_location: i32 = -1, depth_bias: f32 = 0.0,
depth_bias_slope: f32 = 0.0, formats: u8[16] = [0; 16] }
fn pipeline(vs: const u8[:], fs: const u8[:]) -> Pipeline // and (vs, fs, desc)
fn release(p: Pipeline)
struct PassDesc { color: Texture[4], resolve: Texture[4], depth: Texture, layer: i32 = 0,
mip: i32 = 0, clear_color: bool = true, clear_depth: bool = true,
color_value: float4 = float4 { 0.0, 0.0, 0.0, 1.0 }, depth_value: f32 = 1.0 }
fn begin_pass(desc: PassDesc) -> bool
fn begin_pass(target: Texture, depth: Texture, clear: float4) -> bool // Texture { 0 }: no depth
fn begin_screen(clear: float4) -> bool
fn end_pass()
fn bind(p: Pipeline)
fn bind_vertex_buffer(b: Buffer) // and (slot, b, offset): slot 1 per instance
fn bind_index_buffer(b: Buffer, index_size: i64) // 2 or 4 bytes; and (b, size, offset)
fn bind_texture(stage: i64, slot: i64, t: Texture, s: Sampler) // VERTEX, FRAGMENT, COMPUTE
fn bind_storage_texture(stage: i64, slot: i64, t: Texture)
fn bind_storage_buffer(stage: i64, slot: i64, b: Buffer)
fn uniforms(stage: i64, slot: i64, u: T) // a flat struct as a uniform block
fn push_uniforms(stage: i64, slot: i64, data: const u8[:])
fn viewport(x: f32, y: f32, w: f32, h: f32) fn scissor(x: i64, y: i64, w: i64, h: i64)
fn draw(vertices: i64) fn draw_instanced(vertices: i64, instances: i64)
fn draw_indexed(indices: i64) fn draw_indexed_instanced(indices: i64, instances: i64)
fn draw(vertices, instances, first_vertex, first_instance)
fn draw_indexed(indices, instances, first_index, vertex_offset, first_instance)
```
A pipeline is not tied to the formats it draws into; it is made for each
pass's targets the first time it is bound in one. The vertex shader's inputs
are read in location order, one after another, from one packed element,
laid out as a Goose struct lays out its fields:
`struct Vertex { pos: float3, uv: float2 }` feeds
`layout(location = 0) in vec3` and `layout(location = 1) in vec2`. The
inputs from `instance_location` on come per instance from vertex buffer 1,
and `formats[location]` overrides a location's format
(`VERTEX_UBYTE4_NORM` for a color as four bytes, `VERTEX_HALF2`, and the
rest of `VERTEX_*`). Primitives
`TRIANGLES`, `TRIANGLE_STRIP`, `LINES`, `LINE_STRIP`, `POINTS`; culls
`CULL_NONE`, `CULL_FRONT`, `CULL_BACK` (front faces counterclockwise unless
`clockwise`); blends `BLEND_NONE`, `BLEND_ALPHA`, `BLEND_ADD`,
`BLEND_PREMULTIPLIED`, `BLEND_MULTIPLY`.
Uniform blocks are laid out by std140, while Goose structs are packed. In
std140 a vec3 or vec4 member starts on a multiple of 16 bytes and a block's
size rounds up to 16, so the matching struct needs padding
(`struct Place { rect: float4, depth: f32, pad 12 }`). Pushing a struct
whose size differs from the shader's block is a misuse. On Direct3D 12 a
shader's `gl_InstanceIndex` does not count a draw's `first_instance`, so
keep `first_instance` at 0 if the shader reads `gl_InstanceIndex`.
### Compute
```goose
struct ComputePipeline { id: u32 }
fn compute_pipeline(cs: const u8[:]) -> ComputePipeline
fn begin_compute(rw_buffers: const Buffer[:]) -> bool // and (rw_buffers, rw_textures)
fn bind(p: ComputePipeline)
fn dispatch(x: i64, y: i64, z: i64) // workgroups of the shader's local size
fn end_compute()
fn release(p: ComputePipeline)
```
### Matrices
```goose
struct mat4 { c0: float4, c1: float4, c2: float4, c3: float4 } // column-major, as GLSL's mat4
let mat4_identity
fn mul(a: mat4, b: mat4) -> mat4 fn mul(m: mat4, v: float4) -> float4
fn translate(t: float3) -> mat4 fn scale(s: float3) -> mat4
fn rotate(axis: float3, angle: f32) -> mat4
fn perspective(fovy: f32, aspect: f32, near: f32, far: f32) -> mat4 // right-handed, depth 0..1
fn ortho(left, right, bottom, top, near, far: f32) -> mat4
fn look_at(eye: float3, target: float3, up: float3) -> mat4
```
Clip space is SDL_GPU's on every backend: y up, depth from 0 to 1, texture
coordinates with (0, 0) at the top left.
## audio
Procedural PCM sound effects over SDL3, independent of a window or graphics
device. `import audio;` uses the `audio::` namespace. It is a module of the
`gfx` package ([goose_gfx](https://github.com/aardappel/goose_gfx)). It is compiled into the
compiler with `-DGOOSE_BUILTIN_LIBS=all` and otherwise built on first use;
`goose build` links it. See the package's [docs/audio.md](https://github.com/aardappel/goose_gfx/blob/main/docs/audio.md) for the mixer.
| Function | Behavior |
|---|---|
| `available() -> bool` | True when the native layer is linked; no device probe. |
| `open(sample_rate = 48000) -> bool` | Open the default output device, stereo output. |
| `open_offline(sample_rate = 48000) -> bool` | Open the same mixer without a device or background thread. |
| `close()` | Stop playback, free every sound, invalidate handles; safe to repeat. |
| `create_sound(samples: const f32[:], sample_rate = 48000, channels = 1) -> Sound` | Copy mono or interleaved stereo PCM into owned storage. |
| `release_sound(sound) -> bool` | Stop its voices and free the PCM. Zero is harmless. |
| `play(sound, volume = 1.0, pan = 0.0, pitch = 1.0, looping = false) -> Voice` | Start an independent voice, allowing overlapping repeats. |
| `playing(voice) -> bool` | Whether the voice is still mixing. Stale/zero handles return false. |
| `stop(voice)`, `stop_all()` | Stop one or every voice. Completed/stale/zero voices are harmless. |
| `set_voice(voice, volume, pan, pitch) -> bool` | Change a live voice without restarting. |
| `render(out: f32[:]) -> bool` | Offline only: write interleaved stereo output and advance playback. |
| `error() -> u8[>..]` | Most recent failure; successful calls do not clear it. |
| `error_into(out: u8[:]) -> i64` | Copy as much error text as fits, returning its full length. |
All calls run on the main thread; calls reachable from `thread_fn` are rejected.
`Sound { id: u64 }` and `Voice { id: u64 }` are explicit resources. Sound creation
copies the source immediately: Goose can then mutate or free its array. Releasing
a sound stops all its voices. Completed voices recycle automatically. A new
resource never aliases an old handle, even across `close()` and `open()`.
There is one mixer, at most `MAX_SOUNDS = 256` sounds and `MAX_VOICES = 64`
simultaneous voices. Exhaustion returns zero without stealing a voice. Rates
are 8000..192000 Hz. PCM must be nonempty, finite, in `[-1, 1]`, with complete
frames (two samples per stereo frame). Volume is `0..1`; pan is `-1..1`,
centered at zero, attenuating the opposite channel; pitch is `0.125..8` and
changes speed and duration together. Mixing uses linear interpolation and clips
the final sum to `[-1, 1]`. Leave headroom when many effects overlap.
Invalid arguments, stale sounds, unavailable devices and resource exhaustion
return false or a zero handle and set `error()`. They do not abort. Stopping or
querying a completed voice is normal; updating it returns false. Offline
`render` needs an even sample count, allows empty output, and advances exactly
`out.len / 2` frames. Device mode advances automatically; `playing()` describes
the mixer, so a little audio already buffered by the device can remain after
completion or stop. The module has no file decoding, streaming input,
recording, or effects.
```goose
import audio;
import math;
import os;
fn main() {
if !audio::open() { print(audio::error()); return; }
var pcm: f32[>..] = [];
for i in 12000 {
let t = i as f64 / 48000.0;
let envelope = 1.0 - i as f64 / 12000.0;
pcm.push((sin(TAU * 440.0 * t) * 0.2 * envelope) as f32);
}
let tone = audio::create_sound(pcm);
let voice = audio::play(tone);
while audio::playing(voice) { sleep_ms(5); }
sleep_ms(100); // Let the device finish the already mixed tail.
audio::close();
}
```
## physics
Rigid body physics on Box3D: worlds, bodies, shapes of every kind Box3D has,
nine kinds of joints, contact, sensor, hit, move and joint events, ray and
shape casts and overlap queries, a character mover, and recording and replay.
Like `gfx`, the module is optional: it needs a compiler built with the
`third_party/box3d` submodule, and a program using it links what
`goose --physics-link msvc|cc` prints (`cl game.c @`,
`cc game.c -o game @`; a program that also uses `gfx` adds gfx's
response file). A compiler built without it still typechecks and generates
C for such a program; only running the program in-process fails.
Everything is in namespace `physics`; `samples/28_physics_boxes.goose` is a
complete program, and the package's [docs/physics.md](https://github.com/aardappel/goose_physics/blob/main/docs/physics.md) says how it works.
The API is Box3D's under Goose names: `b3Body_GetPosition` is
`position(body)`, `b3CreateRevoluteJoint` is `create_revolute_joint`, a
getter uses the property name and a setter prefixes it with `set_`. Box3D
works in meters, kilograms and seconds and has no built-in up direction;
the default gravity is -10 along y. Where Box3D takes a callback, this API
uses other forms: events and query results come back as fresh arrays (each
also has an `_into` form that copies into a slice the caller provides and
returns how many there are), and friction and restitution mixing is chosen
from the `MIX_*` rules. Custom contact filters, pre-solve callbacks, debug
drawing and the standalone dynamic tree are not included.
Physics runs on the main thread; a `thread_fn` reaching it is a compile
error. Box3D spreads a step over worker threads of its own, as many as
`WorldDef.workers` asks for, and the results are the same for any number of
workers.
### Errors
A call that can fail for reasons outside the program, such as a file that
is not there or points that make no hull, returns false or a zero handle,
and `error()` says why. A call that the program should not have made is
reported when it happens and skipped, and the program aborts at the next
`step()`, `destroy(world)` or `check()`. Examples are a destroyed body, a
revolute function on a prismatic joint, and a height field on a dynamic
body. Every handle carries a generation, so using a destroyed object is
reported as an error instead of crashing.
```goose
fn error() -> u8[>..] fn check() fn misuse_count() -> i64
fn available() -> bool fn version() -> Version // Box3D's
fn byte_count() -> i64 // what Box3D has allocated
fn set_length_units_per_meter(units: f32) // before anything else is made
fn length_units_per_meter() -> f32
fn world_count() -> i64 fn max_world_count() -> i64
```
### Values
```goose
struct Quat { x: f32, y: f32, z: f32, w: f32 } // a rotation; w the scalar part
struct Transform { p: float3, q: Quat } // rotate by q, then move by p
struct Mat3 { cx: float3, cy: float3, cz: float3 } // by columns
struct AABB { lower: float3, upper: float3 }
struct Plane { normal: float3, offset: f32 }
struct MassData { mass: f32, center: float3, inertia: Mat3 }
struct Sphere { center: float3, radius: f32 }
struct Capsule { center1: float3, center2: float3, radius: f32 }
let quat_identity let transform_identity
fn quat_axis_angle(axis: float3, angle: f32) -> Quat
fn mul(a: Quat, b: Quat) -> Quat // b, then a
fn rotate(q: Quat, v: float3) -> float3 fn inv_rotate(q: Quat, v: float3) -> float3
fn conjugate(q: Quat) -> Quat fn normalize(q: Quat) -> Quat
fn rotation_matrix(q: Quat) -> Mat3
fn transform_point(t: Transform, p: float3) -> float3 // and inv_transform_point
fn mul(a: Transform, b: Transform) -> Transform fn inv_mul(a, b) -> Transform
```
### Worlds
```goose
struct World { id: u32 }
struct WorldDef { gravity: float3 = { 0, -10, 0 }, restitution_threshold: f32 = 1.0,
hit_event_threshold: f32 = 1.0, contact_hertz: f32 = 30.0,
contact_damping_ratio: f32 = 10.0, contact_speed: f32 = 3.0,
maximum_linear_speed: f32 = 400.0, enable_sleep: bool = true,
enable_continuous: bool = true, workers: i32 = 1, friction_mixing: i32 = 0,
restitution_mixing: i32 = 0, user_data: u64 = 0, capacity: Capacity }
fn create_world() -> World // and (def)
fn destroy(w: World) fn is_valid(w: World) -> bool
fn step(w: World, time_step: f32, sub_steps: i64) // 1/60 and 4 are the usual
fn gravity(w) -> float3 fn set_gravity(w, gravity: float3)
fn enable_sleeping(w, flag) fn sleeping_enabled(w) -> bool // and continuous, warm_starting
fn restitution_threshold(w) -> f32 fn hit_event_threshold(w) -> f32 // and set_...
fn maximum_linear_speed(w) -> f32 fn contact_recycle_distance(w) -> f32 // and set_...
fn set_contact_tuning(w, hertz: f32, damping_ratio: f32, push_speed: f32)
fn set_friction_mixing(w, rule: i64) fn set_restitution_mixing(w, rule: i64)
fn worker_count(w) -> i64 fn set_worker_count(w, count: i64) // 1 to MAX_WORKERS
fn explode(w, def: ExplosionDef) // position, radius, falloff, impulse_per_area, mask_bits
fn bounds(w) -> AABB fn awake_body_count(w) -> i64
fn profile(w) -> Profile fn counters(w) -> Counters fn max_capacity(w) -> Capacity
fn user_data(w) -> u64 fn set_user_data(w, data: u64)
```
At most 128 worlds exist at once. Mixing rules: `MIX_DEFAULT` (Box3D's: the
geometric mean of the frictions, the larger restitution), `MIX_GEOMETRIC`,
`MIX_MIN`, `MIX_MAX`, `MIX_AVERAGE`, `MIX_MULTIPLY`.
### Bodies
```goose
struct Body { id: u64 }
struct BodyDef { body_type: i32 = 0, position: float3, rotation: Quat = quat_identity,
linear_velocity: float3, angular_velocity: float3, linear_damping: f32 = 0.0,
angular_damping: f32 = 0.0, gravity_scale: f32 = 1.0,
sleep_threshold: f32 = 0.05, safety_factor: f32 = 0.5,
motion_locks: MotionLocks, enable_sleep: bool = true, is_awake: bool = true,
is_bullet: bool = false, is_enabled: bool = true,
allow_fast_rotation: bool = false, enable_contact_recycling: bool = true,
user_data: u64 = 0 }
fn create_body(w: World, def: BodyDef) -> Body
fn destroy(b: Body) // with its shapes and joints
fn is_valid(b) -> bool fn world(b) -> World
fn body_type(b) -> i32 fn set_type(b, body_type: i64) // STATIC, KINEMATIC, DYNAMIC
fn name(b) -> u8[>..] fn set_name(b, name: const u8[:])
fn user_data(b) -> u64 fn set_user_data(b, data: u64)
fn position(b) -> float3 fn rotation(b) -> Quat fn transform(b) -> Transform
fn transforms(bodies: const Body[:], out: Transform[:]) // many in one call
fn set_transform(b, position: float3, rotation: Quat) // a teleport
fn local_point(b, p) -> float3 fn world_point(b, p) -> float3 // and local_/world_vector
fn linear_velocity(b) -> float3 fn angular_velocity(b) -> float3 // and set_...
fn local_point_velocity(b, p) -> float3 fn world_point_velocity(b, p) -> float3
fn set_target_transform(b, target: Transform, time_step: f32, wake: bool) // kinematic
fn apply_force(b, force: float3, point: float3, wake: bool) fn apply_force_to_center(b, force, wake)
fn apply_torque(b, torque: float3, wake: bool)
fn apply_linear_impulse(b, impulse, point, wake) fn apply_linear_impulse_to_center(b, impulse, wake)
fn apply_angular_impulse(b, impulse: float3, wake: bool)
fn mass(b) -> f32 fn inverse_mass(b) -> f32 fn rotational_inertia(b) -> Mat3
fn world_inverse_inertia(b) -> Mat3 fn local_center(b) -> float3 fn world_center(b) -> float3
fn mass_data(b) -> MassData fn set_mass_data(b, data: MassData) fn apply_mass_from_shapes(b)
fn linear_damping(b) -> f32 fn angular_damping(b) -> f32 fn gravity_scale(b) -> f32 // and set_...
fn is_awake(b) -> bool fn set_awake(b, awake: bool) // the whole island
fn enable_sleep(b, flag) fn sleep_enabled(b) -> bool
fn sleep_threshold(b) -> f32 fn safety_factor(b) -> f32 // and set_...
fn is_enabled(b) -> bool fn disable(b) fn enable(b)
fn motion_locks(b) -> MotionLocks fn set_motion_locks(b, locks: MotionLocks)
fn is_bullet(b) -> bool fn set_bullet(b, flag: bool)
fn fast_rotation_allowed(b) -> bool fn allow_fast_rotation(b, flag: bool)
fn contact_recycling_enabled(b) -> bool fn enable_contact_recycling(b, flag: bool)
fn enable_hit_events(b, flag: bool) // on each of its shapes
fn shape_count(b) -> i64 fn shapes(b) -> Shape[>..]
fn joint_count(b) -> i64 fn joints(b) -> Joint[>..]
fn contacts(b) -> Manifold[>..] // those touching
fn aabb(b) -> AABB fn min_extent(b) -> f32 fn max_extent(b) -> float3
fn closest_point(b, target: float3) -> float3, f32
```
Queries against one body, placed where the query says: `cast_ray(b, origin,
translation, filter, max_fraction, body_transform) -> RayHit`, `cast_shape`,
`overlap_shape`, `collide_mover(b, origin, mover, filter, body_transform) ->
PlaneHit[>..]` and `time_of_impact_mover(b, origin, mover, translation,
filter, transform1, transform2) -> ToiHit`.
### Shapes
```goose
struct Shape { id: u64 }
struct ShapeDef { material: SurfaceMaterial, density: f32 = 1000.0, filter: Filter,
is_sensor: bool = false, enable_sensor_events: bool = false,
enable_contact_events: bool = false, enable_hit_events: bool = false,
invoke_contact_creation: bool = true, update_body_mass: bool = true,
enable_speculative_contact: bool = true, explosion_scale: f32 = 1.0,
user_data: u64 = 0 }
struct SurfaceMaterial { friction: f32 = 0.6, restitution: f32 = 0.0,
rolling_resistance: f32 = 0.0, tangent_velocity: float3,
user_material_id: u64 = 0, custom_color: u32 = 0 }
struct Filter { category_bits: u64 = ALL_BITS, mask_bits: u64 = ALL_BITS, group_index: i32 = 0 }
fn create_sphere_shape(b: Body, def: ShapeDef, sphere: Sphere) -> Shape
fn create_capsule_shape(b, def, capsule: Capsule) -> Shape
fn create_box_shape(b, def, half_extents: float3) -> Shape // and (..., frame: Transform)
fn create_hull_shape(b, def, hull: Hull) -> Shape
fn create_transformed_hull_shape(b, def, hull, transform: Transform, scale: float3) -> Shape
fn create_mesh_shape(b, def, mesh: Mesh) -> Shape // and (..., scale, materials)
fn create_height_field_shape(b, def, h: HeightField) -> Shape // static bodies; and (..., materials)
fn create_compound_shape(b, def, c: Compound) -> Shape // static bodies
fn destroy(s: Shape) // and (s, update_body_mass: bool)
fn is_valid(s) -> bool fn shape_type(s) -> i32 fn body(s) -> Body fn world(s) -> World
fn is_sensor(s) -> bool fn name(s) -> u8[>..] fn user_data(s) -> u64 // and set_...
fn density(s) -> f32 fn set_density(s, density: f32, update_body_mass: bool)
fn friction(s) -> f32 fn restitution(s) -> f32 // and set_...
fn surface_material(s) -> SurfaceMaterial fn set_surface_material(s, material)
fn mesh_material_count(s) -> i64 fn mesh_material(s, i) -> SurfaceMaterial // and set_...
fn shape_filter(s) -> Filter fn set_shape_filter(s, filter: Filter, invoke_contacts: bool)
fn enable_contact_events(s, flag) fn contact_events_enabled(s) -> bool // and sensor_, hit_
fn sphere(s) -> Sphere fn capsule(s) -> Capsule fn hull(s) -> Hull // hull(): a copy
fn mesh(s) -> Mesh fn mesh_scale(s) -> float3 fn height_field(s) -> HeightField
fn compound(s) -> Compound
fn set_sphere(s, sphere) fn set_capsule(s, capsule) fn set_hull(s, hull) fn set_mesh(s, mesh, scale)
fn ray_cast(s, origin: float3, translation: float3) -> CastOutput // in the world
fn contacts(s) -> Manifold[>..] fn sensor_overlaps(s) -> Shape[>..]
fn aabb(s) -> AABB fn mass_data(s) -> MassData fn closest_point(s, target) -> float3
fn apply_wind(s, wind: float3, drag: f32, lift: f32, max_speed: f32, wake: bool)
```
Shape types `SHAPE_SPHERE`, `SHAPE_CAPSULE`, `SHAPE_HULL`, `SHAPE_MESH`,
`SHAPE_HEIGHT_FIELD`, `SHAPE_COMPOUND`. Two shapes collide when each
shape's category is in the other's mask, unless they share a group index:
shapes in a positive group always collide, and shapes in a negative group
never do. A mesh collides only with
shapes that are not meshes. The default density is water's, 1000 kg/m³.
### Geometry
Hulls, meshes, height fields and baked compounds are made once and shared by
shapes. A shape keeps the geometry it was made from. Destroying a mesh,
height field or compound that shapes use releases the handle, and the data
is freed when the last shape using it is destroyed. A hull is copied into
each world that uses it.
```goose
struct Hull { id: u32 } struct Mesh { id: u32 }
struct HeightField { id: u32 } struct Compound { id: u32 }
fn create_hull(points: const float3[:]) -> Hull // zero for points in a plane; and (..., max_vertices)
fn create_box_hull(half_extents: float3) -> Hull // and (..., frame)
fn create_cylinder_hull(height, radius, y_offset: f32, sides: i64) -> Hull
fn create_cone_hull(height, radius1, radius2: f32, slices: i64) -> Hull
fn create_rock_hull(radius: f32) -> Hull
fn transformed(h: Hull, transform: Transform, scale: float3) -> Hull
fn create_mesh(vertices: const float3[:], indices: const i32[:]) -> Mesh
fn create_mesh(vertices, indices, material_indices: const u8[:], def: MeshDef) -> Mesh
fn create_grid_mesh(x_count, z_count: i64, cell_width: f32, material_count: i64, identify_edges: bool) -> Mesh
fn create_wave_mesh(...) fn create_torus_mesh(...) fn create_box_mesh(center, extent, identify_edges)
fn create_hollow_box_mesh(center, extent) fn create_platform_mesh(center, height, top_width, bottom_width)
fn create_height_field(heights: const f32[:], material_indices: const u8[:], def: HeightFieldDef) -> HeightField
fn create_grid_height_field(rows, columns: i64, scale: float3, make_holes: bool) -> HeightField
fn create_wave_height_field(rows, columns, scale, row_frequency, column_frequency, make_holes)
fn create_compound(spheres: const CompoundSphere[:], capsules: const CompoundCapsule[:],
hulls: const CompoundHull[:], meshes: const CompoundMesh[:]) -> Compound
fn destroy(h: Hull) fn is_valid(h: Hull) -> bool fn info(h: Hull) -> HullInfo // each kind
fn vertices(h: Hull) -> float3[>..] fn triangles(h: Hull) -> int3[>..] // and for Mesh
```
The same geometry can also be queried on its own, in its own frame:
`compute_mass(g, density)` for spheres, capsules and hulls,
`compute_aabb(g, transform)`, `ray_cast(g, origin, translation,
max_fraction) -> CastOutput`, `overlap(g, transform, points, radius) -> bool`
and `shape_cast(g, points, radius, translation, max_fraction, can_encroach)
-> CastOutput` for each kind (a mesh takes its scale first). Between two
point clouds with radii, there are `shape_distance`, `shape_cast` and
`time_of_impact` over two `Sweep`s, with `sweep_transform(sweep, time)`.
### Joints
```goose
struct Joint { id: u64 }
struct JointDef { body_a: Body, body_b: Body, frame_a: Transform, frame_b: Transform,
collide_connected: bool = false, force_threshold: f32, torque_threshold: f32,
constraint_hertz: f32 = 60.0, constraint_damping_ratio: f32 = 2.0,
draw_scale: f32 = 1.0, user_data: u64 = 0 }
fn joint_def(a: Body, b: Body, anchor: float3) -> JointDef // and (a, b, frame: Transform)
fn local_frame(b: Body, world_frame: Transform) -> Transform
fn create_revolute_joint(w: World, def: RevoluteJointDef) -> RevoluteJoint
// and create_distance_joint, _filter_, _motor_, _parallel_, _prismatic_, _spherical_,
// _weld_, _wheel_: each kind's definition starts with `base: JointDef`.
```
Each joint kind has its own handle type, such as `struct RevoluteJoint {
joint: Joint }`. Kind-specific functions take that handle. Functions common
to all joints take its `joint` field:
```goose
fn destroy(j: Joint) // and (j, wake_bodies: bool)
fn is_valid(j) -> bool fn joint_type(j) -> i32 fn body_a(j) -> Body fn body_b(j) -> Body
fn world(j) -> World fn local_frame_a(j) -> Transform // and _b, and set_...
fn collide_connected(j) -> bool fn user_data(j) -> u64 // and set_...
fn wake_bodies(j) fn is_awake(j) -> bool
fn constraint_force(j) -> float3 fn constraint_torque(j) -> float3
fn linear_separation(j) -> f32 fn angular_separation(j) -> f32
fn constraint_tuning(j) -> f32, f32 fn set_constraint_tuning(j, hertz, damping_ratio)
fn force_threshold(j) -> f32 fn torque_threshold(j) -> f32 // and set_...
```
| Kind | What it does | Its own |
|---|---|---|
| `RevoluteJoint` | a hinge about the frames' z axis | `angle`, `enable_limit`/`set_limits`/`lower_limit`/`upper_limit`, `enable_motor`/`set_motor_speed`/`set_max_motor_torque`/`motor_torque`, `enable_spring`/`set_spring_hertz`/`set_spring_damping_ratio`/`set_target_angle` |
| `PrismaticJoint` | a slider along the frames' x axis | `translation`, `speed`, limits, a motor (`set_max_motor_force`, `motor_force`), a spring to `set_target_translation` |
| `DistanceJoint` | two anchors a distance apart | `rest_length`/`set_rest_length`, `current_length`, `set_length_range`/`min_length`/`max_length`, a spring with `set_spring_force_range`, a motor |
| `SphericalJoint` | a ball and socket | `enable_cone_limit`/`set_cone_limit`/`cone_angle`, `set_twist_limits`/`twist_angle`, a spring to `set_target_rotation`, a motor to `set_motor_velocity` |
| `WeldJoint` | two bodies as one | `set_linear_hertz`, `set_angular_hertz` and their damping ratios; 0 hertz is rigid |
| `WheelJoint` | a wheel on a suspension | `enable_suspension`/limits, `enable_spin_motor`/`set_spin_motor_speed`/`spin_speed`, `enable_steering`/`set_target_steering_angle`/`steering_angle` |
| `MotorJoint` | drives body b relative to body a | `set_linear_velocity`, `set_angular_velocity`, their limits, and springs |
| `ParallelJoint` | keeps the frames' z axes parallel | `set_spring_hertz`, `set_spring_damping_ratio`, `set_max_torque` |
| `FilterJoint` | only stops the two colliding | |
Every setter has its getter. Kinds are `JOINT_REVOLUTE` and so on; using a
kind's function on a joint of another kind is a misuse.
### Events
Events are read after the step they happened in. Each function returns an
array of what the last step produced:
```goose
fn contact_begin_events(w) -> ContactEvent[>..] // shape_a, shape_b, contact
fn contact_end_events(w) -> ContactEvent[>..]
fn contact_hit_events(w) -> ContactHitEvent[>..] // point, normal, approach_speed, materials
fn sensor_begin_events(w) -> SensorEvent[>..] // sensor, visitor
fn sensor_end_events(w) -> SensorEvent[>..]
fn body_move_events(w) -> BodyMoveEvent[>..] // transform, body, user_data, fell_asleep
fn joint_events(w) -> JointEvent[>..] // joint, user_data: past its thresholds
struct Contact { ... }
fn is_valid(c: Contact) -> bool fn manifolds(c: Contact) -> Manifold[>..]
```
A shape reports contacts with `enable_contact_events`, hits with
`enable_hit_events` (faster than the world's `hit_event_threshold`), and
sensor visits with `enable_sensor_events` on both the sensor and the visitor.
A `Manifold` has the contact and its shapes, the normal from a to b, and up
to four `ManifoldPoint`s with their anchors, separation and impulses.
### Queries
```goose
struct QueryFilter { category_bits: u64 = ALL_BITS, mask_bits: u64 = ALL_BITS }
fn overlap_aabb(w, box: AABB, filter: QueryFilter) -> Shape[>..]
fn overlap_shape(w, origin: float3, points: const float3[:], radius: f32, filter) -> Shape[>..]
fn cast_ray(w, origin: float3, translation: float3, filter) -> RayHit[>..] // nearest first
fn cast_ray_closest(w, origin, translation, filter) -> RayHit // hit false: none
fn cast_shape(w, origin, points, radius, translation, filter) -> RayHit[>..]
```
A query shape is the convex hull of up to `MAX_PROXY_POINTS` points around
`origin`, grown by `radius`: one point is a sphere, two a capsule, eight the
corners of a box.
### A character mover
```goose
fn cast_mover(w, origin: float3, mover: Capsule, translation: float3, filter) -> f32
fn collide_mover(w, origin, mover, filter) -> PlaneHit[>..]
fn solve_planes(target_delta: float3, planes: CollisionPlane[:]) -> float3, i64
fn clip_vector(vector: float3, planes: const CollisionPlane[:]) -> float3
```
`cast_mover` determines how far a capsule can move. `collide_mover` returns
the planes it touches. Pass these as `CollisionPlane`s to `solve_planes` to
find the allowed move closest to the target. `clip_vector` removes velocity
components that point into those planes.
### Recording and replay
```goose
struct Recording { id: u32 } struct Player { id: u32 }
fn create_recording() -> Recording fn load_recording(path: const u8[:]) -> Recording
fn start_recording(w: World, r: Recording) fn stop_recording(w: World)
fn size(r) -> i64 fn bytes(r) -> u8[>..] fn save(r, path: const u8[:]) -> bool
fn validate_replay(r, workers: i64) -> bool // replays it; the same?
fn create_player(r, workers: i64) -> Player
fn step(p: Player) -> bool fn sub_step(p) fn at_pre_step(p) -> bool
fn restart(p) fn seek(p, frame: i64) fn frame(p) -> i64 fn frame_count(p) -> i64
fn at_end(p) -> bool fn diverged(p) -> bool fn diverge_frame(p) -> i64
fn world(p) -> World fn body_count(p) -> i64 fn body(p, index: i64) -> Body
fn info(p) -> PlayerInfo fn set_worker_count(p, count: i64)
fn destroy(r: Recording) fn destroy(p: Player)
```
A recording holds everything a world does between `start_recording` and
`stop_recording`. A player replays it into a world of its own, step by
step, and checks that the result is the same. The recorded bodies exist
once the replay has made them; `body(p, k)` is the k-th body made.
```goose
let world = physics::create_world();
let ground = physics::create_body(world, physics::BodyDef { position: float3 { 0.0, -1.0, 0.0 } });
physics::create_box_shape(ground, physics::ShapeDef {}, float3 { 20.0, 1.0, 20.0 });
let crate = physics::create_body(world, physics::BodyDef { body_type: physics::DYNAMIC,
position: float3 { 0.0, 4.0, 0.0 } });
physics::create_box_shape(crate, physics::ShapeDef {}, float3 { 0.5, 0.5, 0.5 });
for i in 90 { physics::step(world, 1.0 / 60.0, 4); }
print(physics::position(crate).y); // resting on the ground: about 0.5
physics::destroy(world);
```
## ui
Windows and widgets on Nuklear, the immediate-mode GUI library, drawn
through `gfx`: windows, rows and layout spaces, groups, trees, list views,
labels, buttons, check boxes, options, selectables, sliders, knobs, progress
bars, color pickers, properties, text fields and editors, charts, popups,
combo boxes, contextual menus, tooltips, menus, styles and fonts. Like
`gfx`, the module is optional: it needs a compiler built with the
`third_party/nuklear` submodule, and a program using it links what
`goose --ui-link msvc|cc` prints, together with gfx's if it renders through
gfx (`cl app.c @ @`). A compiler built without it still
typechecks and generates C for such a program; only running the program
in-process fails. Everything is in namespace `ui`;
`samples/29_ui_todo.goose` is a complete program, and the package's
[docs/ui.md](https://github.com/aardappel/goose_gfx/blob/main/docs/ui.md) says how it works.
The API is Nuklear's under Goose names: `nk_button_label` is
`button_label`. In immediate mode, the program declares on each frame which
windows and widgets there are, in order, and the same calls report what the
user did: `button_label` returns true on the frame the button is clicked,
`checkbox_label` flips the `bool` it is given, and `slider_float` moves the
`f32`. The program keeps its own data; a context stores only where windows
are and what is open, active or scrolled.
```goose
guard gfx::open("sound", 400, 300) else { abort(str("gfx: ", gfx::error())); }
let ctx = ui::create(13.0);
var volume: f32 = 0.5;
var muted = false;
while gfx::frame() {
ui::input_from_gfx(ctx);
if ui::begin(ctx, "Sound", ui::rect(20, 20, 220, 130), ui::WINDOW_TITLE | ui::WINDOW_MOVABLE) {
ui::layout_row_dynamic(ctx, 24.0, 1);
ui::slider_float(ctx, 0.0, volume, 1.0, 0.05);
ui::checkbox_label(ctx, "Muted", muted);
if ui::button_label(ctx, "Reset") { volume = 0.5; }
}
ui::end(ctx);
gfx::begin_screen(float4 { 0.1, 0.1, 0.1, 1.0 });
gfx::end_pass();
ui::render(ctx);
}
ui::destroy(ctx);
```
A frame consists of input (here from gfx), then the windows, each `begin`
followed by its `end` whether or not `begin` returned true, and then
drawing what the windows built. Inside a window every widget takes the next
place in the current row, so a row must come first (`layout_row_*`). The
ui runs on the main thread; a `thread_fn` reaching it is a compile error.
### Errors
A call that can fail for reasons outside the program, such as a font file
that is not there or bytes that are not a font, returns false or a zero
handle, and `error()` says why. A call that the program should not have
made is printed when it happens and skipped, and the program aborts at the
next `input_begin`, `convert`, `render`, `destroy(context)` or `check()`.
Examples are a widget outside a window, a group ended that was never begun,
a window begun inside another, a key that is not a valid key, and a
destroyed handle. Contexts, font atlases, fonts and text editors are
handles with a generation, so using a destroyed one is reported as an
error instead of crashing.
```goose
fn error() -> u8[>..] fn check() fn misuse_count() -> i64 fn available() -> bool
```
### Contexts and input
```goose
struct Context { id: u32 }
fn create(font_height: f32) -> Context // Nuklear's own font; 13 is its native size
fn create(font_height: f32, scale: f32) -> Context // drawn at a scale (set_scale)
fn create_from_file(path: const u8[:], font_height: f32) -> Context // a TrueType font of its own
fn create_from_memory(ttf: const u8[:], font_height: f32) -> Context // from the font's bytes
fn create_context(font: Font) -> Context // a font from a baked atlas
fn destroy(c: Context) fn is_valid(c: Context) -> bool
fn context_atlas(c) -> FontAtlas fn font(c) -> Font // drawn with now
fn clear(c) // ends a frame; input_begin does it for a frame that drew
fn set_scale(c, scale: f32) -> bool fn scale(c) -> f32 // pixels per ui unit
fn set_delta_time(c, seconds: f32) fn delta_time(c) -> f32
fn set_clipboard(c, text: const u8[:]) // what KEY_PASTE pastes
fn copied(c) -> u8[>..], bool // what a text field copied or cut, if any
fn input_begin(c) fn input_end(c)
fn input_motion(c, x: i64, y: i64)
fn input_button(c, button: i64, x: i64, y: i64, down: bool) // BUTTON_LEFT, _MIDDLE, _RIGHT, ...
fn input_scroll(c, amount: float2)
fn input_key(c, key: i64, down: bool) // KEY_*: editing keys and shortcuts
fn input_text(c, text: const u8[:]) // and input_char, input_glyph, input_unicode
```
The whole ui can be drawn bigger or smaller with `set_scale`. The program
picks the scale as it likes, for example from the window's size, the
display, or a setting. The layout stays the same: windows, rows, fonts and
the style keep their sizes in the ui's own units. The scale is the number
of pixels per unit; `convert` and `render` draw at that scale, and input
positions are given in those pixels. A context made by `create` or
`create_from_*` has its font baked again at the scale, so its text stays
sharp. (Nuklear's built-in font imitates a bitmap font and scales well only
by whole numbers; a TrueType font scales well by any amount.) A context
made on a font of the program's own keeps that font's atlas as it is;
`bake(atlas, scale)` makes the atlas sharp at a scale, and is called again
at each new scale. `set_scale` is called between frames, not during one.
Input goes between `input_begin` and `input_end`, before any window, in one
batch per frame; `input_from_gfx` (below) does all of this. Positions are
in pixels. A key pressed and released in the same frame counts as a press.
Nuklear looks at a frame's keys afterwards, in the order of their `KEY_*`
values. A button's press or release also zeroes the frame's mouse motion.
The input that widgets see can also be queried directly:
```goose
fn input_mouse(c) -> Mouse // pos, prev, delta, scroll_delta, grab state
fn input_is_mouse_down(c, button) -> bool // and _pressed, _released
fn input_is_key_down(c, key) -> bool // and _pressed, _released
fn input_is_mouse_hovering_rect(c, r: Rect) -> bool // and _prev_, _still_
fn input_has_mouse_click_in_rect(c, button, r) -> bool // where the last click was
fn input_is_mouse_click_in_rect(c, button, r) -> bool // a release there this frame
fn input_is_mouse_hovering_delay_rect(c, r, timer: f32&, delay: f32) -> bool
```
A click is a button going down or up. The `has_` queries look at where the
button's last click was, and the `is_` queries look for a click in this
frame. The `_delay`
queries count hovering time in a timer the program keeps between frames
(also `_still_delay` and `_still_delay_clicked`).
A program that shares its input with the ui, such as a game with keys of
its own, can ask which of the two the input belongs to:
```goose
fn wants_keyboard(c) -> bool // a text field or a property being typed into has the keys
fn wants_mouse(c) -> bool // over a window or popup taking input, or dragging from one
```
Clicking a text field gives it keyboard focus. It keeps focus until a click
outside it, a call to `edit_unfocus`, or its window closing or being omitted
from a frame. Moving the mouse does not change this. The field also stays
focused while its window is behind another window, hidden, or covered by its own
popup. It receives no keys until the window is in front, shown again, or the
popup is closed.
The ui owns mouse input while the pointer is over a window or popup, or while a
button pressed there remains held. A button pressed elsewhere keeps mouse input
with the program until release. A window or popup created with
`WINDOW_NO_INPUT`, such as a message log or hover information, passes mouse
input through to the program. It cannot be moved, scaled, scrolled, or brought
to the front, and its widgets receive no clicks. Nuklear's `item_is_any_active`
is true whenever the mouse is over a window, so it cannot determine who owns
keyboard input.
### Text rendering and game HUDs
`ui` provides text rendering for gfx applications as well as widgets:
`ui::label` uses row/space layout, and `ui::draw_text` draws at an explicit
rectangle on the current window's canvas. `ui::create(13.0)` supplies a
built-in font without an asset file; `create_from_file` and
`create_from_memory` supply TrueType fonts. Font measurement and atlas
access are listed under [Fonts](#fonts).
For a HUD, create a dedicated context once, hide its window background, and
use `WINDOW_NO_INPUT | WINDOW_NO_SCROLLBAR` with no title or border. The
window supplies a canvas and clipping; it does not need interactive widgets.
The following fits inside a gfx program (after opening gfx):
```goose
let hud = ui::create(13.0);
if hud.id == 0 { abort(ui::error()); }
var style = ui::style(hud);
style.window.fixed_background = ui::style_item_hide();
ui::set_style(hud, style);
while gfx::frame() {
ui::input_from_gfx(hud);
if ui::begin(hud, "HUD", ui::rect(0, 0, 320, 80),
ui::WINDOW_NO_INPUT | ui::WINDOW_NO_SCROLLBAR) {
ui::draw_text(hud, ui::rect(12, 12, 280, 24), "Health: 100",
ui::font(hud), ui::Color { 0, 0, 0, 0 },
ui::Color { 255, 255, 255, 255 });
}
ui::end(hud); // Always paired with begin, even when begin returns false.
// Draw the game scene here and end its render pass.
ui::render(hud);
}
ui::destroy(hud); // Before gfx::close().
```
Draw the HUD after the game scene. `ui::render` uses gfx internally and
retains the scene beneath it. For a resizable full-screen overlay, update
the window's bounds to the viewport; for a custom renderer, ui can also
export its text and geometry commands or converted triangles. World-space
sprites, billboard orientation and sprite animation belong to the game's
sprite renderer; ordinary HUD text can use this existing font path.
### Fonts
```goose
struct FontAtlas { id: u32 } struct Font { id: u32 }
fn create_font_atlas() -> FontAtlas fn destroy(a: FontAtlas)
fn add_default_font(a, height: f32) -> Font // ProggyClean
fn add_font_from_file(a, path: const u8[:], height: f32) -> Font
fn add_font_from_memory(a, ttf: const u8[:], height: f32) -> Font
// each also (..., config: FontConfig) and (..., config, ranges: const u32[:])
fn bake(a) -> bool // then the fonts can be used
fn bake(a, scale: f32) -> bool // glyphs scale times the size, for a ui at that scale;
// again at another, fonts kept
fn atlas_size(a) -> int2 fn atlas_pixels(a) -> u8[>..] // RGBA
fn set_texture(a, texture: u32) fn texture(a) -> u32
fn fonts(a) -> Font[>..] fn atlas(f: Font) -> FontAtlas
fn info(f) -> FontInfo // height, ascent, descent, glyph count, fallback
fn text_width(f, text: const u8[:]) -> f32
fn find_glyph(f, codepoint: u32) -> FontGlyph, bool
fn glyph_ranges(which: i64) -> u32[>..] // RANGE_DEFAULT, _CHINESE, _CYRILLIC, _KOREAN
```
An atlas bakes its fonts into one image; a context draws with one of them
and uses the atlas's white texel for its shapes. `FontConfig` holds these
settings, with Nuklear's defaults: oversampling, pixel snapping, spacing,
the characters to bake (`ranges`, a `RANGE_*` set, or pairs of first and
last character given as an array), the fallback glyph, and `merge_mode`,
which adds the glyphs to the atlas's first font. Fonts are added before the
atlas is first baked. The atlas keeps their data, and baking it again at
another scale replaces their glyphs and image in place, so contexts drawing
with its fonts keep using them and draw sharp at the new scale. A context
draws its shapes with the atlas of the font it was made with or last given
by `style_set_font`; destroying an atlas a context still uses is an error.
### Windows and layout
```goose
fn begin(c, title: const u8[:], bounds: Rect, flags: i64) -> bool // WINDOW_* flags
fn begin_titled(c, name, title, bounds, flags) -> bool
fn end(c)
fn window_get_bounds(c) -> Rect // and _position, _size, _content_region, _scroll, ...
fn window_has_focus(c) -> bool fn window_is_hovered(c) -> bool
fn window_is_collapsed(c, name) -> bool // and _closed, _hidden, _active
fn window_set_bounds(c, name, bounds: Rect) // and _position, _size, _focus, set_scroll
fn window_close(c, name) fn window_collapse(c, name, state: i64) // MINIMIZED, MAXIMIZED
fn window_show(c, name, state: i64) // SHOWN, HIDDEN; and the _if forms
fn layout_row_dynamic(c, height: f32, cols: i64) // equal parts of the width
fn layout_row_static(c, height: f32, item_width: i64, cols: i64)
fn layout_row(c, format: i64, height: f32, ratios: const f32[:]) // DYNAMIC, STATIC
fn layout_row_begin(c, format, height, cols) fn layout_row_push(c, value: f32) fn layout_row_end(c)
fn layout_row_template_begin(c, height) // push_dynamic, push_variable(min), push_static(w), _end
fn layout_space_begin(c, format, height, widget_count) fn layout_space_push(c, bounds: Rect)
fn layout_space_end(c) // and _bounds, _to_screen, _to_local
fn layout_set_min_row_height(c, height) fn spacer(c) fn spacing(c, cols)
```
A row lays out the widgets after it, and repeats until the next row call.
`begin` returns false for a collapsed or hidden window, and its `end` still
follows. Windows are found by name, so two windows in one frame cannot
share a name.
### Groups, trees and list views
```goose
fn group_begin(c, title, flags) -> bool fn group_end(c)
fn group_scrolled_begin(c, offset: Scroll, title, flags) -> bool
fn group_scrolled_end(c) -> Scroll fn group_get_scroll(c, id) -> Scroll
fn tree_push(c, type: i64, title, initial: i64) -> bool // TREE_NODE or _TAB, MINIMIZED..
fn tree_pop(c)
fn tree_push_id(c, type, title, initial, id: i64) -> bool // same titles apart
fn tree_image_push(c, type, img: Image, title, initial) -> bool
fn tree_state_push(c, type, title, state: i64&) -> bool fn tree_state_pop(c)
fn tree_element_push(c, type, title, initial, selected: bool&) -> bool fn tree_element_pop(c)
fn list_view_begin(c, id, flags, row_height: i64, row_count: i64) -> ListView, bool
fn list_view_end(c) // build rows view.begin until view.end only
```
A group is a scrolled panel inside a window, with its own rows; its end
follows only when its begin returned true. A tree node's contents go
between a push that returned true and its pop.
### Widgets
```goose
fn label(c, text: const u8[:], align: i64) // TEXT_LEFT, TEXT_CENTERED, TEXT_RIGHT
fn label_colored(c, text, align, color: Color) fn label_wrap(c, text)
fn value_int(c, prefix, v: i64) // and _bool, _uint, _float, _color_*
fn link_label(c, text, align) -> bool // and _colored, _underline, _styled, ...
fn image(c, img: Image) fn image_color(c, img, color)
fn button_label(c, text) -> bool // clicked this frame
fn button_symbol(c, symbol: i64) -> bool // SYMBOL_*; and _color, _image, _*_label
fn button_label_styled(c, style: const StyleButton&, text) -> bool // and for each kind
fn button_push_behavior(c, behavior: i64) -> bool // BUTTON_DEFAULT, BUTTON_REPEATER
fn button_pop_behavior(c) -> bool
fn checkbox_label(c, text, active: bool&) -> bool // and _align, _flags_label
fn check_label(c, text, active: bool) -> bool // the new state
fn radio_label(c, text, active: bool&) -> bool fn option_label(c, text, active: bool) -> bool
fn selectable_label(c, text, align, value: bool&) -> bool // and _image_, _symbol_; select_*
fn slider_float(c, min: f32, value: f32&, max: f32, step: f32) -> bool // and slider_int
fn slide_float(c, min, value, max, step) -> f32 // and slide_int
fn knob_float(c, min, value: f32&, max, step, zero_direction: i64, dead_zone_degrees: f32) -> bool
fn progress(c, cur: i64&, max: i64, modifiable: bool) -> bool fn prog(c, cur, max, modifiable) -> i64
fn color_picker(c, color: Colorf, format: i64) -> Colorf // RGB, RGBA
fn color_pick(c, color: Colorf&, format) -> bool
fn property_float(c, name, min, value: f32&, max, step, inc_per_pixel: f32) -> bool
// and _int, _double; propertyi, propertyf, propertyd return the value
fn widget(c) -> i64, Rect // a place for a widget of the program's own: WIDGET_*
fn widget_bounds(c) -> Rect // the next widget's place, and _position, _size, ...
fn widget_is_hovered(c) -> bool fn widget_is_mouse_clicked(c, button) -> bool
fn widget_disable_begin(c) fn widget_disable_end(c) // shown dimmed, inert
```
The `_label` functions take any Goose string, so a Nuklear `_text`
function maps to the same call. A property is dragged, stepped with its
arrows or typed into; a name starting with `#` is not shown. Sliders and
knobs need a non-empty range.
### Text editing
```goose
fn edit_string(c, flags: i64, text: T&, filter: i64) -> i64 // u8[..k], u8[..]
fn edit_string(c, flags, buffer: u8[:], len: i64&, filter) -> i64
fn edit_buffer(c, flags, edit: TextEdit, filter) -> i64 // keeps undo
fn edit_focus(c, flags) fn edit_unfocus(c) // the next edit
fn filter_accepts(filter: i64, rune: u32) -> bool
fn create_text_edit() -> TextEdit fn destroy(e: TextEdit)
fn text(e) -> u8[>..] fn set_text(e, text) fn state(e) -> TextEditState
fn set_cursor(e, cursor, select_start, select_end) fn set_mode(e, mode: i64)
fn textedit_text(e, text) fn textedit_delete(e, at, len) fn textedit_delete_selection(e)
fn textedit_select_all(e) fn textedit_cut(e) -> bool fn textedit_paste(e, text) -> bool
fn textedit_undo(e) fn textedit_redo(e)
```
Flags are `EDIT_*`, with the usual sets `EDIT_SIMPLE`, `EDIT_FIELD` (one
line, selectable, clipboard) and `EDIT_BOX` (several lines). The result
reports `EDIT_ACTIVE` or `_INACTIVE`, `_ACTIVATED` and `_DEACTIVATED` in
this frame, and `_COMMITTED` for Enter with `EDIT_SIG_ENTER`. A field
accepts what fits in its text's capacity and passes a `FILTER_*`
(`DEFAULT`, `ASCII`, `FLOAT`, `DECIMAL`, `HEX`, `OCT`, `BINARY`); a paste
accepts what typing would. A text editor (`TextEdit`) holds its own text,
cursor, selection, mode and undo history; a field over a buffer keeps no
undo between frames.
Editing keys are `KEY_*`; `KEY_COPY` and `KEY_CUT` hand the selection to
`copied`, `KEY_PASTE` pastes `set_clipboard`'s text.
### Popups, combo boxes, menus and tooltips
```goose
fn popup_begin(c, type: i64, title, flags, rect: Rect) -> bool // POPUP_STATIC, _DYNAMIC
fn popup_close(c) fn popup_end(c) // and popup_get_scroll, popup_set_scroll
fn combo(c, items: const u8[:][:], selected: i64, item_height: i64, size: float2) -> i64
fn combobox(c, items, selected: i64&, item_height, size) -> bool
// and combo_separator, combo_string over one string of items
fn combo_begin_label(c, selected: const u8[:], size) -> bool // and _color, _symbol, _image
fn combo_item_label(c, text, align) -> bool fn combo_close(c) fn combo_end(c)
fn contextual_begin(c, flags, size: float2, trigger_bounds: Rect) -> bool
fn contextual_item_label(c, text, align) -> bool fn contextual_close(c) fn contextual_end(c)
fn tooltip(c, text) fn tooltip_offset(c, text, position: i64, offset: float2) // TOP_LEFT..
fn tooltip_begin(c, width: f32) -> bool fn tooltip_end(c)
fn do_tooltip(c, text, bounds: Rect) // when hovering bounds; and _delay(.., timer: f32&)
fn menubar_begin(c) fn menubar_end(c) // first in its window
fn menu_begin_label(c, text, align, size: float2) -> bool // and _image, _symbol forms
fn menu_item_label(c, text, align) -> bool fn menu_close(c) fn menu_end(c)
```
A popup, combo box, contextual menu, menu or tooltip is a small window of
its own, drawn above the one it opens from, with rows inside. Each end
follows only a begin that returned true, and none of them opens inside
another. A tooltip is requested before the widget it is about, when
`widget_is_hovered` is true.
### Charts
```goose
fn chart_begin(c, type: i64, count: i64, min: f32, max: f32) -> bool // CHART_LINES, _COLUMN
fn chart_begin_colored(c, type, color: Color, active: Color, count, min, max) -> bool
fn chart_add_slot(c, type, count, min, max) // and _colored: another line or columns
fn chart_push(c, value: f32) -> i64 // CHART_HOVERING, CHART_CLICKED
fn chart_push_slot(c, value, slot: i64) -> i64
fn chart_end(c)
fn plot(c, type, values: const f32[:]) // a whole chart, scaled to the values
```
### Style and colors
```goose
fn style(c) -> Style fn set_style(c, s: const Style&)
fn push_style(c, s) -> bool fn pop_style(c) -> bool
fn style_default(c) fn style_from_table(c, table: const Color[:]) // one per COLOR_*
fn default_color_table() -> Color[>..] fn style_get_color_by_name(color: i64) -> u8[>..]
fn style_set_font(c, f: Font) fn style_push_font(c, f) -> bool fn style_pop_font(c) -> bool
fn style_load_cursor(c, which: i64, cursor: Cursor) // CURSOR_*
fn style_load_all_cursors(c, cursors: const Cursor[:]) // one per CURSOR_*
fn style_set_cursor(c, which: i64) -> bool fn style_show_cursor(c) fn style_hide_cursor(c)
fn style_item_color(color) -> StyleItem // and _image, _nine_slice, _hide
fn rgb(r, g, b: i64) -> Color // and rgba, rgba_u32, rgb_f, rgba_f, rgb_cf, rgb_hex, hsv, hsva, ...
fn color_cf(c: Color) -> Colorf // and color_u32, color_hex_rgba, color_hsv_f, color_hsva_f
fn rgb_factor(c: Color, factor: f32) -> Color
```
`Style` mirrors Nuklear's `nk_style` field for field (text, link, buttons,
toggles, selectables, sliders, knobs, progress bars, properties, edits,
charts, scroll bars, tabs, combo boxes, windows), except for its font and
cursors. To change the style, read it, change fields, and set it back. A
`StyleItem` is a color, an image or a nine-slice image, by `kind`. Software
cursors are images Nuklear draws at the mouse.
### Images and rectangles
```goose
struct Rect { x: f32, y: f32, w: f32, h: f32 }
struct Image { texture: u32, w: u16, h: u16, region: u16[4] } // a renderer's texture
fn rect(x, y, w, h) -> Rect // f32 or i64; and recta, rect_pos, rect_size, get_null_rect
fn image_id(texture: u32) -> Image fn texture_image(t: gfx::Texture) -> Image
fn subimage_id(texture, w: u16, h: u16, region: Rect) -> Image // part of a w x h texture
fn nine_slice_id(texture, l, t, r, b: u16) -> NineSlice // and sub9slice_id, with the size
```
### Drawing and what was drawn
```goose
fn stroke_line(c, x0, y0, x1, y1: f32, thickness: f32, color: Color)
// and stroke_curve, _rect, _circle, _arc, _triangle, _polyline, _polygon
fn fill_rect(c, r: Rect, rounding: f32, color)
// and fill_rect_multi_color, _circle, _arc, _triangle, _polygon
fn draw_image(c, r: Rect, img: Image, color) fn draw_nine_slice(c, r, slice: NineSlice, color)
fn draw_text(c, r: Rect, text, font: Font, background: Color, foreground: Color)
fn push_scissor(c, r: Rect)
fn commands(c) -> Command[>..] fn command_points(c) -> int2[>..] fn command_text(c) -> u8[>..]
fn convert(c, config: ConvertConfig) -> i64 // CONVERT_SUCCESS or what went wrong
fn vertices(c) -> Vertex[>..] fn indices(c) -> u32[>..] fn draw_commands(c) -> DrawCommand[>..]
fn null_texture(c) -> u32
```
The canvas functions draw onto the window being built, in screen
coordinates, clipped to the scissor. Together with `widget`, they are the
way to draw a widget of the program's own. Once a frame's windows have
ended, its drawing is available to any renderer in two forms:
* Nuklear's commands: one `Command` per shape, image or text, with the
points of polygons and the bytes of texts stored beside them;
* triangles from `convert`: vertices with a position, texture coordinate
and color, 32-bit indices, and draw calls that each have a scissor and a
texture (shapes use the null texture).
Both stay available until the next frame's input.
### Through gfx
```goose
fn render(c: Context) // over the screen
fn render(c, target: gfx::Texture) // over a COLOR_TARGET texture
fn render(c, target, config: ConvertConfig)
fn input_from_gfx(c: Context) // after gfx::frame(), before any window
```
`render` converts the frame and draws it with alpha blending; it is called
outside a pass. It uploads each font atlas to a gfx texture the first time,
and releases that texture with the atlas (`released_textures()` lists the
textures destroyed this way, for a renderer of the program's own). The
resources it keeps (a pipeline, a sampler and buffers) are made once, and
again after gfx is closed and opened. `input_from_gfx` passes gfx's last
frame of input to the context, in order:
* the mouse;
* editing keys (Delete, Backspace, Enter, Tab, Home, End, the arrows, Page
Up and Down, Escape);
* shortcuts with Ctrl or Command (A, C, X, V, Z, Y);
* typed text (it turns gfx's text input on);
* the frame's duration;
* the clipboard in both directions, pasting the system clipboard's text and
putting there what a field copied.
`render` and `input_from_gfx` both work in the screen's pixels, so on a
high-density display the ui has as many pixels as elsewhere, and so looks
smaller, until the program sets a scale.