module: rename events -> systems, remove 'event arguments'
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
83d436ffa4
commit
22ac26b57e
19 changed files with 287 additions and 320 deletions
|
|
@ -5,7 +5,7 @@ const gpu = mach.gpu;
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -16,7 +16,7 @@ pipeline: *gpu.RenderPipeline,
|
||||||
|
|
||||||
pub fn deinit(core: *mach.Core.Mod, game: *Mod) void {
|
pub fn deinit(core: *mach.Core.Mod, game: *Mod) void {
|
||||||
game.state().pipeline.release();
|
game.state().pipeline.release();
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(game: *Mod, core: *mach.Core.Mod) !void {
|
fn init(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
|
|
@ -59,7 +59,7 @@ fn init(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
});
|
});
|
||||||
try updateWindowTitle(core);
|
try updateWindowTitle(core);
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(important): remove need for returning an error here
|
// TODO(important): remove need for returning an error here
|
||||||
|
|
@ -69,7 +69,7 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
|
||||||
var iter = mach.core.pollEvents();
|
var iter = mach.core.pollEvents();
|
||||||
while (iter.next()) |event| {
|
while (iter.next()) |event| {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.close => core.send(.exit, .{}), // Tell mach.Core to exit the app
|
.close => core.schedule(.exit), // Tell mach.Core to exit the app
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ fn tick(core: *mach.Core.Mod, game: *Mod) !void {
|
||||||
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
|
|
||||||
// update the window title every second
|
// update the window title every second
|
||||||
if (game.state().title_timer.read() >= 1.0) {
|
if (game.state().title_timer.read() >= 1.0) {
|
||||||
|
|
@ -131,5 +131,5 @@ fn updateWindowTitle(core: *mach.Core.Mod) !void {
|
||||||
mach.core.inputRate(),
|
mach.core.inputRate(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
core.send(.update, .{});
|
core.schedule(.update);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub const components = .{
|
||||||
.follower = .{ .type = void },
|
.follower = .{ .type = void },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -39,8 +39,8 @@ pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub fn deinit(core: *mach.Core.Mod, renderer: *Renderer.Mod) void {
|
pub fn deinit(core: *mach.Core.Mod, renderer: *Renderer.Mod) void {
|
||||||
renderer.send(.deinit, .{});
|
renderer.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(important): remove need for returning an error here
|
// TODO(important): remove need for returning an error here
|
||||||
|
|
@ -53,7 +53,7 @@ fn init(
|
||||||
renderer: *Renderer.Mod,
|
renderer: *Renderer.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
renderer.send(.init, .{});
|
renderer.schedule(.init);
|
||||||
|
|
||||||
// Create our player entity.
|
// Create our player entity.
|
||||||
const player = try entities.new();
|
const player = try entities.new();
|
||||||
|
|
@ -77,7 +77,7 @@ fn init(
|
||||||
.player = player,
|
.player = player,
|
||||||
});
|
});
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(important): remove need for returning an error here
|
// TODO(important): remove need for returning an error here
|
||||||
|
|
@ -114,7 +114,7 @@ fn tick(
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}), // Send an event telling mach to exit the app
|
.close => core.schedule(.exit), // Send an event telling mach to exit the app
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,5 +208,5 @@ fn tick(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.send(.render_frame, .{});
|
renderer.schedule(.render_frame);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub const components = .{
|
||||||
.scale = .{ .type = f32 },
|
.scale = .{ .type = f32 },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.render_frame = .{ .handler = renderFrame },
|
.render_frame = .{ .handler = renderFrame },
|
||||||
|
|
@ -185,5 +185,5 @@ fn renderFrame(
|
||||||
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ frame_render_pass: *gpu.RenderPassEncoder = undefined,
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -41,22 +41,22 @@ pub const events = .{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn deinit(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod) !void {
|
fn deinit(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod) !void {
|
||||||
sprite_pipeline.send(.deinit, .{});
|
sprite_pipeline.schedule(.deinit);
|
||||||
glyphs.send(.deinit, .{});
|
glyphs.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void {
|
fn init(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void {
|
||||||
sprite_pipeline.send(.init, .{});
|
sprite_pipeline.schedule(.init);
|
||||||
glyphs.send(.init, .{});
|
glyphs.schedule(.init);
|
||||||
|
|
||||||
// Prepare which glyphs we will render
|
// Prepare which glyphs we will render
|
||||||
glyphs.send(.prepare, .{});
|
glyphs.schedule(.prepare);
|
||||||
|
|
||||||
// Run our init code after glyphs module is initialized.
|
// Run our init code after glyphs module is initialized.
|
||||||
game.send(.after_init, .{});
|
game.schedule(.after_init);
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn afterInit(
|
fn afterInit(
|
||||||
|
|
@ -71,7 +71,7 @@ fn afterInit(
|
||||||
const pipeline = try entities.new();
|
const pipeline = try entities.new();
|
||||||
texture.reference();
|
texture.reference();
|
||||||
try sprite_pipeline.set(pipeline, .texture, texture);
|
try sprite_pipeline.set(pipeline, .texture, texture);
|
||||||
sprite_pipeline.send(.update, .{});
|
sprite_pipeline.schedule(.update);
|
||||||
|
|
||||||
// We can create entities, and set components on them. Note that components live in a module
|
// We can create entities, and set components on them. Note that components live in a module
|
||||||
// namespace, e.g. the `Sprite` module could have a 3D `.location` component with a different
|
// namespace, e.g. the `Sprite` module could have a 3D `.location` component with a different
|
||||||
|
|
@ -83,7 +83,7 @@ fn afterInit(
|
||||||
try sprite.set(player, .pipeline, pipeline);
|
try sprite.set(player, .pipeline, pipeline);
|
||||||
try sprite.set(player, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height)));
|
try sprite.set(player, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height)));
|
||||||
try sprite.set(player, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))));
|
try sprite.set(player, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))));
|
||||||
sprite.send(.update, .{});
|
sprite.schedule(.update);
|
||||||
|
|
||||||
game.init(.{
|
game.init(.{
|
||||||
.timer = try mach.Timer.start(),
|
.timer = try mach.Timer.start(),
|
||||||
|
|
@ -133,7 +133,7 @@ fn tick(
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}),
|
.close => core.schedule(.exit),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,10 +199,10 @@ fn tick(
|
||||||
&Mat4x4.scale(Vec3.splat(1.0)),
|
&Mat4x4.scale(Vec3.splat(1.0)),
|
||||||
);
|
);
|
||||||
try sprite.set(game.state().player, .transform, player_transform);
|
try sprite.set(game.state().player, .transform, player_transform);
|
||||||
sprite.send(.update, .{});
|
sprite.schedule(.update);
|
||||||
|
|
||||||
// Perform pre-render work
|
// Perform pre-render work
|
||||||
sprite_pipeline.send(.pre_render, .{});
|
sprite_pipeline.schedule(.pre_render);
|
||||||
|
|
||||||
// Create a command encoder for this frame
|
// Create a command encoder for this frame
|
||||||
const label = @tagName(name) ++ ".tick";
|
const label = @tagName(name) ++ ".tick";
|
||||||
|
|
@ -228,10 +228,10 @@ fn tick(
|
||||||
|
|
||||||
// Render our sprite batch
|
// Render our sprite batch
|
||||||
sprite_pipeline.state().render_pass = game.state().frame_render_pass;
|
sprite_pipeline.state().render_pass = game.state().frame_render_pass;
|
||||||
sprite_pipeline.send(.render, .{});
|
sprite_pipeline.schedule(.render);
|
||||||
|
|
||||||
// Finish the frame once rendering is done.
|
// Finish the frame once rendering is done.
|
||||||
game.send(.end_frame, .{});
|
game.schedule(.end_frame);
|
||||||
|
|
||||||
game.state().time += delta_time;
|
game.state().time += delta_time;
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +247,7 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
game.state().frame_render_pass.release();
|
game.state().frame_render_pass.release();
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
@ -257,7 +257,7 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
"glyphs [ FPS: {d} ] [ Sprites: {d} ]",
|
"glyphs [ FPS: {d} ] [ Sprites: {d} ]",
|
||||||
.{ game.state().frame_count, game.state().sprites },
|
.{ game.state().frame_count, game.state().sprites },
|
||||||
);
|
);
|
||||||
core.send(.update, .{});
|
core.schedule(.update);
|
||||||
game.state().fps_timer.reset();
|
game.state().fps_timer.reset();
|
||||||
game.state().frame_count = 0;
|
game.state().frame_count = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const assets = @import("assets");
|
||||||
pub const name = .glyphs;
|
pub const name = .glyphs;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.prepare = .{ .handler = prepare },
|
.prepare = .{ .handler = prepare },
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.after_init = .{ .handler = afterInit },
|
.after_init = .{ .handler = afterInit },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
|
|
@ -38,8 +38,8 @@ pub const components = .{
|
||||||
ghost_key_mode: bool = false,
|
ghost_key_mode: bool = false,
|
||||||
|
|
||||||
fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
|
fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
|
||||||
audio.send(.init, .{});
|
audio.schedule(.init);
|
||||||
app.send(.after_init, .{});
|
app.schedule(.after_init);
|
||||||
|
|
||||||
// Initialize piano module state
|
// Initialize piano module state
|
||||||
app.init(.{});
|
app.init(.{});
|
||||||
|
|
@ -50,18 +50,18 @@ fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
|
||||||
std.debug.print("[arrow up] increase volume 10%\n", .{});
|
std.debug.print("[arrow up] increase volume 10%\n", .{});
|
||||||
std.debug.print("[arrow down] decrease volume 10%\n", .{});
|
std.debug.print("[arrow down] decrease volume 10%\n", .{});
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
|
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
|
||||||
// Configure the audio module to send our app's .audio_state_change event when an entity's sound
|
// Configure the audio module to send our app's .audio_state_change event when an entity's sound
|
||||||
// finishes playing.
|
// finishes playing.
|
||||||
audio.state().on_state_change = app.event(.audio_state_change);
|
audio.state().on_state_change = app.system(.audio_state_change);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
|
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
|
||||||
audio.send(.deinit, .{});
|
audio.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audioStateChange(
|
fn audioStateChange(
|
||||||
|
|
@ -135,7 +135,7 @@ fn tick(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}),
|
.close => core.schedule(.exit),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +175,7 @@ fn tick(
|
||||||
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fillTone(audio: *mach.Audio.Mod, frequency: f32) ![]const f32 {
|
fn fillTone(audio: *mach.Audio.Mod, frequency: f32) ![]const f32 {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.after_init = .{ .handler = afterInit },
|
.after_init = .{ .handler = afterInit },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
|
|
@ -38,8 +38,8 @@ fn init(
|
||||||
audio: *mach.Audio.Mod,
|
audio: *mach.Audio.Mod,
|
||||||
app: *Mod,
|
app: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
audio.send(.init, .{});
|
audio.schedule(.init);
|
||||||
app.send(.after_init, .{});
|
app.schedule(.after_init);
|
||||||
|
|
||||||
const bgm_fbs = std.io.fixedBufferStream(assets.bgm.bit_bit_loop);
|
const bgm_fbs = std.io.fixedBufferStream(assets.bgm.bit_bit_loop);
|
||||||
const sfx_fbs = std.io.fixedBufferStream(assets.sfx.sword1);
|
const sfx_fbs = std.io.fixedBufferStream(assets.sfx.sword1);
|
||||||
|
|
@ -65,18 +65,18 @@ fn init(
|
||||||
std.debug.print("[arrow up] increase volume 10%\n", .{});
|
std.debug.print("[arrow up] increase volume 10%\n", .{});
|
||||||
std.debug.print("[arrow down] decrease volume 10%\n", .{});
|
std.debug.print("[arrow down] decrease volume 10%\n", .{});
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
|
fn afterInit(audio: *mach.Audio.Mod, app: *Mod) void {
|
||||||
// Configure the audio module to send our app's .audio_state_change event when an entity's sound
|
// Configure the audio module to send our app's .audio_state_change event when an entity's sound
|
||||||
// finishes playing.
|
// finishes playing.
|
||||||
audio.state().on_state_change = app.event(.audio_state_change);
|
audio.state().on_state_change = app.system(.audio_state_change);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
|
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
|
||||||
audio.send(.deinit, .{});
|
audio.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audioStateChange(
|
fn audioStateChange(
|
||||||
|
|
@ -135,7 +135,7 @@ fn tick(
|
||||||
try audio.set(e, .playing, true);
|
try audio.set(e, .playing, true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}),
|
.close => core.schedule(.exit),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,5 +175,5 @@ fn tick(
|
||||||
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
core.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ frame_render_pass: *gpu.RenderPassEncoder = undefined,
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -47,8 +47,8 @@ fn deinit(
|
||||||
core: *mach.Core.Mod,
|
core: *mach.Core.Mod,
|
||||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||||
) !void {
|
) !void {
|
||||||
sprite_pipeline.send(.deinit, .{});
|
sprite_pipeline.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
|
|
@ -58,7 +58,7 @@ fn init(
|
||||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
sprite_pipeline.send(.init, .{});
|
sprite_pipeline.schedule(.init);
|
||||||
|
|
||||||
// We can create entities, and set components on them. Note that components live in a module
|
// We can create entities, and set components on them. Note that components live in a module
|
||||||
// namespace, e.g. the `.mach_gfx_sprite` module could have a 3D `.location` component with a different
|
// namespace, e.g. the `.mach_gfx_sprite` module could have a 3D `.location` component with a different
|
||||||
|
|
@ -68,7 +68,7 @@ fn init(
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
const pipeline = try entities.new();
|
const pipeline = try entities.new();
|
||||||
try sprite_pipeline.set(pipeline, .texture, try loadTexture(core, allocator));
|
try sprite_pipeline.set(pipeline, .texture, try loadTexture(core, allocator));
|
||||||
sprite_pipeline.send(.update, .{});
|
sprite_pipeline.schedule(.update);
|
||||||
|
|
||||||
// Create our player sprite
|
// Create our player sprite
|
||||||
const player = try entities.new();
|
const player = try entities.new();
|
||||||
|
|
@ -76,7 +76,7 @@ fn init(
|
||||||
try sprite.set(player, .size, vec2(32, 32));
|
try sprite.set(player, .size, vec2(32, 32));
|
||||||
try sprite.set(player, .uv_transform, Mat3x3.translate(vec2(0, 0)));
|
try sprite.set(player, .uv_transform, Mat3x3.translate(vec2(0, 0)));
|
||||||
try sprite.set(player, .pipeline, pipeline);
|
try sprite.set(player, .pipeline, pipeline);
|
||||||
sprite.send(.update, .{});
|
sprite.schedule(.update);
|
||||||
|
|
||||||
game.init(.{
|
game.init(.{
|
||||||
.timer = try mach.Timer.start(),
|
.timer = try mach.Timer.start(),
|
||||||
|
|
@ -91,7 +91,7 @@ fn init(
|
||||||
.pipeline = pipeline,
|
.pipeline = pipeline,
|
||||||
});
|
});
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(
|
fn tick(
|
||||||
|
|
@ -128,7 +128,7 @@ fn tick(
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}),
|
.close => core.schedule(.exit),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +181,10 @@ fn tick(
|
||||||
player_pos.v[0] += direction.x() * speed * delta_time;
|
player_pos.v[0] += direction.x() * speed * delta_time;
|
||||||
player_pos.v[1] += direction.y() * speed * delta_time;
|
player_pos.v[1] += direction.y() * speed * delta_time;
|
||||||
try sprite.set(game.state().player, .transform, Mat4x4.translate(player_pos));
|
try sprite.set(game.state().player, .transform, Mat4x4.translate(player_pos));
|
||||||
sprite.send(.update, .{});
|
sprite.schedule(.update);
|
||||||
|
|
||||||
// Perform pre-render work
|
// Perform pre-render work
|
||||||
sprite_pipeline.send(.pre_render, .{});
|
sprite_pipeline.schedule(.pre_render);
|
||||||
|
|
||||||
// Create a command encoder for this frame
|
// Create a command encoder for this frame
|
||||||
const label = @tagName(name) ++ ".tick";
|
const label = @tagName(name) ++ ".tick";
|
||||||
|
|
@ -210,10 +210,10 @@ fn tick(
|
||||||
|
|
||||||
// Render our sprite batch
|
// Render our sprite batch
|
||||||
sprite_pipeline.state().render_pass = game.state().frame_render_pass;
|
sprite_pipeline.state().render_pass = game.state().frame_render_pass;
|
||||||
sprite_pipeline.send(.render, .{});
|
sprite_pipeline.schedule(.render);
|
||||||
|
|
||||||
// Finish the frame once rendering is done.
|
// Finish the frame once rendering is done.
|
||||||
game.send(.end_frame, .{});
|
game.schedule(.end_frame);
|
||||||
|
|
||||||
game.state().time += delta_time;
|
game.state().time += delta_time;
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +229,7 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
game.state().frame_render_pass.release();
|
game.state().frame_render_pass.release();
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
@ -239,7 +239,7 @@ fn endFrame(game: *Mod, core: *mach.Core.Mod) !void {
|
||||||
"sprite [ FPS: {d} ] [ Sprites: {d} ]",
|
"sprite [ FPS: {d} ] [ Sprites: {d} ]",
|
||||||
.{ game.state().frame_count, game.state().sprites },
|
.{ game.state().frame_count, game.state().sprites },
|
||||||
);
|
);
|
||||||
core.send(.update, .{});
|
core.schedule(.update);
|
||||||
game.state().fps_timer.reset();
|
game.state().fps_timer.reset();
|
||||||
game.state().frame_count = 0;
|
game.state().frame_count = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ frame_render_pass: *gpu.RenderPassEncoder = undefined,
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -58,8 +58,8 @@ fn deinit(
|
||||||
core: *mach.Core.Mod,
|
core: *mach.Core.Mod,
|
||||||
text_pipeline: *gfx.TextPipeline.Mod,
|
text_pipeline: *gfx.TextPipeline.Mod,
|
||||||
) !void {
|
) !void {
|
||||||
text_pipeline.send(.deinit, .{});
|
text_pipeline.schedule(.deinit);
|
||||||
core.send(.deinit, .{});
|
core.schedule(.deinit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
|
|
@ -70,7 +70,7 @@ fn init(
|
||||||
text_style: *gfx.TextStyle.Mod,
|
text_style: *gfx.TextStyle.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
text_pipeline.send(.init, .{});
|
text_pipeline.schedule(.init);
|
||||||
|
|
||||||
// TODO: a better way to initialize entities with default values
|
// TODO: a better way to initialize entities with default values
|
||||||
// TODO(text): most of these style options are not respected yet.
|
// TODO(text): most of these style options are not respected yet.
|
||||||
|
|
@ -98,7 +98,7 @@ fn init(
|
||||||
// Create a text rendering pipeline
|
// Create a text rendering pipeline
|
||||||
const pipeline = try entities.new();
|
const pipeline = try entities.new();
|
||||||
try text_pipeline.set(pipeline, .is_pipeline, {});
|
try text_pipeline.set(pipeline, .is_pipeline, {});
|
||||||
text_pipeline.send(.update, .{});
|
text_pipeline.schedule(.update);
|
||||||
|
|
||||||
// Create some text
|
// Create some text
|
||||||
const player = try entities.new();
|
const player = try entities.new();
|
||||||
|
|
@ -129,7 +129,7 @@ fn init(
|
||||||
.pipeline = pipeline,
|
.pipeline = pipeline,
|
||||||
});
|
});
|
||||||
|
|
||||||
core.send(.start, .{});
|
core.schedule(.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(
|
fn tick(
|
||||||
|
|
@ -166,7 +166,7 @@ fn tick(
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.close => core.send(.exit, .{}),
|
.close => core.schedule(.exit),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,10 +225,10 @@ fn tick(
|
||||||
player_pos.v[1] += direction.y() * speed * delta_time;
|
player_pos.v[1] += direction.y() * speed * delta_time;
|
||||||
try text.set(game.state().player, .transform, Mat4x4.scaleScalar(upscale).mul(&Mat4x4.translate(player_pos)));
|
try text.set(game.state().player, .transform, Mat4x4.scaleScalar(upscale).mul(&Mat4x4.translate(player_pos)));
|
||||||
try text.set(game.state().player, .dirty, true);
|
try text.set(game.state().player, .dirty, true);
|
||||||
text.send(.update, .{});
|
text.schedule(.update);
|
||||||
|
|
||||||
// Perform pre-render work
|
// Perform pre-render work
|
||||||
text_pipeline.send(.pre_render, .{});
|
text_pipeline.schedule(.pre_render);
|
||||||
|
|
||||||
// Create a command encoder for this frame
|
// Create a command encoder for this frame
|
||||||
const label = @tagName(name) ++ ".tick";
|
const label = @tagName(name) ++ ".tick";
|
||||||
|
|
@ -254,10 +254,10 @@ fn tick(
|
||||||
|
|
||||||
// Render our text batch
|
// Render our text batch
|
||||||
text_pipeline.state().render_pass = game.state().frame_render_pass;
|
text_pipeline.state().render_pass = game.state().frame_render_pass;
|
||||||
text_pipeline.send(.render, .{});
|
text_pipeline.schedule(.render);
|
||||||
|
|
||||||
// Finish the frame once rendering is done.
|
// Finish the frame once rendering is done.
|
||||||
game.send(.end_frame, .{});
|
game.schedule(.end_frame);
|
||||||
|
|
||||||
game.state().time += delta_time;
|
game.state().time += delta_time;
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +277,7 @@ fn endFrame(
|
||||||
game.state().frame_render_pass.release();
|
game.state().frame_render_pass.release();
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
core.send(.present_frame, .{});
|
core.schedule(.present_frame);
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
@ -300,7 +300,7 @@ fn endFrame(
|
||||||
"text [ FPS: {d} ] [ Texts: {d} ] [ Glyphs: {d} ]",
|
"text [ FPS: {d} ] [ Texts: {d} ] [ Glyphs: {d} ]",
|
||||||
.{ game.state().frame_count, num_texts, num_glyphs },
|
.{ game.state().frame_count, num_texts, num_glyphs },
|
||||||
);
|
);
|
||||||
core.send(.update, .{});
|
core.schedule(.update);
|
||||||
game.state().fps_timer.reset();
|
game.state().fps_timer.reset();
|
||||||
game.state().frame_count = 0;
|
game.state().frame_count = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub const components = .{
|
||||||
.index = .{ .type = usize },
|
.index = .{ .type = usize },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.audio_tick = .{ .handler = audioTick },
|
.audio_tick = .{ .handler = audioTick },
|
||||||
|
|
@ -32,7 +32,7 @@ ms_render_ahead: f32 = 16,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
ctx: sysaudio.Context,
|
ctx: sysaudio.Context,
|
||||||
player: sysaudio.Player,
|
player: sysaudio.Player,
|
||||||
on_state_change: ?mach.AnyEvent = null,
|
on_state_change: ?mach.AnySystem = null,
|
||||||
output_mu: std.Thread.Mutex = .{},
|
output_mu: std.Thread.Mutex = .{},
|
||||||
output: SampleBuffer,
|
output: SampleBuffer,
|
||||||
mixing_buffer: ?std.ArrayListUnmanaged(f32) = null,
|
mixing_buffer: ?std.ArrayListUnmanaged(f32) = null,
|
||||||
|
|
@ -168,7 +168,7 @@ fn audioTick(entities: *mach.Entities.Mod, audio: *Mod) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audio.state().on_state_change) |on_state_change_event| {
|
if (audio.state().on_state_change) |on_state_change_event| {
|
||||||
if (did_state_change) audio.sendAnyEvent(on_state_change_event);
|
if (did_state_change) audio.scheduleAny(on_state_change_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write our rendered samples to the fifo, expanding its size as needed and converting our f32
|
// Write our rendered samples to the fifo, expanding its size as needed and converting our f32
|
||||||
|
|
@ -201,7 +201,7 @@ fn writeFn(audio_opaque: ?*anyopaque, output: []u8) void {
|
||||||
const format_size = audio.state().player.format().size();
|
const format_size = audio.state().player.format().size();
|
||||||
const render_num_samples = @divExact(output.len, format_size);
|
const render_num_samples = @divExact(output.len, format_size);
|
||||||
audio.state().render_num_samples = render_num_samples;
|
audio.state().render_num_samples = render_num_samples;
|
||||||
audio.send(.audio_tick, .{});
|
audio.schedule(.audio_tick);
|
||||||
|
|
||||||
// Read the prepared audio samples and directly @memcpy them to the output buffer.
|
// Read the prepared audio samples and directly @memcpy them to the output buffer.
|
||||||
audio.state().output_mu.lock();
|
audio.state().output_mu.lock();
|
||||||
|
|
@ -210,7 +210,7 @@ fn writeFn(audio_opaque: ?*anyopaque, output: []u8) void {
|
||||||
if (read_slice.len < output.len) {
|
if (read_slice.len < output.len) {
|
||||||
// We do not have enough audio data prepared. Busy-wait until we do, otherwise the audio
|
// We do not have enough audio data prepared. Busy-wait until we do, otherwise the audio
|
||||||
// thread may become de-sync'd with the loop responsible for producing it.
|
// thread may become de-sync'd with the loop responsible for producing it.
|
||||||
audio.send(.audio_tick, .{});
|
audio.schedule(.audio_tick);
|
||||||
if (audio.state().debug) log.debug("resync, found {} samples but need {} (nano timestamp {})", .{ read_slice.len / format_size, output.len / format_size, std.time.nanoTimestamp() });
|
if (audio.state().debug) log.debug("resync, found {} samples but need {} (nano timestamp {})", .{ read_slice.len / format_size, output.len / format_size, std.time.nanoTimestamp() });
|
||||||
|
|
||||||
audio.state().output_mu.unlock();
|
audio.state().output_mu.unlock();
|
||||||
|
|
|
||||||
12
src/Core.zig
12
src/Core.zig
|
|
@ -10,7 +10,7 @@ pub const name = .mach_core;
|
||||||
|
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.start = .{ .handler = start, .description =
|
.start = .{ .handler = start, .description =
|
||||||
\\ Send this once your app is initialized and ready for .app.tick events.
|
\\ Send this once your app is initialized and ready for .app.tick events.
|
||||||
},
|
},
|
||||||
|
|
@ -128,7 +128,7 @@ fn init(entities: *mach.Entities.Mod, core: *Mod) !void {
|
||||||
.main_window = main_window,
|
.main_window = main_window,
|
||||||
});
|
});
|
||||||
|
|
||||||
mach.core.mods.send(.app, .init, .{});
|
mach.core.mods.schedule(.app, .init);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(entities: *mach.Entities.Mod) !void {
|
fn update(entities: *mach.Entities.Mod) !void {
|
||||||
|
|
@ -153,12 +153,12 @@ fn presentFrame(core: *Mod) !void {
|
||||||
mach.core.swap_chain.present();
|
mach.core.swap_chain.present();
|
||||||
|
|
||||||
// Signal that mainThreadTick is done
|
// Signal that mainThreadTick is done
|
||||||
core.send(.main_thread_tick_done, .{});
|
core.schedule(.main_thread_tick_done);
|
||||||
},
|
},
|
||||||
.exiting => {
|
.exiting => {
|
||||||
// Exit opportunity is here, deinitialize now
|
// Exit opportunity is here, deinitialize now
|
||||||
core.state().run_state = .deinitializing;
|
core.state().run_state = .deinitializing;
|
||||||
mach.core.mods.send(.app, .deinit, .{});
|
mach.core.mods.schedule(.app, .deinit);
|
||||||
},
|
},
|
||||||
else => return,
|
else => return,
|
||||||
}
|
}
|
||||||
|
|
@ -181,13 +181,13 @@ fn deinit(entities: *mach.Entities.Mod, core: *Mod) !void {
|
||||||
core.state().run_state = .exited;
|
core.state().run_state = .exited;
|
||||||
|
|
||||||
// Signal that mainThreadTick is done
|
// Signal that mainThreadTick is done
|
||||||
core.send(.main_thread_tick_done, .{});
|
core.schedule(.main_thread_tick_done);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mainThreadTick(core: *Mod) !void {
|
fn mainThreadTick(core: *Mod) !void {
|
||||||
if (core.state().run_state != .running) return;
|
if (core.state().run_state != .running) return;
|
||||||
_ = try mach.core.update(null);
|
_ = try mach.core.update(null);
|
||||||
mach.core.mods.send(.app, .tick, .{});
|
mach.core.mods.schedule(.app, .tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(core: *Mod) void {
|
fn exit(core: *Mod) void {
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ pub fn initModule() !void {
|
||||||
// Initialize the global set of Mach modules used in the program.
|
// Initialize the global set of Mach modules used in the program.
|
||||||
try mods.init(std.heap.c_allocator);
|
try mods.init(std.heap.c_allocator);
|
||||||
|
|
||||||
mods.send(.mach_core, .init, .{});
|
mods.schedule(.mach_core, .init);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tick runs a single step of the main loop on the main OS thread.
|
/// 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.
|
/// Returns true if tick() should be called again, false if the application should exit.
|
||||||
pub fn tick() !bool {
|
pub fn tick() !bool {
|
||||||
mods.send(.mach_core, .main_thread_tick, .{});
|
mods.schedule(.mach_core, .main_thread_tick);
|
||||||
|
|
||||||
// Dispatch events until this .mach_core.main_thread_tick_done is sent
|
// Dispatch events until this .mach_core.main_thread_tick_done is sent
|
||||||
try mods.dispatch(&stack_space, .{ .until = .{
|
try mods.dispatch(&stack_space, .{ .until = .{
|
||||||
.module_name = mods.moduleNameToID(.mach_core),
|
.module_name = mods.moduleNameToID(.mach_core),
|
||||||
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
|
.system = mods.systemToID(.mach_core, .main_thread_tick_done),
|
||||||
} });
|
} });
|
||||||
|
|
||||||
return mods.mod.mach_core.state().run_state != .exited;
|
return mods.mod.mach_core.state().run_state != .exited;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ pub const components = .{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ pub const components = .{
|
||||||
.built = .{ .type = BuiltPipeline, .description = "internal" },
|
.built = .{ .type = BuiltPipeline, .description = "internal" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ pub const components = .{
|
||||||
.built = .{ .type = BuiltText, .description = "internal" },
|
.built = .{ .type = BuiltText, .description = "internal" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ pub const components = .{
|
||||||
.built = .{ .type = BuiltPipeline, .description = "internal" },
|
.built = .{ .type = BuiltPipeline, .description = "internal" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.init = .{ .handler = fn () void },
|
.init = .{ .handler = fn () void },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ pub const EntityID = @import("module/main.zig").EntityID; // TODO: rename to jus
|
||||||
pub const Archetype = @import("module/main.zig").Archetype;
|
pub const Archetype = @import("module/main.zig").Archetype;
|
||||||
|
|
||||||
pub const ModuleID = @import("module/main.zig").ModuleID;
|
pub const ModuleID = @import("module/main.zig").ModuleID;
|
||||||
pub const EventID = @import("module/main.zig").EventID;
|
pub const SystemID = @import("module/main.zig").SystemID;
|
||||||
pub const AnyEvent = @import("module/main.zig").AnyEvent;
|
pub const AnySystem = @import("module/main.zig").AnySystem;
|
||||||
pub const merge = @import("module/main.zig").merge;
|
pub const merge = @import("module/main.zig").merge;
|
||||||
pub const builtin_modules = @import("module/main.zig").builtin_modules;
|
pub const builtin_modules = @import("module/main.zig").builtin_modules;
|
||||||
pub const Entities = @import("module/main.zig").Entities;
|
pub const Entities = @import("module/main.zig").Entities;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ pub const Archetype = @import("Archetype.zig");
|
||||||
pub const ModSet = @import("module.zig").ModSet;
|
pub const ModSet = @import("module.zig").ModSet;
|
||||||
pub const Modules = @import("module.zig").Modules;
|
pub const Modules = @import("module.zig").Modules;
|
||||||
pub const ModuleID = @import("module.zig").ModuleID;
|
pub const ModuleID = @import("module.zig").ModuleID;
|
||||||
pub const EventID = @import("module.zig").EventID;
|
pub const SystemID = @import("module.zig").SystemID;
|
||||||
pub const AnyEvent = @import("module.zig").AnyEvent;
|
pub const AnySystem = @import("module.zig").AnySystem;
|
||||||
pub const Merge = @import("module.zig").Merge;
|
pub const Merge = @import("module.zig").Merge;
|
||||||
pub const merge = @import("module.zig").merge;
|
pub const merge = @import("module.zig").merge;
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ test "entities DB" {
|
||||||
pub const components = .{
|
pub const components = .{
|
||||||
.id = .{ .type = u32 },
|
.id = .{ .type = u32 },
|
||||||
};
|
};
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ test "entities DB" {
|
||||||
pub const components = .{
|
pub const components = .{
|
||||||
.id = .{ .type = u16 },
|
.id = .{ .type = u16 },
|
||||||
};
|
};
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -97,8 +97,10 @@ test "entities DB" {
|
||||||
try physics.set(player3, .id, 1003);
|
try physics.set(player3, .id, 1003);
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Send events to modules
|
// Schedule systems to run
|
||||||
world.mod.renderer.send(.tick, .{});
|
world.mod.renderer.schedule(.tick);
|
||||||
|
|
||||||
|
// Dispatch systems
|
||||||
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
try world.dispatch(&stack_space, .{});
|
try world.dispatch(&stack_space, .{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,3 @@
|
||||||
//! Module system
|
|
||||||
//!
|
|
||||||
//! ## Events
|
|
||||||
//!
|
|
||||||
//! Every piece of logic in a Mach module runs in response to an event.
|
|
||||||
//!
|
|
||||||
//! Events are used to schedule the execution order of event handlers. What we call event handlers
|
|
||||||
//! are often called 'systems' in other game engines following ECS design patterns. Typically,
|
|
||||||
//! other engines require that you express a specific order you'd like systems to execute in via
|
|
||||||
//! e.g. a sorting integer.
|
|
||||||
//!
|
|
||||||
//! Mach's module system has only events and event handlers. In order to get system/event-handler B
|
|
||||||
//! to run after A, A simply needs to send an event that module B defines an event handler for.
|
|
||||||
//! These are simple functions, with some dependency injection.
|
|
||||||
//!
|
|
||||||
//! Event handlers are also a point-of-parallelism opportunity, i.e. depending on what event
|
|
||||||
//! handlers do within their function body (whether it be adding/removing entities/components,
|
|
||||||
//! sending events, or reading/writing module state), the event scheduler can determine which event
|
|
||||||
//! handlers may be eligible for parallel execution without data races or non-deterministic behavior.
|
|
||||||
//! Mach does not yet implement this today, but will in the future.
|
|
||||||
//!
|
|
||||||
//! Events are simply a name associated with a function, as well as some (simple data type) parameters
|
|
||||||
//! that function may expect. They are however **high-level** communication between modules, i.e.
|
|
||||||
//! scheduling the execution of functions which are generally expected to do a reasonable amount of
|
|
||||||
//! work, since events are a dynamic dispatch point and not e.g. inline function calls.
|
|
||||||
//!
|
|
||||||
//! Events are also the foundation for:
|
|
||||||
//!
|
|
||||||
//! * Graphical editor integration, e.g. event names and parameters could be enumerated via an
|
|
||||||
//! external process to your program, allowing a graphical editor to craft and send events with
|
|
||||||
//! payloads to your program over e.g. a socket.
|
|
||||||
//! * Debugging facilities - for example the entire program can be analyzed as a sequence of named
|
|
||||||
//! events being dispatched, showing you the execution of e.g. a frame. Or, events could be saved
|
|
||||||
//! to disk and replayed/inspected later.
|
|
||||||
//! * Networking between modules - events could be serialized and sent over the network.
|
|
||||||
//! * Loops executing at different frequencies - a 'loop' is simply events firing in a circular loop,
|
|
||||||
//! e.g. you could have multiple loops of events going at the same time, using an event as a
|
|
||||||
//! synchronization point:
|
|
||||||
//! * .render_begin -> wait for .physics_sync -> .game_tick -> .game_draw_frame -> .render_end -> .render_begin
|
|
||||||
//! * .physics_begin -> .poll_input -> .physics_calculate -> .physics_sync -> .physics_end -> .physics_begin
|
|
||||||
//!
|
|
||||||
//! ## Event arguments
|
|
||||||
//!
|
|
||||||
//! Event arguments should only be used to convey stateless information.
|
|
||||||
//!
|
|
||||||
//! Good use-cases for event arguments are typically ones where you would want a graphical editor
|
|
||||||
//! to be able to convey to your program that something should be done, for example:
|
|
||||||
//! * `.spawn_monsters` with an argument conveying the number of monsters to spawn.
|
|
||||||
//! * `.set_entity_position` with an argument conveying what to set an entity's position to
|
|
||||||
//!
|
|
||||||
//! On the other hand, bad use-cases for event arguments tend to be stateful:
|
|
||||||
//!
|
|
||||||
//! * Anything involving pointers (which may be completely prohibited in the future)
|
|
||||||
//! * `.render_players` with an argument conveying to render specific player entities, rather than
|
|
||||||
//! the event having no arguments and instead looking at which entities have a component/tag
|
|
||||||
//! indicating they should be rendered.
|
|
||||||
//!
|
|
||||||
//! These examples are bad because if these events' arguments were to be e.g. serialized and saved
|
|
||||||
//! to disk, and then replayed later in a future execution of the program, you may find that the
|
|
||||||
//! arguments no longer make sense in a replay of the program.
|
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
@ -76,12 +15,12 @@ fn ModuleInterface(comptime M: type) type {
|
||||||
return M;
|
return M;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validateModule(comptime M: type, comptime events: bool) void {
|
fn validateModule(comptime M: type, comptime systems: bool) void {
|
||||||
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
||||||
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`: " ++ @typeName(M));
|
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`: " ++ @typeName(M));
|
||||||
if (@typeInfo(@TypeOf(M.name)) != .EnumLiteral) @compileError("mach: module must have `pub const name = .foobar;`, found type:" ++ @typeName(M.name));
|
if (@typeInfo(@TypeOf(M.name)) != .EnumLiteral) @compileError("mach: module must have `pub const name = .foobar;`, found type:" ++ @typeName(M.name));
|
||||||
if (events) {
|
if (systems) {
|
||||||
if (@hasDecl(M, "events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " events ", M.events);
|
if (@hasDecl(M, "systems")) validateSystems("mach: module ." ++ @tagName(M.name) ++ " systems ", M.systems);
|
||||||
_ = ComponentTypesM(M);
|
_ = ComponentTypesM(M);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,11 +34,11 @@ fn Serializable(comptime T: type) type {
|
||||||
|
|
||||||
// TODO: add runtime module support
|
// TODO: add runtime module support
|
||||||
pub const ModuleID = u32;
|
pub const ModuleID = u32;
|
||||||
pub const EventID = u32;
|
pub const SystemID = u32;
|
||||||
|
|
||||||
pub const AnyEvent = struct {
|
pub const AnySystem = struct {
|
||||||
module_id: ModuleID,
|
module_id: ModuleID,
|
||||||
event_id: EventID,
|
system_id: SystemID,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type-returning variant of merge()
|
/// Type-returning variant of merge()
|
||||||
|
|
@ -180,23 +119,23 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
inline for (modules) |M| _ = ModuleInterface(M);
|
inline for (modules) |M| _ = ModuleInterface(M);
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
pub const LocalEvent = LocalEventEnum(modules);
|
pub const System = SystemEnum(modules);
|
||||||
|
|
||||||
/// Enables looking up a component type by module name and component name.
|
/// Enables looking up a component type by module name and component name.
|
||||||
/// e.g. @field(@field(ComponentTypesByName, "module_name"), "component_name")
|
/// e.g. @field(@field(ComponentTypesByName, "module_name"), "component_name")
|
||||||
pub const component_types_by_name = ComponentTypesByName(modules){};
|
pub const component_types_by_name = ComponentTypesByName(modules){};
|
||||||
|
|
||||||
const Event = struct {
|
const Dispatch = struct {
|
||||||
module_name: ModuleID,
|
module_name: ModuleID,
|
||||||
event_name: EventID,
|
system_name: SystemID,
|
||||||
args_slice: []u8,
|
args_slice: []u8,
|
||||||
args_alignment: u32,
|
args_alignment: u32,
|
||||||
};
|
};
|
||||||
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
const DispatchQueue = std.fifo.LinearFifo(Dispatch, .Dynamic);
|
||||||
|
|
||||||
events_mu: std.Thread.RwLock = .{},
|
dispatch_queue_mu: std.Thread.RwLock = .{},
|
||||||
args_queue: std.ArrayListUnmanaged(u8) = .{},
|
dispatch_args_queue: std.ArrayListUnmanaged(u8) = .{},
|
||||||
events: EventQueue,
|
dispatch_queue: DispatchQueue,
|
||||||
mod: ModsByName(modules),
|
mod: ModsByName(modules),
|
||||||
// TODO: pass mods directly instead of ComponentTypesByName?
|
// TODO: pass mods directly instead of ComponentTypesByName?
|
||||||
entities: Database(modules),
|
entities: Database(modules),
|
||||||
|
|
@ -222,14 +161,14 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
m.* = .{
|
m.* = .{
|
||||||
.entities = entities,
|
.entities = entities,
|
||||||
// TODO(module): better default allocations
|
// TODO(module): better default allocations
|
||||||
.args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024),
|
.dispatch_args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024),
|
||||||
.events = EventQueue.init(allocator),
|
.dispatch_queue = DispatchQueue.init(allocator),
|
||||||
.mod = undefined,
|
.mod = undefined,
|
||||||
.debug_trace = debug_trace,
|
.debug_trace = debug_trace,
|
||||||
};
|
};
|
||||||
errdefer m.args_queue.deinit(allocator);
|
errdefer m.dispatch_args_queue.deinit(allocator);
|
||||||
errdefer m.events.deinit();
|
errdefer m.dispatch_queue.deinit();
|
||||||
try m.events.ensureTotalCapacity(1024); // TODO(module): better default allocations
|
try m.dispatch_queue.ensureTotalCapacity(1024); // TODO(module): better default allocations
|
||||||
|
|
||||||
// Default initialize m.mod
|
// Default initialize m.mod
|
||||||
inline for (@typeInfo(@TypeOf(m.mod)).Struct.fields) |field| {
|
inline for (@typeInfo(@TypeOf(m.mod)).Struct.fields) |field| {
|
||||||
|
|
@ -243,73 +182,100 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(m: *@This(), allocator: std.mem.Allocator) void {
|
pub fn deinit(m: *@This(), allocator: std.mem.Allocator) void {
|
||||||
m.args_queue.deinit(allocator);
|
m.dispatch_args_queue.deinit(allocator);
|
||||||
m.events.deinit();
|
m.dispatch_queue.deinit();
|
||||||
m.entities.deinit();
|
m.entities.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
||||||
/// local event handler requires.
|
/// system requires.
|
||||||
fn LocalArgs(module_name: ModuleName(modules), event_name: LocalEvent) type {
|
fn SystemArgs(module_name: ModuleName(modules), system_name: System) type {
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
if (M.name != module_name) continue;
|
if (M.name != module_name) continue;
|
||||||
return LocalArgsM(M, event_name);
|
return SystemArgsM(M, system_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an event enum for a single module, to an event enum for all modules.
|
/// Converts a system enum for a single module, to a system enum for all modules.
|
||||||
fn moduleToGlobalEvent(
|
fn moduleToGlobalSystemName(
|
||||||
comptime M: type,
|
comptime M: type,
|
||||||
comptime EventEnumM: anytype,
|
comptime SysEnumM: anytype,
|
||||||
comptime EventEnum: anytype,
|
comptime SysEnum: anytype,
|
||||||
comptime event_name: EventEnumM(M),
|
comptime system_name: SysEnumM(M),
|
||||||
) EventEnum(modules) {
|
) SysEnum(modules) {
|
||||||
return comptime stringToEnum(EventEnum(modules), @tagName(event_name)).?;
|
return comptime stringToEnum(SysEnum(modules), @tagName(system_name)).?;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an event to a specific module
|
/// Schedule the specified system to run later
|
||||||
pub fn send(
|
pub fn schedule(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where module_name/event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where module_name/system_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime module_name: ModuleName(modules),
|
comptime module_name: ModuleName(modules),
|
||||||
// TODO(important): cleanup comptime
|
// TODO(important): cleanup comptime
|
||||||
comptime event_name: LocalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
comptime system_name: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
||||||
args: LocalArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).__state), event_name),
|
) void {
|
||||||
|
m.scheduleWithArgs(module_name, system_name, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule the specified system to run later, passing some additional arguments.
|
||||||
|
///
|
||||||
|
/// Today, any arguments are allowed, but in the future these will be restricted to simple
|
||||||
|
/// data types
|
||||||
|
/// , non-pointers, and you will want to ensure they are not stateful in order for
|
||||||
|
/// your program to work with future debugging tools.
|
||||||
|
///
|
||||||
|
/// In general, scheduleWithArgs should really only be used for cross-language, cross-process,
|
||||||
|
/// or cross-network behavior. If you otherwise need to get data from one system to another
|
||||||
|
/// you should be using entities and components.
|
||||||
|
pub fn scheduleWithArgs(
|
||||||
|
m: *@This(),
|
||||||
|
// TODO: is a variant of this function where module_name/system_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
|
comptime module_name: ModuleName(modules),
|
||||||
|
// TODO(important): cleanup comptime
|
||||||
|
comptime system_name: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
||||||
|
args: SystemArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).__state), system_name),
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
const event_name_g: LocalEvent = comptime moduleToGlobalEvent(
|
const system_name_g: System = comptime moduleToGlobalSystemName(
|
||||||
// TODO(important): cleanup comptime
|
// TODO(important): cleanup comptime
|
||||||
@TypeOf(@field(m.mod, @tagName(module_name)).__state),
|
@TypeOf(@field(m.mod, @tagName(module_name)).__state),
|
||||||
LocalEventEnumM,
|
SystemEnumM,
|
||||||
LocalEventEnum,
|
SystemEnum,
|
||||||
event_name,
|
system_name,
|
||||||
);
|
);
|
||||||
m.sendInternal(@intFromEnum(module_name), @intFromEnum(event_name_g), args);
|
m.sendInternal(@intFromEnum(module_name), @intFromEnum(system_name_g), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an event to a specific module, using a dynamic (not known to the compiled program) module and event name.
|
/// Schedule the specified system to run later, using fully dynamic parameters (i.e. to run
|
||||||
pub fn sendDynamic(m: *@This(), module_name: ModuleID, event_name: EventID, args: anytype) void {
|
/// a system not known to the program at compile time.)
|
||||||
|
pub fn scheduleDynamic(m: *@This(), module_name: ModuleID, system_name: SystemID) void {
|
||||||
|
m.scheduleDynamicWithArgs(module_name, system_name, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule the specified system to run later, using fully dynamic parameters (i.e. to run
|
||||||
|
/// a system not known to the program at compile time.)
|
||||||
|
pub fn scheduleDynamicWithArgs(m: *@This(), module_name: ModuleID, system_name: SystemID, args: anytype) void {
|
||||||
// TODO: runtime safety/debugging
|
// TODO: runtime safety/debugging
|
||||||
// TODO: check args do not have obviously wrong things, like comptime values
|
// TODO: check args do not have obviously wrong things, like comptime values
|
||||||
// TODO: if module_name and event_name are valid enums, can we type-check args at runtime?
|
// TODO: if module_name and system_name are valid enums, can we type-check args at runtime?
|
||||||
m.sendInternal(module_name, event_name, args);
|
m.sendInternal(module_name, system_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendInternal(m: *@This(), module_name: ModuleID, event_name: EventID, args: anytype) void {
|
fn sendInternal(m: *@This(), module_name: ModuleID, system_name: SystemID, args: anytype) void {
|
||||||
// TODO: verify arguments are valid, e.g. not comptime types
|
// TODO: verify arguments are valid, e.g. not comptime types
|
||||||
_ = Serializable(@TypeOf(args));
|
_ = Serializable(@TypeOf(args));
|
||||||
|
|
||||||
// TODO: debugging
|
// TODO: debugging
|
||||||
m.events_mu.lock();
|
m.dispatch_queue_mu.lock();
|
||||||
defer m.events_mu.unlock();
|
defer m.dispatch_queue_mu.unlock();
|
||||||
|
|
||||||
const args_bytes = std.mem.asBytes(&args);
|
const args_bytes = std.mem.asBytes(&args);
|
||||||
m.args_queue.appendSliceAssumeCapacity(args_bytes);
|
m.dispatch_args_queue.appendSliceAssumeCapacity(args_bytes);
|
||||||
m.events.writeItemAssumeCapacity(.{
|
m.dispatch_queue.writeItemAssumeCapacity(.{
|
||||||
.module_name = module_name,
|
.module_name = module_name,
|
||||||
.event_name = event_name,
|
.system_name = system_name,
|
||||||
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. m.args_queue.items.len],
|
.args_slice = m.dispatch_args_queue.items[m.dispatch_args_queue.items.len - args_bytes.len .. m.dispatch_args_queue.items.len],
|
||||||
.args_alignment = @alignOf(@TypeOf(args)),
|
.args_alignment = @alignOf(@TypeOf(args)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -321,29 +287,29 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: docs
|
// TODO: docs
|
||||||
pub fn localEventToID(
|
pub fn systemToID(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
comptime module_name: ModuleName(modules),
|
comptime module_name: ModuleName(modules),
|
||||||
// TODO(important): cleanup comptime
|
// TODO(important): cleanup comptime
|
||||||
local_event: LocalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
system: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
||||||
) EventID {
|
) SystemID {
|
||||||
return @intFromEnum(local_event);
|
return @intFromEnum(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DispatchOptions = struct {
|
pub const DispatchOptions = struct {
|
||||||
/// If specified, instructs that dispatching should occur until the specified local
|
/// If specified, instructs that dispatching should occur until the specified system
|
||||||
/// event has been dispatched.
|
/// has been dispatched.
|
||||||
///
|
///
|
||||||
/// If null, dispatching occurs until the event queue is completely empty.
|
/// If null, dispatching occurs until the system queue is completely empty.
|
||||||
until: ?struct {
|
until: ?struct {
|
||||||
module_name: ModuleID,
|
module_name: ModuleID,
|
||||||
local_event: EventID,
|
system: SystemID,
|
||||||
} = null,
|
} = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Dispatches pending events, invoking their event handlers.
|
/// Dispatches pending systems, invoking their handlers.
|
||||||
///
|
///
|
||||||
/// Stack space must be large enough to fit the uninjected arguments of any event handler
|
/// Stack space must be large enough to fit the uninjected arguments of any system handler
|
||||||
/// which may be invoked, e.g. 8MB. It may be heap-allocated.
|
/// which may be invoked, e.g. 8MB. It may be heap-allocated.
|
||||||
pub fn dispatch(
|
pub fn dispatch(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
|
|
@ -383,55 +349,55 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
// TODO: PGO
|
// TODO: PGO
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Dequeue the next event
|
// Dequeue the next system
|
||||||
m.events_mu.lock();
|
m.dispatch_queue_mu.lock();
|
||||||
var ev = m.events.readItem() orelse {
|
var d = m.dispatch_queue.readItem() orelse {
|
||||||
m.events_mu.unlock();
|
m.dispatch_queue_mu.unlock();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pop the arguments off the ev.args_slice stack, so we can release args_slice space.
|
// Pop the arguments off the d.args_slice stack, so we can release args_slice space.
|
||||||
// Otherwise when we release m.events_mu someone may add more events' arguments
|
// Otherwise when we release m.dispatch_queue_mu someone may add more system' arguments
|
||||||
// to the buffer which would make it tricky to find a good point-in-time to release
|
// to the buffer which would make it tricky to find a good point-in-time to release
|
||||||
// argument buffer space.
|
// argument buffer space.
|
||||||
const aligned_addr = std.mem.alignForward(usize, @intFromPtr(stack_space.ptr), ev.args_alignment);
|
const aligned_addr = std.mem.alignForward(usize, @intFromPtr(stack_space.ptr), d.args_alignment);
|
||||||
const align_offset = aligned_addr - @intFromPtr(stack_space.ptr);
|
const align_offset = aligned_addr - @intFromPtr(stack_space.ptr);
|
||||||
|
|
||||||
@memcpy(stack_space[align_offset .. align_offset + ev.args_slice.len], ev.args_slice);
|
@memcpy(stack_space[align_offset .. align_offset + d.args_slice.len], d.args_slice);
|
||||||
ev.args_slice = stack_space[align_offset .. align_offset + ev.args_slice.len];
|
d.args_slice = stack_space[align_offset .. align_offset + d.args_slice.len];
|
||||||
|
|
||||||
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
m.dispatch_args_queue.shrinkRetainingCapacity(m.dispatch_args_queue.items.len - d.args_slice.len);
|
||||||
m.events_mu.unlock();
|
m.dispatch_queue_mu.unlock();
|
||||||
|
|
||||||
// Dispatch the local event
|
// Dispatch the system
|
||||||
try m.callLocal(@enumFromInt(ev.module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
try m.callSystem(@enumFromInt(d.module_name), @enumFromInt(d.system_name), d.args_slice, injectable);
|
||||||
|
|
||||||
// If we only wanted to dispatch until this event, then return.
|
// If we only wanted to dispatch until this system, then return.
|
||||||
if (options.until) |until| {
|
if (options.until) |until| {
|
||||||
if (until.module_name == ev.module_name and until.local_event == ev.event_name) return;
|
if (until.module_name == d.module_name and until.system == d.system_name) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call local event handler with the specified name in the specified module
|
/// Call system handler with the specified name in the specified module
|
||||||
inline fn callLocal(m: *@This(), module_name: ModuleName(modules), event_name: LocalEvent, args: []u8, injectable: anytype) !void {
|
inline fn callSystem(m: *@This(), module_name: ModuleName(modules), system_name: System, args: []u8, injectable: anytype) !void {
|
||||||
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
if (@typeInfo(@TypeOf(system_name)).Enum.fields.len == 0) return;
|
||||||
switch (event_name) {
|
switch (system_name) {
|
||||||
inline else => |ev_name| {
|
inline else => |ev_name| {
|
||||||
switch (module_name) {
|
switch (module_name) {
|
||||||
inline else => |mod_name| {
|
inline else => |mod_name| {
|
||||||
const M = @field(NamespacedModules(modules){}, @tagName(mod_name));
|
const M = @field(NamespacedModules(modules){}, @tagName(mod_name));
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
if (@hasDecl(M, "events")) inline for (@typeInfo(@TypeOf(M.events)).Struct.fields) |field| {
|
if (@hasDecl(M, "systems")) inline for (@typeInfo(@TypeOf(M.systems)).Struct.fields) |field| {
|
||||||
comptime if (!std.mem.eql(u8, @tagName(ev_name), field.name)) continue;
|
comptime if (!std.mem.eql(u8, @tagName(ev_name), field.name)) continue;
|
||||||
if (m.debug_trace) log.debug("trace: .{s}.{s}", .{
|
if (m.debug_trace) log.debug("trace: .{s}.{s}", .{
|
||||||
@tagName(M.name),
|
@tagName(M.name),
|
||||||
@tagName(ev_name),
|
@tagName(ev_name),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handler = @field(M.events, @tagName(ev_name)).handler;
|
const handler = @field(M.systems, @tagName(ev_name)).handler;
|
||||||
if (@typeInfo(@TypeOf(handler)) == .Type) continue; // Pre-declaration of what args an event has, nothing to do.
|
if (@typeInfo(@TypeOf(handler)) == .Type) continue; // Pre-declaration of what args an system has, nothing to do.
|
||||||
if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares local event .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
|
if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares system .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
|
||||||
@tagName(M.name),
|
@tagName(M.name),
|
||||||
@tagName(ev_name),
|
@tagName(ev_name),
|
||||||
@typeName(@TypeOf(handler)),
|
@typeName(@TypeOf(handler)),
|
||||||
|
|
@ -444,7 +410,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes an event handler with optionally injected arguments.
|
/// Invokes a system handler with optionally injected arguments.
|
||||||
inline fn callHandler(handler: anytype, args_data: []u8, injectable: anytype, comptime debug_name: anytype) !void {
|
inline fn callHandler(handler: anytype, args_data: []u8, injectable: anytype, comptime debug_name: anytype) !void {
|
||||||
const Handler = @TypeOf(handler);
|
const Handler = @TypeOf(handler);
|
||||||
const StdArgs = UninjectedArgsTuple(Handler);
|
const StdArgs = UninjectedArgsTuple(Handler);
|
||||||
|
|
@ -481,7 +447,7 @@ pub fn ModsByName(comptime modules: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Modules() causes analysis of event handlers' function signatures, whose parameters include
|
// Note: Modules() causes analysis of system handlers' function signatures, whose parameters include
|
||||||
// references to ModSet(modules).Mod(). As a result, the type returned here may never invoke Modules()
|
// references to ModSet(modules).Mod(). As a result, the type returned here may never invoke Modules()
|
||||||
// or depend on its result. However, it can analyze the global set of modules on its own, since no
|
// or depend on its result. However, it can analyze the global set of modules on its own, since no
|
||||||
// module's type should embed the result of Modules().
|
// module's type should embed the result of Modules().
|
||||||
|
|
@ -603,7 +569,7 @@ pub fn ModSet(comptime modules: anytype) type {
|
||||||
return &m.__state;
|
return &m.__state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a read-only version of the module's state. If an event handler is
|
/// Returns a read-only version of the module's state. If a system handler is
|
||||||
/// read-only (i.e. only ever reads state/components/entities), then its events can
|
/// read-only (i.e. only ever reads state/components/entities), then its events can
|
||||||
/// be skipped during e.g. record-and-replay of events from disk.
|
/// be skipped during e.g. record-and-replay of events from disk.
|
||||||
///
|
///
|
||||||
|
|
@ -646,34 +612,38 @@ pub fn ModSet(comptime modules: anytype) type {
|
||||||
try m.__entities.removeComponent(entity, module_tag, component_name);
|
try m.__entities.removeComponent(entity, module_tag, component_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn send(m: *@This(), comptime event_name: LocalEventEnumM(M), args: LocalArgsM(M, event_name)) void {
|
pub inline fn schedule(m: *@This(), comptime system_name: SystemEnumM(M)) void {
|
||||||
|
m.scheduleWithArgs(system_name, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn scheduleWithArgs(m: *@This(), comptime system_name: SystemEnumM(M), args: SystemArgsM(M, system_name)) void {
|
||||||
const ModulesT = Modules(modules);
|
const ModulesT = Modules(modules);
|
||||||
const MByName = ModsByName(modules);
|
const MByName = ModsByName(modules);
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
mods.send(module_tag, event_name, args);
|
mods.scheduleWithArgs(module_tag, system_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn event(_: *@This(), comptime event_name: LocalEventEnumM(M)) AnyEvent {
|
pub inline fn system(_: *@This(), comptime system_name: SystemEnumM(M)) AnySystem {
|
||||||
const module_name_g: ModuleName(modules) = M.name;
|
const module_name_g: ModuleName(modules) = M.name;
|
||||||
const event_name_g: Modules(modules).LocalEvent = comptime Modules(modules).moduleToGlobalEvent(
|
const system_name_g: Modules(modules).System = comptime Modules(modules).moduleToGlobalSystemName(
|
||||||
M,
|
M,
|
||||||
LocalEventEnumM,
|
SystemEnumM,
|
||||||
LocalEventEnum,
|
SystemEnum,
|
||||||
event_name,
|
system_name,
|
||||||
);
|
);
|
||||||
return .{
|
return .{
|
||||||
.module_id = @intFromEnum(module_name_g),
|
.module_id = @intFromEnum(module_name_g),
|
||||||
.event_id = @intFromEnum(event_name_g),
|
.system_id = @intFromEnum(system_name_g),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn sendAnyEvent(m: *@This(), ev: AnyEvent) void {
|
pub inline fn scheduleAny(m: *@This(), sys: AnySystem) void {
|
||||||
const ModulesT = Modules(modules);
|
const ModulesT = Modules(modules);
|
||||||
const MByName = ModsByName(modules);
|
const MByName = ModsByName(modules);
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
mods.sendDynamic(ev.module_id, ev.event_id, .{});
|
mods.scheduleDynamic(sys.module_id, sys.system_id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -752,30 +722,26 @@ pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tests
|
// TODO: tests
|
||||||
fn LocalArgsM(comptime M: type, event_name: anytype) type {
|
fn SystemArgsM(comptime M: type, system_name: anytype) type {
|
||||||
return ArgsM(M, event_name, "events");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
|
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
|
const which = "systems";
|
||||||
if (!@hasDecl(M, which)) return @TypeOf(.{});
|
if (!@hasDecl(M, which)) return @TypeOf(.{});
|
||||||
|
|
||||||
const m_events = @field(M, which); // M.events
|
inline for (@typeInfo(@TypeOf(M.systems)).Struct.fields) |field| {
|
||||||
inline for (@typeInfo(@TypeOf(m_events)).Struct.fields) |field| {
|
comptime if (!std.mem.eql(u8, field.name, @tagName(system_name))) continue;
|
||||||
comptime if (!std.mem.eql(u8, field.name, @tagName(event_name))) continue;
|
if (!@hasField(@TypeOf(M.systems), @tagName(system_name))) @compileError(std.fmt.comptimePrint("mach: module .{s} declares no {s} system .{s}", .{
|
||||||
if (!@hasField(@TypeOf(m_events), @tagName(event_name))) @compileError(std.fmt.comptimePrint("mach: module .{s} declares no {s} event .{s}", .{
|
|
||||||
@tagName(M.name),
|
@tagName(M.name),
|
||||||
which,
|
which,
|
||||||
@tagName(event_name),
|
@tagName(system_name),
|
||||||
}));
|
}));
|
||||||
const handler = @field(m_events, @tagName(event_name)).handler;
|
const handler = @field(M.systems, @tagName(system_name)).handler;
|
||||||
const Handler = switch (@typeInfo(@TypeOf(handler))) {
|
const Handler = switch (@typeInfo(@TypeOf(handler))) {
|
||||||
.Type => handler, // Pre-declaration of what args an event has
|
.Type => handler, // Pre-declaration of what args an event has
|
||||||
.Fn => blk: {
|
.Fn => blk: {
|
||||||
if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares {s} event .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
|
if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares {s} system .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
|
||||||
@tagName(M.name),
|
@tagName(M.name),
|
||||||
which,
|
which,
|
||||||
@tagName(event_name),
|
@tagName(system_name),
|
||||||
@typeName(@TypeOf(handler)),
|
@typeName(@TypeOf(handler)),
|
||||||
}));
|
}));
|
||||||
break :blk @TypeOf(handler);
|
break :blk @TypeOf(handler);
|
||||||
|
|
@ -784,16 +750,16 @@ fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
|
||||||
};
|
};
|
||||||
return UninjectedArgsTuple(Handler);
|
return UninjectedArgsTuple(Handler);
|
||||||
}
|
}
|
||||||
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no " ++ which ++ " event handler for ." ++ @tagName(event_name));
|
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no " ++ which ++ " system handler for ." ++ @tagName(system_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enum describing every possible comptime-known local event name
|
/// enum describing every possible comptime-known system name
|
||||||
fn LocalEventEnum(comptime modules: anytype) type {
|
fn SystemEnum(comptime modules: anytype) type {
|
||||||
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
for (modules) |M| {
|
for (modules) |M| {
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
if (@hasDecl(M, "events")) inline for (@typeInfo(@TypeOf(M.events)).Struct.fields) |field| {
|
if (@hasDecl(M, "systems")) inline for (@typeInfo(@TypeOf(M.systems)).Struct.fields) |field| {
|
||||||
const exists_already = blk: {
|
const exists_already = blk: {
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
|
||||||
break :blk false;
|
break :blk false;
|
||||||
|
|
@ -814,12 +780,12 @@ fn LocalEventEnum(comptime modules: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enum describing every possible comptime-known local event name
|
/// enum describing every possible comptime-known system name
|
||||||
fn LocalEventEnumM(comptime M: anytype) type {
|
fn SystemEnumM(comptime M: anytype) type {
|
||||||
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
if (@hasDecl(M, "events")) inline for (@typeInfo(@TypeOf(M.events)).Struct.fields) |field| {
|
if (@hasDecl(M, "systems")) inline for (@typeInfo(@TypeOf(M.systems)).Struct.fields) |field| {
|
||||||
const exists_already = blk: {
|
const exists_already = blk: {
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
|
||||||
break :blk false;
|
break :blk false;
|
||||||
|
|
@ -909,17 +875,17 @@ fn NamespacedModules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tests
|
// TODO: tests
|
||||||
fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void {
|
fn validateSystems(comptime error_prefix: anytype, comptime systems: anytype) void {
|
||||||
if (@typeInfo(@TypeOf(events)) != .Struct or @typeInfo(@TypeOf(events)).Struct.is_tuple) {
|
if (@typeInfo(@TypeOf(systems)) != .Struct or @typeInfo(@TypeOf(systems)).Struct.is_tuple) {
|
||||||
@compileError(error_prefix ++ "expected a struct .{}, found: " ++ @typeName(@TypeOf(events)));
|
@compileError(error_prefix ++ "expected a struct .{}, found: " ++ @typeName(@TypeOf(systems)));
|
||||||
}
|
}
|
||||||
inline for (@typeInfo(@TypeOf(events)).Struct.fields) |field| {
|
inline for (@typeInfo(@TypeOf(systems)).Struct.fields) |field| {
|
||||||
const Event = field.type;
|
const Event = field.type;
|
||||||
if (@typeInfo(Event) != .Struct) @compileError(std.fmt.comptimePrint(
|
if (@typeInfo(Event) != .Struct) @compileError(std.fmt.comptimePrint(
|
||||||
error_prefix ++ "expected .{s} = .{{}}, found type: {s}",
|
error_prefix ++ "expected .{s} = .{{}}, found type: {s}",
|
||||||
.{ field.name, @typeName(Event) },
|
.{ field.name, @typeName(Event) },
|
||||||
));
|
));
|
||||||
const event = @field(events, field.name);
|
const event = @field(systems, field.name);
|
||||||
|
|
||||||
// Verify .handler field
|
// Verify .handler field
|
||||||
if (!@hasField(Event, "handler")) @compileError(std.fmt.comptimePrint(
|
if (!@hasField(Event, "handler")) @compileError(std.fmt.comptimePrint(
|
||||||
|
|
@ -1101,7 +1067,7 @@ test ModuleInterface {
|
||||||
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1122,7 +1088,7 @@ test Modules {
|
||||||
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1131,7 +1097,7 @@ test Modules {
|
||||||
|
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1155,10 +1121,10 @@ test Modules {
|
||||||
testing.refAllDeclsRecursive(Sprite2D);
|
testing.refAllDeclsRecursive(Sprite2D);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "event name" {
|
test "system name" {
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.foo = .{ .handler = foo },
|
.foo = .{ .handler = foo },
|
||||||
.bar = .{ .handler = bar },
|
.bar = .{ .handler = bar },
|
||||||
.baz = .{ .handler = baz },
|
.baz = .{ .handler = baz },
|
||||||
|
|
@ -1173,7 +1139,7 @@ test "event name" {
|
||||||
|
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.foo_unused = .{ .handler = fn (f32, i32) void },
|
.foo_unused = .{ .handler = fn (f32, i32) void },
|
||||||
.bar_unused = .{ .handler = fn (i32, f32) void },
|
.bar_unused = .{ .handler = fn (i32, f32) void },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
|
@ -1188,7 +1154,7 @@ test "event name" {
|
||||||
|
|
||||||
const Sprite2D = ModuleInterface(struct {
|
const Sprite2D = ModuleInterface(struct {
|
||||||
pub const name = .engine_sprite2d;
|
pub const name = .engine_sprite2d;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.foobar = .{ .handler = fooBar },
|
.foobar = .{ .handler = fooBar },
|
||||||
};
|
};
|
||||||
|
|
@ -1204,7 +1170,7 @@ test "event name" {
|
||||||
Sprite2D,
|
Sprite2D,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const locals = @typeInfo(Ms.LocalEvent).Enum;
|
const locals = @typeInfo(Ms.System).Enum;
|
||||||
try testing.expect(type, u3).eql(locals.tag_type);
|
try testing.expect(type, u3).eql(locals.tag_type);
|
||||||
try testing.expect(usize, 8).eql(locals.fields.len);
|
try testing.expect(usize, 8).eql(locals.fields.len);
|
||||||
try testing.expect([]const u8, "foo").eql(locals.fields[0].name);
|
try testing.expect([]const u8, "foo").eql(locals.fields[0].name);
|
||||||
|
|
@ -1356,7 +1322,7 @@ test UninjectedArgsTuple {
|
||||||
TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(fn (a: *Foo, b: *Bar, c: Foo, d: Bar, i: f32) void));
|
TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(fn (a: *Foo, b: *Bar, c: Foo, d: Bar, i: f32) void));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "event name calling" {
|
test "system name calling" {
|
||||||
const global = struct {
|
const global = struct {
|
||||||
var ticks: usize = 0;
|
var ticks: usize = 0;
|
||||||
var physics_updates: usize = 0;
|
var physics_updates: usize = 0;
|
||||||
|
|
@ -1365,7 +1331,7 @@ test "event name calling" {
|
||||||
};
|
};
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
.calc = .{ .handler = calc },
|
.calc = .{ .handler = calc },
|
||||||
|
|
@ -1385,7 +1351,7 @@ test "event name calling" {
|
||||||
});
|
});
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
};
|
};
|
||||||
|
|
@ -1408,10 +1374,10 @@ test "event name calling" {
|
||||||
try modules.init(testing.allocator);
|
try modules.init(testing.allocator);
|
||||||
defer modules.deinit(testing.allocator);
|
defer modules.deinit(testing.allocator);
|
||||||
|
|
||||||
// Check we can use .callLocal() with a runtime-known event and module name.
|
// Check we can use .callSystem() with a runtime-known system and module name.
|
||||||
const alloc = try testing.allocator.create(u3);
|
const alloc = try testing.allocator.create(u3);
|
||||||
defer testing.allocator.destroy(alloc);
|
defer testing.allocator.destroy(alloc);
|
||||||
const LE = @TypeOf(modules).LocalEvent;
|
const LE = @TypeOf(modules).System;
|
||||||
const m_alloc = try testing.allocator.create(u3);
|
const m_alloc = try testing.allocator.create(u3);
|
||||||
defer testing.allocator.destroy(m_alloc);
|
defer testing.allocator.destroy(m_alloc);
|
||||||
const M = ModuleName(modules2);
|
const M = ModuleName(modules2);
|
||||||
|
|
@ -1419,9 +1385,9 @@ test "event name calling" {
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_renderer));
|
m_alloc.* = @intFromEnum(@as(M, .engine_renderer));
|
||||||
alloc.* = @intFromEnum(@as(LE, .update));
|
alloc.* = @intFromEnum(@as(LE, .update));
|
||||||
var module_name = @as(M, @enumFromInt(m_alloc.*));
|
var module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
var local_event_name = @as(LE, @enumFromInt(alloc.*));
|
var local_system_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callSystem(module_name, local_system_name, &.{}, .{});
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callSystem(module_name, local_system_name, &.{}, .{});
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
try testing.expect(usize, 0).eql(global.ticks);
|
||||||
try testing.expect(usize, 0).eql(global.physics_updates);
|
try testing.expect(usize, 0).eql(global.physics_updates);
|
||||||
try testing.expect(usize, 2).eql(global.renderer_updates);
|
try testing.expect(usize, 2).eql(global.renderer_updates);
|
||||||
|
|
@ -1429,15 +1395,15 @@ test "event name calling" {
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
||||||
alloc.* = @intFromEnum(@as(LE, .update));
|
alloc.* = @intFromEnum(@as(LE, .update));
|
||||||
module_name = @as(M, @enumFromInt(m_alloc.*));
|
module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
local_system_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callSystem(module_name, local_system_name, &.{}, .{});
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||||
|
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
||||||
alloc.* = @intFromEnum(@as(LE, .calc));
|
alloc.* = @intFromEnum(@as(LE, .calc));
|
||||||
module_name = @as(M, @enumFromInt(m_alloc.*));
|
module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
local_system_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callSystem(module_name, local_system_name, &.{}, .{});
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
try testing.expect(usize, 0).eql(global.ticks);
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||||
|
|
@ -1462,7 +1428,7 @@ test "dispatch" {
|
||||||
});
|
});
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
.update_with_struct_arg = .{ .handler = updateWithStructArg },
|
.update_with_struct_arg = .{ .handler = updateWithStructArg },
|
||||||
|
|
@ -1492,7 +1458,7 @@ test "dispatch" {
|
||||||
});
|
});
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const events = .{
|
pub const systems = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.frame_done = .{ .handler = fn (i32) void },
|
.frame_done = .{ .handler = fn (i32) void },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
|
|
@ -1529,28 +1495,27 @@ test "dispatch" {
|
||||||
try modules.init(testing.allocator);
|
try modules.init(testing.allocator);
|
||||||
defer modules.deinit(testing.allocator);
|
defer modules.deinit(testing.allocator);
|
||||||
|
|
||||||
const LE = @TypeOf(modules).LocalEvent;
|
const LE = @TypeOf(modules).System;
|
||||||
const M = ModuleName(modules2);
|
const M = ModuleName(modules2);
|
||||||
|
|
||||||
// Local events
|
// Systems
|
||||||
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
modules.send(.engine_renderer, .update, .{});
|
modules.schedule(.engine_renderer, .update);
|
||||||
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 1).eql(global.renderer_updates);
|
try testing.expect(usize, 1).eql(global.renderer_updates);
|
||||||
modules.send(.engine_physics, .update, .{});
|
modules.schedule(.engine_physics, .update);
|
||||||
modules.send(.engine_physics, .update_with_struct_arg, .{.{}});
|
modules.scheduleWithArgs(.engine_physics, .update_with_struct_arg, .{.{}});
|
||||||
modules.sendDynamic(
|
modules.scheduleDynamic(
|
||||||
@intFromEnum(@as(M, .engine_physics)),
|
@intFromEnum(@as(M, .engine_physics)),
|
||||||
@intFromEnum(@as(LE, .calc)),
|
@intFromEnum(@as(LE, .calc)),
|
||||||
.{},
|
|
||||||
);
|
);
|
||||||
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 2).eql(global.physics_updates);
|
try testing.expect(usize, 2).eql(global.physics_updates);
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
|
|
||||||
// Local events
|
// Systems
|
||||||
modules.send(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
modules.scheduleWithArgs(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
||||||
modules.send(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
modules.scheduleWithArgs(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
||||||
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
||||||
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue