Skip to content

Examples

Four standalone programs live in examples/, each one file. Every one builds its own archive first, so any of them runs from a fresh clone with no setup:

Terminal window
zig build example-01 # Pack a directory and read a file back
zig build example-02 # Load assets without allocating per asset
zig build example-03 # Look assets up by compile-time handle
zig build example-04 # Stream an entry instead of buffering it
zig build examples # Build all four without running them

Everything they write goes to zig-out/examples/, which git ignores. They build as part of zig build test, so a stale example fails CI rather than rotting quietly.

The output below is what these actually print.

01_pack_and_read.zig

The round trip end to end: pack, Archive.open, iterating entries, readAlloc, and verify. It also re-reads the original file from disk and compares, so the round-trip claim is checked rather than asserted.

Terminal window
$ zig build example-01
packed 5 files, 13309 bytes -> 12565 bytes
archive holds 5 entries:
audio/blip.wav 2444 bytes deflate
data/noise.bin 4096 bytes store
jimmy.png 6138 bytes deflate
levels/level-01.json 398 bytes deflate
shaders/sprite.frag 233 bytes deflate
read jimmy.png: 6138 bytes
starts with PNG signature: true
identical to the file on disk
verified all 5 entries

Note data/noise.bin is store - it is incompressible, so pack fell back to writing it verbatim rather than growing it.

02_load_assets.zig

The shape a game actually wants: open the archive once at startup, allocate one buffer sized to your largest asset, then find and read into it forever. It also shows what a missing path and an undersized buffer do.

Terminal window
$ zig build example-02
jimmy.png 6138 bytes deflate PNG image
levels/level-01.json 398 bytes deflate JSON
shaders/sprite.frag 233 bytes deflate GLSL source
data/noise.bin 4096 bytes store binary
levels/level-99.json is not in this archive
reading jimmy.png into 16 bytes: BufferTooSmall

Nothing past the single gpa.alloc at startup touches the allocator. A missing path is null from find, not an error; an undersized buffer is error.BufferTooSmall from read.

03_asset_handles.zig

Turning a mistyped asset path from a runtime null into a compile error, using assetId and findId.

Terminal window
$ zig build example-03
handles baked into the binary:
0x40a8e93753577eb5 audio/blip.wav
0xbc5d025c01217e9b data/noise.bin
0xda77d2ecbff2fabb jimmy.png
0x3a43adffebb476d2 levels/level-01.json
0xc51bb341f35636a4 shaders/sprite.frag
loading by handle:
jimmy.png 6138 bytes
audio/blip.wav 2444 bytes
levels/level-01.json 398 bytes
archive.find("jimy.png") -> null, discovered at run time
load(&archive, .@"jimy.png") -> caught by the compiler

The last two lines are the whole point. The enum is written by hand here so the example stands alone; in a real project zpack ids generates it. See asset handles.

04_stream_entry.zig

A 1 MiB texture streamed through a 68 KiB buffer with entryReader, hashing as it goes since entryReader verifies nothing itself.

Terminal window
$ zig build example-04
asset 1048576 bytes (116281 on disk, deflate)
buffer 69632 bytes is 15x smaller than the asset
streamed 1048576 bytes in 32 chunks
largest chunk held at once: 65536 bytes
hash matches the one recorded at pack time
peak memory for the contents: 69632 bytes, not 1048576
entryReader with a 1 KiB buffer: BufferTooSmall
stored entry through that same 1 KiB buffer: 4096 bytes
archive.verify() runs the same pass over every entry

The last two lines show the asymmetry: a deflated entry needs at least stream_buffer_len for its history window, but a stored entry accepts any buffer, even one far smaller than the entry.

The example generates its texture at runtime rather than checking a large binary into the repository.

examples/example-assets/ is small and chosen to cover the cases that behave differently, not to be realistic:

File Why it is there
jimmy.png A real image, at the top level rather than in a subdirectory
audio/blip.wav A real container format, one directory deep
levels/level-01.json Text, so it compresses hard - 398 bytes down to 144
shaders/sprite.frag More text, and a second entry sharing no parent with the first
data/noise.bin Incompressible, so pack falls back to storing it verbatim

That last one is the point of the set: an archive built from this tree contains both deflate and store entries, so every example exercises both paths without having to explain the difference twice.