Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
strategy:
fail-fast: false
# fiber only supports x86_64 (System V ABI and Windows). GitHub's macOS
# runners are ARM64 and the context switch is x86_64-only, so macOS is
# intentionally omitted from the matrix.
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: Build
run: zig build

- name: Test
run: zig build test
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `examples/basic.zig` and `examples/scheduler.zig`, runnable via
`zig build examples`.
- GitHub Actions CI building and testing on Linux and Windows (x86_64).

### Removed

- `src/main.zig`; the demo now lives under `examples/`.

## [0.1.0] - 2026-07-22

Initial release.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# fiber

[![CI](https://github.com/itsakeyfut/fiber/actions/workflows/ci.yml/badge.svg)](https://github.com/itsakeyfut/fiber/actions/workflows/ci.yml)
[![Zig](https://img.shields.io/badge/zig-0.16.0-orange.svg)](https://ziglang.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Expand Down Expand Up @@ -78,10 +79,11 @@ pub fn main() !void {
}
```

A runnable version lives in [`src/main.zig`](src/main.zig):
Runnable, expanded versions live in [`examples/`](examples/) — a single-fiber
walkthrough and a small cooperative scheduler over several fibers:

```sh
zig build run
zig build examples
```

## API
Expand Down
37 changes: 22 additions & 15 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,30 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});

const exe = b.addExecutable(.{
.name = "fiber-demo",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "fiber", .module = fiber_mod },
},
}),
});
b.installArtifact(exe);
// Examples: `zig build examples` builds and runs each program in examples/.
const example_step = b.step("examples", "Build and run the examples");
const examples = [_][]const u8{ "basic", "scheduler" };
for (examples) |name| {
const exe = b.addExecutable(.{
.name = name,
.root_module = b.createModule(.{
.root_source_file = b.path(b.fmt("examples/{s}.zig", .{name})),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "fiber", .module = fiber_mod },
},
}),
});
b.installArtifact(exe);

const run_exe = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the demo");
run_step.dependOn(&run_exe.step);
const run_example = b.addRunArtifact(exe);
run_example.step.dependOn(b.getInstallStep());
if (b.args) |args| run_example.addArgs(args);
example_step.dependOn(&run_example.step);
}

// Tests: `zig build test`.
const tests = b.addTest(.{ .root_module = fiber_mod });
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run unit tests");
Expand Down
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"build.zig",
"build.zig.zon",
"src",
"examples",
"LICENSE",
"README.md",
"CHANGELOG.md",
Expand Down
37 changes: 37 additions & 0 deletions examples/basic.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Basic fiber usage.
//!
//! Create a single fiber, resume it repeatedly, and watch how it suspends at
//! each `yield` and continues from exactly where it left off the next time it
//! is resumed. Run with `zig build examples`.

const std = @import("std");
const fiber = @import("fiber");
const Fiber = fiber.Fiber;

fn counter(_: *Fiber) void {
var i: usize = 0;
while (i < 3) : (i += 1) {
std.debug.print(" fiber: tick {d}\n", .{i});
Fiber.yield(); // freeze here; control returns to the caller
}
std.debug.print(" fiber: finished\n", .{});
}

pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;

const f = try Fiber.create(gpa, &counter);
defer f.destroy();

std.debug.print("main: created fiber (state={s})\n", .{@tagName(f.state)});

var resumes: usize = 0;
while (f.state != .done) {
resumes += 1;
std.debug.print("main: resume #{d}\n", .{resumes});
f.resumeFiber();
std.debug.print("main: back in main (state={s})\n", .{@tagName(f.state)});
}

std.debug.print("main: fiber done after {d} resumes\n", .{resumes});
}
65 changes: 65 additions & 0 deletions examples/scheduler.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! A tiny cooperative scheduler.
//!
//! Several fibers share a single OS thread. Each runs until it voluntarily
//! yields, and the scheduler round-robins over them until every fiber finishes.
//! Their output interleaves — this is the core of a cooperative task system,
//! the kind of thing a game engine drives once per frame. Run with
//! `zig build examples`.

const std = @import("std");
const fiber = @import("fiber");
const Fiber = fiber.Fiber;

/// Build a worker entry point that logs `steps` progress lines under `name`,
/// yielding after each one. Each fiber needs its own entry function, so we
/// generate one per task at comptime.
fn makeWorker(comptime name: []const u8, comptime steps: usize) *const fn (*Fiber) void {
return &struct {
fn run(_: *Fiber) void {
var i: usize = 0;
while (i < steps) : (i += 1) {
std.debug.print(" [{s}] step {d}/{d}\n", .{ name, i + 1, steps });
Fiber.yield();
}
std.debug.print(" [{s}] finished\n", .{name});
}
}.run;
}

pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;

const entries = [_]*const fn (*Fiber) void{
makeWorker("A", 3),
makeWorker("B", 5),
makeWorker("C", 2),
};

var fibers: [entries.len]*Fiber = undefined;
var created: usize = 0;
errdefer for (fibers[0..created]) |f| f.destroy();
for (&fibers, entries) |*slot, entry| {
slot.* = try Fiber.create(gpa, entry);
created += 1;
}
defer for (fibers) |f| f.destroy();

std.debug.print("scheduler: running {d} fibers\n", .{fibers.len});

// Round-robin: each round gives every still-running fiber one turn, until
// they have all reached `.done`.
var round: usize = 0;
var remaining: usize = fibers.len;
while (remaining > 0) {
round += 1;
std.debug.print("-- round {d} --\n", .{round});
remaining = 0;
for (fibers) |f| {
if (f.state == .done) continue;
f.resumeFiber();
if (f.state != .done) remaining += 1;
}
}

std.debug.print("scheduler: all fibers finished in {d} rounds\n", .{round});
}
26 changes: 0 additions & 26 deletions src/main.zig

This file was deleted.

Loading