module: write module events using a struct pattern

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-04-03 08:13:22 -07:00 committed by Stephen Gutekanst
parent 582a3c07f6
commit 17db5498ee
13 changed files with 245 additions and 203 deletions

View file

@ -20,9 +20,9 @@ pub const components = .{
.{ .name = .follower, .type = void }, .{ .name = .follower, .type = void },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
// Each module must have a globally unique name declared, it is impossible to use two modules with // Each module must have a globally unique name declared, it is impossible to use two modules with

View file

@ -26,10 +26,10 @@ pub const components = .{
.{ .name = .scale, .type = f32 }, .{ .name = .scale, .type = f32 },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .global = .deinit, .handler = deinit }, .deinit = .{ .handler = deinit },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
// TODO: this shouldn't be a packed struct, it should be extern. // TODO: this shouldn't be a packed struct, it should be extern.

View file

@ -40,9 +40,9 @@ const d0 = 0.000001;
pub const name = .game; pub const name = .game;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const events = .{ pub const global_events = .{
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
pub const Pipeline = enum(u32) { pub const Pipeline = enum(u32) {

View file

@ -8,10 +8,13 @@ const assets = @import("assets");
pub const name = .game_text; pub const name = .game_text;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const events = .{ pub const global_events = .{
.{ .global = .deinit, .handler = deinit }, .deinit = .{ .handler = deinit },
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .local = .prepare, .handler = prepare }, };
pub const local_events = .{
.prepare = .{ .handler = prepare },
}; };
const RegionMap = std.AutoArrayHashMapUnmanaged(u21, mach.gfx.Atlas.Region); const RegionMap = std.AutoArrayHashMapUnmanaged(u21, mach.gfx.Atlas.Region);

View file

@ -41,9 +41,9 @@ const d0 = 0.000001;
pub const name = .game; pub const name = .game;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const events = .{ pub const global_events = .{
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
pub const Pipeline = enum(u32) { pub const Pipeline = enum(u32) {

View file

@ -44,10 +44,10 @@ const d0 = 0.000001;
pub const name = .game; pub const name = .game;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const events = .{ pub const global_events = .{
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .global = .deinit, .handler = deinit }, .deinit = .{ .handler = deinit },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
pub const Pipeline = enum(u32) { pub const Pipeline = enum(u32) {

View file

@ -753,7 +753,6 @@ test "example" {
const all_components = ComponentTypesByName(.{ const all_components = ComponentTypesByName(.{
struct { struct {
pub const name = .game; pub const name = .game;
pub const events = .{};
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .{ .name = .name, .type = []const u8 },
.{ .name = .location, .type = Location }, .{ .name = .location, .type = Location },
@ -858,7 +857,6 @@ test "many entities" {
const all_components = ComponentTypesByName(.{ const all_components = ComponentTypesByName(.{
struct { struct {
pub const name = .game; pub const name = .game;
pub const events = .{};
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .{ .name = .name, .type = []const u8 },
.{ .name = .location, .type = Location }, .{ .name = .location, .type = Location },

View file

@ -49,8 +49,8 @@ test "example" {
pub const components = .{ pub const components = .{
.{ .name = .id, .type = u32 }, .{ .name = .id, .type = u32 },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
fn tick(physics: *Modules(modules).Mod(Physics)) void { fn tick(physics: *Modules(modules).Mod(Physics)) void {
@ -63,8 +63,8 @@ test "example" {
pub const components = .{ pub const components = .{
.{ .name = .id, .type = u16 }, .{ .name = .id, .type = u16 },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
fn tick( fn tick(

View file

@ -73,14 +73,12 @@ test "query" {
const all_components = ComponentTypesByName(.{ const all_components = ComponentTypesByName(.{
struct { struct {
pub const name = .game; pub const name = .game;
pub const events = .{};
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .{ .name = .name, .type = []const u8 },
}; };
}, },
struct { struct {
pub const name = .physics; pub const name = .physics;
pub const events = .{};
pub const components = .{ pub const components = .{
.{ .name = .location, .type = Location }, .{ .name = .location, .type = Location },
.{ .name = .rotation, .type = Rotation }, .{ .name = .rotation, .type = Rotation },
@ -88,7 +86,6 @@ test "query" {
}, },
struct { struct {
pub const name = .renderer; pub const name = .renderer;
pub const events = .{};
}, },
}){}; }){};

View file

@ -19,17 +19,20 @@ pub const Engine = struct {
pub const name = .engine; pub const name = .engine;
pub const Mod = Modules.Mod(@This()); pub const Mod = Modules.Mod(@This());
pub const events = .{ pub const global_events = .{
.{ .local = .init, .handler = init }, .init = .{ .handler = fn () void },
.{ .local = .deinit, .handler = deinit }, .deinit = .{ .handler = fn () void },
.{ .local = .exit, .handler = exit }, .tick = .{ .handler = fn () void },
.{ .local = .begin_pass, .handler = beginPass }, .exit = .{ .handler = fn () void },
.{ .local = .end_pass, .handler = endPass }, };
.{ .local = .present, .handler = present },
.{ .global = .init, .handler = fn () void }, pub const local_events = .{
.{ .global = .deinit, .handler = fn () void }, .init = .{ .handler = init },
.{ .global = .tick, .handler = fn () void }, .deinit = .{ .handler = deinit },
.{ .global = .exit, .handler = fn () void }, .exit = .{ .handler = exit },
.begin_pass = .{ .handler = beginPass },
.end_pass = .{ .handler = endPass },
.present = .{ .handler = present },
}; };
fn init(engine: *Mod) !void { fn init(engine: *Mod) !void {

View file

@ -43,13 +43,16 @@ pub const components = .{
}, },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .deinit, .handler = deinit }, .deinit = .{ .handler = deinit },
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .local = .init_pipeline, .handler = initPipeline }, };
.{ .local = .updated, .handler = updated },
.{ .local = .pre_render, .handler = preRender }, pub const local_events = .{
.{ .local = .render, .handler = render }, .init_pipeline = .{ .handler = initPipeline },
.updated = .{ .handler = updated },
.pre_render = .{ .handler = preRender },
.render = .{ .handler = render },
}; };
const Uniforms = extern struct { const Uniforms = extern struct {

View file

@ -78,13 +78,16 @@ pub const components = .{
}, },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .deinit, .handler = deinit }, .deinit = .{ .handler = deinit },
.{ .global = .init, .handler = init }, .init = .{ .handler = init },
.{ .local = .init_pipeline, .handler = initPipeline }, };
.{ .local = .updated, .handler = updated },
.{ .local = .pre_render, .handler = preRender }, pub const local_events = .{
.{ .local = .render, .handler = render }, .init_pipeline = .{ .handler = initPipeline },
.updated = .{ .handler = updated },
.pre_render = .{ .handler = preRender },
.render = .{ .handler = render },
}; };
const Uniforms = extern struct { const Uniforms = extern struct {

View file

@ -11,10 +11,9 @@ fn ModuleInterface(comptime M: type) type {
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`"); if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`");
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));
const prefix = "mach: module ." ++ @tagName(M.name) ++ " "; // TODO: enable once parameter dependency loop has been resolved
if (!@hasDecl(M, "events")) @compileError(prefix ++ "must have `pub const events = .{};`"); // if (@hasDecl(M, "global_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " global_events ", M.global_events);
// TODO: re-enable this validation once module event handler arguments would not pose a type dependency loop // if (@hasDecl(M, "local_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " local_events ", M.global_events);
// validateEvents("mach: module ." ++ @tagName(M.name) ++ " ", M.events);
_ = ComponentTypesM(M); _ = ComponentTypesM(M);
return M; return M;
} }
@ -231,20 +230,20 @@ pub fn Modules(comptime modules2: anytype) type {
switch (event_name) { switch (event_name) {
inline else => |ev_name| { inline else => |ev_name| {
inline for (modules) |M| { inline for (modules) |M| {
// TODO: DRY with callLocal
_ = ModuleInterface(M); // Validate the module _ = ModuleInterface(M); // Validate the module
inline for (M.events) |event| { if (@hasDecl(M, "global_events")) inline for (@typeInfo(@TypeOf(M.global_events)).Struct.fields) |field| {
const Ev = @TypeOf(event); comptime if (!std.mem.eql(u8, @tagName(ev_name), field.name)) continue;
const name_tag = if (@hasField(Ev, "global")) event.global else continue; const handler = @field(M.global_events, @tagName(ev_name)).handler;
if (name_tag != ev_name) continue; if (@typeInfo(@TypeOf(handler)) == .Type) continue; // Pre-declaration of what args an event has, nothing to do.
switch (@typeInfo(@TypeOf(event.handler))) { if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares global event .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
.Fn => try callHandler(event.handler, args, injectable), @tagName(M.name),
.Type => switch (@typeInfo(event.handler)) { @tagName(ev_name),
.Fn => {}, // Pre-declaration of what args an event has, nothing to run.
else => unreachable, @typeName(@TypeOf(handler)),
}, }));
else => unreachable, try callHandler(handler, args, injectable);
} };
}
} }
}, },
} }
@ -257,23 +256,20 @@ pub fn Modules(comptime modules2: anytype) type {
inline else => |ev_name| { inline else => |ev_name| {
switch (module_name) { switch (module_name) {
inline else => |mod_name| { inline else => |mod_name| {
// TODO: DRY with callGlobal
const M = @field(NamespacedModules(@This().modules){}, @tagName(mod_name)); const M = @field(NamespacedModules(@This().modules){}, @tagName(mod_name));
_ = ModuleInterface(M); // Validate the module _ = ModuleInterface(M); // Validate the module
if (@hasDecl(M, "local_events")) inline for (@typeInfo(@TypeOf(M.local_events)).Struct.fields) |field| {
inline for (M.events) |event| { comptime if (!std.mem.eql(u8, @tagName(ev_name), field.name)) continue;
const Ev = @TypeOf(event); const handler = @field(M.local_events, @tagName(ev_name)).handler;
const name_tag = if (@hasField(Ev, "local")) event.local else continue; if (@typeInfo(@TypeOf(handler)) == .Type) continue; // Pre-declaration of what args an event has, nothing to do.
if (name_tag != ev_name) continue; if (@typeInfo(@TypeOf(handler)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares local event .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
switch (@typeInfo(@TypeOf(event.handler))) { @tagName(M.name),
.Fn => try callHandler(event.handler, args, injectable), @tagName(ev_name),
.Type => switch (@typeInfo(event.handler)) { @typeName(@TypeOf(handler)),
.Fn => {}, // Pre-declaration of what args an event has, nothing to run. }));
else => unreachable, try callHandler(handler, args, injectable);
}, };
else => unreachable,
}
break;
}
}, },
} }
}, },
@ -487,18 +483,39 @@ fn UninjectedArgsTuple(
return Tuple(std_args); return Tuple(std_args);
} }
pub fn LocalArgsM(comptime M: type, event_name: anytype) type { // TODO: tests
_ = ModuleInterface(M); // Validate the module fn LocalArgsM(comptime M: type, event_name: anytype) type {
inline for (M.events) |event| { return ArgsM(M, event_name, "local");
const Ev = @TypeOf(event); }
const name_tag = if (@hasField(Ev, "local")) event.local else continue;
if (name_tag != event_name) continue;
const Handler = switch (@typeInfo(@TypeOf(event.handler))) { // TODO: tests
.Fn => @TypeOf(event.handler), fn GlobalArgsM(comptime M: type, event_name: anytype) type {
.Type => switch (@typeInfo(event.handler)) { return ArgsM(M, event_name, "global");
.Fn => event.handler, }
else => unreachable,
fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
_ = ModuleInterface(M); // Validate the module
if (!@hasDecl(M, which ++ "_events")) return @TypeOf(.{});
const m_events = @field(M, which ++ "_events"); // M.local_events or M.global_events
inline for (@typeInfo(@TypeOf(m_events)).Struct.fields) |field| {
comptime if (!std.mem.eql(u8, field.name, @tagName(event_name))) continue;
if (!@hasField(@TypeOf(m_events), @tagName(event_name))) @compileError(std.fmt.comptimePrint("mach: module .{s} declares no {s} event .{s}", .{
@tagName(M.name),
which,
@tagName(event_name),
}));
const handler = @field(m_events, @tagName(event_name)).handler;
const Handler = switch (@typeInfo(@TypeOf(handler))) {
.Type => handler, // Pre-declaration of what args an event has
.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}", .{
@tagName(M.name),
which,
@tagName(event_name),
@typeName(@TypeOf(handler)),
}));
break :blk @TypeOf(handler);
}, },
else => unreachable, else => unreachable,
}; };
@ -508,52 +525,26 @@ pub fn LocalArgsM(comptime M: type, event_name: anytype) type {
// `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`. // `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`.
return UninjectedArgsTuple(TupleHACK, Handler); return UninjectedArgsTuple(TupleHACK, Handler);
} }
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no .local event handler for ." ++ @tagName(event_name)); @compileError("mach: module ." ++ @tagName(M.name) ++ " has no " ++ which ++ " event handler for ." ++ @tagName(event_name));
}
pub fn GlobalArgsM(comptime M: type, event_name: anytype) type {
_ = ModuleInterface(M); // Validate the module
inline for (M.events) |event| {
const Ev = @TypeOf(event);
const name_tag = if (@hasField(Ev, "global")) event.global else continue;
if (name_tag != event_name) continue;
const Handler = switch (@typeInfo(@TypeOf(event.handler))) {
.Fn => @TypeOf(event.handler),
.Type => switch (@typeInfo(event.handler)) {
.Fn => event.handler,
else => unreachable,
},
else => unreachable,
};
// TODO: passing std.meta.Tuple here instead of TupleHACK results in a compiler
// segfault. The only difference is that TupleHACk does not produce a real tuple,
// `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`.
return UninjectedArgsTuple(TupleHACK, Handler);
}
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no .global event handler for ." ++ @tagName(event_name));
} }
// TODO: DRY with GlobalEventEnum
/// enum describing every possible comptime-known local event name /// enum describing every possible comptime-known local event name
fn LocalEventEnum(comptime modules: anytype) type { fn LocalEventEnum(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
inline for (M.events) |event| { if (@hasDecl(M, "local_events")) inline for (@typeInfo(@TypeOf(M.local_events)).Struct.fields) |field| {
const Event = @TypeOf(event);
const name_tag = if (@hasField(Event, "local")) event.local else continue;
const exists_already = blk: { const exists_already = blk: {
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, @tagName(name_tag))) 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;
}; };
if (!exists_already) { if (!exists_already) {
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(name_tag), .value = i }}; enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = field.name, .value = i }};
i += 1; i += 1;
} }
} };
} }
return @Type(.{ return @Type(.{
.Enum = .{ .Enum = .{
@ -565,26 +556,76 @@ fn LocalEventEnum(comptime modules: anytype) type {
}); });
} }
// TODO: DRY with GlobalEventEnumM
/// enum describing every possible comptime-known local event name
fn LocalEventEnumM(comptime M: anytype) type {
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
var i: u32 = 0;
_ = ModuleInterface(M); // Validate the module
if (@hasDecl(M, "local_events")) inline for (@typeInfo(@TypeOf(M.local_events)).Struct.fields) |field| {
const exists_already = blk: {
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
break :blk false;
};
if (!exists_already) {
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = field.name, .value = i }};
i += 1;
}
};
return @Type(.{
.Enum = .{
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
.fields = enum_fields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_exhaustive = true,
},
});
}
// TODO: DRY with LocalEventEnum
/// enum describing every possible comptime-known global event name /// enum describing every possible comptime-known global event name
fn GlobalEventEnum(comptime modules: anytype) type { fn GlobalEventEnum(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
inline for (M.events) |event| { if (@hasDecl(M, "global_events")) inline for (@typeInfo(@TypeOf(M.global_events)).Struct.fields) |field| {
const Event = @TypeOf(event);
const name_tag = if (@hasField(Event, "global")) event.global else continue;
const exists_already = blk: { const exists_already = blk: {
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, @tagName(name_tag))) 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;
}; };
if (!exists_already) { if (!exists_already) {
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(name_tag), .value = i }}; enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = field.name, .value = i }};
i += 1; i += 1;
} }
};
} }
return @Type(.{
.Enum = .{
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
.fields = enum_fields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_exhaustive = true,
},
});
}
// TODO: DRY with LocalEventEnumM
/// enum describing every possible comptime-known global event name
fn GlobalEventEnumM(comptime M: anytype) type {
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
var i: u32 = 0;
_ = ModuleInterface(M); // Validate the module
if (@hasDecl(M, "global_events")) inline for (@typeInfo(@TypeOf(M.global_events)).Struct.fields) |field| {
const exists_already = blk: {
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, field.name)) break :blk true;
break :blk false;
};
if (!exists_already) {
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = field.name, .value = i }};
i += 1;
} }
};
return @Type(.{ return @Type(.{
.Enum = .{ .Enum = .{
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0, .tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
@ -636,31 +677,21 @@ fn NamespacedModules(comptime modules: anytype) type {
// TODO: tests // TODO: tests
fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void { fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void {
if (@typeInfo(@TypeOf(events)) != .Struct or !@typeInfo(@TypeOf(events)).Struct.is_tuple) { if (@typeInfo(@TypeOf(events)) != .Struct or @typeInfo(@TypeOf(events)).Struct.is_tuple) {
@compileError(error_prefix ++ "expected a tuple of structs, found: " ++ @typeName(@TypeOf(events))); @compileError(error_prefix ++ "expected a struct .{}, found: " ++ @typeName(@TypeOf(events)));
} }
inline for (events, 0..) |event, i| { inline for (@typeInfo(@TypeOf(events)).Struct.fields) |field| {
const Event = @TypeOf(event); const Event = field.type;
if (@typeInfo(Event) != .Struct) @compileError(std.fmt.comptimePrint( if (@typeInfo(Event) != .Struct) @compileError(std.fmt.comptimePrint(
error_prefix ++ "expected a tuple of structs, found tuple element ({}): {s}", error_prefix ++ "expected .{s} = .{{}}, found type: {s}",
.{ i, @typeName(Event) }, .{ field.name, @typeName(Event) },
)); ));
const event = @field(events, field.name);
// Verify .global = .foo, or .local = .foo, event handler name field // Verify .handler field
const name_tag = if (@hasField(Event, "global")) event.global else if (@hasField(Event, "local")) event.local else @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) missing field `.global = .foo` or `.local = .foo` (event handler kind / name)",
.{i},
));
const is_global = if (@hasField(Event, "global")) true else false;
if (@typeInfo(@TypeOf(name_tag)) != .EnumLiteral) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) expected field `.{s} = .foo`, found: {s}",
.{ i, if (is_global) "global" else "local", @typeName(@TypeOf(name_tag)) },
));
// Verify .handler = fn, field
if (!@hasField(Event, "handler")) @compileError(std.fmt.comptimePrint( if (!@hasField(Event, "handler")) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) missing field `.handler = fn`", error_prefix ++ ".{s} missing field `.handler = fn` or `.handler = @TypeOf(fn)`",
.{i}, .{field.name},
)); ));
const valid_handler_type = switch (@typeInfo(@TypeOf(event.handler))) { const valid_handler_type = switch (@typeInfo(@TypeOf(event.handler))) {
.Fn => true, .Fn => true,
@ -671,8 +702,8 @@ fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void
else => false, else => false,
}; };
if (!valid_handler_type) @compileError(std.fmt.comptimePrint( if (!valid_handler_type) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) expected field `.handler = fn`, found: {s}", error_prefix ++ ".{s} field .handler expected `.handler = fn` or `.handler = @TypeOf(fn)`, found found: {s}",
.{ i, @typeName(@TypeOf(event.handler)) }, .{ field.name, @typeName(@TypeOf(event.handler)) },
)); ));
} }
} }
@ -860,8 +891,8 @@ test ModuleInterface {
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" }, .{ .name = .location, .type = @Vector(3, f32), .description = "A location component" },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
fn tick() !void {} fn tick() !void {}
@ -881,8 +912,8 @@ test Modules {
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" }, .{ .name = .location, .type = @Vector(3, f32), .description = "A location component" },
}; };
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
fn tick() !void {} fn tick() !void {}
@ -890,8 +921,8 @@ 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 global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
}; };
/// Renderer module components /// Renderer module components
@ -902,7 +933,6 @@ test Modules {
const Sprite2D = ModuleInterface(struct { const Sprite2D = ModuleInterface(struct {
pub const name = .engine_sprite2d; pub const name = .engine_sprite2d;
pub const events = .{};
}); });
var modules: Modules(.{ var modules: Modules(.{
@ -921,11 +951,13 @@ test "event name" {
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const components = .{}; pub const components = .{};
pub const events = .{ pub const global_events = .{
.{ .global = .foo, .handler = foo }, .foo = .{ .handler = foo },
.{ .global = .bar, .handler = bar }, .bar = .{ .handler = bar },
.{ .local = .baz, .handler = baz }, };
.{ .local = .bam, .handler = bam }, pub const local_events = .{
.baz = .{ .handler = baz },
.bam = .{ .handler = bam },
}; };
fn foo() !void {} fn foo() !void {}
@ -937,12 +969,12 @@ test "event name" {
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{}; pub const components = .{};
pub const events = .{ pub const global_events = .{
.{ .global = .foo_unused, .handler = fn (f32, i32) void }, .foo_unused = .{ .handler = fn (f32, i32) void },
.{ .global = .bar_unused, .handler = fn (i32, f32) void }, .bar_unused = .{ .handler = fn (i32, f32) void },
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
.{ .global = .foo, .handler = foo }, .foo = .{ .handler = foo },
.{ .global = .bar, .handler = bar }, .bar = .{ .handler = bar },
}; };
fn tick() !void {} fn tick() !void {}
@ -952,13 +984,13 @@ 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 global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
.{ .global = .foobar, .handler = foobar }, .foobar = .{ .handler = fooBar },
}; };
fn tick() void {} // same .tick as .engine_renderer.tick fn tick() void {} // same .tick as .engine_renderer.tick
fn foobar() void {} fn fooBar() void {}
}); });
const Ms = Modules(.{ const Ms = Modules(.{
@ -987,15 +1019,12 @@ test "event name" {
test ModuleName { test ModuleName {
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const events = .{};
}); });
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const events = .{};
}); });
const Sprite2D = ModuleInterface(struct { const Sprite2D = ModuleInterface(struct {
pub const name = .engine_sprite2d; pub const name = .engine_sprite2d;
pub const events = .{};
}); });
const Ms = Modules(.{ const Ms = Modules(.{
Physics, Physics,
@ -1134,10 +1163,12 @@ test "event name calling" {
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const components = .{}; pub const components = .{};
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
.{ .local = .update, .handler = update }, };
.{ .local = .calc, .handler = calc }, pub const local_events = .{
.update = .{ .handler = update },
.calc = .{ .handler = calc },
}; };
fn tick() void { fn tick() void {
@ -1155,9 +1186,11 @@ test "event name calling" {
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{}; pub const components = .{};
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
.{ .local = .update, .handler = update }, };
pub const local_events = .{
.update = .{ .handler = update },
}; };
fn tick() void { fn tick() void {
@ -1237,15 +1270,15 @@ test "dispatch" {
}{}; }{};
const Minimal = ModuleInterface(struct { const Minimal = ModuleInterface(struct {
pub const name = .engine_minimal; pub const name = .engine_minimal;
pub const events = .{};
}); });
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const components = .{}; pub const global_events = .{
pub const events = .{ .tick = .{ .handler = tick },
.{ .global = .tick, .handler = tick }, };
.{ .local = .update, .handler = update }, pub const local_events = .{
.{ .local = .calc, .handler = calc }, .update = .{ .handler = update },
.calc = .{ .handler = calc },
}; };
fn tick() void { fn tick() void {
@ -1263,12 +1296,14 @@ test "dispatch" {
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{}; pub const components = .{};
pub const events = .{ pub const global_events = .{
.{ .global = .tick, .handler = tick }, .tick = .{ .handler = tick },
.{ .global = .frame_done, .handler = fn (i32) void }, .frame_done = .{ .handler = fn (i32) void },
.{ .local = .update, .handler = update }, };
.{ .local = .basic_args, .handler = basicArgs }, pub const local_events = .{
.{ .local = .injected_args, .handler = injectedArgs }, .update = .{ .handler = update },
.basic_args = .{ .handler = basicArgs },
.injected_args = .{ .handler = injectedArgs },
}; };
pub const frameDone = fn (i32) void; pub const frameDone = fn (i32) void;