How the `--embed` Flag Uses C23 `#embed` to Bundle Data Files into the ArmorPaint Binary
ArmorPaint's --embed flag bundles asset files directly into the compiled executable by generating a C23 #embed header that compiles raw file bytes into the binary, eliminating runtime file I/O dependencies.
The armory3d/armorpaint repository leverages modern C23 preprocessor capabilities to create self-contained binaries. When building with the --embed flag, the build pipeline intercepts asset paths and converts them into compile-time byte arrays using the #embed directive, requiring Clang 19 or newer for full C23 support.
Command-Line Detection and Flag Propagation
Parsing the --embed Argument in paint/project.js
The build system first detects the embedding request during project initialization. In paint/project.js, the global configuration object evaluates process arguments to set the embedding flag:
globalThis.flags.embed = os_argv().indexOf("--embed") >= 0;
This boolean flag propagates throughout the build system, signaling subsequent stages to generate the embedded asset headers rather than relying on external file paths.
Asset Collection and Filtering
Selecting Files for Embedding
Assets are registered via project.add_assets() calls throughout the configuration. Each asset entry can specify a noembed property to exclude specific files from binary inclusion:
- Assets with
noembed: true(typically plain text files or debug resources) are skipped during header generation - All other assets are queued for embedding into the final binary
The build script collects these paths into an internal embed_files list, which serves as the input for the header generation phase.
C23 #embed Header Generation
How make.js Generates embed.h
The core embedding logic resides in base/tools/make.js (around line 2748), where the build script constructs build/embed.h. For each asset marked for embedding, the generator creates a C constant array utilizing the C23 #embed directive:
const unsigned char default_mesh_obj[] = {
#embed "build/temp/data/meshes/default.obj"
};
const unsigned char default_texture_png[] = {
#embed "build/temp/data/textures/default.png"
};
The #embed directive instructs the compiler to read the specified file and expand its raw bytes as comma-separated integer literals at compile time. This approach eliminates the need for external file I/O or manual hex dumping during the build process.
Generated Lookup Tables
Alongside the byte arrays, make.js generates parallel lookup tables to enable runtime asset retrieval:
char *embed_keys[] = {
"./data/meshes/default.obj",
"./data/textures/default.png",
};
const unsigned char *embed_values[] = {
default_mesh_obj,
default_texture_png,
};
const int embed_sizes[] = {
sizeof(default_mesh_obj),
sizeof(default_texture_png),
};
int embed_count = 2;
These arrays allow the engine to locate embedded assets by path string without filesystem access.
Compilation and Runtime Access
From Source Bytes to Runtime Lookup
When compiling with Clang 19 or newer, the preprocessor handles the #embed directives before the compiler generates object code. The resulting binary contains the asset data in the .rodata section, mapped directly into memory at load time.
At runtime, ArmorPaint queries the embedding tables to resolve asset requests:
embedded_asset_t *get_embedded_asset(const char *name) {
for (int i = 0; i < embed_count; i++) {
if (strcmp(embed_keys[i], name) == 0) {
return &(embedded_asset_t){
.data = embed_values[i],
.size = embed_sizes[i]
};
}
}
// Fallback to filesystem when asset is not embedded
return load_from_disk(name);
}
If the requested asset exists in the embedded tables, the function returns a pointer to the binary-resident data. Otherwise, the engine falls back to standard file I/O, allowing the same codebase to function in both embedded and development modes.
Build Usage Examples
To generate a fully embedded binary, invoke the build script with the --embed flag:
# Native build with embedded assets
../base/make --embed
# WebAssembly build with embedded assets
../base/make --target wasm --compile --embed
The --embed flag works across all supported targets, including Windows, Linux, macOS, and WebAssembly builds. Note that enabling embedding increases binary size proportionally to the total size of included assets.
Summary
- The
--embedflag is parsed inpaint/project.jsand stored inglobalThis.flags.embed - Assets are filtered via the
noembedproperty before header generation base/tools/make.jsgeneratesbuild/embed.hcontaining C23#embeddirectives for each asset file- The generated header provides
embed_keys,embed_values,embed_sizes, andembed_countarrays for runtime lookup - Clang 19+ processes
#embeddirectives at compile time, baking file contents directly into the binary - Runtime code queries the embedding tables first, falling back to filesystem I/O only when necessary
Frequently Asked Questions
What compiler version is required to build with --embed?
ArmorPaint requires Clang 19 or newer to support the C23 #embed preprocessor directive. Older compilers will fail to parse the generated embed.h header file. GCC 15 and MSVC with /std:c23 also support this feature, though the Armory build pipeline specifically targets Clang for cross-platform consistency.
How do I exclude specific files from being embedded?
Pass noembed: true in the asset configuration within paint/project.js. Files marked with this property remain external dependencies and are loaded from the filesystem at runtime rather than being compiled into the binary. This is useful for large assets or content that changes frequently during development.
Does embedding assets affect runtime performance?
Embedding eliminates filesystem I/O overhead during asset loading, resulting in faster startup times and guaranteed availability of critical resources. However, the initial binary load time may increase slightly due to larger executable size. The embedded data resides in read-only memory pages that can be shared across process instances on most operating systems.
Can I use --embed with WebAssembly builds?
Yes, the --embed flag functions identically for WebAssembly targets. When compiling with --target wasm --compile --embed, the asset data is included in the compiled .wasm module. This creates a single distributable file without requiring external asset hosting or CORS configuration for web deployments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →