const std = @import("std"); const app_pkg = @import("app"); const Core = @import("../Core.zig"); const structs = @import("../structs.zig"); const enums = @import("../enums.zig"); const js = struct { extern fn machCanvasInit(selector_id: *u8) CanvasId; extern fn machCanvasDeinit(canvas: CanvasId) void; extern fn machCanvasSetTitle(canvas: CanvasId, title: [*]const u8, len: u32) void; extern fn machCanvasSetSize(canvas: CanvasId, width: u32, height: u32) void; extern fn machCanvasSetFullscreen(canvas: CanvasId, value: bool) void; extern fn machCanvasGetWindowWidth(canvas: CanvasId) u32; extern fn machCanvasGetWindowHeight(canvas: CanvasId) u32; extern fn machCanvasGetFramebufferWidth(canvas: CanvasId) u32; extern fn machCanvasGetFramebufferHeight(canvas: CanvasId) u32; extern fn machSetMouseCursor(cursor_name: [*]const u8, len: u32) void; extern fn machEmitCloseEvent() void; extern fn machSetWaitEvent(timeout: f64) void; extern fn machHasEvent() bool; extern fn machEventShift() i32; extern fn machEventShiftFloat() f64; extern fn machChangeShift() u32; extern fn machPerfNow() f64; extern fn machLog(str: [*]const u8, len: u32) void; extern fn machLogWrite(str: [*]const u8, len: u32) void; extern fn machLogFlush() void; extern fn machPanic(str: [*]const u8, len: u32) void; }; const common = @import("common.zig"); comptime { common.checkApplication(app_pkg); } const App = app_pkg.App; pub const CanvasId = u32; pub const Platform = struct { id: CanvasId, selector_id: []const u8, last_window_size: structs.Size, last_framebuffer_size: structs.Size, last_cursor_position: structs.WindowPos, last_key_mods: structs.KeyMods, pub fn init(allocator: std.mem.Allocator, eng: *Core) !Platform { var selector = [1]u8{0} ** 15; const id = js.machCanvasInit(&selector[0]); var platform = Platform{ .id = id, .selector_id = try allocator.dupe(u8, selector[0 .. selector.len - @as(u32, if (selector[selector.len - 1] == 0) 1 else 0)]), .last_window_size = .{ .width = js.machCanvasGetWindowWidth(id), .height = js.machCanvasGetWindowHeight(id), }, .last_framebuffer_size = .{ .width = js.machCanvasGetFramebufferWidth(id), .height = js.machCanvasGetFramebufferHeight(id), }, // TODO initialize these properly .last_cursor_position = .{ .x = 0, .y = 0, }, .last_key_mods = .{ .shift = false, .control = false, .alt = false, .super = false, .caps_lock = false, .num_lock = false, }, }; try platform.setOptions(eng.options); return platform; } pub fn setOptions(platform: *Platform, options: structs.Options) !void { // NOTE: size limits do not exists on wasm js.machCanvasSetSize(platform.id, options.width, options.height); const title = std.mem.span(options.title); js.machCanvasSetTitle(platform.id, title.ptr, title.len); js.machCanvasSetFullscreen(platform.id, options.fullscreen); } pub fn setShouldClose(_: *Platform, value: bool) void { if (value) js.machEmitCloseEvent(); } pub fn setWaitEvent(_: *Platform, timeout: f64) void { js.machSetWaitEvent(timeout); } pub fn getFramebufferSize(platform: *Platform) structs.Size { return platform.last_framebuffer_size; } pub fn getWindowSize(platform: *Platform) structs.Size { return platform.last_window_size; } pub fn setMouseCursor(_: *Platform, cursor: enums.MouseCursor) !void { const cursor_name = @tagName(cursor); js.machSetMouseCursor(cursor_name.ptr, cursor_name.len); } fn pollChanges(platform: *Platform) void { const change_type = js.machChangeShift(); switch (change_type) { 1 => { const width = js.machChangeShift(); const height = js.machChangeShift(); const device_pixel_ratio = js.machChangeShift(); platform.last_window_size = .{ .width = @divFloor(width, device_pixel_ratio), .height = @divFloor(height, device_pixel_ratio), }; platform.last_framebuffer_size = .{ .width = width, .height = height, }; }, else => {}, } } pub fn hasEvent(_: *Platform) bool { return js.machHasEvent(); } pub fn pollEvent(platform: *Platform) ?structs.Event { const event_type = js.machEventShift(); return switch (event_type) { 1, 2 => key_down: { const key = @intToEnum(enums.Key, js.machEventShift()); switch (key) { .left_shift, .right_shift => platform.last_key_mods.shift = true, .left_control, .right_control => platform.last_key_mods.control = true, .left_alt, .right_alt => platform.last_key_mods.alt = true, .left_super, .right_super => platform.last_key_mods.super = true, .caps_lock => platform.last_key_mods.caps_lock = true, .num_lock => platform.last_key_mods.num_lock = true, else => {}, } break :key_down switch (event_type) { 1 => structs.Event{ .key_press = .{ .key = key, .mods = platform.last_key_mods, }, }, 2 => structs.Event{ .key_repeat = .{ .key = key, .mods = platform.last_key_mods, }, }, else => unreachable, }; }, 3 => key_release: { const key = @intToEnum(enums.Key, js.machEventShift()); switch (key) { .left_shift, .right_shift => platform.last_key_mods.shift = false, .left_control, .right_control => platform.last_key_mods.control = false, .left_alt, .right_alt => platform.last_key_mods.alt = false, .left_super, .right_super => platform.last_key_mods.super = false, .caps_lock => platform.last_key_mods.caps_lock = false, .num_lock => platform.last_key_mods.num_lock = false, else => {}, } break :key_release structs.Event{ .key_release = .{ .key = key, .mods = platform.last_key_mods, }, }; }, 4 => mouse_motion: { const x = @intToFloat(f64, js.machEventShift()); const y = @intToFloat(f64, js.machEventShift()); platform.last_cursor_position = .{ .x = x, .y = y, }; break :mouse_motion structs.Event{ .mouse_motion = .{ .pos = .{ .x = x, .y = y, }, }, }; }, 5 => structs.Event{ .mouse_press = .{ .button = toMachButton(js.machEventShift()), .pos = platform.last_cursor_position, .mods = platform.last_key_mods, }, }, 6 => structs.Event{ .mouse_release = .{ .button = toMachButton(js.machEventShift()), .pos = platform.last_cursor_position, .mods = platform.last_key_mods, }, }, 7 => structs.Event{ .mouse_scroll = .{ .xoffset = @floatCast(f32, sign(js.machEventShiftFloat())), .yoffset = @floatCast(f32, sign(js.machEventShiftFloat())), }, }, 8 => structs.Event.focus_gained, 9 => structs.Event.focus_lost, else => null, }; } inline fn sign(val: f64) f64 { return switch (val) { 0.0 => 0.0, else => -val, }; } fn toMachButton(button: i32) enums.MouseButton { return switch (button) { 0 => .left, 1 => .middle, 2 => .right, 3 => .four, 4 => .five, else => unreachable, }; } }; pub const BackingTimer = struct { initial: f64 = undefined, const WasmTimer = @This(); pub fn start() !WasmTimer { return WasmTimer{ .initial = js.machPerfNow() }; } pub fn read(timer: *WasmTimer) u64 { return timeToNs(js.machPerfNow() - timer.initial); } pub fn reset(timer: *WasmTimer) void { timer.initial = js.machPerfNow(); } pub fn lap(timer: *WasmTimer) u64 { const now = js.machPerfNow(); const initial = timer.initial; timer.initial = now; return timeToNs(now - initial); } fn timeToNs(t: f64) u64 { return @floatToInt(u64, t) * 1000000; } }; var app: App = undefined; var core: Core = undefined; export fn wasmInit() void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); core = Core.init(allocator) catch unreachable; app.init(&core) catch {}; } export fn wasmUpdate() void { // Poll internal events, like resize core.internal.pollChanges(); core.delta_time_ns = core.timer.lapPrecise(); core.delta_time = @intToFloat(f32, core.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s); app.update(&core) catch core.setShouldClose(true); } export fn wasmDeinit() void { app.deinit(&core); } pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level; pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{}; const LogError = error{}; const LogWriter = std.io.Writer(void, LogError, writeLog); fn writeLog(_: void, msg: []const u8) LogError!usize { js.machLogWrite(msg.ptr, msg.len); return msg.len; } pub fn log( 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; js.machLogFlush(); } pub fn panic(msg: []const u8, _: ?*std.builtin.StackTrace) noreturn { js.machPanic(msg.ptr, msg.len); unreachable; }