diff --git a/build.zig b/build.zig index d0572a0..2d76bd6 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Translator = @import("translate_c").Translator; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -10,13 +11,25 @@ pub fn build(b: *std.Build) void { // in this directory. const llvm = b.option(bool, "llvm", "Build with llvm"); + const translate_c = b.dependency("translate_c", .{}); + + const t: Translator = .init(translate_c, .{ + .c_source_file = b.path("src/c.h"), + .target = target, + .optimize = optimize, + .default_init = false, + .warnings = .@"error", + }); + const mod = b.addModule("puzzle", .{ .root_source_file = b.path("src/root.zig"), .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "c", .module = t.mod }, + }, }); - mod.addIncludePath(b.path("src")); - const exe = b.addExecutable(.{ .name = "wl-main", .use_llvm = llvm, @@ -36,7 +49,7 @@ pub fn build(b: *std.Build) void { exe.root_module.addIncludePath(b.path("src")); exe.root_module.addCSourceFiles(.{ .language = .c, - .files = &.{ "src/xdg-shell.c", }, + .files = &.{ "src/xdg-shell.c", "src/stbi.c", }, }); b.installArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon index 93c97e0..7accd90 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -25,51 +25,18 @@ .fingerprint = 0x5fad1fab9564c392, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ - // See `zig fetch --save ` for a command-line interface for adding dependencies. - //.example = .{ - // // When updating this field to a new URL, be sure to delete the corresponding - // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. If the contents of a URL change this will result in a hash mismatch - // // which will prevent zig from using it. - // .url = "https://example.com/foo.tar.gz", - // - // // This is computed from the file contents of the directory of files that is - // // obtained after fetching `url` and applying the inclusion rules given by - // // `paths`. - // // - // // This field is the source of truth; packages do not come from a `url`; they - // // come from a `hash`. `url` is just one of many possible mirrors for how to - // // obtain a package matching this `hash`. - // // - // // Uses the [multihash](https://multiformats.io/multihash/) format. - // .hash = "...", - // - // // When this is provided, the package is found in a directory relative to the - // // build root. In this case the package's hash is irrelevant and therefore not - // // computed. This field and `url` are mutually exclusive. - // .path = "foo", - // - // // When this is set to `true`, a package is declared to be lazily - // // fetched. This makes the dependency only get fetched if it is - // // actually used. - // .lazy = false, - //}, + .translate_c = .{ + .url = "git+https://codeberg.org/ifreund/translate-c#fdf30e6298cd1184ae6da5602aff0cb5d4cde4cb", + .hash = "translate_c-0.0.0-Q_BUWrT0BgDOFSyiz0UVBIpBe3-CiEqx9ej2EecgKcBD", + }, }, - // Specifies the set of files and directories that are included in this package. - // Only files and directories listed here are included in the `hash` that - // is computed for this package. Only files listed here will remain on disk - // when using the zig package manager. As a rule of thumb, one should list - // files required for compilation plus any license(s). - // Paths are relative to the build root. Use the empty string (`""`) to refer to - // the build root itself. - // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", diff --git a/shell.nix b/shell.nix index fcccd91..14ad96e 100644 --- a/shell.nix +++ b/shell.nix @@ -1,14 +1,18 @@ with import {}; mkShell { packages = [ - zig - zls + pkg-config wayland-scanner - gdb - lldb ]; buildInputs = [ + libc + wayland + libxkbcommon + ]; + + LD_LIBRARY_PATH = lib.makeLibraryPath [ + libc wayland libxkbcommon ]; diff --git a/src/c.h b/src/c.h new file mode 100644 index 0000000..e0b86d6 --- /dev/null +++ b/src/c.h @@ -0,0 +1 @@ +#include "stb_image.h" diff --git a/src/main.zig b/src/main.zig index 0696054..f572339 100644 --- a/src/main.zig +++ b/src/main.zig @@ -87,7 +87,7 @@ fn createShmFile(pool_size: usize) !std.posix.fd_t { return error.ShmOpenError; } - try std.posix.ftruncate(result, pool_size); + _ = linux.ftruncate(result, @intCast(pool_size)); std.debug.print("[Wayland] ftruncate shm to size {d}B\n", .{pool_size}); return result; @@ -99,7 +99,7 @@ fn createSharedMemoryPool(wl_state: WaylandState) !MemoryPool { const fd: c_int = try createShmFile(pool_size); - const prot = linux.PROT.READ | std.os.linux.PROT.WRITE; + const prot: linux.PROT = .{ .READ = true, .WRITE = true }; const flags: linux.MAP = .{ .TYPE = .SHARED }; const ret = try std.posix.mmap(null, pool_size, prot, flags, fd, 0); @@ -125,7 +125,6 @@ fn wlFrameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, callback_data std.debug.print("[Wayland] Frame ready\n", .{}); wl_state.frame_commited = true; - } const frame_callback_listener: c.wl_callback_listener = .{ @@ -559,7 +558,7 @@ fn wlPointerHandleFrame(data: ?*anyopaque, wl_pointer: ?*c.wl_pointer) callconv( _ = wl_pointer; const wl_state: *WaylandState = @ptrCast(@alignCast(data)); - // std.debug.print("[Wayland] Pointer frame\n", .{}); + std.debug.print("[Wayland] Pointer frame\n", .{}); if (wl_state.pointer_event.mask.ENTER) { wl_state.app_input.mouse_present = true; @@ -627,14 +626,16 @@ fn wlKeyboardHandleKeymap( std.debug.assert(format == c.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); const wl_state: *WaylandState = @ptrCast(@alignCast(data)); - const map_shm: []align(4096) const u8 = std.posix.mmap(null, size, linux.PROT.READ, .{ .TYPE = .SHARED }, fd, 0) catch { + const prot: linux.PROT = .{ .READ = true, .WRITE = false }; + const flags: linux.MAP = .{ .TYPE = .SHARED }; + const map_shm: []align(4096) const u8 = std.posix.mmap(null, size, prot, flags, fd, 0) catch { std.debug.panic("[Wayland] Keyboard keymap handler failed mmap", .{}); }; const keymap: ?*c.xkb_keymap = c.xkb_keymap_new_from_string(wl_state.xkb_context, @ptrCast(map_shm), c.XKB_KEYMAP_FORMAT_TEXT_V1, c.XKB_KEYMAP_COMPILE_NO_FLAGS); std.posix.munmap(map_shm); - std.posix.close(fd); + _ = linux.close(fd); const xkb_state: ?*c.xkb_state = c.xkb_state_new(keymap); @@ -688,7 +689,7 @@ fn wlKeyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32 } break :blk i; }; - const str: []const u8 = buf[0 .. buf_end]; + const str: []const u8 = buf[0..buf_end]; std.debug.print("{s} {any}\n", .{ str, pressed }); switch (sym) { @@ -781,6 +782,7 @@ fn registryHandleGlobal( version: u32, ) callconv(.c) void { var wl_state: *WaylandState = @ptrCast(@alignCast(data)); + std.debug.print("[Wayland] Got interface {s} version {d}.\n", .{ interface, version }); if (c.strcmp(interface, c.wl_compositor_interface.name) == 0) { std.debug.print("[Wayland] Binding to interface {s} version {d}.\n", .{ interface, version }); wl_state.compositor = @ptrCast(@alignCast(c.wl_registry_bind(registry, name, &c.wl_compositor_interface, version))); @@ -809,13 +811,13 @@ fn registryHandleGlobalRemove( _ = name; } - const registry_listener: c.wl_registry_listener = .{ .global = registryHandleGlobal, .global_remove = registryHandleGlobalRemove, }; fn pollEvents(wl_state: *WaylandState, fds: *[1]linux.pollfd) void { + var count: u32 = 0; while (true) { const cont = std.posix.poll(fds, 0) catch |err| { switch (err) { @@ -838,6 +840,7 @@ fn pollEvents(wl_state: *WaylandState, fds: *[1]linux.pollfd) void { for (fds) |fd| { if (fd.revents & std.posix.POLL.IN > 0) { const ret = c.wl_display_dispatch(wl_state.display); + count += 1; if (ret < 0) { std.debug.print("Dispatch error {d}.\n", .{ret}); wl_state.running = false; @@ -846,9 +849,13 @@ fn pollEvents(wl_state: *WaylandState, fds: *[1]linux.pollfd) void { } } } + + std.debug.print("[Wayland] Polled and dispatched {d} events.\n", .{count}); } -pub fn main() u8 { +pub fn main(init: std.process.Init) u8 { + const io = init.io; + const display = c.wl_display_connect(null); if (display == null) { std.debug.print("Error connecting to wayland display.\n", .{}); @@ -920,14 +927,29 @@ pub fn main() u8 { wl_state.window_width = 1280; wl_state.window_height = 960; + const clock = std.Io.Clock.awake; + const clock_resolution = clock.resolution(io) catch { + std.debug.print("[Wayland] ERROR: awake clock resoultion not supported.\n", .{}); + return 1; + }; + { + var buf: [34]u8 = undefined; + var w: std.Io.Writer = .fixed(&buf); + w.print("{any}", .{clock_resolution}) catch { + std.debug.print("[Wayland] ERROR: awake clock resoultion not supported.\n", .{}); + return 1; + }; + std.debug.print("Clock resolution is: {s}.\n", .{w.buffered()}); + } + var shared_memory_pool: ?*c.wl_shm_pool = undefined; var shared_memory_pool_data: []u8 = undefined; - const monitor_update_hz: f64 = 60; + const monitor_update_hz: f64 = 30; const game_update_hz: f64 = monitor_update_hz; const frame_target_time_s: f64 = 1.0 / game_update_hz; const frame_target_time_ms: f64 = std.time.ms_per_s * frame_target_time_s; - const frame_target_time_ns: u64 = std.time.ns_per_ms * @as(u64, @intFromFloat(frame_target_time_ms)); + const frame_target_time_ns = std.Io.Duration.fromNanoseconds(std.time.ns_per_ms * @as(u64, @intFromFloat(frame_target_time_ms))); // Memory var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -951,10 +973,7 @@ pub fn main() u8 { std.debug.print("[Wayland] Added frame callback with ret {d}.\n", .{ret}); } - var timer = std.time.Timer.start() catch { - std.debug.print("[Wayland] ERROR: timer not supported.\n", .{}); - return 1; - }; + var start = std.Io.Clock.Timestamp.now(io, clock); var frame_count: u32 = 0; @@ -966,7 +985,41 @@ pub fn main() u8 { }, }; while (wl_state.running) { - pollEvents(&wl_state, &fds); + // pollEvents(&wl_state, &fds); + var count: u32 = 0; + outer: while (true) { + const cont = std.posix.poll(&fds, 0) catch |err| { + switch (err) { + error.Unexpected => { + std.debug.print("Poll error: Unexpected.\n", .{}); + }, + error.SystemResources => { + std.debug.print("Poll error: SystemResources: Kernel has no space for table allocations.\n", .{}); + }, + else => { + std.debug.print("Poll error: network stuff idk.\n", .{}); + }, + } + + return 0; + }; + + if (cont == 0) break; + + for (fds) |fd| { + if (fd.revents & std.posix.POLL.IN > 0) { + const ret = c.wl_display_dispatch(wl_state.display); + count += 1; + if (ret < 0) { + std.debug.print("Dispatch error {d}.\n", .{ret}); + wl_state.running = false; + break :outer; + } + } + } + } + + std.debug.print("[Wayland] Polled and dispatched {d} events.\n", .{count}); if (wl_state.resize) { const new_pool = createSharedMemoryPool(wl_state) catch { @@ -1008,7 +1061,7 @@ pub fn main() u8 { c.wl_surface_commit(wl_state.surface); } - const render_start_time: u64 = timer.read(); + const render_start_time = clock.now(io); wl_state.app_input.delta_time = frame_target_time_s; @@ -1019,22 +1072,26 @@ pub fn main() u8 { .data = shared_memory_pool_data, }; // wl_state.app_input.controls = puzzle.platform.Controls{ .key = old_key }; - puzzle.updateAndRender(&app_memory, app_offscreen_buffer, &wl_state.app_input); + puzzle.updateAndRender(io, &app_memory, app_offscreen_buffer, &wl_state.app_input); - const render_end_time: u64 = timer.read(); - const render_time = render_end_time - render_start_time; - const render_time_ms: f64 = @as(f64, @floatFromInt(render_time)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); + const render_end_time = clock.now(io); + const render_time = render_start_time.durationTo(render_end_time); - std.debug.print("[Wayland] Render: {d:2.3}ms.\n", .{ render_time_ms }); + std.debug.print("[Wayland] Render: {f}ms.\n", .{render_time}); - var sleep_time: u64 = frame_target_time_ns; - if (render_time < frame_target_time_ns) { - sleep_time -= render_time; - const sleep_time_ms: f64 = @as(f64, @floatFromInt(sleep_time)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); - std.debug.print("[Wayland] Sleeping: {d:2.3}ms.\n", .{ sleep_time_ms }); - std.posix.nanosleep(0, sleep_time); + var sleep_time = std.Io.Timestamp.now(io, clock); + sleep_time = sleep_time.addDuration(frame_target_time_ns); + if (render_time.toNanoseconds() < frame_target_time_ns.toNanoseconds()) { + sleep_time = sleep_time.subDuration(render_time); + std.debug.print("[Wayland] Sleeping: {d}ms.\n", .{sleep_time}); + const with_clock = std.Io.Timestamp.withClock(sleep_time, clock); + const timeout = std.Io.Timeout{ .deadline = with_clock }; + timeout.sleep(io) catch { + std.debug.print("[Wayland] Error sleeping\n", .{}); + return 1; + }; } else { - sleep_time = 0; + sleep_time = std.Io.Timestamp.zero; std.debug.print("[Wayland] MISSED FRAME\n", .{}); } @@ -1042,24 +1099,20 @@ pub fn main() u8 { c.wl_surface_damage_buffer(wl_state.surface, 0, 0, wl_state.window_width, wl_state.window_height); c.wl_surface_commit(wl_state.surface); - var wait_frame: u32 = 0; - while (!wl_state.frame_commited) { - _ = c.wl_display_dispatch(display); - wait_frame += 1; - } - std.debug.print("[Wayland] Waited for {d} frame(s).\n", .{wait_frame}); + _ = c.wl_display_roundtrip(wl_state.display); wl_state.frame_commited = false; // std.posix.nanosleep(0, std.time.ns_per_ms * 32); - const current = timer.lap(); - const current_ms: f64 = @as(f64, @floatFromInt(current)) / std.time.ns_per_ms; + const last = std.Io.Clock.Timestamp.now(io, clock); + const current = start.durationTo(last); + start = last; // std.debug.print( // "[Wayland] Render: {d:2.3}ms, Sleeping: {d:2.3}ms, Frame: {d:.3}ms.\n", // .{ render_time_ms, sleep_time_ms, current_ms }, // ); - std.debug.print("[Wayland] Frame done: {d:.3}ms.\n", .{ current_ms }); + std.debug.print("[Wayland] Frame done: {f}ms.\n", .{current.raw}); frame_count += 1; } diff --git a/src/platform.zig b/src/platform.zig index 3fbd844..4fbd3cd 100644 --- a/src/platform.zig +++ b/src/platform.zig @@ -1,11 +1,5 @@ const std = @import("std"); -pub const c = @cImport({ - @cDefine("STB_IMAGE_IMPLEMENTATION", ""); - @cDefine("STBI_ONLY_PNG", ""); - @cInclude("stb_image.h"); -}); - pub fn kiloBytes(bytes: usize) usize { return bytes * 1024; } diff --git a/src/root.zig b/src/root.zig index 0b1ae74..1ccf76b 100644 --- a/src/root.zig +++ b/src/root.zig @@ -6,6 +6,7 @@ const epoch = std.time.epoch; const MonthAndDay = epoch.MonthAndDay; pub const platform = @import("platform.zig"); +const c = @import("c"); const V2 = @import("V2.zig"); const Image = @import("Image.zig"); @@ -193,10 +194,10 @@ fn tilesIntersects(r1: V2, r2: V2) bool { const a = pointInsideRect(r2a, r1, tile_size); const b = pointInsideRect(r2b, r1, tile_size); - const c = pointInsideRect(r2c, r1, tile_size); + const @"c'" = pointInsideRect(r2c, r1, tile_size); const d = pointInsideRect(r2d, r1, tile_size); - result = a or b or c or d; + result = a or b or @"c'" or d; return result; } @@ -211,10 +212,10 @@ fn rectsIntersects(r1: V2, r1_size: V2, r2: V2, r2_size: V2) bool { const a = pointInsideRect(r2a, r1, r1_size); const b = pointInsideRect(r2b, r1, r1_size); - const c = pointInsideRect(r2c, r1, r1_size); + const @"c'" = pointInsideRect(r2c, r1, r1_size); const d = pointInsideRect(r2d, r1, r1_size); - result = a or b or c or d; + result = a or b or @"c'" or d; return result; } @@ -447,8 +448,8 @@ fn drawString(buffer: platform.OffscreenBuffer, font: Font, pos: V2, scale: f32, var current_pos = pos; const font_size = 32; const font_width_scaled = scale * font_size; - for (str) |c| { - const char_image_pos = font.findCharImagePos(c); + for (str) |char| { + const char_image_pos = font.findCharImagePos(char); const pos_x: i32 = @intFromFloat(char_image_pos.x); const pos_y: i32 = @intFromFloat(char_image_pos.y); @@ -487,7 +488,7 @@ fn loadPng(image_path: []const u8) ?Image { var x: c_int = undefined; var y: c_int = undefined; var n: c_int = undefined; - const image_data: [*c]platform.c.stbi_uc = platform.c.stbi_load(@ptrCast(image_path), &x, &y, &n, 0); + const image_data: [*c]c.stbi_uc = c.stbi_load(@ptrCast(image_path), &x, &y, &n, 0); if (image_data != 0) { const stride = n * x; @@ -506,7 +507,7 @@ fn loadPng(image_path: []const u8) ?Image { } } -pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: *platform.AppInput) void { +pub fn updateAndRender(io: std.Io, memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: *platform.AppInput) void { var state: *State = @ptrCast(@alignCast(memory.permanent_storage)); const mid = V2{ @@ -516,8 +517,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu if (!memory.initialized) { state.has_won = false; - const timestamp = std.time.timestamp(); - const epoch_seconds = epoch.EpochSeconds{ .secs = @intCast(timestamp) }; + const clock = std.Io.Clock.real; + const timestamp = clock.now(io); + const epoch_seconds = epoch.EpochSeconds{ .secs = @intCast(timestamp.toSeconds()) }; const epoch_day = epoch_seconds.getEpochDay(); const year_and_day = epoch_day.calculateYearDay(); const day: MonthAndDay = year_and_day.calculateMonthDay(); diff --git a/src/stbi.c b/src/stbi.c new file mode 100644 index 0000000..f81a87a --- /dev/null +++ b/src/stbi.c @@ -0,0 +1,3 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#include "stb_image.h"