core: refactor
This commit is contained in:
parent
c254337e4b
commit
266e7a548b
38 changed files with 4119 additions and 4836 deletions
1025
src/Core.zig
1025
src/Core.zig
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,4 @@
|
|||
const std = @import("std");
|
||||
const core = @import("main.zig");
|
||||
const Timer = @import("Timer.zig");
|
||||
|
||||
pub const Frequency = @This();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
1385
src/core/Wayland.zig
1385
src/core/Wayland.zig
File diff suppressed because it is too large
Load diff
1166
src/core/X11.zig
1166
src/core/X11.zig
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)) {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
52
src/main.zig
52
src/main.zig
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue