core: refactor

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

View file

@ -7,25 +7,18 @@ pub const Mod = mach.Mod(@This());
pub const systems = .{
.init = .{ .handler = init },
.after_init = .{ .handler = afterInit },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
};
title_timer: mach.Timer,
pipeline: *gpu.RenderPipeline,
pub fn deinit(core: *mach.Core.Mod, game: *Mod) void {
pub fn deinit(game: *Mod) void {
game.state().pipeline.release();
core.schedule(.deinit);
}
fn init(game: *Mod, core: *mach.Core.Mod) !void {
core.schedule(.init);
game.schedule(.after_init);
}
fn afterInit(game: *Mod, core: *mach.Core.Mod) !void {
// Create our shader module
const shader_module = core.state().device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
@ -64,14 +57,12 @@ fn afterInit(game: *Mod, core: *mach.Core.Mod) !void {
.pipeline = pipeline,
});
try updateWindowTitle(core);
core.schedule(.start);
}
fn tick(core: *mach.Core.Mod, game: *Mod) !void {
fn update(core: *mach.Core.Mod, game: *Mod) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS event.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
while (iter.next()) |event| {
switch (event) {
.close => core.schedule(.exit), // Tell mach.Core to exit the app
@ -81,7 +72,7 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Create a command encoder
@ -115,9 +106,6 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
defer command.release();
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
// Present the frame
core.schedule(.present_frame);
// update the window title every second
if (game.state().title_timer.read() >= 1.0) {
game.state().title_timer.reset();
@ -126,15 +114,13 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
}
fn updateWindowTitle(core: *mach.Core.Mod) !void {
try mach.Core.printTitle(
core,
try core.state().printTitle(
core.state().main_window,
"core-custom-entrypoint [ {d}fps ] [ Input {d}hz ]",
.{
// TODO(Core)
mach.core.frameRate(),
mach.core.inputRate(),
core.state().frameRate(),
core.state().inputRate(),
},
);
core.schedule(.update);
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -7,9 +8,11 @@ pub const modules = .{
};
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -7,25 +7,18 @@ pub const Mod = mach.Mod(@This());
pub const systems = .{
.init = .{ .handler = init },
.after_init = .{ .handler = afterInit },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
};
title_timer: mach.Timer,
pipeline: *gpu.RenderPipeline,
pub fn deinit(core: *mach.Core.Mod, game: *Mod) void {
pub fn deinit(game: *Mod) void {
game.state().pipeline.release();
core.schedule(.deinit);
}
fn init(game: *Mod, core: *mach.Core.Mod) !void {
core.schedule(.init);
game.schedule(.after_init);
}
fn afterInit(game: *Mod, core: *mach.Core.Mod) !void {
// Create our shader module
const shader_module = core.state().device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
@ -63,15 +56,11 @@ fn afterInit(game: *Mod, core: *mach.Core.Mod) !void {
.title_timer = try mach.Timer.start(),
.pipeline = pipeline,
});
try updateWindowTitle(core);
core.schedule(.start);
// try updateWindowTitle(core);
}
fn tick(core: *mach.Core.Mod, game: *Mod) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS event.
// TODO(Core)
var iter = mach.core.pollEvents();
fn update(core: *mach.Core.Mod, game: *Mod) !void {
var iter = core.state().pollEvents();
while (iter.next()) |event| {
switch (event) {
.close => core.schedule(.exit), // Tell mach.Core to exit the app
@ -81,11 +70,11 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Create a command encoder
const label = @tagName(name) ++ ".tick";
const label = @tagName(name) ++ ".update";
const encoder = core.state().device.createCommandEncoder(&.{ .label = label });
defer encoder.release();
@ -115,9 +104,6 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
defer command.release();
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
// Present the frame
core.schedule(.present_frame);
// update the window title every second
if (game.state().title_timer.read() >= 1.0) {
game.state().title_timer.reset();
@ -126,15 +112,13 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
}
fn updateWindowTitle(core: *mach.Core.Mod) !void {
try mach.Core.printTitle(
core,
try core.state().printTitle(
core.state().main_window,
"core-custom-entrypoint [ {d}fps ] [ Input {d}hz ]",
.{
// TODO(Core)
mach.core.frameRate(),
mach.core.inputRate(),
core.state().frameRate(),
core.state().inputRate(),
},
);
core.schedule(.update);
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -7,9 +8,11 @@ pub const modules = .{
};
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -25,7 +25,7 @@ pub const components = .{
pub const systems = .{
.init = .{ .handler = init },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
};
// Define the globally unique name of our module. You can use any name here, but keep in mind no
@ -38,9 +38,8 @@ pub const name = .app;
// Note that Mod.state() returns an instance of our module struct.
pub const Mod = mach.Mod(@This());
pub fn deinit(core: *mach.Core.Mod, renderer: *Renderer.Mod) void {
pub fn deinit(renderer: *Renderer.Mod) void {
renderer.schedule(.deinit);
core.schedule(.deinit);
}
fn init(
@ -48,11 +47,9 @@ fn init(
// of the program we can have these types injected here, letting us work with other modules in
// our program seamlessly and with a type-safe API:
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
renderer: *Renderer.Mod,
game: *Mod,
) !void {
core.schedule(.init);
renderer.schedule(.init);
// Create our player entity.
@ -76,11 +73,9 @@ fn init(
.spawn_timer = try mach.Timer.start(),
.player = player,
});
core.schedule(.start);
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
renderer: *Renderer.Mod,
@ -88,7 +83,7 @@ fn tick(
) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS event.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
var direction = game.state().direction;
var spawning = game.state().spawning;
while (iter.next()) |event| {

View file

@ -125,11 +125,11 @@ fn renderFrame(
) !void {
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Create a command encoder
const label = @tagName(name) ++ ".tick";
const label = @tagName(name) ++ ".renderFrame";
const encoder = core.state().device.createCommandEncoder(&.{ .label = label });
defer encoder.release();
@ -178,7 +178,4 @@ fn renderFrame(
var command = encoder.finish(&.{ .label = label });
defer command.release();
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
// Present the frame
core.schedule(.present_frame);
}

View file

@ -9,11 +9,12 @@ pub const modules = .{
@import("Renderer.zig"),
};
// TODO: move this to a mach "entrypoint" zig module
pub fn main() !void {
// Initialize mach core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -35,19 +35,17 @@ pub const Mod = mach.Mod(@This());
pub const systems = .{
.init = .{ .handler = init },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.after_init = .{ .handler = afterInit },
.end_frame = .{ .handler = endFrame },
};
fn deinit(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod) !void {
fn deinit(sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod) !void {
sprite_pipeline.schedule(.deinit);
glyphs.schedule(.deinit);
core.schedule(.deinit);
}
fn init(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void {
core.schedule(.init);
fn init(sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void {
sprite_pipeline.schedule(.init);
glyphs.schedule(.init);
@ -64,7 +62,6 @@ fn afterInit(
sprite_pipeline: *gfx.SpritePipeline.Mod,
glyphs: *Glyphs.Mod,
game: *Mod,
core: *mach.Core.Mod,
) !void {
// Create a sprite rendering pipeline
const texture = glyphs.state().texture;
@ -96,11 +93,9 @@ fn afterInit(
.time = 0,
.pipeline = pipeline,
});
core.schedule(.start);
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
sprite: *gfx.Sprite.Mod,
@ -110,7 +105,7 @@ fn tick(
) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS events.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
var direction = game.state().direction;
var spawning = game.state().spawning;
while (iter.next()) |event| {
@ -177,7 +172,7 @@ fn tick(
var location = entity_transform.translation();
// TODO: formatting
// TODO(Core)
if (location.x() < -@as(f32, @floatFromInt(mach.core.size().width)) / 1.5 or location.x() > @as(f32, @floatFromInt(mach.core.size().width)) / 1.5 or location.y() < -@as(f32, @floatFromInt(mach.core.size().height)) / 1.5 or location.y() > @as(f32, @floatFromInt(mach.core.size().height)) / 1.5) {
if (location.x() < -@as(f32, @floatFromInt(core.state().size().width)) / 1.5 or location.x() > @as(f32, @floatFromInt(core.state().size().width)) / 1.5 or location.y() < -@as(f32, @floatFromInt(core.state().size().height)) / 1.5 or location.y() > @as(f32, @floatFromInt(core.state().size().height)) / 1.5) {
try entities.remove(id);
game.state().sprites -= 1;
continue;
@ -212,7 +207,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Begin render pass
@ -253,13 +248,11 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
// Every second, update the window title with the FPS
if (game.state().fps_timer.read() >= 1.0) {
try mach.Core.printTitle(
core,
try core.state().printTitle(
core.state().main_window,
"glyphs [ FPS: {d} ] [ Sprites: {d} ]",
.{ game.state().frame_count, game.state().sprites },
);
core.schedule(.update);
game.state().fps_timer.reset();
game.state().frame_count = 0;
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -10,9 +11,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -44,20 +44,18 @@ pub const systems = .{
.init = .{ .handler = init },
.after_init = .{ .handler = afterInit },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.end_frame = .{ .handler = endFrame },
.audio_state_change = .{ .handler = audioStateChange },
};
fn deinit(
core: *mach.Core.Mod,
text_pipeline: *gfx.TextPipeline.Mod,
sprite_pipeline: *gfx.SpritePipeline.Mod,
audio: *mach.Audio.Mod,
) !void {
text_pipeline.schedule(.deinit);
sprite_pipeline.schedule(.deinit);
core.schedule(.deinit);
audio.schedule(.deinit);
}
@ -66,13 +64,11 @@ fn init(
text_pipeline: *gfx.TextPipeline.Mod,
text: *gfx.Text.Mod,
sprite_pipeline: *gfx.SpritePipeline.Mod,
core: *mach.Core.Mod,
game: *Mod,
) !void {
// If you want to try fullscreen:
// try core.set(core.state().main_window, .fullscreen, true);
core.schedule(.init);
audio.schedule(.init);
text.schedule(.init);
text_pipeline.schedule(.init);
@ -150,7 +146,6 @@ fn afterInit(
.pipeline = pipeline,
.sfx = sfx,
});
core.schedule(.start);
}
fn audioStateChange(entities: *mach.Entities.Mod) !void {
@ -169,7 +164,7 @@ fn audioStateChange(entities: *mach.Entities.Mod) !void {
}
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
sprite: *gfx.Sprite.Mod,
@ -181,7 +176,7 @@ fn tick(
) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS events.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
var gotta_go_fast = game.state().gotta_go_fast;
while (iter.next()) |event| {
switch (event) {
@ -286,7 +281,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Begin render pass

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -11,9 +12,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -27,7 +27,7 @@ pub const systems = .{
.init = .{ .handler = init },
.after_init = .{ .handler = afterInit },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.audio_state_change = .{ .handler = audioStateChange },
};
@ -37,8 +37,7 @@ pub const components = .{
ghost_key_mode: bool = false,
fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
core.schedule(.init);
fn init(audio: *mach.Audio.Mod, app: *Mod) void {
audio.schedule(.init);
app.schedule(.after_init);
@ -50,8 +49,6 @@ fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
std.debug.print("[spacebar] enable ghost-key mode (demonstrate seamless back-to-back sound playback)\n", .{});
std.debug.print("[arrow up] increase volume 10%\n", .{});
std.debug.print("[arrow down] decrease volume 10%\n", .{});
core.schedule(.start);
}
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
@ -60,9 +57,8 @@ fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
audio.state().on_state_change = app.system(.audio_state_change);
}
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
fn deinit(audio: *mach.Audio.Mod) void {
audio.schedule(.deinit);
core.schedule(.deinit);
}
fn audioStateChange(
@ -94,14 +90,14 @@ fn audioStateChange(
}
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
audio: *mach.Audio.Mod,
app: *Mod,
) !void {
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| {
@ -143,7 +139,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Create a command encoder
@ -208,7 +204,7 @@ fn fillTone(audio: *mach.Audio.Mod, frequency: f32) ![]const f32 {
}
// TODO(Core)
fn keyToFrequency(key: mach.core.Key) f32 {
fn keyToFrequency(key: mach.Core.Key) f32 {
// The frequencies here just come from a piano frequencies chart. You can google for them.
return switch (key) {
// First row of piano keys, the highest.

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -9,9 +10,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -21,7 +21,7 @@ pub const systems = .{
.init = .{ .handler = init },
.after_init = .{ .handler = afterInit },
.deinit = .{ .handler = deinit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.audio_state_change = .{ .handler = audioStateChange },
};
@ -33,11 +33,9 @@ sfx: mach.Audio.Opus,
fn init(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
audio: *mach.Audio.Mod,
app: *Mod,
) !void {
core.schedule(.init);
audio.schedule(.init);
app.schedule(.after_init);
@ -63,8 +61,6 @@ fn init(
std.debug.print("[typing] Play SFX\n", .{});
std.debug.print("[arrow up] increase volume 10%\n", .{});
std.debug.print("[arrow down] decrease volume 10%\n", .{});
core.schedule(.start);
}
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
@ -104,14 +100,14 @@ fn audioStateChange(
}
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
audio: *mach.Audio.Mod,
app: *Mod,
) !void {
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
while (iter.next()) |event| {
switch (event) {
.key_press => |ev| switch (ev.key) {
@ -141,7 +137,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Create a command encoder

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -9,9 +10,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -40,7 +40,7 @@ pub const systems = .{
.init = .{ .handler = init },
.deinit = .{ .handler = deinit },
.after_init = .{ .handler = afterInit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.end_frame = .{ .handler = endFrame },
};
@ -53,11 +53,9 @@ fn deinit(
}
fn init(
core: *mach.Core.Mod,
sprite_pipeline: *gfx.SpritePipeline.Mod,
game: *Mod,
) !void {
core.schedule(.init);
sprite_pipeline.schedule(.init);
game.schedule(.after_init);
}
@ -99,11 +97,9 @@ fn afterInit(
.allocator = allocator,
.pipeline = pipeline,
});
core.schedule(.start);
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
sprite: *gfx.Sprite.Mod,
@ -112,7 +108,7 @@ fn tick(
) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS events.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
var direction = game.state().direction;
var spawning = game.state().spawning;
while (iter.next()) |event| {
@ -201,7 +197,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Begin render pass
@ -242,13 +238,11 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
// Every second, update the window title with the FPS
if (game.state().fps_timer.read() >= 1.0) {
try mach.Core.printTitle(
core,
try core.state().printTitle(
core.state().main_window,
"sprite [ FPS: {d} ] [ Sprites: {d} ]",
.{ game.state().frame_count, game.state().sprites },
);
core.schedule(.update);
game.state().fps_timer.reset();
game.state().frame_count = 0;
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -9,9 +10,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

View file

@ -38,7 +38,7 @@ pub const systems = .{
.init = .{ .handler = init },
.deinit = .{ .handler = deinit },
.after_init = .{ .handler = afterInit },
.tick = .{ .handler = tick },
.update = .{ .handler = update },
.end_frame = .{ .handler = endFrame },
};
@ -52,21 +52,15 @@ const text1: []const []const u8 = &.{
const text2: []const []const u8 = &.{"$!?"};
fn deinit(
core: *mach.Core.Mod,
text_pipeline: *gfx.TextPipeline.Mod,
) !void {
fn deinit(text_pipeline: *gfx.TextPipeline.Mod) !void {
text_pipeline.schedule(.deinit);
core.schedule(.deinit);
}
fn init(
core: *mach.Core.Mod,
text: *gfx.Text.Mod,
text_pipeline: *gfx.TextPipeline.Mod,
game: *Mod,
) !void {
core.schedule(.init);
text.schedule(.init);
text_pipeline.schedule(.init);
game.schedule(.after_init);
@ -74,7 +68,6 @@ fn init(
fn afterInit(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
text: *gfx.Text.Mod,
text_pipeline: *gfx.TextPipeline.Mod,
text_style: *gfx.TextStyle.Mod,
@ -112,11 +105,9 @@ fn afterInit(
.style1 = style1,
.pipeline = pipeline,
});
core.schedule(.start);
}
fn tick(
fn update(
entities: *mach.Entities.Mod,
core: *mach.Core.Mod,
text: *gfx.Text.Mod,
@ -125,7 +116,7 @@ fn tick(
) !void {
// TODO(important): event polling should occur in mach.Core module and get fired as ECS events.
// TODO(Core)
var iter = mach.core.pollEvents();
var iter = core.state().pollEvents();
var direction = game.state().direction;
var spawning = game.state().spawning;
while (iter.next()) |event| {
@ -214,7 +205,7 @@ fn tick(
// Grab the back buffer of the swapchain
// TODO(Core)
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
const back_buffer_view = core.state().swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release();
// Begin render pass
@ -272,13 +263,11 @@ fn endFrame(
}
}
try mach.Core.printTitle(
core,
try core.state().printTitle(
core.state().main_window,
"text [ FPS: {d} ] [ Texts: {d} ] [ Glyphs: {d} ]",
.{ game.state().frame_count, num_texts, num_glyphs },
);
core.schedule(.update);
game.state().fps_timer.reset();
game.state().frame_count = 0;
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const mach = @import("mach");
// The global list of Mach modules registered for use in our application.
@ -9,9 +10,11 @@ pub const modules = .{
// TODO(important): use standard entrypoint instead
pub fn main() !void {
// Initialize mach.Core
try mach.core.initModule();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Main loop
while (try mach.core.tick()) {}
var app = try mach.App.init(allocator, .app);
defer app.deinit(allocator);
try app.run(.{ .allocator = allocator });
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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