Chat about this codebase

AI-powered code exploration

Online

Project Overview

Mach is a Zig-based game engine and graphics toolkit designed for high-performance, cross-platform development of games, visualizations, and GUI applications. Its modular architecture lets you pick and integrate only the subsystems you need, reducing bloat and easing maintenance.

Why Mach Exists

• Streamline cross-platform graphics and window management in Zig
• Provide a unified toolkit for rendering, input, asset loading, and GUI
• Leverage Zig’s performance and safety features for real-time applications

Problems It Solves

• Boilerplate reduction for Vulkan, Metal, DirectX setup
• Consistent input handling across Windows, macOS, Linux
• Flexible asset pipeline for textures, models, shaders
• Integrated GUI support alongside core rendering

Key Features

• Modular design: import only the drivers and backends you use
• Low-level graphics bindings: direct access to Vulkan/Metal APIs
• Windowing & context management: single API for all platforms
• Input system: keyboard, mouse, gamepad with event polling
• Asset loading: images, 3D models, shader compilation
• Immediate-mode GUI: built-in widgets and layout tools
• Extensible middleware: audio, physics, networking (community plugins)

Licensing

• Code is dual-licensed under Apache 2.0 or MIT (your choice)
– Retain the appropriate header in each source file
– Include LICENSE-APACHE and/or LICENSE-MIT in your distribution root
• Documentation, examples, and media are CC-BY-4.0
– Add a short credit notice when reusing docs or assets
• For directory-specific terms, check nested LICENSE files

Refer to LICENSE, LICENSE-APACHE, and LICENSE-MIT in the repo root for full legal terms.

Getting Started

This guide takes you from zero to a running red triangle in minutes. You’ll install Zig, clone the Mach repo, build the core-triangle example, and run it.

1. Install Zig (Pinned Version)

Mach pins its Zig compiler in .zigversion. Install that exact version to avoid compatibility issues.

Contents of .zigversion

0.14.0-dev.2577+271452d22

Using zigup (recommended):

# Install zigup (if not already)
curl -sSf https://zigup.dev/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

# Install and activate the pinned version
zigup install $(cat .zigversion)
zigup run -- zig version   # verify matches .zigversion

Or download a matching build from ziglang.org and ensure zig version outputs the same string.

2. Clone Mach

git clone https://github.com/hexops/mach.git
cd mach

The first build will automatically fetch Mach’s external dependencies defined in build.zig.zon.

3. Build the Core-Triangle Example

Core-triangle uses Mach’s WebGPU support (sysgpu) and the example build path in build.zig.

# Build core-triangle (with sysgpu enabled)
zig build -Dsysgpu=true -Dexamples=core-triangle

On success you’ll see a build artifact under zig-out/bin/core-triangle.

4. Run the Triangle

# Run the built example
zig build run -Dsysgpu=true -Dexamples=core-triangle

A window should appear displaying a solid red triangle.

Troubleshooting

  • If you see missing libraries on Linux, install Vulkan and X11 development packages.
  • On macOS ensure metal and coregraphics frameworks are available (installed by default).
  • On Windows you may need the Windows 10+ SDK for DX12 support.

What Just Happened

  • build.zig read your .zigversion Zig compiler.
  • It fetched only the mach.core and mach.sysgpu modules needed by core-triangle.
  • It compiled the WGSL shaders at compile time (@embedFile("examples/core-triangle/shader.wgsl")).
  • You ran the example via the auto-generated Zig build commands.

Now you have a working Mach project and a foundation for exploring deeper APIs like audio, input, and multiple windows.

Core Concepts

Mach organizes engine functionality into modular subsystems—Core (window/input), Graphics, Audio, Math, Modules/Objects, Timing, and System wrappers (sysgpu, sysaudio). Every application follows a consistent init → tick → shutdown pattern, leveraging high-priority audio and GPU threads for real-time performance.

1. Initialization Sequence

Initialize Core first, then optional subsystems. mach.Core.init sets up window, GPU device, input, and event queue. After Core, initialize graphics or audio as needed:

const std   = @import("std");
const mach  = @import("mach");
pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // 1. Initialize core (window, GPU, input)
    var core = try mach.Core.init(allocator, .{
        .title = "My Mach App",
        .width = 1280,
        .height = 720,
        .vsync = true,
    });

    // 2. Initialize graphics (optional)
    const gfx = try mach.gfx.main.init(allocator, &core);

    // 3. Initialize audio (optional)
    var audio = try mach.Audio.init(allocator, .{ .sample_rate = 48_000, .channels = 2 });

    // 4. Enter main loop
    const exit_code = runLoop(&core, &gfx, &audio) catch |err| {
        std.debug.print("Error: {any}\n", .{err});
        1
    };

    // 5. Shutdown subsystems in reverse order
    audio.deinit();
    gfx.deinit();
    core.deinit();
    return exit_code;
}

2. Main Loop and Tick Functions

Each frame, process input/events, update logic, submit graphics and audio:

fn runLoop(core: *mach.Core, gfx: *mach.gfx.main.Context, audio: *mach.Audio.Context) !u8 {
    while (core.tick()) {
        // -- Input & Events already handled by core.tick()

        // -- Game update
        updateGame(core);

        // -- Graphics: record and submit draw commands
        try gfx.beginFrame();
        renderScene(gfx);
        gfx.endFrame();

        // -- Audio: mix & submit
        try audio.tick();
    }
    return 0;
}

3. Graphics Abstraction (sysgpu + mach.gfx)

Mach uses sysgpu for cross-API GPU resources and mach.gfx for sprites/text:

// Create a GPU buffer via sysgpu
var buffer = try sysgpu.Buffer.create(&core.device, .{
    .size = 1024 * @sizeOf(f32),
    .usage = .VERTEX,
});
// Map, write data, then unmap:
const data = try buffer.mapWrite();
for (data) |*elem, i| elem.* = f32(i) * 0.5;
buffer.unmap();

// Submit buffer in a render pass
const pass = try core.device.beginRenderPass(.{/* attachments */});
pass.setVertexBuffer(0, buffer.handle, 0, buffer.size);
pass.draw(3, 1, 0, 0);
pass.end();
core.device.submit();

For batched sprites and text, see mach.gfx.sprite and mach.gfx.text pipelines.

4. Audio System (mixSamples & sysaudio)

Mach’s audio mixer accumulates multiple sources into a render buffer, then submits via sysaudio:

// In initialization:
var audio = try mach.Audio.init(allocator, .{ .sample_rate=48_000, .channels=2 });

// Per‐frame in audio.tick():
pub fn tick(self: *Context) !void {
    // Zero mixing buffer to avoid carry-over
    @memset(self.mix_buffer.items, 0);

    // Mix each active source
    for (self.buffers.items) |*buf| {
        if (!buf.playing) continue;
        buf.index = mach.Audio.mixSamples(
            self.mix_buffer.items,
            @intCast(u8, self.channels),
            buf.samples,
            buf.index,
            buf.channels,
            buf.volume,
        );
        if (buf.index >= buf.samples.len) buf.playing = false;
    }

    // Convert f32 → device format and submit
    try sysaudio.Stream.write(self.stream, self.mix_buffer.items);
}

5. Math Conventions

All matrices are column-major, left-handed, +Y up. Use Mat4x4 and Vec types:

const math = @import("mach/math");
const view = math.Mat4x4.translate(math.vec3(0, 0, -10));
const proj = math.Mat4x4.projection2D(.{
    .left=-8, .right=8,
    .bottom=-4.5, .top=4.5,
    .near=0.1, .far=100,
});
const mvp  = view.mul(&proj);

6. Modules & Object Management

Use Objects and Modules generics from src/module.zig to register subsystems and track typed entities:

const ObjMgr = module.Objects(u64, MyObjectData);
var objects: ObjMgr = ObjMgr.init(allocator);

// Create an object with custom data
const id = try objects.create(.{ .position = .{0, 0}, .velocity = .{1, 1} });

// Query or delete
if (objects.exists(id)) {
    var data = objects.get(id);
    data.position += data.velocity;
}
objects.delete(id);

7. Timing Utilities

Use Timer and Frequency from src/time/main.zig for precise timing:

var timer = mach.time.Timer.init(); // high-precision clock

// Fixed timestep loop example
const dt = mach.time.Frequency.seconds(1) / 60; // nanoseconds per 1/60s
var accumulated: u64 = 0;

while (core.tick()) {
    const frame_ns = timer.elapsed();
    timer.reset();
    accumulated += frame_ns;

    while (accumulated >= dt) {
        fixedUpdate();
        accumulated -= dt;
    }
    render();
}

These core concepts form the foundation for any Mach‐based application, ensuring consistent initialization, update, rendering, and shutdown across platforms.

Examples & Tutorials

This section delivers step-by-step guides for common tasks in the hexops/mach repository. To get started, please pick one of the topics below or describe another area you’d like covered, along with any relevant file summaries or context:

Common Topics

  • Config file loader utility (config/loader.go)
  • API client initialization (client/client.go)
  • Error-handling middleware (http/middleware/errors.go)
  • Database migration script usage (db/migrations/…)
  • Writing a new Mach task (tasks/your_task.go)

What We Need from You

  1. The specific feature or file you want documented
  2. A brief summary of its purpose or key methods
  3. Any example inputs/outputs or usage scenarios

With that information, we’ll produce a focused, example-driven tutorial tailored to hexops/mach.

Architecture Deep Dive

This section dives into low-level mechanics of Mach’s backends and subsystems. It helps you build new platforms or debug initialization, memory management, and audio streaming.


Linux Backend Initialization and Fallback

Describe how Mach chooses between X11 and Wayland for window creation, handles errors, and falls back when needed.

Overview

  • Entry point: Linux.initWindow(core, window_id) in src/core/Linux.zig.
  • Reads MACH_BACKEND (case-insensitive “x11” or “wayland”), defaults to Wayland.
  • On failure, logs the error and attempts the other backend.
  • Reports missing shared libraries if both fail.
  • After success, calls warnAboutIncompleteFeatures().

Core Logic

pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
    // 1) Determine backend
    const desired: BackendEnum = blk: {
        const env = std.process.getEnvVarOwned(core.allocator, "MACH_BACKEND") catch break :blk .wayland;
        defer core.allocator.free(env);
        if (std.ascii.eqlIgnoreCase(env, "x11")) break :blk .x11;
        if (std.ascii.eqlIgnoreCase(env, "wayland")) break :blk .wayland;
        std.debug.panic("mach: unknown MACH_BACKEND: {s}", .{env});
    };

    // 2) Try desired, then fallback
    switch (desired) {
        .x11 => {
            X11.initWindow(core, window_id) catch |err| {
                log.err("X11 init failed: {s}\nFalling back to Wayland\n", .{errMsg(err)});
                Wayland.initWindow(core, window_id) catch |e| fallbackFail(x11Missing(), e);
            };
        },
        .wayland => {
            Wayland.initWindow(core, window_id) catch |err| {
                log.err("Wayland init failed: {s}\nFalling back to X11\n", .{errMsg(err)});
                X11.initWindow(core, window_id) catch |e| fallbackFail(waylandMissing(), e);
            };
        },
    }

    // 3) Warn if some features remain unimplemented
    try warnAboutIncompleteFeatures(desired, &MISSING_FEATURES_X11, &MISSING_FEATURES_WAYLAND, core.allocator);
}

Supporting Functions

fn errMsg(err: anyerror) []const u8 {
    return switch (err) {
        error.LibraryNotFound => "Missing library",
        error.FailedToConnectToDisplay => "Failed to connect",
        error.NoServerSideDecorationSupport => "No decoration support",
        else => "Unknown error",
    };
}

fn x11Missing() []const []const u8 { return &MISSING_FEATURES_X11; }
fn waylandMissing() []const []const u8 { return &MISSING_FEATURES_WAYLAND; }

fn fallbackFail(missing_libs: []const []const u8, last_err: anyerror) !void {
    var list = std.ArrayList(u8).init(core.allocator);
    defer list.deinit();
    for (missing_libs) |lib| try list.appendSlice("\t* " ++ lib ++ "\n");
    log.err("Both backends failed. Missing:\n{s}", .{list.items});
    return last_err;
}

Practical Tips

  • Force backend: export MACH_BACKEND=x11 or wayland.
  • Watch stderr for “Falling back…” messages.
  • Features like resizing, VSync, cursor may show one-time warnings.

GPU Memory Allocation (D3D12 MemoryAllocator)

Mach groups D3D12 heap sub-allocations to reduce fragmentation and heap churn.

Core Types

  • MemoryAllocator: top-level pool, holds multiple MemoryGroups.
  • MemoryGroup: categories by resource type (buffer, RTV/DSV texture, other texture) and memory location (gpu_only, cpu_to_gpu, gpu_to_cpu).
  • MemoryHeap: wraps one ID3D12Heap and a gpu_allocator.Allocator for sub-allocations.
  • AllocationCreateDescriptor: hints (location, size, alignment, resource_category).
  • ResourceCreateDescriptor: describes ID3D12_RESOURCE_DESC for placed resources.

Initialization

In your Device.init:

// After creating d3d12_device
try device.mem_allocator.init(device);
try device.mem_allocator_textures.init(device);

This queries D3D12_OPTIONS.ResourceHeapTier and sets up groups.

High-Level Buffer Allocation

// Creates a placed buffer resource
const resource = try device.createD3dBuffer(usageFlags, size);
// resource.d3d_resource: ID3D12Resource*
// resource.allocation: heap + offset

Under the hood:

const alloc_desc = AllocationCreateDescriptor{
    .location = .gpu_only,
    .size = conv.d3d12ResourceSizeForBuffer(size, usageFlags),
    .alignment = info.Alignment, // from GetResourceAllocationInfo
    .resource_category = .buffer,
};
const allocation = try device.mem_allocator.allocate(&alloc_desc);
defer allocation.alloc.free(); // or via Resource.deinit()
device.d3d_device.CreatePlacedResource(
    allocation.heap.heap,
    allocation.offset,
    &buffer_desc,
    initialState,
    null,
    uuidof(ID3D12Resource),
    &resource_ptr,
);

Creating Textures or Other Resources

var desc: c.D3D12_RESOURCE_DESC = /* populate */;
const create_desc = ResourceCreateDescriptor{
    .location = .gpu_only,
    .resource_category = .other_texture,
    .resource_desc = &desc,
    .clear_value = null,
    .initial_state = D3D12_RESOURCE_STATE_COMMON,
};
const tex = try device.mem_allocator.createResource(&create_desc);
// tex.allocation holds heap+offset for lifetime management

Cleanup and Diagnostics

  • Resource.deinit() frees sub-allocation and releases the D3D12 resource.
  • Empty heaps retire automatically when all allocations free.
  • After shutdown:
device.mem_allocator.reportMemoryLeaks();
device.mem_allocator_textures.reportMemoryLeaks();

ALSA Playback Stream Creation and Control

Shows how Mach’s ALSA backend enumerates devices, creates a playback stream, and controls playback and volume.

1. Initialize Context & Refresh Devices

const alsa = @import("sysaudio/alsa.zig").Context;
var ctx = try alsa.init(std.heap.page_allocator, .{
    .app_name = "MyApp",
    .deviceChangeFn = null,
    .user_data = null,
});
try ctx.alsa.refresh(&ctx.alsa);
const devices = ctx.alsa.devices(ctx.alsa);

2. Select Default Playback Device

const defaultDev = ctx.alsa.defaultDevice(ctx.alsa, .playback)
    orelse return error.NoPlaybackDevice;

3. Create the Player

const writeFn: main.WriteFn = myWriteCallback;
const opts: main.StreamOptions = .{
    .format = null,
    .sample_rate = null,
    .media_role = .default,
    .user_data = null,
};
var player = try ctx.alsa.createPlayer(&ctx.alsa, defaultDev, writeFn, opts);

4. Start and Control Playback

// Launch write thread
try player.alsa.start();

// Play / Pause
try player.alsa.play();
try player.alsa.pause();
const paused = player.alsa.paused();

// Volume: [0.0 .. 1.0]
try player.alsa.setVolume(player.alsa, 0.75);
const vol = try player.alsa.volume(player.alsa);

5. Cleanup

player.alsa.deinit();
ctx.alsa.deinit(&ctx.alsa);

Notes

  • refresh() auto-detects channel maps and formats.
  • Use deviceChangeFn to handle hot-plug events on /dev/snd.
  • Override default latency via snd_pcm_set_params only when necessary.

API Reference

This section groups all public-facing types, functions, constants, and shader DSL constructs in the mach library. Each subsection corresponds to a logical module or feature area.

Audio Module (src/Audio.zig)

mixSamples(dst: []align(al) f32, dst_channels: u8, src: []align(al) const f32, src_index: usize, src_channels: u8, src_volume: f32) usize
SIMD-accelerated mixing with per-buffer gain and channel conversion.
linearToDecibel(linear: f32) f32
Convert linear amplitude to decibels.
decibelToLinear(db: f32) f32
Convert decibel value back to linear amplitude.
pub const AudioFormat
Supported sample formats (e.g., f32, i16).
pub const DeviceConfig
Configuration for opening an audio device.
pub const StreamConfig
Sample rate, channels, and buffer size.

Sysaudio Backends (src/sysaudio/*.zig)

init(cfg: DeviceConfig) !void
Initialize the system-audio backend.
start() !void / stop() !void
Control audio streaming.
convertTo(dst: []align(al) u8, src: []const f32, format: AudioFormat) void
Convert float-32 buffer to hardware format.

Math Module (src/math/*.zig)

Types
pub const Vec2, Vec3, Vec4
pub const Mat3, Mat4
pub const Quaternion
Functions
dot(a: VecN, b: VecN) T
cross(a: Vec3, b: Vec3) Vec3
length(v: VecN) T / normalize(v: VecN) VecN
translate(m: Mat4, v: Vec3) Mat4 / rotate(m: Mat4, angle: T, axis: Vec3) Mat4
perspective(fov: T, aspect: T, near: T, far: T) Mat4
lookAt(eye: Vec3, center: Vec3, up: Vec3) Mat4

Shader DSL (src/Shader.zig)

Macros & Functions
pub fn shader(comptime stage: ShaderStage, src: []const u8) ShaderModule
@vertex(fn: anytype) anytype / @fragment(fn: anytype) anytype
uniform(name: []const u8, T: type) var
varying(name: []const u8, T: type) var
@encode(value: anytype, fmt: TextureFormat) anytype
@decode(value: anytype, fmt: TextureFormat) anytype
Types & Enums
pub const ShaderStage = enum { Vertex, Fragment }
pub const TextureFormat

Testing Helpers (src/testing.zig)

pub fn expect(comptime T: type, expected: T) Expect(T)
pub const ExpectFloat, ExpectVector, ExpectBytes, ExpectComptime
• Methods on Expect(T):
.eql(actual: T) !void
.eqlApprox(actual: T, tol: U) !void
.eqlBinary(actual: T) !void

Utilities (src/util/*.zig)

alignDown(addr: usize, a: usize) usize / alignUp(addr: usize, a: usize) usize
clamp(val: T, min: T, max: T) T
lerp(a: T, b: T, t: T) T
mixChannels(dst: []align(al) f32, src: []align(al) f32, channels: u8) void

Constants

pub const DefaultSampleRate = 48_000
pub const DefaultBufferSize = 512
pub const MaxChannels = 8
pub const simdVectorLength = @import("std").simd.suggestVectorLength(f32)

Each item above appears in generated docs via zig doc. Use these groupings to navigate and extend the mach API.

Development & Contribution Guide

Get Mach running locally, customize its build, and add or verify features.

Building Mach from Source

Mach uses a Zig-based build script (build.zig) with fine-grained flags. By default, zig build compiles everything. Use flags to speed up CI or local iteration.

Available flags

  • --examples – build/install example apps under examples/…
  • --libs – build/install static libraries only (e.g. mach-sysgpu)
  • --mach – top-level mach module and its lazy deps (freetype, opus, font-assets)
  • --core – core windowing/backend (@import("mach").core)
  • --sysaudio – audio backend + its tests (tests/sysaudio/{record,zine}.zig)
  • --sysgpu – GPU backend + static lib target

Common commands

# Full build (default)
$ zig build

# Core engine + GPU only
$ zig build --core --sysgpu

# Examples only
$ zig build --examples

# Static libs only
$ zig build --libs

# View help & all flags
$ zig build --help

Under the hood, each flag sets a want_* boolean:

const build_all    = !any_flag;
const want_mach    = build_all or flag_mach;
const want_core    = build_all or want_mach or flag_core;
const want_sysaudio= build_all or want_mach or flag_sysaudio;
const want_sysgpu  = build_all or want_mach or want_core or flag_sysgpu;

if (want_core)     linkCore(b, mach_module);
if (want_sysaudio) linkSysaudio(b, mach_module);
if (want_sysgpu)   linkSysgpu(b, mach_module);
if (flag_examples) buildExamples(b);
if (flag_libs)     buildStaticLibs(b);

Practical notes

  • Lazy-fetches only needed dependencies (freetype, Xcode frameworks, etc.)
  • Zig’s comptime and dead-code elimination keep your binary lean
  • Combine flags to tailor CI, packaging, or local iteration

Running and Writing Tests

Mach provides src/testing.zig for flexible, readable assertions in Zig tests. Import its API in any *.zig under tests/….

Basic Usage

const std     = @import("std");
const testing = @import("src/testing.zig");

// A simple test harness
test "vector addition matches expected" {
    const a = .{1.0, 2.0, 3.0};
    const b = .{4.0, 5.0, 6.0};
    const result = addVectors(a, b);
    try testing.expect(std.testing, testing.ExpectVec3(result), .{10e-6}, .{5.0, 7.0, 9.0});
}

Key APIs

expect(h: std.testing.Test, E: ExpectT, epsilon: T, expected: T)
Generic: compares floats, vectors, matrices with approximate equality.
ExpectBytes(actual: []const u8)
Exact binary or string comparisons.
ExpectFloat(actual: f64)
Custom epsilon per comparison.

Advanced: Custom Epsilon

test "matrix inversion close enough" {
    const actual = invertMatrix(someMatrix);
    // tighter epsilon for sensitive data
    try testing.expect(std.testing, testing.ExpectMat4(actual), .0001, expectedMatrix);
}

Adding New Tests

  1. Create tests/your_feature_test.zig.
  2. const testing = @import("src/testing.zig");
  3. Write test "description" { … try testing.expect(…) }.
  4. Run all tests:
    $ zig test src/testing.zig tests/your_feature_test.zig -- …flags…

Extending the Build Script

To add a custom component:

  1. In build.zig, define a new flag:
    const flag_myfeature = b.standardTargetOptions.*.?; // inline flag parse
    
  2. Derive a want_myfeature boolean alongside existing want_*.
  3. Wrap your link or build step:
    if (want_myfeature) linkMyFeature(b, mach_module);
    
  4. Add registration under b.installStep if it produces artifacts.

Local Iteration Workflow

# Build core + run fast subset of tests
$ zig build --core && zig test src/testing.zig tests/my_core_test.zig

# After adding a new example
$ zig build --examples && examples/your_example

By following these guidelines, you can efficiently build, test, and extend Mach from source.

Advanced Usage & Extensibility

Extend and optimise your application with custom render pipelines, embed the Mach IPC module, package across platforms, toggle GameMode on Linux, and profile frame rates.


Custom Render Pipelines

Leverage src/sysgpu/utils.zig to build and manage GPU pipeline layouts and formats.

Setting Up a Pipeline Layout

const std = @import("std");
const gpu = @import("src/sysgpu/utils.zig");

pub fn createStandardPipeline(allocator: *std.mem.Allocator) !gpu.PipelineLayout {
    // Define a bind group for a uniform buffer and a sampled texture
    const bindGroups = [_]gpu.BindGroupLayoutDescriptor{
        .{
            .bindings = &[_]gpu.BindGroupLayoutBinding{
                // Binding 0: uniform buffer
                .{ .binding = 0, .visibility = .VERTEX, .ty = gpu.BindingType.UniformBuffer, .count = 1 },
                // Binding 1: sampled texture
                .{ .binding = 1, .visibility = .FRAGMENT, .ty = gpu.BindingType.SampledTexture, .count = 1 },
            },
        },
    };

    const layoutDesc = gpu.DefaultPipelineLayoutDescriptor{
        .bindGroupLayouts = &bindGroups,
    };

    var manager = gpu.Manager.init(allocator);
    return manager.createPipelineLayout(layoutDesc);
}

Key utilities:

  • alignUp(value, alignment) to pad buffer sizes.
  • findChained for traversing Vulkan-like pNext chains.
  • Format enums in gpu.FormatType to map native texture formats.

Embedding Mach

Use the built-in Mach module (src/mach.zig) to interact with Mach IPC objects and ports directly from Zig.

const std = @import("std");
const mach = @import("src/mach.zig");

pub fn demoMach() void {
    // Initialize the Mach object store
    var store = mach.Objects.init();

    // Create a Mach port object
    const port = store.create(.port, null) catch |err| {
        std.debug.print("Failed to create Mach port: {}\n", .{err});
        return;
    };

    // Thread-safe configuration
    mach.lock(&port.mutex);
    port.configure(.dontRoute);
    mach.unlock(&port.mutex);

    // Send a simple message
    const msg = mach.Message.init(port, "hello");
    port.send(msg) catch |err| {
        std.debug.print("Send error: {}\n", .{err});
    };

    // Clean up
    store.delete(port);
}

Highlights:

  • mach.Objects handles creation, lookup, and deletion.
  • mach.lock / mach.unlock ensure thread safety.
  • Message and port abstractions wrap raw Mach APIs.

Packaging Across Operating Systems

Produce portable binaries and handle optional dependencies.

Cross-Compiling with Zig

# Linux GNU
zig build -Dtarget=x86_64-linux-gnu

# macOS Darwin
zig build -Dtarget=x86_64-macos-gnu

# Windows MSI
zig build -Dtarget=x86_64-windows-gnu

Tips:

  • On Linux, dynamic libgamemode.so loads at runtime; no link-time dependency on non-Linux.
  • Bundle assets and shader binaries into your release directory for each platform.
  • Use Zig’s –strip and –release-small flags to reduce final binary size.

Linux GameMode Toggling

Control system-wide performance mode via src/gamemode.zig. On unsupported OSes calls become no-ops.

const gamemode = @import("src/gamemode.zig");

pub fn runLoop() void {
    gamemode.start();      // hint: call before heavy work
    defer gamemode.stop(); // defer ensures we leave GameMode

    while (shouldContinue()) {
        // Query and adapt if GameMode isn't active
        if (!gamemode.isActive()) {
            // e.g., reduce thread count or lower quality
        }
        renderFrame();
    }
}

Under the hood:

  • .init() dynamically loads libgamemode on Linux.
  • Fallback implementation on other platforms returns immediately.
  • Errors log via your Zig logger; no panics.

Performance Profiling with Frequency

Regulate and measure your frame rate using src/time/Frequency.zig.

const std = @import("std");
const freq = @import("src/time/Frequency.zig");

pub fn main() !void {
    var allocator = std.heap.page_allocator;
    var tracker = freq.Frequency.init(60.0); // target 60 FPS

    while (shouldRun()) {
        tracker.tick();                         // mark frame start
        updateSimulation();
        renderFrame();

        const sleepNs = tracker.delay();        // compute delay to maintain rate
        if (sleepNs > 0) std.time.sleepNanoseconds(sleepNs) catch {};
        std.debug.print("FPS: {d}\n", .{tracker.actualRate()});
    }
}
  • Call tick() once per iteration.
  • delay() returns the nanoseconds to sleep to hit your target.
  • actualRate() provides a running measurement of achieved frequency.