core: refactor

This commit is contained in:
Ali Cheraghi 2024-07-13 01:07:20 +03:30 committed by Stephen Gutekanst
parent c254337e4b
commit 266e7a548b
38 changed files with 4119 additions and 4836 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
const std = @import("std");
const core = @import("main.zig");
const Timer = @import("Timer.zig");
pub const Frequency = @This();

View file

@ -1,25 +1,25 @@
const std = @import("std");
const core = @import("main.zig");
const KeyBitSet = std.StaticBitSet(@intFromEnum(core.Key.max) + 1);
const MouseButtonSet = std.StaticBitSet(@as(u4, @intFromEnum(core.MouseButton.max)) + 1);
const Core = @import("../Core.zig");
const KeyBitSet = std.StaticBitSet(@intFromEnum(Core.Key.max) + 1);
const MouseButtonSet = std.StaticBitSet(@as(u4, @intFromEnum(Core.MouseButton.max)) + 1);
const InputState = @This();
keys: KeyBitSet = KeyBitSet.initEmpty(),
mouse_buttons: MouseButtonSet = MouseButtonSet.initEmpty(),
mouse_position: core.Position = .{ .x = 0, .y = 0 },
mouse_position: Core.Position = .{ .x = 0, .y = 0 },
pub inline fn isKeyPressed(self: InputState, key: core.Key) bool {
pub inline fn isKeyPressed(self: InputState, key: Core.Key) bool {
return self.keys.isSet(@intFromEnum(key));
}
pub inline fn isKeyReleased(self: InputState, key: core.Key) bool {
pub inline fn isKeyReleased(self: InputState, key: Core.Key) bool {
return !self.isKeyPressed(key);
}
pub inline fn isMouseButtonPressed(self: InputState, button: core.MouseButton) bool {
pub inline fn isMouseButtonPressed(self: InputState, button: Core.MouseButton) bool {
return self.mouse_buttons.isSet(@intFromEnum(button));
}
pub inline fn isMouseButtonReleased(self: InputState, button: core.MouseButton) bool {
pub inline fn isMouseButtonReleased(self: InputState, button: Core.MouseButton) bool {
return !self.isMouseButtonPressed(button);
}

View file

@ -1,20 +1,20 @@
const std = @import("std");
const platform = @import("platform.zig");
const builtin = @import("builtin");
const WasmTimer = @import("wasm/Timer.zig");
const PlatformTimer = if (builtin.cpu.arch == .wasm32) WasmTimer else NativeTimer;
pub const Timer = @This();
const Timer = @This();
internal: platform.Timer,
platform: PlatformTimer,
/// Initialize the timer.
pub fn start() !Timer {
return Timer{
.internal = try platform.Timer.start(),
};
return .{ .platform = try PlatformTimer.start() };
}
/// Reads the timer value since start or the last reset in nanoseconds.
pub inline fn readPrecise(timer: *Timer) u64 {
return timer.internal.read();
return timer.platform.read();
}
/// Reads the timer value since start or the last reset in seconds.
@ -24,15 +24,35 @@ pub inline fn read(timer: *Timer) f32 {
/// Resets the timer value to 0/now.
pub inline fn reset(timer: *Timer) void {
timer.internal.reset();
timer.platform.reset();
}
/// Returns the current value of the timer in nanoseconds, then resets it.
pub inline fn lapPrecise(timer: *Timer) u64 {
return timer.internal.lap();
return timer.platform.lap();
}
/// Returns the current value of the timer in seconds, then resets it.
pub inline fn lap(timer: *Timer) f32 {
return @as(f32, @floatFromInt(timer.lapPrecise())) / @as(f32, @floatFromInt(std.time.ns_per_s));
}
const NativeTimer = struct {
timer: std.time.Timer,
pub fn start() !NativeTimer {
return .{ .timer = try std.time.Timer.start() };
}
pub inline fn read(timer: *NativeTimer) u64 {
return timer.timer.read();
}
pub inline fn reset(timer: *NativeTimer) void {
timer.timer.reset();
}
pub inline fn lap(timer: *NativeTimer) u64 {
return timer.timer.lap();
}
};

View file

@ -1,2 +1,473 @@
pub const Core = @import("wasm/Core.zig");
pub const Timer = @import("wasm/Timer.zig");
const std = @import("std");
const js = @import("js.zig");
const Timer = @import("Timer.zig");
const mach = @import("../../../main.zig");
const gpu = mach.gpu;
const InitOptions = @import("../../../Core.zig").InitOptions;
const Event = @import("../../../Core.zig").Event;
const KeyEvent = @import("../../../Core.zig").KeyEvent;
const MouseButtonEvent = @import("../../../Core.zig").MouseButtonEvent;
const MouseButton = @import("../../../Core.zig").MouseButton;
const Size = @import("../../../Core.zig").Size;
const Position = @import("../../../Core.zig").Position;
const DisplayMode = @import("../../../Core.zig").DisplayMode;
const SizeLimit = @import("../../../Core.zig").SizeLimit;
const CursorShape = @import("../../../Core.zig").CursorShape;
const VSyncMode = @import("../../../Core.zig").VSyncMode;
const CursorMode = @import("../../../Core.zig").CursorMode;
const Key = @import("../../../Core.zig").Key;
const KeyMods = @import("../../../Core.zig").KeyMods;
const Joystick = @import("../../../Core.zig").Joystick;
const InputState = @import("../../InputState.zig");
const Frequency = @import("../../Frequency.zig");
// Custom std.log implementation which logs to the browser console.
pub fn defaultLog(
comptime message_level: std.log.Level,
comptime scope: @Type(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
const writer = LogWriter{ .context = {} };
writer.print(message_level.asText() ++ prefix ++ format ++ "\n", args) catch return;
machLogFlush();
}
// Custom @panic implementation which logs to the browser console.
pub fn defaultPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
_ = ret_addr;
machPanic(msg.ptr, msg.len);
unreachable;
}
pub extern "mach" fn machPanic(str: [*]const u8, len: u32) void;
pub extern "mach" fn machLogWrite(str: [*]const u8, len: u32) void;
pub extern "mach" fn machLogFlush() void;
const LogError = error{};
const LogWriter = std.io.Writer(void, LogError, writeLog);
fn writeLog(_: void, msg: []const u8) LogError!usize {
machLogWrite(msg.ptr, msg.len);
return msg.len;
}
pub const Core = @This();
allocator: std.mem.Allocator,
frame: *Frequency,
input: *Frequency,
id: js.CanvasId,
input_state: InputState,
joysticks: [JoystickData.max_joysticks]JoystickData,
pub const EventIterator = struct {
core: *Core,
pub inline fn next(self: *EventIterator) ?Event {
while (true) {
const event_int = js.machEventShift();
if (event_int == -1) return null;
const event_type = @as(std.meta.Tag(Event), @enumFromInt(event_int));
return switch (event_type) {
.key_press, .key_repeat, .key_release => blk: {
const key = @as(Key, @enumFromInt(js.machEventShift()));
switch (event_type) {
.key_press => {
self.core.input_state.keys.set(@intFromEnum(key));
break :blk Event{
.key_press = .{
.key = key,
.mods = self.makeKeyMods(),
},
};
},
.key_repeat => break :blk Event{
.key_repeat = .{
.key = key,
.mods = self.makeKeyMods(),
},
},
.key_release => {
self.core.input_state.keys.unset(@intFromEnum(key));
break :blk Event{
.key_release = .{
.key = key,
.mods = self.makeKeyMods(),
},
};
},
else => unreachable,
}
continue;
},
.mouse_motion => blk: {
const x = @as(f64, @floatFromInt(js.machEventShift()));
const y = @as(f64, @floatFromInt(js.machEventShift()));
self.core.input_state.mouse_position = .{ .x = x, .y = y };
break :blk Event{
.mouse_motion = .{
.pos = .{
.x = x,
.y = y,
},
},
};
},
.mouse_press => blk: {
const button = toMachButton(js.machEventShift());
self.core.input_state.mouse_buttons.set(@intFromEnum(button));
break :blk Event{
.mouse_press = .{
.button = button,
.pos = self.core.input_state.mouse_position,
.mods = self.makeKeyMods(),
},
};
},
.mouse_release => blk: {
const button = toMachButton(js.machEventShift());
self.core.input_state.mouse_buttons.unset(@intFromEnum(button));
break :blk Event{
.mouse_release = .{
.button = button,
.pos = self.core.input_state.mouse_position,
.mods = self.makeKeyMods(),
},
};
},
.mouse_scroll => Event{
.mouse_scroll = .{
.xoffset = @as(f32, @floatCast(std.math.sign(js.machEventShiftFloat()))),
.yoffset = @as(f32, @floatCast(std.math.sign(js.machEventShiftFloat()))),
},
},
.joystick_connected => blk: {
const idx: u8 = @intCast(js.machEventShift());
const btn_count: usize = @intCast(js.machEventShift());
const axis_count: usize = @intCast(js.machEventShift());
if (idx >= JoystickData.max_joysticks) continue;
var data = &self.core.joysticks[idx];
data.present = true;
data.button_count = @min(JoystickData.max_button_count, btn_count);
data.axis_count = @min(JoystickData.max_axis_count, axis_count);
js.machJoystickName(idx, &data.name, JoystickData.max_name_len);
break :blk Event{ .joystick_connected = @enumFromInt(idx) };
},
.joystick_disconnected => blk: {
const idx: u8 = @intCast(js.machEventShift());
if (idx >= JoystickData.max_joysticks) continue;
var data = &self.core.joysticks[idx];
data.present = false;
data.button_count = 0;
data.axis_count = 0;
@memset(&data.buttons, false);
@memset(&data.axes, 0);
break :blk Event{ .joystick_disconnected = @enumFromInt(idx) };
},
.framebuffer_resize => blk: {
const width = @as(u32, @intCast(js.machEventShift()));
const height = @as(u32, @intCast(js.machEventShift()));
const pixel_ratio = @as(u32, @intCast(js.machEventShift()));
break :blk Event{
.framebuffer_resize = .{
.width = width * pixel_ratio,
.height = height * pixel_ratio,
},
};
},
.focus_gained => Event.focus_gained,
.focus_lost => Event.focus_lost,
else => null,
};
}
}
fn makeKeyMods(self: EventIterator) KeyMods {
const is = self.core.input_state;
return .{
.shift = is.isKeyPressed(.left_shift) or is.isKeyPressed(.right_shift),
.control = is.isKeyPressed(.left_control) or is.isKeyPressed(.right_control),
.alt = is.isKeyPressed(.left_alt) or is.isKeyPressed(.right_alt),
.super = is.isKeyPressed(.left_super) or is.isKeyPressed(.right_super),
// FIXME(estel): I think the logic for these two are wrong, but unlikely it matters
// in a browser. To correct them we need to actually use `KeyboardEvent.getModifierState`
// in javascript and bring back that info in here.
.caps_lock = is.isKeyPressed(.caps_lock),
.num_lock = is.isKeyPressed(.num_lock),
};
}
};
const JoystickData = struct {
present: bool,
button_count: usize,
axis_count: usize,
name: [max_name_len:0]u8,
buttons: [max_button_count]bool,
axes: [max_axis_count]f32,
// 16 as it's the maximum number of joysticks supported by GLFW.
const max_joysticks = 16;
const max_name_len = 64;
const max_button_count = 32;
const max_axis_count = 16;
};
pub fn init(
core: *Core,
allocator: std.mem.Allocator,
frame: *Frequency,
input: *Frequency,
options: InitOptions,
) !void {
_ = options;
var selector = [1]u8{0} ** 15;
const id = js.machCanvasInit(&selector[0]);
core.* = Core{
.allocator = allocator,
.frame = frame,
.input = input,
.id = id,
.input_state = .{},
.joysticks = std.mem.zeroes([JoystickData.max_joysticks]JoystickData),
};
// TODO(wasm): wgpu support
try core.frame.start();
try core.input.start();
}
pub fn deinit(self: *Core) void {
js.machCanvasDeinit(self.id);
}
pub inline fn update(self: *Core, app: anytype) !bool {
self.frame.tick();
self.input.tick();
if (try app.update()) return true;
if (@hasDecl(std.meta.Child(@TypeOf(app)), "updateMainThread")) {
if (app.updateMainThread() catch |err| @panic(@errorName(err))) {
return true;
}
}
return false;
}
pub inline fn pollEvents(self: *Core) EventIterator {
return EventIterator{
.core = self,
};
}
pub fn setTitle(self: *Core, title: [:0]const u8) void {
js.machCanvasSetTitle(self.id, title.ptr, title.len);
}
pub fn setDisplayMode(self: *Core, _mode: DisplayMode) void {
var mode = _mode;
if (mode == .borderless) {
// borderless fullscreen window has no meaning in web
mode = .fullscreen;
}
js.machCanvasSetDisplayMode(self.id, @intFromEnum(mode));
}
pub fn displayMode(self: *Core) DisplayMode {
return @as(DisplayMode, @enumFromInt(js.machDisplayMode(self.id)));
}
pub fn setBorder(self: *Core, value: bool) void {
_ = self;
_ = value;
}
pub fn border(self: *Core) bool {
_ = self;
return false;
}
pub fn setHeadless(self: *Core, value: bool) void {
_ = self;
_ = value;
}
pub fn headless(self: *Core) bool {
_ = self;
return false;
}
pub fn setVSync(self: *Core, mode: VSyncMode) void {
_ = mode;
self.frame.target = 0;
}
// TODO(wasm): https://github.com/gpuweb/gpuweb/issues/1224
pub fn vsync(self: *Core) VSyncMode {
_ = self;
return .double;
}
pub fn setSize(self: *Core, value: Size) void {
js.machCanvasSetSize(self.id, value.width, value.height);
}
pub fn size(self: *Core) Size {
return .{
.width = js.machCanvasWidth(self.id),
.height = js.machCanvasHeight(self.id),
};
}
pub fn setSizeLimit(self: *Core, limit: SizeLimit) void {
js.machCanvasSetSizeLimit(
self.id,
if (limit.min.width) |val| @as(i32, @intCast(val)) else -1,
if (limit.min.height) |val| @as(i32, @intCast(val)) else -1,
if (limit.max.width) |val| @as(i32, @intCast(val)) else -1,
if (limit.max.height) |val| @as(i32, @intCast(val)) else -1,
);
}
pub fn sizeLimit(self: *Core) SizeLimit {
return .{
.min = .{
.width = js.machCanvasMinWidth(self.id),
.height = js.machCanvasMinHeight(self.id),
},
.max = .{
.width = js.machCanvasMaxWidth(self.id),
.height = js.machCanvasMaxHeight(self.id),
},
};
}
pub fn setCursorMode(self: *Core, mode: CursorMode) void {
js.machSetCursorMode(self.id, @intFromEnum(mode));
}
pub fn cursorMode(self: *Core) CursorMode {
return @as(CursorMode, @enumFromInt(js.machCursorMode(self.id)));
}
pub fn setCursorShape(self: *Core, shape: CursorShape) void {
js.machSetCursorShape(self.id, @intFromEnum(shape));
}
pub fn cursorShape(self: *Core) CursorShape {
return @as(CursorShape, @enumFromInt(js.machCursorShape(self.id)));
}
pub fn joystickPresent(core: *Core, joystick: Joystick) bool {
const idx: u8 = @intFromEnum(joystick);
return core.joysticks[idx].present;
}
pub fn joystickName(core: *Core, joystick: Joystick) ?[:0]const u8 {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
return std.mem.span(&data.name);
}
pub fn joystickButtons(core: *Core, joystick: Joystick) ?[]const bool {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
js.machJoystickButtons(idx, &data.buttons, JoystickData.max_button_count);
return data.buttons[0..data.button_count];
}
pub fn joystickAxes(core: *Core, joystick: Joystick) ?[]const f32 {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
js.machJoystickAxes(idx, &data.axes, JoystickData.max_axis_count);
return data.buttons[0..data.button_count];
}
pub fn keyPressed(self: *Core, key: Key) bool {
return self.input_state.isKeyPressed(key);
}
pub fn keyReleased(self: *Core, key: Key) bool {
return self.input_state.isKeyReleased(key);
}
pub fn mousePressed(self: *Core, button: MouseButton) bool {
return self.input_state.isMouseButtonPressed(button);
}
pub fn mouseReleased(self: *Core, button: MouseButton) bool {
return self.input_state.isMouseButtonReleased(button);
}
pub fn mousePosition(self: *Core) Core.Position {
return self.input_state.mouse_position;
}
pub inline fn adapter(_: *Core) *gpu.Adapter {
unreachable;
}
pub inline fn device(_: *Core) *gpu.Device {
unreachable;
}
pub inline fn swapChain(_: *Core) *gpu.SwapChain {
unreachable;
}
pub inline fn descriptor(self: *Core) gpu.SwapChain.Descriptor {
return .{
.label = "main swap chain",
.usage = .{ .render_attachment = true },
.format = .bgra8_unorm, // TODO(wasm): is this correct?
.width = js.machCanvasFramebufferWidth(self.id),
.height = js.machCanvasFramebufferHeight(self.id),
.present_mode = .fifo, // TODO(wasm): https://github.com/gpuweb/gpuweb/issues/1224
};
}
pub inline fn outOfMemory(self: *Core) bool {
_ = self;
return false;
}
pub inline fn wakeMainThread(self: *Core) void {
_ = self;
}
fn toMachButton(button: i32) MouseButton {
return switch (button) {
0 => .left,
1 => .middle,
2 => .right,
3 => .four,
4 => .five,
else => unreachable,
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,8 @@
const std = @import("std");
const builtin = @import("builtin");
const mach = @import("../../main.zig");
const gamemode = mach.gamemode;
const mach_core = mach.core;
const mach = @import("../main.zig");
const gpu = mach.gpu;
pub inline fn printUnhandledErrorCallback(_: void, ty: gpu.ErrorType, message: [*:0]const u8) void {
switch (ty) {
.validation => std.log.err("gpu: validation error: {s}\n", .{message}),
.out_of_memory => std.log.err("gpu: out of memory: {s}\n", .{message}),
.device_lost => std.log.err("gpu: device lost: {s}\n", .{message}),
.unknown => std.log.err("gpu: unknown error: {s}\n", .{message}),
else => unreachable,
}
std.process.exit(1);
}
pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.BackendType {
const backend = std.process.getEnvVarOwned(
allocator,
@ -40,50 +27,3 @@ pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.BackendType {
@panic("unknown MACH_GPU_BACKEND type");
}
/// Check if gamemode should be activated
pub fn wantGamemode(allocator: std.mem.Allocator) error{ OutOfMemory, InvalidUtf8 }!bool {
const use_gamemode = std.process.getEnvVarOwned(
allocator,
"MACH_USE_GAMEMODE",
) catch |err| switch (err) {
error.EnvironmentVariableNotFound => return true,
else => |e| return e,
};
defer allocator.free(use_gamemode);
return !(std.ascii.eqlIgnoreCase(use_gamemode, "off") or std.ascii.eqlIgnoreCase(use_gamemode, "false"));
}
const gamemode_log = std.log.scoped(.gamemode);
pub fn initLinuxGamemode() bool {
gamemode.start();
if (!gamemode.isActive()) return false;
gamemode_log.info("gamemode: activated", .{});
return true;
}
pub fn deinitLinuxGamemode() void {
gamemode.stop();
gamemode_log.info("gamemode: deactivated", .{});
}
pub const RequestAdapterResponse = struct {
status: gpu.RequestAdapterStatus,
adapter: ?*gpu.Adapter,
message: ?[*:0]const u8,
};
pub inline fn requestAdapterCallback(
context: *RequestAdapterResponse,
status: gpu.RequestAdapterStatus,
adapter: ?*gpu.Adapter,
message: ?[*:0]const u8,
) void {
context.* = RequestAdapterResponse{
.status = status,
.adapter = adapter,
.message = message,
};
}

View file

@ -1,768 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
pub const sysgpu = @import("../main.zig").sysgpu;
pub const sysjs = @import("mach-sysjs");
pub const Timer = @import("Timer.zig");
const Frequency = @import("Frequency.zig");
const platform = @import("platform.zig");
const mach = @import("../main.zig");
pub var mods: mach.Modules = undefined;
var stack_space: [8 * 1024 * 1024]u8 = undefined;
pub fn initModule() !void {
// Initialize the global set of Mach modules used in the program.
try mods.init(std.heap.c_allocator);
mods.schedule(.mach_core, .init_module);
}
/// Tick runs a single step of the main loop on the main OS thread.
///
/// Returns true if tick() should be called again, false if the application should exit.
pub fn tick() !bool {
mods.schedule(.mach_core, .main_thread_tick);
// Dispatch events until this .mach_core.main_thread_tick_done is sent
try mods.dispatch(&stack_space, .{ .until = .{
.module_name = mods.moduleNameToID(.mach_core),
.system = mods.systemToID(.mach_core, .main_thread_tick_done),
} });
return mods.mod.mach_core.state().run_state != .exited;
}
/// Returns the error set that the function F returns.
fn ErrorSet(comptime F: type) type {
return @typeInfo(@typeInfo(F).Fn.return_type.?).ErrorUnion.error_set;
}
const gpu = sysgpu.sysgpu;
pub fn AppInterface(comptime app_entry: anytype) void {
if (!@hasDecl(app_entry, "App")) {
@compileError("expected e.g. `pub const App = mach.App(modules, init)' (App definition missing in your main Zig file)");
}
const App = app_entry.App;
if (@typeInfo(App) != .Struct) {
@compileError("App must be a struct type. Found:" ++ @typeName(App));
}
if (@hasDecl(App, "init")) {
const InitFn = @TypeOf(@field(App, "init"));
if (InitFn != fn (app: *App) ErrorSet(InitFn)!void)
@compileError("expected 'pub fn init(app: *App) !void' found '" ++ @typeName(InitFn) ++ "'");
} else {
@compileError("App must export 'pub fn init(app: *App) !void'");
}
if (@hasDecl(App, "update")) {
const UpdateFn = @TypeOf(@field(App, "update"));
if (UpdateFn != fn (app: *App) ErrorSet(UpdateFn)!bool)
@compileError("expected 'pub fn update(app: *App) !bool' found '" ++ @typeName(UpdateFn) ++ "'");
} else {
@compileError("App must export 'pub fn update(app: *App) !bool'");
}
if (@hasDecl(App, "updateMainThread")) {
const UpdateMainThreadFn = @TypeOf(@field(App, "updateMainThread"));
if (UpdateMainThreadFn != fn (app: *App) ErrorSet(UpdateMainThreadFn)!bool)
@compileError("expected 'pub fn updateMainThread(app: *App) !bool' found '" ++ @typeName(UpdateMainThreadFn) ++ "'");
}
if (@hasDecl(App, "deinit")) {
const DeinitFn = @TypeOf(@field(App, "deinit"));
if (DeinitFn != fn (app: *App) void)
@compileError("expected 'pub fn deinit(app: *App) void' found '" ++ @typeName(DeinitFn) ++ "'");
} else {
@compileError("App must export 'pub fn deinit(app: *App) void'");
}
}
/// wasm32: custom std.log implementation which logs to the browser console.
/// other: std.log.defaultLog
pub const defaultLog = platform.Core.defaultLog;
/// wasm32: custom @panic implementation which logs to the browser console.
/// other: std.debug.default_panic
pub const defaultPanic = platform.Core.defaultPanic;
/// The allocator used by mach-core for any allocations. Must be specified before the first call to
/// core.init()
pub var allocator: std.mem.Allocator = undefined;
/// A buffer which you may use to write the window title to. See core.setTitle() for details.
pub var title: [256:0]u8 = undefined;
/// May be read inside `App.init`, `App.update`, and `App.deinit`.
///
/// No synchronization is performed, so these fields may not be accessed in `App.updateMainThread`.
pub var adapter: *gpu.Adapter = undefined;
pub var device: *gpu.Device = undefined;
pub var queue: *gpu.Queue = undefined;
pub var swap_chain: *gpu.SwapChain = undefined;
pub var descriptor: gpu.SwapChain.Descriptor = undefined;
/// The time in seconds between the last frame and the current frame.
///
/// Higher frame rates will report higher values, for example if your application is running at
/// 60FPS this will report 0.01666666666 (1.0 / 60) seconds, and if it is running at 30FPS it will
/// report twice that, 0.03333333333 (1.0 / 30.0) seconds.
///
/// For example, instead of rotating an object 360 degrees every frame `rotation += 6.0` (one full
/// rotation every second, but only if your application is running at 60FPS) you may instead multiply
/// by this number `rotation += 360.0 * core.delta_time` which results in one full rotation every
/// second, no matter what frame rate the application is running at.
pub var delta_time: f32 = 0;
pub var delta_time_ns: u64 = 0;
var frame: Frequency = undefined;
var input: Frequency = undefined;
var internal: platform.Core = undefined;
/// All memory will be copied or returned to the caller once init() finishes.
pub const Options = struct {
is_app: bool = false,
headless: bool = false,
display_mode: DisplayMode = .windowed,
border: bool = true,
title: [:0]const u8 = "Mach core",
size: Size = .{ .width = 1920 / 2, .height = 1080 / 2 },
power_preference: gpu.PowerPreference = .undefined,
required_features: ?[]const gpu.FeatureName = null,
required_limits: ?gpu.Limits = null,
swap_chain_usage: gpu.Texture.UsageFlags = .{
.render_attachment = true,
},
};
pub fn init(options_in: Options) !void {
// Copy window title into owned buffer.
var opt = options_in;
if (opt.title.len < title.len) {
@memcpy(title[0..opt.title.len], opt.title);
title[opt.title.len] = 0;
opt.title = title[0..opt.title.len :0];
}
frame = .{
.target = 0,
.delta_time = &delta_time,
.delta_time_ns = &delta_time_ns,
};
input = .{ .target = 1 };
try platform.Core.init(
&internal,
allocator,
&frame,
&input,
opt,
);
}
pub inline fn deinit() void {
return internal.deinit();
}
pub inline fn update(app_ptr: anytype) !bool {
return try internal.update(app_ptr);
}
pub const EventIterator = struct {
internal: platform.Core.EventIterator,
pub inline fn next(self: *EventIterator) ?Event {
return self.internal.next();
}
};
pub inline fn pollEvents() EventIterator {
return .{ .internal = internal.pollEvents() };
}
/// Sets the window title. The string must be owned by Core, and will not be copied or freed. It is
/// advised to use the `core.title` buffer for this purpose, e.g.:
///
/// ```
/// const title = try std.fmt.bufPrintZ(&core.title, "Hello, world!", .{});
/// core.setTitle(title);
/// ```
pub inline fn setTitle(value: [:0]const u8) void {
return internal.setTitle(value);
}
/// Sets the window title. Uses the `core.title` buffer.
pub inline fn printTitle(fmt: []const u8, args: anytype) !void {
const value = try std.fmt.bufPrintZ(&title, fmt, args);
return internal.setTitle(value);
}
/// Set the window mode
pub inline fn setDisplayMode(mode: DisplayMode) void {
return internal.setDisplayMode(mode);
}
/// Returns the window mode
pub inline fn displayMode() DisplayMode {
return internal.displayMode();
}
pub inline fn setBorder(value: bool) void {
return internal.setBorder(value);
}
pub inline fn border() bool {
return internal.border();
}
pub inline fn setHeadless(value: bool) void {
return internal.setHeadless(value);
}
pub inline fn headless() bool {
return internal.headless();
}
pub const VSyncMode = enum {
/// Potential screen tearing.
/// No synchronization with monitor, render frames as fast as possible.
///
/// Not available on WASM, fallback to double
none,
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
///
/// Tries to stay one frame ahead of the monitor, so when it's ready for the next frame it is
/// already prepared.
double,
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
///
/// Tries to stay two frames ahead of the monitor, so when it's ready for the next frame it is
/// already prepared.
///
/// Not available on WASM, fallback to double
triple,
};
/// Set refresh rate synchronization mode. Default `.triple`
///
/// Calling this function also implicitly calls setFrameRateLimit for you:
/// ```
/// .none => setFrameRateLimit(0) // unlimited
/// .double => setFrameRateLimit(0) // unlimited
/// .triple => setFrameRateLimit(2 * max_monitor_refresh_rate)
/// ```
pub inline fn setVSync(mode: VSyncMode) void {
return internal.setVSync(mode);
}
/// Returns refresh rate synchronization mode.
pub inline fn vsync() VSyncMode {
return internal.vsync();
}
/// Sets the frame rate limit. Default 0 (unlimited)
///
/// This is applied *in addition* to the vsync mode.
pub inline fn setFrameRateLimit(limit: u32) void {
frame.target = limit;
}
/// Returns the frame rate limit, or zero if unlimited.
pub inline fn frameRateLimit() u32 {
return frame.target;
}
/// Set the window size, in subpixel units.
pub inline fn setSize(value: Size) void {
return internal.setSize(value);
}
/// Returns the window size, in subpixel units.
pub inline fn size() Size {
return internal.size();
}
/// Set the minimum and maximum allowed size for the window.
pub inline fn setSizeLimit(size_limit: SizeLimit) void {
return internal.setSizeLimit(size_limit);
}
/// Returns the minimum and maximum allowed size for the window.
pub inline fn sizeLimit() SizeLimit {
return internal.sizeLimit();
}
pub inline fn setCursorMode(mode: CursorMode) void {
return internal.setCursorMode(mode);
}
pub inline fn cursorMode() CursorMode {
return internal.cursorMode();
}
pub inline fn setCursorShape(cursor: CursorShape) void {
return internal.setCursorShape(cursor);
}
pub inline fn cursorShape() CursorShape {
return internal.cursorShape();
}
// TODO(feature): add joystick/gamepad support https://github.com/hexops/mach/issues/884
// /// Checks if the given joystick is still connected.
// pub inline fn joystickPresent(joystick: Joystick) bool {
// return internal.joystickPresent(joystick);
// }
// /// Retreives the name of the joystick.
// /// Returns `null` if the joystick isnt connected.
// pub inline fn joystickName(joystick: Joystick) ?[:0]const u8 {
// return internal.joystickName(joystick);
// }
// /// Retrieves the state of the buttons of the given joystick.
// /// A value of `true` indicates the button is pressed, `false` the button is released.
// /// No remapping is done, so the order of these buttons are joystick-dependent and should be
// /// consistent across platforms.
// ///
// /// Returns `null` if the joystick isnt connected.
// ///
// /// Note: For WebAssembly, the remapping is done directly by the web browser, so on that platform
// /// the order of these buttons might be different than on others.
// pub inline fn joystickButtons(joystick: Joystick) ?[]const bool {
// return internal.joystickButtons(joystick);
// }
// /// Retreives the state of the axes of the given joystick.
// /// The values are always from -1 to 1.
// /// No remapping is done, so the order of these axes are joytstick-dependent and should be
// /// consistent acrsoss platforms.
// ///
// /// Returns `null` if the joystick isnt connected.
// ///
// /// Note: For WebAssembly, the remapping is done directly by the web browser, so on that platform
// /// the order of these axes might be different than on others.
// pub inline fn joystickAxes(joystick: Joystick) ?[]const f32 {
// return internal.joystickAxes(joystick);
// }
pub inline fn keyPressed(key: Key) bool {
return internal.keyPressed(key);
}
pub inline fn keyReleased(key: Key) bool {
return internal.keyReleased(key);
}
pub inline fn mousePressed(button: MouseButton) bool {
return internal.mousePressed(button);
}
pub inline fn mouseReleased(button: MouseButton) bool {
return internal.mouseReleased(button);
}
pub inline fn mousePosition() Position {
return internal.mousePosition();
}
/// Whether mach core has run out of memory. If true, freeing memory should restore it to a
/// functional state.
///
/// Once called, future calls will return false until another OOM error occurs.
///
/// Note that if an App.update function returns any error, including errors.OutOfMemory, it will
/// exit the application.
pub inline fn outOfMemory() bool {
return internal.outOfMemory();
}
/// Asks to wake the main thread. This e.g. allows your `pub fn update` to ask that the main thread
/// transition away from waiting for input, and execute another cycle which involves calling the
/// optional `updateMainThread` callback.
///
/// For example, instead of increasing the input thread target frequency, you may just call this
/// function to wake the main thread when your `updateMainThread` callback needs to be ran.
///
/// May be called from any thread.
pub inline fn wakeMainThread() void {
internal.wakeMainThread();
}
/// Sets the minimum target frequency of the input handling thread.
///
/// Input handling (the main thread) runs at a variable frequency. The thread blocks until there are
/// input events available, or until it needs to unblock in order to achieve the minimum target
/// frequency which is your collaboration point of opportunity with the main thread.
///
/// For example, by default (`setInputFrequency(1)`) mach-core will aim to invoke `updateMainThread`
/// at least once per second (but potentially much more, e.g. once per every mouse movement or
/// keyboard button press.) If you were to increase the input frequency to say 60hz e.g.
/// `setInputFrequency(60)` then mach-core will aim to invoke your `updateMainThread` 60 times per
/// second.
///
/// An input frequency of zero implies unlimited, in which case the main thread will busy-wait.
///
/// # Multithreaded mach-core behavior
///
/// On some platforms, mach-core is able to handle input and rendering independently for
/// improved performance and responsiveness.
///
/// | Platform | Threading |
/// |----------|-----------------|
/// | Desktop | Multi threaded |
/// | Browser | Single threaded |
/// | Mobile | TBD |
///
/// On single-threaded platforms, `update` and the (optional) `updateMainThread` callback are
/// invoked in sequence, one after the other, on the same thread.
///
/// On multi-threaded platforms, `init` and `deinit` are called on the main thread, while `update`
/// is called on a separate rendering thread. The (optional) `updateMainThread` callback can be
/// used in cases where you must run a function on the main OS thread (such as to open a native
/// file dialog on macOS, since many system GUI APIs must be run on the main OS thread.) It is
/// advised you do not use this callback to run any code except when absolutely neccessary, as
/// it is in direct contention with input handling.
///
/// APIs which are not accessible from a specific thread are declared as such, otherwise can be
/// called from any thread as they are internally synchronized.
pub inline fn setInputFrequency(input_frequency: u32) void {
input.target = input_frequency;
}
/// Returns the input frequency, or zero if unlimited (busy-waiting mode)
pub inline fn inputFrequency() u32 {
return input.target;
}
/// Returns the actual number of frames rendered (`update` calls that returned) in the last second.
///
/// This is updated once per second.
pub inline fn frameRate() u32 {
return frame.rate;
}
/// Returns the actual number of input thread iterations in the last second. See setInputFrequency
/// for what this means.
///
/// This is updated once per second.
pub inline fn inputRate() u32 {
return input.rate;
}
/// Returns the underlying native NSWindow pointer
///
/// May only be called on macOS.
pub fn nativeWindowCocoa() *anyopaque {
return internal.nativeWindowCocoa();
}
/// Returns the underlying native Windows' HWND pointer
///
/// May only be called on Windows.
pub fn nativeWindowWin32() std.os.windows.HWND {
return internal.nativeWindowWin32();
}
pub const Size = struct {
width: u32,
height: u32,
pub inline fn eql(a: Size, b: Size) bool {
return a.width == b.width and a.height == b.height;
}
};
pub const SizeOptional = struct {
width: ?u32 = null,
height: ?u32 = null,
pub inline fn eql(a: SizeOptional, b: SizeOptional) bool {
if ((a.width != null) != (b.width != null)) return false;
if ((a.height != null) != (b.height != null)) return false;
if (a.width != null and a.width.? != b.width.?) return false;
if (a.height != null and a.height.? != b.height.?) return false;
return true;
}
};
pub const SizeLimit = struct {
min: SizeOptional,
max: SizeOptional,
pub inline fn eql(a: SizeLimit, b: SizeLimit) bool {
return a.min.eql(b.min) and a.max.eql(b.max);
}
};
pub const Position = struct {
x: f64,
y: f64,
};
pub const Event = union(enum) {
key_press: KeyEvent,
key_repeat: KeyEvent,
key_release: KeyEvent,
char_input: struct {
codepoint: u21,
},
mouse_motion: struct {
pos: Position,
},
mouse_press: MouseButtonEvent,
mouse_release: MouseButtonEvent,
mouse_scroll: struct {
xoffset: f32,
yoffset: f32,
},
joystick_connected: Joystick,
joystick_disconnected: Joystick,
framebuffer_resize: Size,
focus_gained,
focus_lost,
close,
};
pub const KeyEvent = struct {
key: Key,
mods: KeyMods,
};
pub const MouseButtonEvent = struct {
button: MouseButton,
pos: Position,
mods: KeyMods,
};
pub const MouseButton = enum {
left,
right,
middle,
four,
five,
six,
seven,
eight,
pub const max = MouseButton.eight;
};
pub const Key = enum {
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
k,
l,
m,
n,
o,
p,
q,
r,
s,
t,
u,
v,
w,
x,
y,
z,
zero,
one,
two,
three,
four,
five,
six,
seven,
eight,
nine,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
f13,
f14,
f15,
f16,
f17,
f18,
f19,
f20,
f21,
f22,
f23,
f24,
f25,
kp_divide,
kp_multiply,
kp_subtract,
kp_add,
kp_0,
kp_1,
kp_2,
kp_3,
kp_4,
kp_5,
kp_6,
kp_7,
kp_8,
kp_9,
kp_decimal,
kp_equal,
kp_enter,
enter,
escape,
tab,
left_shift,
right_shift,
left_control,
right_control,
left_alt,
right_alt,
left_super,
right_super,
menu,
num_lock,
caps_lock,
print,
scroll_lock,
pause,
delete,
home,
end,
page_up,
page_down,
insert,
left,
right,
up,
down,
backspace,
space,
minus,
equal,
left_bracket,
right_bracket,
backslash,
semicolon,
apostrophe,
comma,
period,
slash,
grave,
unknown,
pub const max = Key.unknown;
};
pub const KeyMods = packed struct(u8) {
shift: bool,
control: bool,
alt: bool,
super: bool,
caps_lock: bool,
num_lock: bool,
_padding: u2 = 0,
};
pub const DisplayMode = enum {
/// Windowed mode.
windowed,
/// Fullscreen mode, using this option may change the display's video mode.
fullscreen,
/// Borderless fullscreen window.
///
/// Beware that true .fullscreen is also a hint to the OS that is used in various contexts, e.g.
///
/// * macOS: Moving to a virtual space dedicated to fullscreen windows as the user expects
/// * macOS: .borderless windows cannot prevent the system menu bar from being displayed
///
/// Always allow users to choose their preferred display mode.
borderless,
};
pub const CursorMode = enum {
/// Makes the cursor visible and behaving normally.
normal,
/// Makes the cursor invisible when it is over the content area of the window but does not
/// restrict it from leaving.
hidden,
/// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This is useful
/// for implementing for example 3D camera controls.
disabled,
};
pub const CursorShape = enum {
arrow,
ibeam,
crosshair,
pointing_hand,
resize_ew,
resize_ns,
resize_nwse,
resize_nesw,
resize_all,
not_allowed,
};
pub const Joystick = enum(u8) {
zero,
};
test {
@import("std").testing.refAllDecls(Timer);
@import("std").testing.refAllDecls(Frequency);
@import("std").testing.refAllDecls(platform);
@import("std").testing.refAllDeclsRecursive(Options);
@import("std").testing.refAllDeclsRecursive(EventIterator);
@import("std").testing.refAllDeclsRecursive(VSyncMode);
@import("std").testing.refAllDeclsRecursive(Size);
@import("std").testing.refAllDeclsRecursive(SizeOptional);
@import("std").testing.refAllDeclsRecursive(SizeLimit);
@import("std").testing.refAllDeclsRecursive(Position);
@import("std").testing.refAllDeclsRecursive(Event);
@import("std").testing.refAllDeclsRecursive(KeyEvent);
@import("std").testing.refAllDeclsRecursive(MouseButtonEvent);
@import("std").testing.refAllDeclsRecursive(MouseButton);
@import("std").testing.refAllDeclsRecursive(Key);
@import("std").testing.refAllDeclsRecursive(KeyMods);
@import("std").testing.refAllDeclsRecursive(DisplayMode);
@import("std").testing.refAllDeclsRecursive(CursorMode);
@import("std").testing.refAllDeclsRecursive(CursorShape);
@import("std").testing.refAllDeclsRecursive(Joystick);
}

View file

@ -1,36 +0,0 @@
// Check that the user's app matches the required interface.
comptime {
if (!@import("builtin").is_test) @import("mach").core.AppInterface(@import("app"));
}
// Forward "app" declarations into our namespace, such that @import("root").foo works as expected.
pub usingnamespace @import("app");
const App = @import("app").App;
const std = @import("std");
const mach = @import("mach");
const core = mach.core;
pub usingnamespace if (!@hasDecl(App, "SYSGPUInterface")) extern struct {
pub const SYSGPUInterface = mach.sysgpu.Impl;
} else struct {};
pub fn main() !void {
// Run from the directory where the executable is located so relative assets can be found.
var buffer: [1024]u8 = undefined;
const path = std.fs.selfExeDirPath(buffer[0..]) catch ".";
std.posix.chdir(path) catch {};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
core.allocator = gpa.allocator();
// Initialize GPU implementation
try mach.sysgpu.Impl.init(core.allocator, .{});
var app: App = undefined;
try app.init();
defer app.deinit();
while (!try core.update(&app)) {}
}

View file

@ -1,74 +0,0 @@
const builtin = @import("builtin");
const options = @import("build-options");
const use_glfw = true;
const use_x11 = false;
const platform = switch (options.core_platform) {
.glfw => @import("platform/glfw.zig"),
.x11 => @import("platform/x11.zig"),
.wayland => @import("platform/wayland.zig"),
.web => @import("platform/wasm.zig"),
};
pub const Core = platform.Core;
pub const Timer = platform.Timer;
// Verifies that a platform implementation exposes the expected function declarations.
comptime {
assertHasDecl(@This(), "Core");
assertHasDecl(@This(), "Timer");
// Core
assertHasDecl(@This().Core, "init");
assertHasDecl(@This().Core, "deinit");
assertHasDecl(@This().Core, "pollEvents");
assertHasDecl(@This().Core, "setTitle");
assertHasDecl(@This().Core, "setDisplayMode");
assertHasDecl(@This().Core, "displayMode");
assertHasDecl(@This().Core, "setBorder");
assertHasDecl(@This().Core, "border");
assertHasDecl(@This().Core, "setHeadless");
assertHasDecl(@This().Core, "headless");
assertHasDecl(@This().Core, "setVSync");
assertHasDecl(@This().Core, "vsync");
assertHasDecl(@This().Core, "setSize");
assertHasDecl(@This().Core, "size");
assertHasDecl(@This().Core, "setSizeLimit");
assertHasDecl(@This().Core, "sizeLimit");
assertHasDecl(@This().Core, "setCursorMode");
assertHasDecl(@This().Core, "cursorMode");
assertHasDecl(@This().Core, "setCursorShape");
assertHasDecl(@This().Core, "cursorShape");
assertHasDecl(@This().Core, "joystickPresent");
assertHasDecl(@This().Core, "joystickName");
assertHasDecl(@This().Core, "joystickButtons");
assertHasDecl(@This().Core, "joystickAxes");
assertHasDecl(@This().Core, "keyPressed");
assertHasDecl(@This().Core, "keyReleased");
assertHasDecl(@This().Core, "mousePressed");
assertHasDecl(@This().Core, "mouseReleased");
assertHasDecl(@This().Core, "mousePosition");
assertHasDecl(@This().Core, "outOfMemory");
// Timer
assertHasDecl(@This().Timer, "start");
assertHasDecl(@This().Timer, "read");
assertHasDecl(@This().Timer, "reset");
assertHasDecl(@This().Timer, "lap");
}
fn assertHasDecl(comptime T: anytype, comptime name: []const u8) void {
if (!@hasDecl(T, name)) @compileError("Core missing declaration: " ++ name);
}

View file

@ -1,479 +0,0 @@
const std = @import("std");
const js = @import("js.zig");
const Timer = @import("Timer.zig");
const mach = @import("../../../main.zig");
const mach_core = @import("../../main.zig");
const gpu = mach.gpu;
const Options = @import("../../main.zig").Options;
const Event = @import("../../main.zig").Event;
const KeyEvent = @import("../../main.zig").KeyEvent;
const MouseButtonEvent = @import("../../main.zig").MouseButtonEvent;
const MouseButton = @import("../../main.zig").MouseButton;
const Size = @import("../../main.zig").Size;
const Position = @import("../../main.zig").Position;
const DisplayMode = @import("../../main.zig").DisplayMode;
const SizeLimit = @import("../../main.zig").SizeLimit;
const CursorShape = @import("../../main.zig").CursorShape;
const VSyncMode = @import("../../main.zig").VSyncMode;
const CursorMode = @import("../../main.zig").CursorMode;
const Key = @import("../../main.zig").Key;
const KeyMods = @import("../../main.zig").KeyMods;
const Joystick = @import("../../main.zig").Joystick;
const InputState = @import("../../InputState.zig");
const Frequency = @import("../../Frequency.zig");
// Custom std.log implementation which logs to the browser console.
pub fn defaultLog(
comptime message_level: std.log.Level,
comptime scope: @Type(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
const writer = LogWriter{ .context = {} };
writer.print(message_level.asText() ++ prefix ++ format ++ "\n", args) catch return;
machLogFlush();
}
// Custom @panic implementation which logs to the browser console.
pub fn defaultPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
_ = ret_addr;
machPanic(msg.ptr, msg.len);
unreachable;
}
pub extern "mach" fn machPanic(str: [*]const u8, len: u32) void;
pub extern "mach" fn machLogWrite(str: [*]const u8, len: u32) void;
pub extern "mach" fn machLogFlush() void;
const LogError = error{};
const LogWriter = std.io.Writer(void, LogError, writeLog);
fn writeLog(_: void, msg: []const u8) LogError!usize {
machLogWrite(msg.ptr, msg.len);
return msg.len;
}
pub const Core = @This();
allocator: std.mem.Allocator,
frame: *Frequency,
input: *Frequency,
id: js.CanvasId,
input_state: InputState,
joysticks: [JoystickData.max_joysticks]JoystickData,
pub const EventIterator = struct {
core: *Core,
pub inline fn next(self: *EventIterator) ?Event {
while (true) {
const event_int = js.machEventShift();
if (event_int == -1) return null;
const event_type = @as(std.meta.Tag(Event), @enumFromInt(event_int));
return switch (event_type) {
.key_press, .key_repeat, .key_release => blk: {
const key = @as(Key, @enumFromInt(js.machEventShift()));
switch (event_type) {
.key_press => {
self.core.input_state.keys.set(@intFromEnum(key));
break :blk Event{
.key_press = .{
.key = key,
.mods = self.makeKeyMods(),
},
};
},
.key_repeat => break :blk Event{
.key_repeat = .{
.key = key,
.mods = self.makeKeyMods(),
},
},
.key_release => {
self.core.input_state.keys.unset(@intFromEnum(key));
break :blk Event{
.key_release = .{
.key = key,
.mods = self.makeKeyMods(),
},
};
},
else => unreachable,
}
continue;
},
.mouse_motion => blk: {
const x = @as(f64, @floatFromInt(js.machEventShift()));
const y = @as(f64, @floatFromInt(js.machEventShift()));
self.core.input_state.mouse_position = .{ .x = x, .y = y };
break :blk Event{
.mouse_motion = .{
.pos = .{
.x = x,
.y = y,
},
},
};
},
.mouse_press => blk: {
const button = toMachButton(js.machEventShift());
self.core.input_state.mouse_buttons.set(@intFromEnum(button));
break :blk Event{
.mouse_press = .{
.button = button,
.pos = self.core.input_state.mouse_position,
.mods = self.makeKeyMods(),
},
};
},
.mouse_release => blk: {
const button = toMachButton(js.machEventShift());
self.core.input_state.mouse_buttons.unset(@intFromEnum(button));
break :blk Event{
.mouse_release = .{
.button = button,
.pos = self.core.input_state.mouse_position,
.mods = self.makeKeyMods(),
},
};
},
.mouse_scroll => Event{
.mouse_scroll = .{
.xoffset = @as(f32, @floatCast(std.math.sign(js.machEventShiftFloat()))),
.yoffset = @as(f32, @floatCast(std.math.sign(js.machEventShiftFloat()))),
},
},
.joystick_connected => blk: {
const idx: u8 = @intCast(js.machEventShift());
const btn_count: usize = @intCast(js.machEventShift());
const axis_count: usize = @intCast(js.machEventShift());
if (idx >= JoystickData.max_joysticks) continue;
var data = &self.core.joysticks[idx];
data.present = true;
data.button_count = @min(JoystickData.max_button_count, btn_count);
data.axis_count = @min(JoystickData.max_axis_count, axis_count);
js.machJoystickName(idx, &data.name, JoystickData.max_name_len);
break :blk Event{ .joystick_connected = @enumFromInt(idx) };
},
.joystick_disconnected => blk: {
const idx: u8 = @intCast(js.machEventShift());
if (idx >= JoystickData.max_joysticks) continue;
var data = &self.core.joysticks[idx];
data.present = false;
data.button_count = 0;
data.axis_count = 0;
@memset(&data.buttons, false);
@memset(&data.axes, 0);
break :blk Event{ .joystick_disconnected = @enumFromInt(idx) };
},
.framebuffer_resize => blk: {
const width = @as(u32, @intCast(js.machEventShift()));
const height = @as(u32, @intCast(js.machEventShift()));
const pixel_ratio = @as(u32, @intCast(js.machEventShift()));
break :blk Event{
.framebuffer_resize = .{
.width = width * pixel_ratio,
.height = height * pixel_ratio,
},
};
},
.focus_gained => Event.focus_gained,
.focus_lost => Event.focus_lost,
else => null,
};
}
}
fn makeKeyMods(self: EventIterator) KeyMods {
const is = self.core.input_state;
return .{
.shift = is.isKeyPressed(.left_shift) or is.isKeyPressed(.right_shift),
.control = is.isKeyPressed(.left_control) or is.isKeyPressed(.right_control),
.alt = is.isKeyPressed(.left_alt) or is.isKeyPressed(.right_alt),
.super = is.isKeyPressed(.left_super) or is.isKeyPressed(.right_super),
// FIXME(estel): I think the logic for these two are wrong, but unlikely it matters
// in a browser. To correct them we need to actually use `KeyboardEvent.getModifierState`
// in javascript and bring back that info in here.
.caps_lock = is.isKeyPressed(.caps_lock),
.num_lock = is.isKeyPressed(.num_lock),
};
}
};
const JoystickData = struct {
present: bool,
button_count: usize,
axis_count: usize,
name: [max_name_len:0]u8,
buttons: [max_button_count]bool,
axes: [max_axis_count]f32,
// 16 as it's the maximum number of joysticks supported by GLFW.
const max_joysticks = 16;
const max_name_len = 64;
const max_button_count = 32;
const max_axis_count = 16;
};
pub fn init(
core: *Core,
allocator: std.mem.Allocator,
frame: *Frequency,
input: *Frequency,
options: Options,
) !void {
_ = options;
var selector = [1]u8{0} ** 15;
const id = js.machCanvasInit(&selector[0]);
core.* = Core{
.allocator = allocator,
.frame = frame,
.input = input,
.id = id,
.input_state = .{},
.joysticks = std.mem.zeroes([JoystickData.max_joysticks]JoystickData),
};
// TODO(wasm): wgpu support
mach_core.adapter = undefined;
mach_core.device = undefined;
mach_core.queue = undefined;
mach_core.swap_chain = undefined;
mach_core.descriptor = undefined;
try core.frame.start();
try core.input.start();
}
pub fn deinit(self: *Core) void {
js.machCanvasDeinit(self.id);
}
pub inline fn update(self: *Core, app: anytype) !bool {
self.frame.tick();
self.input.tick();
if (try app.update()) return true;
if (@hasDecl(std.meta.Child(@TypeOf(app)), "updateMainThread")) {
if (app.updateMainThread() catch |err| @panic(@errorName(err))) {
return true;
}
}
return false;
}
pub inline fn pollEvents(self: *Core) EventIterator {
return EventIterator{
.core = self,
};
}
pub fn setTitle(self: *Core, title: [:0]const u8) void {
js.machCanvasSetTitle(self.id, title.ptr, title.len);
}
pub fn setDisplayMode(self: *Core, _mode: DisplayMode) void {
var mode = _mode;
if (mode == .borderless) {
// borderless fullscreen window has no meaning in web
mode = .fullscreen;
}
js.machCanvasSetDisplayMode(self.id, @intFromEnum(mode));
}
pub fn displayMode(self: *Core) DisplayMode {
return @as(DisplayMode, @enumFromInt(js.machDisplayMode(self.id)));
}
pub fn setBorder(self: *Core, value: bool) void {
_ = self;
_ = value;
}
pub fn border(self: *Core) bool {
_ = self;
return false;
}
pub fn setHeadless(self: *Core, value: bool) void {
_ = self;
_ = value;
}
pub fn headless(self: *Core) bool {
_ = self;
return false;
}
pub fn setVSync(self: *Core, mode: VSyncMode) void {
_ = mode;
self.frame.target = 0;
}
// TODO(wasm): https://github.com/gpuweb/gpuweb/issues/1224
pub fn vsync(self: *Core) VSyncMode {
_ = self;
return .double;
}
pub fn setSize(self: *Core, value: Size) void {
js.machCanvasSetSize(self.id, value.width, value.height);
}
pub fn size(self: *Core) Size {
return .{
.width = js.machCanvasWidth(self.id),
.height = js.machCanvasHeight(self.id),
};
}
pub fn setSizeLimit(self: *Core, limit: SizeLimit) void {
js.machCanvasSetSizeLimit(
self.id,
if (limit.min.width) |val| @as(i32, @intCast(val)) else -1,
if (limit.min.height) |val| @as(i32, @intCast(val)) else -1,
if (limit.max.width) |val| @as(i32, @intCast(val)) else -1,
if (limit.max.height) |val| @as(i32, @intCast(val)) else -1,
);
}
pub fn sizeLimit(self: *Core) SizeLimit {
return .{
.min = .{
.width = js.machCanvasMinWidth(self.id),
.height = js.machCanvasMinHeight(self.id),
},
.max = .{
.width = js.machCanvasMaxWidth(self.id),
.height = js.machCanvasMaxHeight(self.id),
},
};
}
pub fn setCursorMode(self: *Core, mode: CursorMode) void {
js.machSetCursorMode(self.id, @intFromEnum(mode));
}
pub fn cursorMode(self: *Core) CursorMode {
return @as(CursorMode, @enumFromInt(js.machCursorMode(self.id)));
}
pub fn setCursorShape(self: *Core, shape: CursorShape) void {
js.machSetCursorShape(self.id, @intFromEnum(shape));
}
pub fn cursorShape(self: *Core) CursorShape {
return @as(CursorShape, @enumFromInt(js.machCursorShape(self.id)));
}
pub fn joystickPresent(core: *Core, joystick: Joystick) bool {
const idx: u8 = @intFromEnum(joystick);
return core.joysticks[idx].present;
}
pub fn joystickName(core: *Core, joystick: Joystick) ?[:0]const u8 {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
return std.mem.span(&data.name);
}
pub fn joystickButtons(core: *Core, joystick: Joystick) ?[]const bool {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
js.machJoystickButtons(idx, &data.buttons, JoystickData.max_button_count);
return data.buttons[0..data.button_count];
}
pub fn joystickAxes(core: *Core, joystick: Joystick) ?[]const f32 {
const idx: u8 = @intFromEnum(joystick);
var data = &core.joysticks[idx];
if (!data.present) return null;
js.machJoystickAxes(idx, &data.axes, JoystickData.max_axis_count);
return data.buttons[0..data.button_count];
}
pub fn keyPressed(self: *Core, key: Key) bool {
return self.input_state.isKeyPressed(key);
}
pub fn keyReleased(self: *Core, key: Key) bool {
return self.input_state.isKeyReleased(key);
}
pub fn mousePressed(self: *Core, button: MouseButton) bool {
return self.input_state.isMouseButtonPressed(button);
}
pub fn mouseReleased(self: *Core, button: MouseButton) bool {
return self.input_state.isMouseButtonReleased(button);
}
pub fn mousePosition(self: *Core) Core.Position {
return self.input_state.mouse_position;
}
pub inline fn adapter(_: *Core) *gpu.Adapter {
unreachable;
}
pub inline fn device(_: *Core) *gpu.Device {
unreachable;
}
pub inline fn swapChain(_: *Core) *gpu.SwapChain {
unreachable;
}
pub inline fn descriptor(self: *Core) gpu.SwapChain.Descriptor {
return .{
.label = "main swap chain",
.usage = .{ .render_attachment = true },
.format = .bgra8_unorm, // TODO(wasm): is this correct?
.width = js.machCanvasFramebufferWidth(self.id),
.height = js.machCanvasFramebufferHeight(self.id),
.present_mode = .fifo, // TODO(wasm): https://github.com/gpuweb/gpuweb/issues/1224
};
}
pub inline fn outOfMemory(self: *Core) bool {
_ = self;
return false;
}
pub inline fn wakeMainThread(self: *Core) void {
_ = self;
}
fn toMachButton(button: i32) MouseButton {
return switch (button) {
0 => .left,
1 => .middle,
2 => .right,
3 => .four,
4 => .five,
else => unreachable,
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
///! This code is taken from https://github.com/glfw/glfw/blob/master/src/xkb_unicode.c
const c = @import("Core.zig").c;
const c = @import("../X11.zig").c;
const keysym_table = &[_]struct { c.KeySym, u21 }{
.{ 0x01a1, 0x0104 },

View file

@ -29,8 +29,8 @@ pub const components = .{
\\ is configured to be the center of the window:
\\
\\ ```
\\ const width_px: f32 = @floatFromInt(mach.core.size().width);
\\ const height_px: f32 = @floatFromInt(mach.core.size().height);
\\ const width_px: f32 = @floatFromInt(core.state().size().width);
\\ const height_px: f32 = @floatFromInt(core.state().size().height);
\\ const projection = math.Mat4x4.projection2D(.{
\\ .left = -width_px / 2.0,
\\ .right = width_px / 2.0,
@ -345,8 +345,8 @@ fn preRender(entities: *mach.Entities.Mod, core: *mach.Core.Mod, sprite_pipeline
while (q.next()) |v| {
for (v.ids, v.built_pipelines) |id, built| {
const view_projection = sprite_pipeline.get(id, .view_projection) orelse blk: {
const width_px: f32 = @floatFromInt(mach.core.size().width);
const height_px: f32 = @floatFromInt(mach.core.size().height);
const width_px: f32 = @floatFromInt(core.state().size().width);
const height_px: f32 = @floatFromInt(core.state().size().height);
break :blk math.Mat4x4.projection2D(.{
.left = -width_px / 2,
.right = width_px / 2,

View file

@ -25,8 +25,8 @@ pub const components = .{
\\ is configured to be the center of the window:
\\
\\ ```
\\ const width_px: f32 = @floatFromInt(mach.core.size().width);
\\ const height_px: f32 = @floatFromInt(mach.core.size().height);
\\ const width_px: f32 = @floatFromInt(core.state().size().width);
\\ const height_px: f32 = @floatFromInt(core.state().size().height);
\\ const projection = math.Mat4x4.projection2D(.{
\\ .left = -width_px / 2.0,
\\ .right = width_px / 2.0,
@ -378,8 +378,8 @@ fn preRender(entities: *mach.Entities.Mod, core: *mach.Core.Mod, text_pipeline:
while (q.next()) |v| {
for (v.ids, v.built_pipelines) |id, built| {
const view_projection = text_pipeline.get(id, .view_projection) orelse blk: {
const width_px: f32 = @floatFromInt(mach.core.size().width);
const height_px: f32 = @floatFromInt(mach.core.size().height);
const width_px: f32 = @floatFromInt(core.state().size().width);
const height_px: f32 = @floatFromInt(core.state().size().height);
break :blk math.Mat4x4.projection2D(.{
.left = -width_px / 2,
.right = width_px / 2,

View file

@ -1,11 +1,11 @@
const build_options = @import("build-options");
const builtin = @import("builtin");
const std = @import("std");
// Core
pub const core = if (build_options.want_core) @import("core/main.zig") else struct {};
pub const Timer = if (build_options.want_core) core.Timer else struct {};
pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {};
pub const Core = if (build_options.want_core) @import("Core.zig") else struct {};
pub const Timer = if (build_options.want_core) Core.Timer else struct {};
pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {};
// Mach standard library
// gamemode requires libc on linux
@ -32,6 +32,7 @@ pub const modules = blk: {
pub const ModSet = @import("module/main.zig").ModSet;
pub const Modules = @import("module/main.zig").Modules(modules);
pub const Mod = ModSet(modules).Mod;
pub const ModuleName = @import("module/main.zig").ModuleName(modules);
pub const EntityID = @import("module/main.zig").EntityID; // TODO: rename to just Entity?
pub const Archetype = @import("module/main.zig").Archetype;
@ -44,11 +45,52 @@ pub const Entities = @import("module/main.zig").Entities;
pub const is_debug = builtin.mode == .Debug;
pub const App = struct {
mods: *Modules,
comptime main_mod: ModuleName = .app,
pub fn init(allocator: std.mem.Allocator, comptime main_mod: ModuleName) !App {
var mods: *Modules = try allocator.create(Modules);
try mods.init(allocator);
return .{
.mods = mods,
.main_mod = main_mod,
};
}
pub fn deinit(app: *App, allocator: std.mem.Allocator) void {
app.mods.deinit(allocator);
allocator.destroy(app.mods);
}
pub fn run(app: *App, core_options: Core.InitOptions) !void {
var stack_space: [8 * 1024 * 1024]u8 = undefined;
app.mods.mod.mach_core.init(undefined); // TODO
app.mods.scheduleWithArgs(.mach_core, .init, .{core_options});
app.mods.schedule(app.main_mod, .init);
// Main loop
while (!app.mods.mod.mach_core.state().should_close) {
// Dispatch events until queue is empty
try app.mods.dispatch(&stack_space, .{});
// Run `update` when `init` and all other systems are exectued
app.mods.schedule(app.main_mod, .update);
app.mods.schedule(.mach_core, .present_frame);
}
app.mods.schedule(app.main_mod, .deinit);
app.mods.schedule(.mach_core, .deinit);
// Final Dispatch to deinitalize resources
try app.mods.dispatch(&stack_space, .{});
}
};
test {
const std = @import("std");
// TODO: refactor code so we can use this here:
// std.testing.refAllDeclsRecursive(@This());
_ = core;
_ = Core;
_ = gpu;
_ = sysaudio;
_ = sysgpu;

View file

@ -7,6 +7,7 @@ pub const Database = @import("entities.zig").Database;
pub const Archetype = @import("Archetype.zig");
pub const ModSet = @import("module.zig").ModSet;
pub const Modules = @import("module.zig").Modules;
pub const ModuleName = @import("module.zig").ModuleName;
pub const ModuleID = @import("module.zig").ModuleID;
pub const SystemID = @import("module.zig").SystemID;
pub const AnySystem = @import("module.zig").AnySystem;