479 lines
15 KiB
Zig
479 lines
15 KiB
Zig
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,
|
|
};
|
|
}
|