Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zig-0.12.0-dev, package manager and wasm32-emscripten support. #3

Merged
merged 11 commits into from
Jan 17, 2024
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
22 changes: 22 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: build

on: [push, pull_request]

jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v3
- uses: goto-bus-stop/setup-zig@v2
- name: prepare-linux
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
- name: build-native
run: zig build --summary all
- name: build-web
run: zig build --summary all -Dtarget=wasm32-emscripten
4 changes: 2 additions & 2 deletions IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,9 @@ of helper functions in testing code, [like here](https://github.com/floooh/kc85.

```zig
fn step(cpu: *CPU) usize {
var ticks = cpu.exec(0, .{ .func=tick, .userdata=0 });
var ticks = cpu.exec(1, .{ .func=tick, .userdata=0 });
while (!cpu.opdone()) {
ticks += cpu.exec(0, .{ .func=tick, .userdata=0 });
ticks += cpu.exec(1, .{ .func=tick, .userdata=0 });
}
return ticks;
}
Expand Down
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![build](https://github.com/floooh/kc85.zig/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/kc85.zig/actions/workflows/main.yml)

A simple KC85/2, /3 and /4 emulator for Windows, macOS and Linux, written in Zig. Uses the [sokol headers](https://github.com/floooh/sokol) for platform abstraction.

Read more about the KC85 computer series here:
Expand All @@ -10,7 +12,7 @@ Some [implementation notes](https://github.com/floooh/kc85.zig/blob/main/IMPLEME

## Build

With Zig version 0.11.0, on Windows, macOS or Linux:
With Zig the 0.12.0 dev version, on Windows, macOS or Linux:
```
zig build
```
Expand Down Expand Up @@ -40,7 +42,7 @@ Run ```zig build --help``` to see the remaining build targets.
```
zig build run-kc853 -- -slot8 m022 -file data/digger3.tap
```
![Digger Screenshot](screenshots/digger.png)
![Digger Screenshot](screenshots/digger.webp)

Press [Enter] to start a new game round, and [Esc] to continue
after you died. Use the arrows keys to navigate.
Expand All @@ -49,15 +51,15 @@ after you died. Use the arrows keys to navigate.
```
zig build run-kc853 -- -slot8 m022 -file data/jungle.kcc
```
![Jungle Screenshot](screenshots/jungle.png)
![Jungle Screenshot](screenshots/jungle.webp)

Navigate with arrow keys, jump with [Space].

## Run Pengo
```
zig build run-kc853 -- -file data/pengo.kcc
```
![Pengo Screenshot](screenshots/pengo.png)
![Pengo Screenshot](screenshots/pengo.webp)

Navigate with the arrow keys, continue running against
an ice block to push or destroy the block.
Expand All @@ -76,7 +78,21 @@ forth[Enter]
```
(the characters will appear uppercase, don't worry, that's normal)

![FORTH Screenshot](screenshots/forth.png)
![FORTH Screenshot](screenshots/forth.webp)

## Run in web browsers

Currently only running the 'plain' emulator is supported, it's not possible
to load files into the emulator (because command line parsing doesn't work
in browsers, and the Zig stdlib filesystem code doesn't work in
`wasm32-emscripten`):

```bash
zig build -Dtarget=wasm32-emscripten -Doptimize=ReleaseFast run-kc854
```

On the first build, the Emscripten SDK will be downloaded and installed into
the global Zig cache, this might take a couple of minutes.

## Misc Stuff

Expand Down
207 changes: 87 additions & 120 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,90 +1,109 @@
const std = @import("std");
const Builder = std.build.Builder;
const CompileStep = std.build.CompileStep;
const CrossTarget = std.zig.CrossTarget;
const Build = std.Build;
const OptimizeMode = std.builtin.OptimizeMode;
const sokol = @import("sokol");

const KC85Model = enum {
KC85_2,
KC85_3,
KC85_4,
};

pub fn build(b: *Builder) void {
pub fn build(b: *Build) !void {
const target = b.standardTargetOptions(.{});
const mode = b.standardOptimizeOption(.{});
const sokol = buildSokol(b, target, mode, "");
addKC85(b, sokol, target, mode, .KC85_2);
addKC85(b, sokol, target, mode, .KC85_3);
addKC85(b, sokol, target, mode, .KC85_4);
addZ80Test(b, target, mode);
addZ80ZEXDOC(b, target, mode);
addZ80ZEXALL(b, target, mode);
addTests(b);
const optimize = b.standardOptimizeOption(.{});
const dep_sokol = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
});
try addKC85(b, target, optimize, dep_sokol, .KC85_2);
try addKC85(b, target, optimize, dep_sokol, .KC85_3);
try addKC85(b, target, optimize, dep_sokol, .KC85_4);
// don't bother with making the tests run in wasm
if (!target.result.isWasm()) {
addZ80Test(b, target, optimize);
addZ80ZEXDOC(b, target, optimize);
addZ80ZEXALL(b, target, optimize);
addTests(b);
}
}

fn addKC85(b: *Builder, sokol: *CompileStep, target: CrossTarget, optimize: OptimizeMode, comptime kc85_model: KC85Model) void {
fn addKC85(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode, dep_sokol: *Build.Dependency, comptime kc85_model: KC85Model) !void {
const name = switch (kc85_model) {
.KC85_2 => "kc852",
.KC85_3 => "kc853",
.KC85_4 => "kc854",
};
const exe = b.addExecutable(.{
.name = name,
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "src/main.zig" },
});
const options = b.addOptions();
options.addOption(KC85Model, "kc85_model", kc85_model);

const mod_options = options.createModule();
const mod_sokol = b.createModule(.{
.source_file = .{ .path = "src/sokol/sokol.zig" },
});
const mod_host = b.createModule(.{
.source_file = .{ .path = "src/host/host.zig" },
.dependencies = &.{
.{ .name = "sokol", .module = mod_sokol },
},
});
const mod_emu = b.createModule(.{
.source_file = .{ .path = "src/emu/emu.zig" },
.dependencies = &.{
.{ .name = "build_options", .module = mod_options },
},
});
if (!target.result.isWasm()) {
// native build
const kc85 = b.addExecutable(.{
.name = name,
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "src/main.zig" },
});
kc85.root_module.addOptions("build_options", options);
kc85.root_module.addImport("sokol", dep_sokol.module("sokol"));

exe.addModule("build_options", mod_options);
exe.addModule("sokol", mod_sokol);
exe.addModule("host", mod_host);
exe.addModule("emu", mod_emu);
exe.linkLibrary(sokol);
b.installArtifact(exe);
const run = b.addRunArtifact(exe);
run.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run.addArgs(args);
b.installArtifact(kc85);
const run = b.addRunArtifact(kc85);
run.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run.addArgs(args);
}
b.step("run-" ++ name, "Run " ++ name).dependOn(&run.step);
} else {
// web build, compile the Zig code into a static library, and link with the Emscripten linker
const kc85 = b.addStaticLibrary(.{
.name = name,
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "src/main.zig" },
});
kc85.root_module.addOptions("build_options", options);
kc85.root_module.addImport("sokol", dep_sokol.module("sokol"));

const emsdk = dep_sokol.builder.dependency("emsdk", .{});
const link_step = try sokol.emLinkStep(b, .{
.lib_main = kc85,
.target = target,
.optimize = optimize,
.emsdk = emsdk,
.use_webgl2 = true,
.use_emmalloc = true,
.use_filesystem = false,
.shell_file_path = dep_sokol.path("src/sokol/web/shell.html").getPath(b),
// NOTE: This is required to make the Zig @returnAddress() builtin work,
// which is used heavily in the stdlib allocator code (not just
// the GeneralPurposeAllocator).
// The Emscripten runtime error message when the option is missing is:
// Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER
.extra_args = &.{"-sUSE_OFFSET_CONVERTER=1"},
});
const run = sokol.emRunStep(b, .{ .name = name, .emsdk = emsdk });
run.step.dependOn(&link_step.step);
b.step("run-" ++ name, "Run " ++ name).dependOn(&run.step);
}
b.step("run-" ++ name, "Run " ++ name).dependOn(&run.step);
}

fn addTests(b: *Builder) void {
const tests = b.addTest(.{
fn addTests(b: *Build) void {
const tests_exe = b.addTest(.{
.root_source_file = .{ .path = "src/tests.zig" },
});
const test_step = b.step("tests", "Run all tests");
test_step.dependOn(&tests.step);
const run = b.addRunArtifact(tests_exe);
b.step("tests", "Run all tests").dependOn(&run.step);
}

fn addZ80Test(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
fn addZ80Test(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "z80test",
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "tests/z80test.zig" },
.root_source_file = .{ .path = "src/z80test.zig" },
});
exe.addAnonymousModule("emu", .{ .source_file = .{ .path = "src/emu/emu.zig" } });
b.installArtifact(exe);
const run = b.addRunArtifact(exe);
run.step.dependOn(b.getInstallStep());
Expand All @@ -94,18 +113,18 @@ fn addZ80Test(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
b.step("z80test", "Run the Z80 CPU test").dependOn(&run.step);
}

fn addZ80ZEXDOC(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
fn addZ80ZEXDOC(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "z80zexdoc",
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "tests/z80zex.zig" },
.root_source_file = .{ .path = "src/z80zex.zig" },
});
const exe_options = b.addOptions();
exe.addOptions("build_options", exe_options);
exe_options.addOption(bool, "zexdoc", true);
exe_options.addOption(bool, "zexall", false);
exe.addAnonymousModule("emu", .{ .source_file = .{ .path = "src/emu/emu.zig" } });
const options = b.addOptions();
exe.root_module.addOptions("build_options", options);
options.addOption(bool, "zexdoc", true);
options.addOption(bool, "zexall", false);

b.installArtifact(exe);
const run = b.addRunArtifact(exe);
run.step.dependOn(b.getInstallStep());
Expand All @@ -115,18 +134,18 @@ fn addZ80ZEXDOC(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
b.step("z80zexdoc", "Run the Z80 ZEXDOC test").dependOn(&run.step);
}

fn addZ80ZEXALL(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
fn addZ80ZEXALL(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "z80zexall",
.target = target,
.optimize = optimize,
.root_source_file = .{ .path = "tests/z80zex.zig" },
.root_source_file = .{ .path = "src/z80zex.zig" },
});
const exe_options = b.addOptions();
exe.addOptions("build_options", exe_options);
exe_options.addOption(bool, "zexdoc", true);
exe_options.addOption(bool, "zexall", false);
exe.addAnonymousModule("emu", .{ .source_file = .{ .path = "src/emu/emu.zig" } });
const options = b.addOptions();
exe.root_module.addOptions("build_options", options);
options.addOption(bool, "zexdoc", true);
options.addOption(bool, "zexall", false);

b.installArtifact(exe);
const run = b.addRunArtifact(exe);
run.step.dependOn(b.getInstallStep());
Expand All @@ -135,55 +154,3 @@ fn addZ80ZEXALL(b: *Builder, target: CrossTarget, optimize: OptimizeMode) void {
}
b.step("z80zexall", "Run the Z80 ZEXALL test").dependOn(&run.step);
}

fn buildSokol(b: *Builder, target: CrossTarget, optimize: OptimizeMode, comptime prefix_path: []const u8) *CompileStep {
const lib = b.addStaticLibrary(.{
.name = "sokol",
.target = target,
.optimize = optimize,
});
lib.linkLibC();
const sokol_path = prefix_path ++ "src/sokol/c/";
const csources = [_][]const u8{
"sokol_app.c",
"sokol_gfx.c",
"sokol_time.c",
"sokol_audio.c",
"sokol_log.c",
};
if (lib.target.isDarwin()) {
inline for (csources) |csrc| {
lib.addCSourceFile(.{
.file = .{ .path = sokol_path ++ csrc },
.flags = &[_][]const u8{ "-ObjC", "-DIMPL" },
});
}
lib.linkFramework("MetalKit");
lib.linkFramework("Metal");
lib.linkFramework("Cocoa");
lib.linkFramework("QuartzCore");
lib.linkFramework("AudioToolbox");
} else {
inline for (csources) |csrc| {
lib.addCSourceFile(.{
.file = .{ .path = sokol_path ++ csrc },
.flags = &[_][]const u8{"-DIMPL"},
});
}
if (lib.target.isLinux()) {
lib.linkSystemLibrary("X11");
lib.linkSystemLibrary("Xi");
lib.linkSystemLibrary("Xcursor");
lib.linkSystemLibrary("GL");
lib.linkSystemLibrary("asound");
} else if (lib.target.isWindows()) {
lib.linkSystemLibrary("kernel32");
lib.linkSystemLibrary("user32");
lib.linkSystemLibrary("gdi32");
lib.linkSystemLibrary("ole32");
lib.linkSystemLibrary("d3d11");
lib.linkSystemLibrary("dxgi");
}
}
return lib;
}
20 changes: 20 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.{
.name = "kc85",
.version = "0.1.0",
.paths = .{
"data",
"screenshots",
"src",
"tests",
"build.zig",
"build.zig.zon",
"LICENSE",
"README.md",
},
.dependencies = .{
.sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#2edeb6f1633f17215c3d98eea40a2a4074bb5868",
.hash = "1220be380b30924e501c49532b6e3e73136098deb8064756923540da9106e184f8b5",
},
},
}
Binary file removed screenshots/digger.png
Binary file not shown.
Binary file added screenshots/digger.webp
Binary file not shown.
Binary file removed screenshots/forth.png
Binary file not shown.
Binary file added screenshots/forth.webp
Binary file not shown.
Binary file removed screenshots/jungle.png
Binary file not shown.
Binary file added screenshots/jungle.webp
Binary file not shown.
Binary file removed screenshots/pengo.png
Binary file not shown.
Binary file added screenshots/pengo.webp
Binary file not shown.
Loading