{mach,core}: move core sources to libs/core

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2023-01-24 00:11:19 -07:00 committed by Stephen Gutekanst
parent 562b908c84
commit 9bbada90b2
18 changed files with 13 additions and 79 deletions

View file

@ -1,401 +0,0 @@
const builtin = @import("builtin");
const std = @import("std");
const gpu = @import("gpu");
const platform = @import("platform.zig");
pub const Core = @This();
internal: platform.Core,
pub const Options = struct {
is_app: bool = false,
title: [*:0]const u8 = "Mach Engine",
size: Size = .{ .width = 640, .height = 640 },
power_preference: gpu.PowerPreference = .undefined,
required_features: ?[]const gpu.FeatureName = null,
required_limits: ?gpu.Limits = null,
};
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
try platform.Core.init(&core.internal, allocator, options);
}
pub fn deinit(core: *Core) void {
return core.internal.deinit();
}
pub fn hasEvent(core: *Core) bool {
return core.internal.hasEvent();
}
pub fn pollEvents(core: *Core) ?Event {
return core.internal.pollEvents();
}
/// Returns the framebuffer size, in subpixel units.
pub fn framebufferSize(core: *Core) Size {
return core.internal.framebufferSize();
}
/// Sets seconds to wait for an event with timeout when calling `Core.update()`
/// again.
///
/// timeout is in seconds (<= `0.0` disables waiting)
/// - pass `std.math.inf(f64)` to wait with no timeout
///
/// `Core.update()` will return earlier than timeout if an event happens (key press,
/// mouse motion, etc.)
///
/// `Core.update()` can return a bit later than timeout due to timer precision and
/// process scheduling.
pub fn setWaitTimeout(core: *Core, timeout: f64) void {
return core.internal.setWaitTimeout(timeout);
}
/// Set the window title
pub fn setTitle(core: *Core, title: [:0]const u8) void {
return core.internal.setTitle(title);
}
/// Set the window mode
pub fn setDisplayMode(core: *Core, mode: DisplayMode, monitor: ?usize) void {
return core.internal.setDisplayMode(mode, monitor);
}
/// Returns the window mode
pub fn displayMode(core: *Core) DisplayMode {
return core.internal.displayMode();
}
pub fn setBorder(core: *Core, value: bool) void {
return core.internal.setBorder(value);
}
pub fn border(core: *Core) bool {
return core.internal.border();
}
pub fn setHeadless(core: *Core, value: bool) void {
return core.internal.setHeadless(value);
}
pub fn headless(core: *Core) bool {
return core.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 monitor synchronization mode.
pub fn setVSync(core: *Core, mode: VSyncMode) void {
return core.internal.setVSync(mode);
}
/// Returns monitor synchronization mode.
pub fn vsync(core: *Core) VSyncMode {
return core.internal.vsync();
}
/// Set the window size, in subpixel units.
pub fn setSize(core: *Core, value: Size) void {
return core.internal.setSize(value);
}
/// Returns the window size, in subpixel units.
pub fn size(core: *Core) Size {
return core.internal.size();
}
/// Set the minimum and maximum allowed size for the window.
pub fn setSizeLimit(core: *Core, size_limit: SizeLimit) void {
return core.internal.setSizeLimit(size_limit);
}
/// Returns the minimum and maximum allowed size for the window.
pub fn sizeLimit(core: *Core) SizeLimit {
return core.internal.sizeLimit();
}
pub fn setCursorMode(core: *Core, mode: CursorMode) void {
return core.internal.setCursorMode(mode);
}
pub fn cursorMode(core: *Core) CursorMode {
return core.internal.cursorMode();
}
pub fn setCursorShape(core: *Core, cursor: CursorShape) void {
return core.internal.setCursorShape(cursor);
}
pub fn cursorShape(core: *Core) CursorShape {
return core.internal.cursorShape();
}
pub fn adapter(core: *Core) *gpu.Adapter {
return core.internal.adapter();
}
pub fn device(core: *Core) *gpu.Device {
return core.internal.device();
}
pub fn swapChain(core: *Core) *gpu.SwapChain {
return core.internal.swapChain();
}
pub fn descriptor(core: *Core) gpu.SwapChain.Descriptor {
return core.internal.descriptor();
}
pub const Size = struct {
width: u32,
height: u32,
};
pub const SizeOptional = struct {
width: ?u32,
height: ?u32,
};
pub const SizeLimit = struct {
min: SizeOptional,
max: SizeOptional,
};
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,
},
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 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 KeyMods = packed struct {
shift: bool,
control: bool,
alt: bool,
super: bool,
caps_lock: bool,
num_lock: bool,
_reserved: u2 = 0,
};
pub const DisplayMode = enum {
windowed,
fullscreen,
// TODO: fullscreen_windowed,
};
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,
};

View file

@ -1,38 +0,0 @@
const std = @import("std");
const platform = @import("platform.zig");
pub const Timer = @This();
internal: platform.Timer,
/// Initialize the timer.
pub fn start() !Timer {
return Timer{
.internal = try platform.Timer.start(),
};
}
/// Reads the timer value since start or the last reset in nanoseconds.
pub inline fn readPrecise(timer: *Timer) u64 {
return timer.internal.read();
}
/// Reads the timer value since start or the last reset in seconds.
pub inline fn read(timer: *Timer) f32 {
return @intToFloat(f32, timer.readPrecise()) / @intToFloat(f32, std.time.ns_per_s);
}
/// Resets the timer value to 0/now.
pub inline fn reset(timer: *Timer) void {
timer.internal.reset();
}
/// Returns the current value of the timer in nanoseconds, then resets it.
pub inline fn lapPrecise(timer: *Timer) u64 {
return timer.internal.lap();
}
/// Returns the current value of the timer in seconds, then resets it.
pub inline fn lap(timer: *Timer) f32 {
return @intToFloat(f32, timer.lapPrecise()) / @intToFloat(f32, std.time.ns_per_s);
}

View file

@ -1,54 +0,0 @@
const std = @import("std");
pub const Core = @import("Core.zig");
pub const gpu = @import("gpu");
pub const ecs = @import("ecs");
/// The Mach engine ECS module. This enables access to `engine.get(.mach, .core)` `*Core` APIs, as
/// to for example `.setOptions(.{.title = "foobar"})`, or to access the GPU device via
/// `engine.get(.mach, .device)`
pub const module = ecs.Module(.{
.globals = struct {
core: *Core,
device: *gpu.Device,
},
});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
pub fn App(
comptime modules: anytype,
comptime app_init: anytype, // fn (engine: *ecs.World(modules)) !void
) type {
// TODO: validate modules.mach is the expected type.
// TODO: validate init has the right function signature
return struct {
engine: ecs.World(modules),
core: Core,
pub fn init(app: *@This()) !void {
try app.core.init(allocator, .{});
app.* = .{
.core = app.core,
.engine = try ecs.World(modules).init(allocator),
};
app.engine.set(.mach, .core, &app.core);
app.engine.set(.mach, .device, app.core.device());
try app_init(&app.engine);
}
pub fn deinit(app: *@This()) void {
const core = app.engine.get(.mach, .core);
core.deinit();
allocator.destroy(core);
app.engine.deinit();
_ = gpa.deinit();
}
pub fn update(app: *@This()) !bool {
app.engine.tick();
return false;
}
};
}

View file

@ -1,40 +0,0 @@
const builtin = @import("builtin");
pub usingnamespace @import("platform.zig").entry;
comptime {
if (!builtin.is_test) {
if (!@hasDecl(@import("app"), "App")) {
@compileError("expected e.g. `pub const App = mach.App(modules, init)' (App definition missing in your main Zig file)");
}
const App = @import("app").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) @typeInfo(@typeInfo(InitFn).Fn.return_type.?).ErrorUnion.error_set!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) @typeInfo(@typeInfo(UpdateFn).Fn.return_type.?).ErrorUnion.error_set!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, "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'");
}
}
}

View file

@ -1,62 +0,0 @@
const builtin = @import("builtin");
pub usingnamespace if (builtin.cpu.arch == .wasm32)
@import("platform/wasm.zig")
else
@import("platform/native.zig");
// Verifies that a platform implementation exposes the expected function declarations.
comptime {
assertHasDecl(@This(), "entry");
assertHasDecl(@This(), "Core");
assertHasDecl(@This(), "Timer");
// Core
assertHasDecl(@This().Core, "init");
assertHasDecl(@This().Core, "deinit");
assertHasDecl(@This().Core, "hasEvent");
assertHasDecl(@This().Core, "pollEvents");
assertHasDecl(@This().Core, "framebufferSize");
assertHasDecl(@This().Core, "setWaitTimeout");
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, "adapter");
assertHasDecl(@This().Core, "device");
assertHasDecl(@This().Core, "swapChain");
assertHasDecl(@This().Core, "descriptor");
// Timer
assertHasDecl(@This().Timer, "start");
assertHasDecl(@This().Timer, "read");
assertHasDecl(@This().Timer, "reset");
assertHasDecl(@This().Timer, "lap");
}
fn assertHasDecl(comptime T: anytype, comptime name: []const u8) void {
if (!@hasDecl(T, name)) @compileError("Core missing declaration: " ++ name);
}

View file

@ -1,50 +0,0 @@
const std = @import("std");
const gpu = @import("gpu");
const ecs = @import("ecs");
const glfw = @import("glfw");
const Core = @import("../Core.zig");
const native = @import("native.zig");
pub const App = @This();
pub const GPUInterface = gpu.dawn.Interface;
const _ = gpu.Export(GPUInterface);
// Current Limitations:
// 1. Currently, ecs seems to be using some weird compile-time type trickery, so I'm not exactly sure how
// `engine` should be integrated into the C API
// 2. Core might need to expose more state so more API functions can be exposed (for example, the WebGPU API)
// 3. Be very careful about arguments, types, memory, etc - any mismatch will result in undefined behavior
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Returns a pointer to a newly allocated Core
// Will return a null pointer if an error occurred while initializing Core
pub export fn mach_core_init() ?*native.Core {
gpu.Impl.init();
// TODO(libmach): eliminate this allocation
var core = allocator.create(native.Core) catch {
return @intToPtr(?*native.Core, 0);
};
// TODO(libmach): allow passing init options
core.init(allocator, .{}) catch {
// TODO(libmach): better error handling
return @intToPtr(?*native.Core, 0);
};
return core;
}
pub export fn mach_core_deinit(core: *native.Core) void {
native.Core.deinit(core);
}
// pub export fn mach_core_poll_events(core: *native.Core) Core.Event {
// return native.Core.pollEvents(core);
// }
const MachStatus = enum(c_int) {
Success = 0x00000000,
Error = 0x00000001,
};

View file

@ -1,5 +0,0 @@
const std = @import("std");
pub const entry = @import("native/entry.zig");
pub const Core = @import("native/Core.zig");
pub const Timer = std.time.Timer;

View file

@ -1,765 +0,0 @@
const builtin = @import("builtin");
const std = @import("std");
const gpu = @import("gpu");
const glfw = @import("glfw");
const util = @import("util.zig");
const Options = @import("../../Core.zig").Options;
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 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;
pub const Core = @This();
allocator: std.mem.Allocator,
window: glfw.Window,
backend_type: gpu.BackendType,
user_ptr: UserPtr,
instance: *gpu.Instance,
surface: *gpu.Surface,
gpu_adapter: *gpu.Adapter,
gpu_device: *gpu.Device,
swap_chain: *gpu.SwapChain,
swap_chain_desc: gpu.SwapChain.Descriptor,
events: EventQueue,
wait_timeout: f64,
last_size: glfw.Window.Size,
last_pos: glfw.Window.Pos,
size_limit: SizeLimit,
frame_buffer_resized: bool,
current_cursor: CursorShape,
cursors: [@typeInfo(CursorShape).Enum.fields.len]?glfw.Cursor,
cursors_tried: [@typeInfo(CursorShape).Enum.fields.len]bool,
linux_gamemode: ?bool,
const EventQueue = std.TailQueue(Event);
const EventNode = EventQueue.Node;
const UserPtr = struct {
self: *Core,
};
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
const backend_type = try util.detectBackendType(allocator);
glfw.setErrorCallback(errorCallback);
if (!glfw.init(.{}))
glfw.getErrorCode() catch |err| switch (err) {
error.PlatformError,
error.PlatformUnavailable,
=> return err,
else => unreachable,
};
// Create the test window and discover adapters using it (esp. for OpenGL)
var hints = util.glfwWindowHintsForBackend(backend_type);
hints.cocoa_retina_framebuffer = true;
const window = glfw.Window.create(
options.size.width,
options.size.height,
options.title,
null,
null,
hints,
) orelse switch (glfw.mustGetErrorCode()) {
error.InvalidEnum,
error.InvalidValue,
error.FormatUnavailable,
=> unreachable,
error.APIUnavailable,
error.VersionUnavailable,
error.PlatformError,
=> |err| return err,
else => unreachable,
};
switch (backend_type) {
.opengl, .opengles => {
glfw.makeContextCurrent(window);
glfw.getErrorCode() catch |err| switch (err) {
error.PlatformError => return err,
else => unreachable,
};
},
else => {},
}
const instance = gpu.createInstance(null) orelse {
std.log.err("mach: failed to create GPU instance", .{});
std.process.exit(1);
};
const surface = util.createSurfaceForWindow(instance, window, comptime util.detectGLFWOptions());
var response: util.RequestAdapterResponse = undefined;
instance.requestAdapter(&gpu.RequestAdapterOptions{
.compatible_surface = surface,
.power_preference = options.power_preference,
.force_fallback_adapter = false,
}, &response, util.requestAdapterCallback);
if (response.status != .success) {
std.log.err("mach: failed to create GPU adapter: {?s}", .{response.message});
std.log.info("-> maybe try MACH_GPU_BACKEND=opengl ?", .{});
std.process.exit(1);
}
// Print which adapter we are going to use.
var props = std.mem.zeroes(gpu.Adapter.Properties);
response.adapter.getProperties(&props);
if (props.backend_type == .null) {
std.log.err("no backend found for {s} adapter", .{props.adapter_type.name()});
std.process.exit(1);
}
std.log.info("mach: found {s} backend on {s} adapter: {s}, {s}\n", .{
props.backend_type.name(),
props.adapter_type.name(),
props.name,
props.driver_description,
});
// Create a device with default limits/features.
const gpu_device = response.adapter.createDevice(&.{
.required_features_count = if (options.required_features) |v| @intCast(u32, v.len) else 0,
.required_features = if (options.required_features) |v| @as(?[*]const gpu.FeatureName, v.ptr) else null,
.required_limits = if (options.required_limits) |limits| @as(?*gpu.RequiredLimits, &gpu.RequiredLimits{
.limits = limits,
}) else null,
}) orelse {
std.log.err("mach: failed to create GPU device\n", .{});
std.process.exit(1);
};
gpu_device.setUncapturedErrorCallback({}, util.printUnhandledErrorCallback);
const framebuffer_size = window.getFramebufferSize();
const swap_chain_desc = gpu.SwapChain.Descriptor{
.label = "main swap chain",
.usage = .{ .render_attachment = true },
.format = .bgra8_unorm,
.width = framebuffer_size.width,
.height = framebuffer_size.height,
.present_mode = .fifo,
};
const swap_chain = gpu_device.createSwapChain(surface, &swap_chain_desc);
core.* = .{
.allocator = allocator,
.window = window,
.backend_type = backend_type,
.user_ptr = undefined,
.instance = instance,
.surface = surface,
.gpu_adapter = response.adapter,
.gpu_device = gpu_device,
.swap_chain = swap_chain,
.swap_chain_desc = swap_chain_desc,
.events = .{},
.wait_timeout = 0.0,
.last_size = window.getSize(),
.last_pos = window.getPos(),
.size_limit = .{
.min = .{ .width = 350, .height = 350 },
.max = .{ .width = null, .height = null },
},
.frame_buffer_resized = false,
.current_cursor = .arrow,
.cursors = std.mem.zeroes([@typeInfo(CursorShape).Enum.fields.len]?glfw.Cursor),
.cursors_tried = std.mem.zeroes([@typeInfo(CursorShape).Enum.fields.len]bool),
.linux_gamemode = null,
};
core.setSizeLimit(core.size_limit);
core.initCallbacks();
if (builtin.os.tag == .linux and !options.is_app and
core.linux_gamemode == null and try activateGamemode(core.allocator))
core.linux_gamemode = initLinuxGamemode();
}
fn initCallbacks(self: *Core) void {
self.user_ptr = UserPtr{ .self = self };
self.window.setUserPointer(&self.user_ptr);
const key_callback = struct {
fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
const key_event = KeyEvent{
.key = toMachKey(key),
.mods = toMachMods(mods),
};
switch (action) {
.press => pf.pushEvent(.{ .key_press = key_event }),
.repeat => pf.pushEvent(.{ .key_repeat = key_event }),
.release => pf.pushEvent(.{ .key_release = key_event }),
}
_ = scancode;
}
}.callback;
self.window.setKeyCallback(key_callback);
const char_callback = struct {
fn callback(window: glfw.Window, codepoint: u21) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
pf.pushEvent(.{
.char_input = .{
.codepoint = codepoint,
},
});
}
}.callback;
self.window.setCharCallback(char_callback);
const mouse_motion_callback = struct {
fn callback(window: glfw.Window, xpos: f64, ypos: f64) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
pf.pushEvent(.{
.mouse_motion = .{
.pos = .{
.x = xpos,
.y = ypos,
},
},
});
}
}.callback;
self.window.setCursorPosCallback(mouse_motion_callback);
const mouse_button_callback = struct {
fn callback(window: glfw.Window, button: glfw.mouse_button.MouseButton, action: glfw.Action, mods: glfw.Mods) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
const cursor_pos = pf.window.getCursorPos();
const mouse_button_event = MouseButtonEvent{
.button = toMachButton(button),
.pos = .{ .x = cursor_pos.xpos, .y = cursor_pos.ypos },
.mods = toMachMods(mods),
};
switch (action) {
.press => pf.pushEvent(.{ .mouse_press = mouse_button_event }),
.release => pf.pushEvent(.{
.mouse_release = mouse_button_event,
}),
else => {},
}
}
}.callback;
self.window.setMouseButtonCallback(mouse_button_callback);
const scroll_callback = struct {
fn callback(window: glfw.Window, xoffset: f64, yoffset: f64) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
pf.pushEvent(.{
.mouse_scroll = .{
.xoffset = @floatCast(f32, xoffset),
.yoffset = @floatCast(f32, yoffset),
},
});
}
}.callback;
self.window.setScrollCallback(scroll_callback);
const focus_callback = struct {
fn callback(window: glfw.Window, focused: bool) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
pf.pushEvent(if (focused) .focus_gained else .focus_lost);
}
}.callback;
self.window.setFocusCallback(focus_callback);
const framebuffer_size_callback = struct {
fn callback(window: glfw.Window, _: u32, _: u32) void {
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
pf.frame_buffer_resized = true;
}
}.callback;
self.window.setFramebufferSizeCallback(framebuffer_size_callback);
}
fn pushEvent(self: *Core, event: Event) void {
const node = self.allocator.create(EventNode) catch unreachable;
node.* = .{ .data = event };
self.events.append(node);
}
pub fn deinit(self: *Core) void {
for (self.cursors) |glfw_cursor| {
if (glfw_cursor) |cur| {
cur.destroy();
}
}
while (self.events.popFirst()) |ev| {
self.allocator.destroy(ev);
}
if (builtin.os.tag == .linux and
self.linux_gamemode != null and
self.linux_gamemode.?)
deinitLinuxGamemode();
}
pub fn hasEvent(self: *Core) bool {
return self.events.first != null;
}
pub fn pollEvents(self: *Core) ?Event {
if (self.wait_timeout > 0.0) {
if (self.wait_timeout == std.math.inf(f64)) {
// Wait for an event
glfw.waitEvents();
} else {
// Wait for an event with a timeout
glfw.waitEventsTimeout(self.wait_timeout);
}
} else {
// Don't wait for events
glfw.pollEvents();
}
glfw.getErrorCode() catch |err| switch (err) {
error.PlatformError => std.log.err("glfw: failed to poll events", .{}),
error.InvalidValue => unreachable,
else => unreachable,
};
if (self.frame_buffer_resized) blk: {
self.frame_buffer_resized = false;
const framebuffer_size = self.window.getFramebufferSize();
glfw.getErrorCode() catch break :blk;
if (framebuffer_size.width != 0 and framebuffer_size.height != 0) {
self.swap_chain_desc.width = framebuffer_size.width;
self.swap_chain_desc.height = framebuffer_size.height;
self.swap_chain = self.gpu_device.createSwapChain(self.surface, &self.swap_chain_desc);
self.pushEvent(.{
.framebuffer_resize = .{
.width = framebuffer_size.width,
.height = framebuffer_size.height,
},
});
}
}
if (self.window.shouldClose()) {
self.pushEvent(.close);
}
if (self.events.popFirst()) |n| {
const data = n.data;
self.allocator.destroy(n);
return data;
}
return null;
}
pub fn shouldClose(self: *Core) bool {
return self.window.shouldClose();
}
pub fn framebufferSize(self: *Core) Size {
const framebuffer_size = self.window.getFramebufferSize();
return .{
.width = framebuffer_size.width,
.height = framebuffer_size.height,
};
}
pub fn setWaitTimeout(self: *Core, timeout: f64) void {
self.wait_timeout = timeout;
}
pub fn setTitle(self: *Core, title: [:0]const u8) void {
self.window.setTitle(title);
}
pub fn setDisplayMode(self: *Core, mode: DisplayMode, monitor_index: ?usize) !void {
switch (mode) {
.windowed => {
try self.window.setMonitor(
null,
@intCast(i32, self.last_pos.x),
@intCast(i32, self.last_pos.y),
self.last_size.width,
self.last_size.height,
null,
);
},
.fullscreen => {
if (try self.displayMode() == .windowed) {
self.last_size = try self.window.getSize();
self.last_pos = try self.window.getPos();
}
const monitor = blk: {
if (monitor_index) |i| {
const monitor_list = try glfw.Monitor.getAll(self.allocator);
defer self.allocator.free(monitor_list);
break :blk monitor_list[i];
}
break :blk glfw.Monitor.getPrimary();
};
const video_mode = try monitor.?.getVideoMode();
try self.window.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), null);
},
}
}
pub fn displayMode(self: *Core) DisplayMode {
if (self.window.getMonitor()) |_| {
return .fullscreen;
} else {
return .windowed;
}
}
pub fn setBorder(self: *Core, value: bool) void {
self.window.setAttrib(.decorated, value);
}
pub fn border(self: *Core) !bool {
const decorated = try self.window.getAttrib(.decorated);
return decorated == 1;
}
pub fn setHeadless(self: *Core, value: bool) void {
if (value) {
self.window.hide();
} else {
self.window.show();
}
}
pub fn headless(self: *Core) bool {
const visible = self.window.getAttrib(.visible);
return visible == 0;
}
pub fn setVSync(self: *Core, mode: VSyncMode) void {
const framebuffer_size = self.framebufferSize();
self.swap_chain_desc.present_mode = switch (mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
};
self.swap_chain_desc.width = framebuffer_size.width;
self.swap_chain_desc.height = framebuffer_size.height;
self.swap_chain = self.gpu_device.createSwapChain(self.surface, &self.swap_chain_desc);
}
pub fn vsync(self: *Core) VSyncMode {
return switch (self.swap_chain_desc.present_mode) {
.immediate => .none,
.fifo => .double,
.mailbox => .triple,
};
}
pub fn setSize(self: *Core, value: Size) void {
self.window.setSize(.{
.width = value.width,
.height = value.height,
});
}
pub fn size(self: *Core) Size {
const window_size = self.window.getSize();
return .{ .width = window_size.width, .height = window_size.height };
}
pub fn setSizeLimit(self: *Core, limit: SizeLimit) void {
self.window.setSizeLimits(
.{ .width = limit.min.width, .height = limit.min.height },
.{ .width = limit.max.width, .height = limit.max.height },
);
self.size_limit = limit;
}
pub fn sizeLimit(self: *Core) SizeLimit {
return self.size_limit;
}
pub fn setCursorMode(self: *Core, mode: CursorMode) void {
const glfw_mode: glfw.Window.InputModeCursor = switch (mode) {
.normal => .normal,
.hidden => .hidden,
.disabled => .disabled,
};
self.window.setInputModeCursor(glfw_mode);
}
pub fn cursorMode(self: *Core) CursorMode {
const glfw_mode = self.window.getInputModeCursor();
return switch (glfw_mode) {
.normal => .normal,
.hidden => .hidden,
.disabled => .disabled,
};
}
pub fn setCursorShape(self: *Core, cursor: CursorShape) void {
// Try to create glfw standard cursor, but could fail. In the future
// we hope to provide custom backup images for these.
// See https://github.com/hexops/mach/pull/352 for more info
const enum_int = @enumToInt(cursor);
const tried = self.cursors_tried[enum_int];
if (!tried) {
self.cursors_tried[enum_int] = true;
self.cursors[enum_int] = switch (cursor) {
.arrow => glfw.Cursor.createStandard(.arrow) catch null,
.ibeam => glfw.Cursor.createStandard(.ibeam) catch null,
.crosshair => glfw.Cursor.createStandard(.crosshair) catch null,
.pointing_hand => glfw.Cursor.createStandard(.pointing_hand) catch null,
.resize_ew => glfw.Cursor.createStandard(.resize_ew) catch null,
.resize_ns => glfw.Cursor.createStandard(.resize_ns) catch null,
.resize_nwse => glfw.Cursor.createStandard(.resize_nwse) catch null,
.resize_nesw => glfw.Cursor.createStandard(.resize_nesw) catch null,
.resize_all => glfw.Cursor.createStandard(.resize_all) catch null,
.not_allowed => glfw.Cursor.createStandard(.not_allowed) catch null,
};
}
if (self.cursors[enum_int]) |cur| {
self.window.setCursor(cur);
} else {
// TODO: In the future we shouldn't hit this because we'll provide backup
// custom cursors.
// See https://github.com/hexops/mach/pull/352 for more info
std.log.warn("mach: setCursorShape: {s} not yet supported\n", .{@tagName(cursor)});
}
self.current_cursor = cursor;
}
pub fn cursorShape(self: *Core) CursorShape {
return self.current_cursor;
}
pub fn adapter(self: *Core) *gpu.Adapter {
return self.gpu_adapter;
}
pub fn device(self: *Core) *gpu.Device {
return self.gpu_device;
}
pub fn swapChain(self: *Core) *gpu.SwapChain {
return self.swap_chain;
}
pub fn descriptor(self: *Core) gpu.SwapChain.Descriptor {
return self.swap_chain_desc;
}
fn toMachButton(button: glfw.mouse_button.MouseButton) MouseButton {
return switch (button) {
.left => .left,
.right => .right,
.middle => .middle,
.four => .four,
.five => .five,
.six => .six,
.seven => .seven,
.eight => .eight,
};
}
fn toMachKey(key: glfw.Key) Key {
return switch (key) {
.a => .a,
.b => .b,
.c => .c,
.d => .d,
.e => .e,
.f => .f,
.g => .g,
.h => .h,
.i => .i,
.j => .j,
.k => .k,
.l => .l,
.m => .m,
.n => .n,
.o => .o,
.p => .p,
.q => .q,
.r => .r,
.s => .s,
.t => .t,
.u => .u,
.v => .v,
.w => .w,
.x => .x,
.y => .y,
.z => .z,
.zero => .zero,
.one => .one,
.two => .two,
.three => .three,
.four => .four,
.five => .five,
.six => .six,
.seven => .seven,
.eight => .eight,
.nine => .nine,
.F1 => .f1,
.F2 => .f2,
.F3 => .f3,
.F4 => .f4,
.F5 => .f5,
.F6 => .f6,
.F7 => .f7,
.F8 => .f8,
.F9 => .f9,
.F10 => .f10,
.F11 => .f11,
.F12 => .f12,
.F13 => .f13,
.F14 => .f14,
.F15 => .f15,
.F16 => .f16,
.F17 => .f17,
.F18 => .f18,
.F19 => .f19,
.F20 => .f20,
.F21 => .f21,
.F22 => .f22,
.F23 => .f23,
.F24 => .f24,
.F25 => .f25,
.kp_divide => .kp_divide,
.kp_multiply => .kp_multiply,
.kp_subtract => .kp_subtract,
.kp_add => .kp_add,
.kp_0 => .kp_0,
.kp_1 => .kp_1,
.kp_2 => .kp_2,
.kp_3 => .kp_3,
.kp_4 => .kp_4,
.kp_5 => .kp_5,
.kp_6 => .kp_6,
.kp_7 => .kp_7,
.kp_8 => .kp_8,
.kp_9 => .kp_9,
.kp_decimal => .kp_decimal,
.kp_equal => .kp_equal,
.kp_enter => .kp_enter,
.enter => .enter,
.escape => .escape,
.tab => .tab,
.left_shift => .left_shift,
.right_shift => .right_shift,
.left_control => .left_control,
.right_control => .right_control,
.left_alt => .left_alt,
.right_alt => .right_alt,
.left_super => .left_super,
.right_super => .right_super,
.menu => .menu,
.num_lock => .num_lock,
.caps_lock => .caps_lock,
.print_screen => .print,
.scroll_lock => .scroll_lock,
.pause => .pause,
.delete => .delete,
.home => .home,
.end => .end,
.page_up => .page_up,
.page_down => .page_down,
.insert => .insert,
.left => .left,
.right => .right,
.up => .up,
.down => .down,
.backspace => .backspace,
.space => .space,
.minus => .minus,
.equal => .equal,
.left_bracket => .left_bracket,
.right_bracket => .right_bracket,
.backslash => .backslash,
.semicolon => .semicolon,
.apostrophe => .apostrophe,
.comma => .comma,
.period => .period,
.slash => .slash,
.grave_accent => .grave,
.world_1 => .unknown,
.world_2 => .unknown,
.unknown => .unknown,
};
}
fn toMachMods(mods: glfw.Mods) KeyMods {
return .{
.shift = mods.shift,
.control = mods.control,
.alt = mods.alt,
.super = mods.super,
.caps_lock = mods.caps_lock,
.num_lock = mods.num_lock,
};
}
/// Default GLFW error handling callback
fn errorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
std.log.err("glfw: {}: {s}\n", .{ error_code, description });
}
fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 {
return std.process.getEnvVarOwned(allocator, key) catch |err| switch (err) {
error.EnvironmentVariableNotFound => @as(?[]u8, null),
else => |e| e,
};
}
/// Check if gamemode should be activated
fn activateGamemode(allocator: std.mem.Allocator) error{ OutOfMemory, InvalidUtf8 }!bool {
if (try getEnvVarOwned(allocator, "MACH_USE_GAMEMODE")) |env| {
defer allocator.free(env);
return !(std.ascii.eqlIgnoreCase(env, "off") or std.ascii.eqlIgnoreCase(env, "false"));
}
return true;
}
fn initLinuxGamemode() bool {
const gamemode = @import("gamemode");
gamemode.requestStart() catch |err| {
if (!std.mem.containsAtLeast(u8, gamemode.errorString(), 1, "dlopen failed"))
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
return false;
};
std.log.info("Gamemode activated", .{});
return true;
}
fn deinitLinuxGamemode() void {
const gamemode = @import("gamemode");
gamemode.requestEnd() catch |err| {
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
};
}

View file

@ -1,22 +0,0 @@
const std = @import("std");
const gpu = @import("gpu");
const App = @import("app").App;
const util = @import("util.zig");
pub const GPUInterface = gpu.dawn.Interface;
pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{};
pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level;
pub fn main() !void {
gpu.Impl.init();
_ = gpu.Export(GPUInterface);
var app: App = undefined;
try app.init();
defer app.deinit();
while (true) {
const pool = try util.AutoReleasePool.init();
defer util.AutoReleasePool.release(pool);
if (try app.update()) return;
}
}

View file

@ -1,7 +0,0 @@
// Extracted from `zig translate-c tmp.c` with `#include <objc/message.h>` in the file.
pub const SEL = opaque {};
pub const Class = opaque {};
pub extern fn sel_getUid(str: [*c]const u8) ?*SEL;
pub extern fn objc_getClass(name: [*c]const u8) ?*Class;
pub extern fn objc_msgSend() void;

View file

@ -1,187 +0,0 @@
const std = @import("std");
const glfw = @import("glfw");
const gpu = @import("gpu");
const objc = @import("objc_message.zig");
pub inline fn printUnhandledErrorCallback(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void {
switch (typ) {
.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.os.exit(1);
}
fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 {
return std.process.getEnvVarOwned(allocator, key) catch |err| switch (err) {
error.EnvironmentVariableNotFound => @as(?[]u8, null),
else => |e| e,
};
}
pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.BackendType {
const MACH_GPU_BACKEND = try getEnvVarOwned(allocator, "MACH_GPU_BACKEND");
if (MACH_GPU_BACKEND) |backend| {
defer allocator.free(backend);
if (std.ascii.eqlIgnoreCase(backend, "null")) return .null;
if (std.ascii.eqlIgnoreCase(backend, "d3d11")) return .d3d11;
if (std.ascii.eqlIgnoreCase(backend, "d3d12")) return .d3d12;
if (std.ascii.eqlIgnoreCase(backend, "metal")) return .metal;
if (std.ascii.eqlIgnoreCase(backend, "vulkan")) return .vulkan;
if (std.ascii.eqlIgnoreCase(backend, "opengl")) return .opengl;
if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles;
@panic("unknown MACH_GPU_BACKEND type");
}
const target = @import("builtin").target;
if (target.isDarwin()) return .metal;
if (target.os.tag == .windows) return .d3d12;
return .vulkan;
}
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,
};
}
pub fn glfwWindowHintsForBackend(backend: gpu.BackendType) glfw.Window.Hints {
return switch (backend) {
.opengl => .{
// Ask for OpenGL 4.4 which is what the GL backend requires for compute shaders and
// texture views.
.context_version_major = 4,
.context_version_minor = 4,
.opengl_forward_compat = true,
.opengl_profile = .opengl_core_profile,
},
.opengles => .{
.context_version_major = 3,
.context_version_minor = 1,
.client_api = .opengl_es_api,
.context_creation_api = .egl_context_api,
},
else => .{
// Without this GLFW will initialize a GL context on the window, which prevents using
// the window with other APIs (by crashing in weird ways).
.client_api = .no_api,
},
};
}
pub fn detectGLFWOptions() glfw.BackendOptions {
const target = @import("builtin").target;
if (target.isDarwin()) return .{ .cocoa = true };
return switch (target.os.tag) {
.windows => .{ .win32 = true },
.linux => .{ .x11 = true, .wayland = true },
else => .{},
};
}
pub fn createSurfaceForWindow(
instance: *gpu.Instance,
window: glfw.Window,
comptime glfw_options: glfw.BackendOptions,
) *gpu.Surface {
const glfw_native = glfw.Native(glfw_options);
const extension = if (glfw_options.win32) gpu.Surface.Descriptor.NextInChain{
.from_windows_hwnd = &.{
.hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?,
.hwnd = glfw_native.getWin32Window(window),
},
} else if (glfw_options.x11) gpu.Surface.Descriptor.NextInChain{
.from_xlib_window = &.{
.display = glfw_native.getX11Display(),
.window = glfw_native.getX11Window(window),
},
} else if (glfw_options.wayland) gpu.Surface.Descriptor.NextInChain{
.from_wayland_surface = &.{
.display = glfw_native.getWaylandDisplay(),
.surface = glfw_native.getWaylandWindow(window),
},
} else if (glfw_options.cocoa) blk: {
const ns_window = glfw_native.getCocoaWindow(window);
const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque); // [nsWindow contentView]
// Create a CAMetalLayer that covers the whole window that will be passed to CreateSurface.
msgSend(ns_view, "setWantsLayer:", .{true}, void); // [view setWantsLayer:YES]
const layer = msgSend(objc.objc_getClass("CAMetalLayer"), "layer", .{}, ?*anyopaque); // [CAMetalLayer layer]
if (layer == null) @panic("failed to create Metal layer");
msgSend(ns_view, "setLayer:", .{layer.?}, void); // [view setLayer:layer]
// Use retina if the window was created with retina support.
const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64); // [ns_window backingScaleFactor]
msgSend(layer.?, "setContentsScale:", .{scale_factor}, void); // [layer setContentsScale:scale_factor]
break :blk gpu.Surface.Descriptor.NextInChain{ .from_metal_layer = &.{ .layer = layer.? } };
} else unreachable;
return instance.createSurface(&gpu.Surface.Descriptor{
.next_in_chain = extension,
});
}
pub const AutoReleasePool = if (!@import("builtin").target.isDarwin()) opaque {
pub fn init() error{OutOfMemory}!?*AutoReleasePool {
return null;
}
pub fn release(pool: ?*AutoReleasePool) void {
_ = pool;
return;
}
} else opaque {
pub fn init() error{OutOfMemory}!?*AutoReleasePool {
// pool = [NSAutoreleasePool alloc];
var pool = msgSend(objc.objc_getClass("NSAutoreleasePool"), "alloc", .{}, ?*AutoReleasePool);
if (pool == null) return error.OutOfMemory;
// pool = [pool init];
pool = msgSend(pool, "init", .{}, ?*AutoReleasePool);
if (pool == null) unreachable;
return pool;
}
pub fn release(pool: ?*AutoReleasePool) void {
// [pool release];
msgSend(pool, "release", .{}, void);
}
};
// Borrowed from https://github.com/hazeycode/zig-objcrt
pub fn msgSend(obj: anytype, sel_name: [:0]const u8, args: anytype, comptime ReturnType: type) ReturnType {
const args_meta = @typeInfo(@TypeOf(args)).Struct.fields;
const FnType = switch (args_meta.len) {
0 => *const fn (@TypeOf(obj), ?*objc.SEL) callconv(.C) ReturnType,
1 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type) callconv(.C) ReturnType,
2 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type) callconv(.C) ReturnType,
3 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type) callconv(.C) ReturnType,
4 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type, args_meta[3].type) callconv(.C) ReturnType,
else => @compileError("Unsupported number of args"),
};
// NOTE: func is a var because making it const causes a compile error which I believe is a compiler bug
var func = @ptrCast(FnType, &objc.objc_msgSend);
const sel = objc.sel_getUid(@ptrCast([*c]const u8, sel_name));
return @call(.auto, func, .{ obj, sel } ++ args);
}

View file

@ -1,3 +0,0 @@
pub const Core = @import("wasm/Core.zig");
pub const Timer = @import("wasm/Timer.zig");
pub const entry = @import("wasm/entry.zig");

View file

@ -1,295 +0,0 @@
const std = @import("std");
const gpu = @import("gpu");
const js = @import("js.zig");
const Timer = @import("Timer.zig");
const Options = @import("../../Core.zig").Options;
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;
pub const Core = @This();
allocator: std.mem.Allocator,
id: js.CanvasId,
last_cursor_position: Position,
last_key_mods: KeyMods,
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
_ = options;
var selector = [1]u8{0} ** 15;
const id = js.machCanvasInit(&selector[0]);
core.* = Core{
.allocator = allocator,
.id = 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,
},
};
}
pub fn deinit(self: *Core) void {
js.machCanvasDeinit(self.id);
}
pub fn hasEvent(_: *Core) bool {
return js.machHasEvent();
}
pub fn pollEvents(self: *Core) ?Event {
const event_int = js.machEventShift();
if (event_int == -1) return null;
const event_type = @intToEnum(std.meta.Tag(Event), event_int);
return switch (event_type) {
.key_press, .key_repeat => blk: {
const key = @intToEnum(Key, js.machEventShift());
switch (key) {
.left_shift, .right_shift => self.last_key_mods.shift = true,
.left_control, .right_control => self.last_key_mods.control = true,
.left_alt, .right_alt => self.last_key_mods.alt = true,
.left_super, .right_super => self.last_key_mods.super = true,
.caps_lock => self.last_key_mods.caps_lock = true,
.num_lock => self.last_key_mods.num_lock = true,
else => {},
}
break :blk switch (event_type) {
.key_press => Event{
.key_press = .{
.key = key,
.mods = self.last_key_mods,
},
},
.key_repeat => Event{
.key_repeat = .{
.key = key,
.mods = self.last_key_mods,
},
},
else => unreachable,
};
},
.key_release => blk: {
const key = @intToEnum(Key, js.machEventShift());
switch (key) {
.left_shift, .right_shift => self.last_key_mods.shift = false,
.left_control, .right_control => self.last_key_mods.control = false,
.left_alt, .right_alt => self.last_key_mods.alt = false,
.left_super, .right_super => self.last_key_mods.super = false,
.caps_lock => self.last_key_mods.caps_lock = false,
.num_lock => self.last_key_mods.num_lock = false,
else => {},
}
break :blk Event{
.key_release = .{
.key = key,
.mods = self.last_key_mods,
},
};
},
.mouse_motion => blk: {
const x = @intToFloat(f64, js.machEventShift());
const y = @intToFloat(f64, js.machEventShift());
self.last_cursor_position = .{
.x = x,
.y = y,
};
break :blk Event{
.mouse_motion = .{
.pos = .{
.x = x,
.y = y,
},
},
};
},
.mouse_press => Event{
.mouse_press = .{
.button = toMachButton(js.machEventShift()),
.pos = self.last_cursor_position,
.mods = self.last_key_mods,
},
},
.mouse_release => Event{
.mouse_release = .{
.button = toMachButton(js.machEventShift()),
.pos = self.last_cursor_position,
.mods = self.last_key_mods,
},
},
.mouse_scroll => Event{
.mouse_scroll = .{
.xoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())),
.yoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())),
},
},
.framebuffer_resize => blk: {
const width = @intCast(u32, js.machEventShift());
const height = @intCast(u32, js.machEventShift());
const pixel_ratio = @intCast(u32, 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,
};
}
pub fn framebufferSize(self: *Core) Size {
return .{
.width = js.machCanvasFramebufferWidth(self.id),
.height = js.machCanvasFramebufferHeight(self.id),
};
}
pub fn setWaitTimeout(_: *Core, timeout: f64) void {
js.machSetWaitTimeout(timeout);
}
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, monitor: ?usize) void {
_ = monitor;
js.machCanvasSetDisplayMode(self.id, @enumToInt(mode));
}
pub fn displayMode(self: *Core) DisplayMode {
return @intToEnum(DisplayMode, 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 {
_ = self;
_ = mode;
}
// TODO: 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| @intCast(i32, val) else -1,
if (limit.min.height) |val| @intCast(i32, val) else -1,
if (limit.max.width) |val| @intCast(i32, val) else -1,
if (limit.max.height) |val| @intCast(i32, 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, @enumToInt(mode));
}
pub fn cursorMode(self: *Core) CursorMode {
return @intToEnum(CursorMode, js.machCursorMode(self.id));
}
pub fn setCursorShape(self: *Core, shape: CursorShape) void {
js.machSetCursorShape(self.id, @enumToInt(shape));
}
pub fn cursorShape(self: *Core) CursorShape {
return @intToEnum(CursorShape, js.machCursorShape(self.id));
}
pub fn adapter(_: *Core) *gpu.Adapter {
unreachable;
}
pub fn device(_: *Core) *gpu.Device {
unreachable;
}
pub fn swapChain(_: *Core) *gpu.SwapChain {
unreachable;
}
pub fn descriptor(_: *Core) gpu.SwapChain.Descriptor {
unreachable;
}
fn toMachButton(button: i32) MouseButton {
return switch (button) {
0 => .left,
1 => .middle,
2 => .right,
3 => .four,
4 => .five,
else => unreachable,
};
}

View file

@ -1,25 +0,0 @@
const std = @import("std");
const js = @import("js.zig");
pub const Timer = @This();
initial: f64 = undefined,
pub fn start() !Timer {
return Timer{ .initial = js.machPerfNow() };
}
pub fn read(timer: *Timer) u64 {
return (js.machPerfNow() - timer.initial) * std.time.ns_per_ms;
}
pub fn reset(timer: *Timer) void {
timer.initial = js.machPerfNow();
}
pub fn lap(timer: *Timer) u64 {
const now = js.machPerfNow();
const initial = timer.initial;
timer.initial = now;
return @floatToInt(u64, now - initial) * std.time.ns_per_ms;
}

View file

@ -1,51 +0,0 @@
const std = @import("std");
const gpu = @import("gpu");
const App = @import("app").App;
const js = @import("js.zig");
pub const GPUInterface = gpu.StubInterface;
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{};
var app: App = undefined;
export fn wasmInit() void {
app.init() catch unreachable;
}
export fn wasmUpdate() bool {
return app.update() catch unreachable;
}
export fn wasmDeinit() void {
app.deinit();
}
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 const std_options = struct {
pub fn logFn(
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, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
_ = ret_addr;
js.machPanic(msg.ptr, msg.len);
unreachable;
}

View file

@ -1,40 +0,0 @@
pub const CanvasId = u32;
pub extern "mach" fn machLogWrite(str: [*]const u8, len: u32) void;
pub extern "mach" fn machLogFlush() void;
pub extern "mach" fn machPanic(str: [*]const u8, len: u32) void;
pub extern "mach" fn machCanvasInit(selector_id: *u8) CanvasId;
pub extern "mach" fn machCanvasDeinit(canvas: CanvasId) void;
pub extern "mach" fn machCanvasFramebufferWidth(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasFramebufferHeight(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasSetTitle(canvas: CanvasId, title: [*]const u8, len: u32) void;
pub extern "mach" fn machCanvasSetDisplayMode(canvas: CanvasId, mode: u32) void;
pub extern "mach" fn machCanvasDisplayMode(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasSetBorder(canvas: CanvasId, value: bool) void;
pub extern "mach" fn machCanvasBorder(canvas: CanvasId) bool;
pub extern "mach" fn machCanvasSetHeadless(canvas: CanvasId, value: bool) void;
pub extern "mach" fn machCanvasHeadless(canvas: CanvasId) bool;
pub extern "mach" fn machCanvasSetVsync(canvas: CanvasId, mode: u32) void;
pub extern "mach" fn machCanvasVsync(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasSetSize(canvas: CanvasId, width: u32, height: u32) void;
pub extern "mach" fn machCanvasWidth(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasHeight(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasSetSizeLimit(canvas: CanvasId, min_width: i32, min_height: i32, max_width: i32, max_height: i32) void;
pub extern "mach" fn machCanvasMinWidth(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasMinHeight(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasMaxWidth(canvas: CanvasId) u32;
pub extern "mach" fn machCanvasMaxHeight(canvas: CanvasId) u32;
pub extern "mach" fn machSetCursorMode(canvas: CanvasId, mode: u32) void;
pub extern "mach" fn machCursorMode(canvas: CanvasId) u32;
pub extern "mach" fn machSetCursorShape(canvas: CanvasId, shape: u32) void;
pub extern "mach" fn machCursorShape(canvas: CanvasId) u32;
pub extern "mach" fn machShouldClose() bool;
pub extern "mach" fn machHasEvent() bool;
pub extern "mach" fn machSetWaitTimeout(timeout: f64) void;
pub extern "mach" fn machEventShift() i32;
pub extern "mach" fn machEventShiftFloat() f64;
pub extern "mach" fn machChangeShift() u32;
pub extern "mach" fn machPerfNow() f64;

View file

@ -1,511 +0,0 @@
const text_decoder = new TextDecoder();
const text_encoder = new TextEncoder();
const mach = {
canvases: [],
wasm: undefined,
observer: undefined,
events: [],
changes: [],
wait_timeout: 0,
log_buf: "",
init(wasm) {
mach.wasm = wasm;
mach.observer = new MutationObserver((mutables) => {
mutables.forEach((mutable) => {
mach.canvases.forEach((canvas) => {
if (mutable.target == canvas) {
if (mutable.attributeName === "width" ||
mutable.attributeName === "height" ||
mutable.attributeName === "style") {
mutable.target.dispatchEvent(new Event("mach-canvas-resize"));
}
}
});
})
})
},
getString(str, len) {
const memory = mach.wasm.exports.memory.buffer;
return text_decoder.decode(new Uint8Array(memory, str, len));
},
setString(str, buf) {
const memory = mach.wasm.exports.memory.buffer;
const strbuf = text_encoder.encode(str);
const outbuf = new Uint8Array(memory, buf, strbuf.length);
for (let i = 0; i < strbuf.length; i += 1) {
outbuf[i] = strbuf[i];
}
},
machLogWrite(str, len) {
mach.log_buf += mach.getString(str, len);
},
machLogFlush() {
console.log(log_buf);
mach.log_buf = "";
},
machPanic(str, len) {
throw Error(mach.getString(str, len));
},
machCanvasInit(id) {
let canvas = document.createElement("canvas");
canvas.id = "#mach-canvas-" + mach.canvases.length;
canvas.style.border = "1px solid";
canvas.style.position = "absolute";
canvas.style.display = "block";
canvas.tabIndex = 1;
mach.observer.observe(canvas, { attributes: true });
mach.setString(canvas.id, id);
canvas.addEventListener("contextmenu", (ev) => ev.preventDefault());
canvas.addEventListener("keydown", (ev) => {
if (ev.repeat) {
mach.events.push(...[EventCode.key_repeat, convertKeyCode(ev.code)]);
} else {
mach.events.push(...[EventCode.key_press, convertKeyCode(ev.code)]);
}
});
canvas.addEventListener("keyup", (ev) => {
mach.events.push(...[EventCode.key_release, convertKeyCode(ev.code)]);
});
canvas.addEventListener("mousemove", (ev) => {
mach.events.push(...[EventCode.mouse_motion, ev.clientX, ev.clientY]);
});
canvas.addEventListener("mousedown", (ev) => {
mach.events.push(...[EventCode.mouse_press, ev.button]);
});
canvas.addEventListener("mouseup", (ev) => {
mach.events.push(...[EventCode.mouse_release, ev.button]);
});
canvas.addEventListener("wheel", (ev) => {
mach.events.push(...[EventCode.mouse_scroll, ev.deltaX, ev.deltaY]);
});
canvas.addEventListener("mach-canvas-resize", (ev) => {
const cv_index = mach.canvases.findIndex((el) => el === ev.currentTarget);
const cv = mach.canvases[cv_index];
mach.events.push(...[EventCode.framebuffer_resize, cv.width, cv.height, window.devicePixelRatio]);
});
canvas.addEventListener("focus", (ev) => {
mach.events.push(...[EventCode.focus_gained]);
});
canvas.addEventListener("blur", (ev) => {
mach.events.push(...[EventCode.focus_lost]);
});
document.body.appendChild(canvas);
return mach.canvases.push(canvas) - 1;
},
machCanvasDeinit(canvas) {
if (mach.canvases[canvas] != undefined) {
mach.canvases.splice(canvas, 1);
}
},
machCanvasFramebufferWidth(canvas) {
const cv = mach.canvases[canvas];
return cv.width;
},
machCanvasFramebufferHeight(canvas) {
const cv = mach.canvases[canvas];
return cv.height;
},
machCanvasSetTitle(canvas, title, len) {
// TODO
},
machCanvasSetDisplayMode(canvas, mode) {
const cv = mach.canvases[canvas];
switch (mode) {
case DisplayMode.windowed:
document.exitFullscreen();
break;
case DisplayMode.fullscreen:
cv.requestFullscreen();
break;
}
},
machCanvasDisplayMode(canvas) {
if (mach.canvases[canvas].fullscreenElement == null) {
return DisplayMode.windowed;
} else {
return DisplayMode.fullscreen;
}
},
machCanvasSetBorder(canvas, value) {
// TODO
},
machCanvasBorder(canvas) {
// TODO
},
machCanvasSetHeadless(canvas, value) {
// TODO
},
machCanvasHeadless(canvas) {
// TODO
},
machCanvasSetVSync(canvas, mode) {
// TODO
},
machCanvasVSync(canvas) {
// TODO
},
machCanvasSetSize(canvas, width, height) {
const cv = mach.canvases[canvas];
if (width > 0 && height > 0) {
cv.style.width = width + "px";
cv.style.height = height + "px";
cv.width = Math.floor(width * window.devicePixelRatio);
cv.height = Math.floor(height * window.devicePixelRatio);
}
},
machCanvasWidth(canvas) {
const cv = mach.canvases[canvas];
return cv.width / window.devicePixelRatio;
},
machCanvasHeight(canvas) {
const cv = mach.canvases[canvas];
return cv.height / window.devicePixelRatio;
},
machCanvasSetSizeLimit(canvas, min_width, min_height, max_width, max_height) {
const cv = mach.canvases[canvas];
if (min_width == -1) {
cv.style.minWidth = "inherit"
} else {
cv.style.minWidth = min_width + "px";
}
if (min_width == -1) {
cv.style.minHeight = "inherit"
} else {
cv.style.minHeight = min_height + "px";
}
if (min_width == -1) {
cv.style.maxWidth = "inherit"
} else {
cv.style.maxWidth = max_width + "px";
}
if (min_width == -1) {
cv.style.maxHeight = "inherit"
} else {
cv.style.maxHeight = max_height + "px";
}
},
machCanvasMinWidth(canvas) {
const cv = mach.canvases[canvas];
return cv.style.minWidth;
},
machCanvasMinHeight(canvas) {
const cv = mach.canvases[canvas];
return cv.style.minHeight;
},
machCanvasMaxWidth(canvas) {
const cv = mach.canvases[canvas];
return cv.style.maxWidth;
},
machCanvasMaxHeight(canvas) {
const cv = mach.canvases[canvas];
return cv.style.maxHeight;
},
machSetCursorMode(canvas, mode) {
const cv = mach.canvases[canvas];
switch (mode) {
case CursorMode.normal:
cv.style.cursor = 'default';
break;
case CursorMode.hidden:
cv.style.cursor = 'none';
break;
case CursorMode.hidden:
cv.style.cursor = 'none';
break;
}
},
machCursorMode(canvas) {
switch (mach.canvases[canvas].style.cursor) {
case 'none': return CursorMode.hidden;
default: return CursorMode.normal;
}
},
machSetCursorShape(canvas, shape) {
const cv = mach.canvases[canvas];
switch (shape) {
case CursorShape.arrow:
cv.style.cursor = 'default';
break;
case CursorShape.ibeam:
cv.style.cursor = 'text';
break;
case CursorShape.crosshair:
cv.style.cursor = 'crosshair';
break;
case CursorShape.pointing_hand:
cv.style.cursor = 'pointer';
break;
case CursorShape.resize_ew:
cv.style.cursor = 'ew-resize';
break;
case CursorShape.resize_ns:
cv.style.cursor = 'ns-resize';
break;
case CursorShape.resize_nwse:
cv.style.cursor = 'nwse-resize';
break;
case CursorShape.resize_nesw:
cv.style.cursor = 'nesw-resize';
break;
case CursorShape.resize_all:
cv.style.cursor = 'move';
break;
case CursorShape.not_allowed:
cv.style.cursor = 'not-allowed';
break;
}
},
machCursorShape(canvas) {
switch (mach.canvases[canvas].style.cursor) {
case 'default': return CursorShape.arrow;
case 'text': return CursorShape.ibeam;
case 'crosshair': return CursorShape.crosshair;
case 'pointer': return CursorShape.pointing_hand;
case 'ew-resize': return CursorShape.resize_ew;
case 'ns-resize': return CursorShape.resize_ns;
case 'nwse-resize': return CursorShape.resize_nwse;
case 'nesw-resize': return CursorShape.resize_nesw;
case 'move': return CursorShape.resize_all;
case 'not-allowed': return CursorShape.not_allowed;
}
},
machSetWaitTimeout(timeout) {
mach.wait_timeout = timeout;
},
machHasEvent() {
return mach.events.length > 0;
},
machEventShift() {
if (mach.machHasEvent())
return mach.events.shift();
return -1;
},
machEventShiftFloat() {
return mach.machEventShift();
},
machPerfNow() {
return performance.now();
},
};
function convertKeyCode(code) {
const k = Key[code];
if (k != undefined)
return k;
return 118; // Unknown
}
const Key = {
KeyA: 0,
KeyB: 1,
KeyC: 2,
KeyD: 3,
KeyE: 4,
KeyF: 5,
KeyG: 6,
KeyH: 7,
KeyI: 8,
KeyJ: 9,
KeyK: 10,
KeyL: 11,
KeyM: 12,
KeyN: 13,
KeyO: 14,
KeyP: 15,
KeyQ: 16,
KeyR: 17,
KeyS: 18,
KeyT: 19,
KeyU: 20,
KeyV: 21,
KeyW: 22,
KeyX: 23,
KeyY: 24,
KeyZ: 25,
Digit0: 26,
Digit1: 27,
Digit2: 28,
Digit3: 29,
Digit4: 30,
Digit5: 31,
Digit6: 32,
Digit7: 33,
Digit8: 34,
Digit9: 35,
F1: 36,
F2: 37,
F3: 38,
F4: 39,
F5: 40,
F6: 41,
F7: 42,
F8: 43,
F9: 44,
F10: 45,
F11: 46,
F12: 47,
F13: 48,
F14: 49,
F15: 50,
F16: 51,
F17: 52,
F18: 53,
F19: 54,
F20: 55,
F21: 56,
F22: 57,
F23: 58,
F24: 59,
F25: 60,
NumpadDivide: 61,
NumpadMultiply: 62,
NumpadSubtract: 63,
NumpadAdd: 64,
Numpad0: 65,
Numpad1: 66,
Numpad2: 67,
Numpad3: 68,
Numpad4: 69,
Numpad5: 70,
Numpad6: 71,
Numpad7: 72,
Numpad8: 73,
Numpad9: 74,
NumpadDecimal: 75,
NumpadEqual: 76,
NumpadEnter: 77,
Enter: 78,
Escape: 79,
Tab: 80,
ShiftLeft: 81,
ShiftRight: 82,
ControlLeft: 83,
ControlRight: 84,
AltLeft: 85,
AltRight: 86,
OSLeft: 87,
MetaLeft: 87,
OSRight: 88,
MetaRight: 88,
ContextMenu: 89,
NumLock: 90,
CapsLock: 91,
PrintScreen: 92,
ScrollLock: 93,
Pause: 94,
Delete: 95,
Home: 96,
End: 97,
PageUp: 98,
PageDown: 99,
Insert: 100,
ArrowLeft: 101,
ArrowRight: 102,
ArrowUp: 103,
ArrowDown: 104,
Backspace: 105,
Space: 106,
Minus: 107,
Equal: 108,
BracketLeft: 109,
BracketRight: 110,
Backslash: 111,
Semicolon: 112,
Quote: 113,
Comma: 114,
Period: 115,
Slash: 116,
Backquote: 117,
};
const EventCode = {
key_press: 0,
key_repeat: 1,
key_release: 2,
char_input: 3,
mouse_motion: 4,
mouse_press: 5,
mouse_release: 6,
mouse_scroll: 7,
framebuffer_resize: 8,
focus_gained: 9,
focus_lost: 10,
close: 11,
};
const DisplayMode = {
windowed: 0,
fullscreen: 1,
};
const CursorMode = {
normal: 0,
hidden: 1,
disabled: 2,
};
const CursorShape = {
arrow: 0,
ibeam: 1,
crosshair: 2,
pointing_hand: 3,
resize_ew: 4,
resize_ns: 5,
resize_nwse: 6,
resize_nesw: 7,
resize_all: 8,
not_allowed: 9,
};
export { mach };