Skip to content

Binary templates ​

A template can be compiled ahead of time to a small binary file, .xmlui. Loading it skips the XML parser and the expression parser at start-up, and a broken template is caught when you compile rather than when a player opens the screen.

When to use it ​

Use XML text whenUse binary when
You are still designing the screenThe screen is finished and ships with the game
You want hot reloadStart-up time matters, or there are many templates
The template is generated at run timeYou want compile errors at build time

The two forms build identical widgets. Keep the .xml as the source and treat the .xmlui as a build output.

Compile with xmluic ​

willowui offers the xmluic tool. From a project that lists willowui as a package:

sh
thicket tool xmluic compile ui/settings.xml ui/settings.xmlui

thicket tool xmluic ui/settings.xml ui/settings.xmlui (without compile) does the same. A template error prints its line and column and the tool exits with a failure code.

The tool knows the built-in tags. For a template that uses custom widgets, list them in a widget manifest, a JSON file that maps each tag to the module that defines it, and pass it with --widgets:

sh
thicket tool xmluic compile --widgets ui/widgets.json ui/rating.xml ui/rating.xmlui
json
{
  "StarRating": { "import": "./star_rating.tks", "attributes": { "maxStars": "int" } }
}

Each entry needs an import path. attributes is optional and maps an attribute name to a type name. A bare string ("StarRating": "./star_rating.tks") is the short form, and the entries may also sit inside a top-level "widgets" object. The tool only reads the manifest to learn the tag names; it does not load your widget code. At run time, still call registerWidget before you load the .xmlui.

Load it ​

ts
import { Files } from "fs";
const bytes: Buffer = await Files.readFile(moduleDir + "/ui/settings.xmlui");
reg.registerBinary("settings", bytes);
const root: Component = reg.instantiate("settings", controller, screen);

registerBinary throws if the bytes are not a valid template (template "settings": ...).

The format ​

For tools that want to read or write it. All numbers are little-endian.

PartContent
MagicThe four bytes X, U, I, 0x00.
Versionu16, currently 1.
NodeComponent type and text (strings); a u32 attribute count, each attribute as name, static value, expression and an is-event flag; a u32 directive count, each as a type byte, expression and loop variable name; a u32 child count and the child nodes, depth first; then the theme patch (a string, empty for none).
StringsA u32 byte length, then the UTF-8 bytes.
ExpressionsStored as their parsed tree, so no expression is parsed at load time. Literal numbers are stored as 64-bit floats.

encodeIR(node) and decodeIR(bytes) are the public encoder and decoder, and compileXML(text) gives the IRNode tree to encode, so you can build your own pipeline step.