module: remove "global events" concept
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
8d669537dc
commit
83d436ffa4
4 changed files with 48 additions and 232 deletions
|
|
@ -61,7 +61,11 @@ test {
|
||||||
_ = gfx;
|
_ = gfx;
|
||||||
_ = math;
|
_ = math;
|
||||||
_ = testing;
|
_ = testing;
|
||||||
|
std.testing.refAllDeclsRecursive(@import("module/Archetype.zig"));
|
||||||
|
std.testing.refAllDeclsRecursive(@import("module/entities.zig"));
|
||||||
// std.testing.refAllDeclsRecursive(@import("module/main.zig"));
|
// std.testing.refAllDeclsRecursive(@import("module/main.zig"));
|
||||||
|
std.testing.refAllDeclsRecursive(@import("module/module.zig"));
|
||||||
|
std.testing.refAllDeclsRecursive(@import("module/StringTable.zig"));
|
||||||
std.testing.refAllDeclsRecursive(gamemode);
|
std.testing.refAllDeclsRecursive(gamemode);
|
||||||
std.testing.refAllDeclsRecursive(math);
|
std.testing.refAllDeclsRecursive(math);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1099,7 +1099,7 @@ test "example" {
|
||||||
// Resolve archetype by entity ID and print column names
|
// Resolve archetype by entity ID and print column names
|
||||||
const columns = world.archetypeByID(player2).columns;
|
const columns = world.archetypeByID(player2).columns;
|
||||||
try testing.expectEqual(@as(usize, 2), columns.len);
|
try testing.expectEqual(@as(usize, 2), columns.len);
|
||||||
try testing.expectEqualStrings("entity.id", world.component_names.string(columns[0].name));
|
try testing.expectEqualStrings("entities.id", world.component_names.string(columns[0].name));
|
||||||
try testing.expectEqualStrings("game.rotation", world.component_names.string(columns[1].name));
|
try testing.expectEqualStrings("game.rotation", world.component_names.string(columns[1].name));
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
@ -1128,7 +1128,7 @@ test "example" {
|
||||||
try testing.expectEqual(@as(usize, 1), archtype.len);
|
try testing.expectEqual(@as(usize, 1), archtype.len);
|
||||||
try testing.expectEqual(@as(usize, 2), archtype.columns.len);
|
try testing.expectEqual(@as(usize, 2), archtype.columns.len);
|
||||||
|
|
||||||
try testing.expectEqualStrings("entity.id", world.component_names.string(archtype.columns[0].name));
|
try testing.expectEqualStrings("entities.id", world.component_names.string(archtype.columns[0].name));
|
||||||
try testing.expectEqualStrings("game.rotation", world.component_names.string(archtype.columns[1].name));
|
try testing.expectEqualStrings("game.rotation", world.component_names.string(archtype.columns[1].name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1186,16 +1186,16 @@ test "many entities" {
|
||||||
// Confirm archetypes
|
// Confirm archetypes
|
||||||
var columns = archetypes[0].columns;
|
var columns = archetypes[0].columns;
|
||||||
try testing.expectEqual(@as(usize, 1), columns.len);
|
try testing.expectEqual(@as(usize, 1), columns.len);
|
||||||
try testing.expectEqualStrings("entity.id", world.component_names.string(columns[0].name));
|
try testing.expectEqualStrings("entities.id", world.component_names.string(columns[0].name));
|
||||||
|
|
||||||
columns = archetypes[1].columns;
|
columns = archetypes[1].columns;
|
||||||
try testing.expectEqual(@as(usize, 2), columns.len);
|
try testing.expectEqual(@as(usize, 2), columns.len);
|
||||||
try testing.expectEqualStrings("entity.id", world.component_names.string(columns[0].name));
|
try testing.expectEqualStrings("entities.id", world.component_names.string(columns[0].name));
|
||||||
try testing.expectEqualStrings("game.name", world.component_names.string(columns[1].name));
|
try testing.expectEqualStrings("game.name", world.component_names.string(columns[1].name));
|
||||||
|
|
||||||
columns = archetypes[2].columns;
|
columns = archetypes[2].columns;
|
||||||
try testing.expectEqual(@as(usize, 3), columns.len);
|
try testing.expectEqual(@as(usize, 3), columns.len);
|
||||||
try testing.expectEqualStrings("entity.id", world.component_names.string(columns[0].name));
|
try testing.expectEqualStrings("entities.id", world.component_names.string(columns[0].name));
|
||||||
try testing.expectEqualStrings("game.name", world.component_names.string(columns[1].name));
|
try testing.expectEqualStrings("game.name", world.component_names.string(columns[1].name));
|
||||||
try testing.expectEqualStrings("game.location", world.component_names.string(columns[2].name));
|
try testing.expectEqualStrings("game.location", world.component_names.string(columns[2].name));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ test "entities DB" {
|
||||||
pub const components = .{
|
pub const components = .{
|
||||||
.id = .{ .type = u32 },
|
.id = .{ .type = u32 },
|
||||||
};
|
};
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.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 global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -87,9 +87,9 @@ test "entities DB" {
|
||||||
physics.init(.{ .pointer = 123 });
|
physics.init(.{ .pointer = 123 });
|
||||||
_ = physics.state().pointer; // == 123
|
_ = physics.state().pointer; // == 123
|
||||||
|
|
||||||
const player1 = try try entities.new();
|
const player1 = try entities.new();
|
||||||
const player2 = try try entities.new();
|
const player2 = try entities.new();
|
||||||
const player3 = try try entities.new();
|
const player3 = try entities.new();
|
||||||
try physics.set(player1, .id, 1001);
|
try physics.set(player1, .id, 1001);
|
||||||
try renderer.set(player1, .id, 1001);
|
try renderer.set(player1, .id, 1001);
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ test "entities DB" {
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Send events to modules
|
// Send events to modules
|
||||||
world.mod.renderer.sendGlobal(.tick, .{});
|
world.mod.renderer.send(.tick, .{});
|
||||||
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, .{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ fn validateModule(comptime M: type, comptime events: bool) void {
|
||||||
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 (events) {
|
||||||
if (@hasDecl(M, "global_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " global_events ", M.global_events);
|
|
||||||
if (@hasDecl(M, "events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " events ", M.events);
|
if (@hasDecl(M, "events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " events ", M.events);
|
||||||
_ = ComponentTypesM(M);
|
_ = ComponentTypesM(M);
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +180,6 @@ 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 GlobalEvent = GlobalEventEnum(modules);
|
|
||||||
pub const LocalEvent = LocalEventEnum(modules);
|
pub const LocalEvent = LocalEventEnum(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.
|
||||||
|
|
@ -189,7 +187,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
pub const component_types_by_name = ComponentTypesByName(modules){};
|
pub const component_types_by_name = ComponentTypesByName(modules){};
|
||||||
|
|
||||||
const Event = struct {
|
const Event = struct {
|
||||||
module_name: ?ModuleID,
|
module_name: ModuleID,
|
||||||
event_name: EventID,
|
event_name: EventID,
|
||||||
args_slice: []u8,
|
args_slice: []u8,
|
||||||
args_alignment: u32,
|
args_alignment: u32,
|
||||||
|
|
@ -260,16 +258,6 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
|
||||||
/// global event handler requires.
|
|
||||||
fn GlobalArgs(module_name: ModuleName(modules), event_name: GlobalEvent) type {
|
|
||||||
inline for (modules) |M| {
|
|
||||||
_ = ModuleInterface(M); // Validate the module
|
|
||||||
if (M.name != module_name) continue;
|
|
||||||
return GlobalArgsM(M, event_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts an event enum for a single module, to an event enum for all modules.
|
/// Converts an event enum for a single module, to an event enum for all modules.
|
||||||
fn moduleToGlobalEvent(
|
fn moduleToGlobalEvent(
|
||||||
comptime M: type,
|
comptime M: type,
|
||||||
|
|
@ -280,26 +268,6 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
return comptime stringToEnum(EventEnum(modules), @tagName(event_name)).?;
|
return comptime stringToEnum(EventEnum(modules), @tagName(event_name)).?;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a global event which the specified module defines
|
|
||||||
pub fn sendGlobal(
|
|
||||||
m: *@This(),
|
|
||||||
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
|
||||||
comptime module_name: ModuleName(modules),
|
|
||||||
// TODO(important): cleanup comptime
|
|
||||||
comptime event_name: GlobalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
|
||||||
args: GlobalArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).__state), event_name),
|
|
||||||
) void {
|
|
||||||
// TODO: comptime safety/debugging
|
|
||||||
const event_name_g: GlobalEvent = comptime moduleToGlobalEvent(
|
|
||||||
// TODO(important): cleanup comptime
|
|
||||||
@TypeOf(@field(m.mod, @tagName(module_name)).__state),
|
|
||||||
GlobalEventEnumM,
|
|
||||||
GlobalEventEnum,
|
|
||||||
event_name,
|
|
||||||
);
|
|
||||||
m.sendInternal(null, @intFromEnum(event_name_g), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send an event to a specific module
|
/// Send an event to a specific module
|
||||||
pub fn send(
|
pub fn send(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
|
|
@ -320,14 +288,6 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
m.sendInternal(@intFromEnum(module_name), @intFromEnum(event_name_g), args);
|
m.sendInternal(@intFromEnum(module_name), @intFromEnum(event_name_g), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a global event, using a dynamic (not known to the compiled program) event name.
|
|
||||||
pub fn sendGlobalDynamic(m: *@This(), event_name: EventID, args: anytype) void {
|
|
||||||
// TODO: runtime safety/debugging
|
|
||||||
// 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?
|
|
||||||
m.sendInternal(null, event_name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send an event to a specific module, using a dynamic (not known to the compiled program) module and event name.
|
/// Send an event to a specific module, using a dynamic (not known to the compiled program) module and event name.
|
||||||
pub fn sendDynamic(m: *@This(), module_name: ModuleID, event_name: EventID, args: anytype) void {
|
pub fn sendDynamic(m: *@This(), module_name: ModuleID, event_name: EventID, args: anytype) void {
|
||||||
// TODO: runtime safety/debugging
|
// TODO: runtime safety/debugging
|
||||||
|
|
@ -336,7 +296,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
m.sendInternal(module_name, event_name, args);
|
m.sendInternal(module_name, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendInternal(m: *@This(), module_name: ?ModuleID, event_name: EventID, args: anytype) void {
|
fn sendInternal(m: *@This(), module_name: ModuleID, event_name: EventID, 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));
|
||||||
|
|
||||||
|
|
@ -443,48 +403,16 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
||||||
m.events_mu.unlock();
|
m.events_mu.unlock();
|
||||||
|
|
||||||
if (ev.module_name) |module_name| {
|
// Dispatch the local event
|
||||||
// Dispatch the local event
|
try m.callLocal(@enumFromInt(ev.module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
||||||
try m.callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
|
||||||
|
|
||||||
// If we only wanted to dispatch until this event, then return.
|
// If we only wanted to dispatch until this event, then return.
|
||||||
if (options.until) |until| {
|
if (options.until) |until| {
|
||||||
if (until.module_name == module_name and until.local_event == ev.event_name) return;
|
if (until.module_name == ev.module_name and until.local_event == ev.event_name) return;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try m.callGlobal(@enumFromInt(ev.event_name), ev.args_slice, injectable);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call global event handler with the specified name in all modules
|
|
||||||
inline fn callGlobal(m: *@This(), event_name: GlobalEvent, args: []u8, injectable: anytype) !void {
|
|
||||||
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
|
||||||
switch (event_name) {
|
|
||||||
inline else => |ev_name| {
|
|
||||||
inline for (modules) |M| {
|
|
||||||
// TODO(important): DRY with callLocal
|
|
||||||
_ = ModuleInterface(M); // Validate the module
|
|
||||||
if (@hasDecl(M, "global_events")) inline for (@typeInfo(@TypeOf(M.global_events)).Struct.fields) |field| {
|
|
||||||
comptime if (!std.mem.eql(u8, @tagName(ev_name), field.name)) continue;
|
|
||||||
if (m.debug_trace) log.debug("trace(global): .{s}.{s}", .{
|
|
||||||
@tagName(M.name),
|
|
||||||
@tagName(ev_name),
|
|
||||||
});
|
|
||||||
const handler = @field(M.global_events, @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)) != .Fn) @compileError(std.fmt.comptimePrint("mach: module .{s} declares global event .{s} = .{{ .handler = T }}, expected fn but found: {s}", .{
|
|
||||||
@tagName(M.name),
|
|
||||||
@tagName(ev_name),
|
|
||||||
@typeName(@TypeOf(handler)),
|
|
||||||
}));
|
|
||||||
try callHandler(handler, args, injectable, "." ++ @tagName(M.name) ++ "." ++ @tagName(ev_name));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call local event handler with the specified name in the specified module
|
/// Call local event 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 callLocal(m: *@This(), module_name: ModuleName(modules), event_name: LocalEvent, args: []u8, injectable: anytype) !void {
|
||||||
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
||||||
|
|
@ -492,7 +420,6 @@ pub fn Modules(comptime modules: 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(important): DRY with callGlobal
|
|
||||||
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, "events")) inline for (@typeInfo(@TypeOf(M.events)).Struct.fields) |field| {
|
||||||
|
|
@ -727,14 +654,6 @@ pub fn ModSet(comptime modules: anytype) type {
|
||||||
mods.send(module_tag, event_name, args);
|
mods.send(module_tag, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn sendGlobal(m: *@This(), comptime event_name: GlobalEventEnumM(M), args: GlobalArgsM(M, event_name)) void {
|
|
||||||
const ModulesT = Modules(modules);
|
|
||||||
const MByName = ModsByName(modules);
|
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
|
||||||
mods.sendGlobal(module_tag, event_name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn event(_: *@This(), comptime event_name: LocalEventEnumM(M)) AnyEvent {
|
pub inline fn event(_: *@This(), comptime event_name: LocalEventEnumM(M)) AnyEvent {
|
||||||
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 event_name_g: Modules(modules).LocalEvent = comptime Modules(modules).moduleToGlobalEvent(
|
||||||
|
|
@ -837,16 +756,11 @@ fn LocalArgsM(comptime M: type, event_name: anytype) type {
|
||||||
return ArgsM(M, event_name, "events");
|
return ArgsM(M, event_name, "events");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tests
|
|
||||||
fn GlobalArgsM(comptime M: type, event_name: anytype) type {
|
|
||||||
return ArgsM(M, event_name, "global_events");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
|
fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
|
||||||
_ = ModuleInterface(M); // Validate the module
|
_ = ModuleInterface(M); // Validate the module
|
||||||
if (!@hasDecl(M, which)) return @TypeOf(.{});
|
if (!@hasDecl(M, which)) return @TypeOf(.{});
|
||||||
|
|
||||||
const m_events = @field(M, which); // M.events or M.global_events
|
const m_events = @field(M, which); // M.events
|
||||||
inline for (@typeInfo(@TypeOf(m_events)).Struct.fields) |field| {
|
inline for (@typeInfo(@TypeOf(m_events)).Struct.fields) |field| {
|
||||||
comptime if (!std.mem.eql(u8, field.name, @tagName(event_name))) continue;
|
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}", .{
|
if (!@hasField(@TypeOf(m_events), @tagName(event_name))) @compileError(std.fmt.comptimePrint("mach: module .{s} declares no {s} event .{s}", .{
|
||||||
|
|
@ -873,7 +787,6 @@ fn ArgsM(comptime M: type, event_name: anytype, comptime which: anytype) type {
|
||||||
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no " ++ which ++ " event handler for ." ++ @tagName(event_name));
|
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no " ++ which ++ " event handler for ." ++ @tagName(event_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: important! 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{};
|
||||||
|
|
@ -901,7 +814,6 @@ fn LocalEventEnum(comptime modules: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: important! DRY with GlobalEventEnumM
|
|
||||||
/// enum describing every possible comptime-known local event name
|
/// enum describing every possible comptime-known local event name
|
||||||
fn LocalEventEnumM(comptime M: anytype) type {
|
fn LocalEventEnumM(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{};
|
||||||
|
|
@ -927,60 +839,6 @@ fn LocalEventEnumM(comptime M: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: important! DRY with LocalEventEnum
|
|
||||||
/// enum describing every possible comptime-known global event name
|
|
||||||
fn GlobalEventEnum(comptime modules: anytype) type {
|
|
||||||
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
|
||||||
var i: u32 = 0;
|
|
||||||
for (modules) |M| {
|
|
||||||
_ = 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(.{
|
|
||||||
.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: important! 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(.{
|
|
||||||
.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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// enum describing component names for the given module only
|
/// enum describing component names for the given module only
|
||||||
pub fn ComponentNameM(comptime M: type) type {
|
pub fn ComponentNameM(comptime M: type) type {
|
||||||
const components = ComponentTypesM(M){};
|
const components = ComponentTypesM(M){};
|
||||||
|
|
@ -1243,7 +1101,7 @@ test ModuleInterface {
|
||||||
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1264,7 +1122,7 @@ test Modules {
|
||||||
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
.location = .{ .type = @Vector(3, f32), .description = "A location component" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1273,7 +1131,7 @@ test Modules {
|
||||||
|
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1300,11 +1158,9 @@ test Modules {
|
||||||
test "event name" {
|
test "event name" {
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.foo = .{ .handler = foo },
|
.foo = .{ .handler = foo },
|
||||||
.bar = .{ .handler = bar },
|
.bar = .{ .handler = bar },
|
||||||
};
|
|
||||||
pub const events = .{
|
|
||||||
.baz = .{ .handler = baz },
|
.baz = .{ .handler = baz },
|
||||||
.bam = .{ .handler = bam },
|
.bam = .{ .handler = bam },
|
||||||
};
|
};
|
||||||
|
|
@ -1317,7 +1173,7 @@ test "event name" {
|
||||||
|
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.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 },
|
||||||
|
|
@ -1332,7 +1188,7 @@ test "event name" {
|
||||||
|
|
||||||
const Sprite2D = ModuleInterface(struct {
|
const Sprite2D = ModuleInterface(struct {
|
||||||
pub const name = .engine_sprite2d;
|
pub const name = .engine_sprite2d;
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.foobar = .{ .handler = fooBar },
|
.foobar = .{ .handler = fooBar },
|
||||||
};
|
};
|
||||||
|
|
@ -1349,20 +1205,16 @@ test "event name" {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const locals = @typeInfo(Ms.LocalEvent).Enum;
|
const locals = @typeInfo(Ms.LocalEvent).Enum;
|
||||||
try testing.expect(type, u1).eql(locals.tag_type);
|
try testing.expect(type, u3).eql(locals.tag_type);
|
||||||
try testing.expect(usize, 2).eql(locals.fields.len);
|
try testing.expect(usize, 8).eql(locals.fields.len);
|
||||||
try testing.expect([]const u8, "baz").eql(locals.fields[0].name);
|
try testing.expect([]const u8, "foo").eql(locals.fields[0].name);
|
||||||
try testing.expect([]const u8, "bam").eql(locals.fields[1].name);
|
try testing.expect([]const u8, "bar").eql(locals.fields[1].name);
|
||||||
|
try testing.expect([]const u8, "baz").eql(locals.fields[2].name);
|
||||||
const globals = @typeInfo(Ms.GlobalEvent).Enum;
|
try testing.expect([]const u8, "bam").eql(locals.fields[3].name);
|
||||||
try testing.expect(type, u3).eql(globals.tag_type);
|
try testing.expect([]const u8, "foo_unused").eql(locals.fields[4].name);
|
||||||
try testing.expect(usize, 6).eql(globals.fields.len);
|
try testing.expect([]const u8, "bar_unused").eql(locals.fields[5].name);
|
||||||
try testing.expect([]const u8, "foo").eql(globals.fields[0].name);
|
try testing.expect([]const u8, "tick").eql(locals.fields[6].name);
|
||||||
try testing.expect([]const u8, "bar").eql(globals.fields[1].name);
|
try testing.expect([]const u8, "foobar").eql(locals.fields[7].name);
|
||||||
try testing.expect([]const u8, "foo_unused").eql(globals.fields[2].name);
|
|
||||||
try testing.expect([]const u8, "bar_unused").eql(globals.fields[3].name);
|
|
||||||
try testing.expect([]const u8, "tick").eql(globals.fields[4].name);
|
|
||||||
try testing.expect([]const u8, "foobar").eql(globals.fields[5].name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test ModuleName {
|
test ModuleName {
|
||||||
|
|
@ -1386,7 +1238,7 @@ test ModuleName {
|
||||||
|
|
||||||
try testing.expect(type, u2).eql(info.tag_type);
|
try testing.expect(type, u2).eql(info.tag_type);
|
||||||
try testing.expect(usize, 4).eql(info.fields.len);
|
try testing.expect(usize, 4).eql(info.fields.len);
|
||||||
try testing.expect([]const u8, "entity").eql(info.fields[0].name);
|
try testing.expect([]const u8, "entities").eql(info.fields[0].name);
|
||||||
try testing.expect([]const u8, "engine_physics").eql(info.fields[1].name);
|
try testing.expect([]const u8, "engine_physics").eql(info.fields[1].name);
|
||||||
try testing.expect([]const u8, "engine_renderer").eql(info.fields[2].name);
|
try testing.expect([]const u8, "engine_renderer").eql(info.fields[2].name);
|
||||||
try testing.expect([]const u8, "engine_sprite2d").eql(info.fields[3].name);
|
try testing.expect([]const u8, "engine_sprite2d").eql(info.fields[3].name);
|
||||||
|
|
@ -1513,10 +1365,8 @@ test "event name calling" {
|
||||||
};
|
};
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const global_events = .{
|
|
||||||
.tick = .{ .handler = tick },
|
|
||||||
};
|
|
||||||
pub const events = .{
|
pub const events = .{
|
||||||
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
.calc = .{ .handler = calc },
|
.calc = .{ .handler = calc },
|
||||||
};
|
};
|
||||||
|
|
@ -1535,10 +1385,8 @@ test "event name calling" {
|
||||||
});
|
});
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const global_events = .{
|
|
||||||
.tick = .{ .handler = tick },
|
|
||||||
};
|
|
||||||
pub const events = .{
|
pub const events = .{
|
||||||
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1560,21 +1408,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);
|
||||||
|
|
||||||
try modules.callGlobal(.tick, &.{}, .{});
|
// Check we can use .callLocal() with a runtime-known event and module name.
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
|
||||||
|
|
||||||
// Check we can use .callGlobal() with a runtime-known event 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 GE = @TypeOf(modules).GlobalEvent;
|
|
||||||
const LE = @TypeOf(modules).LocalEvent;
|
const LE = @TypeOf(modules).LocalEvent;
|
||||||
alloc.* = @intFromEnum(@as(GE, .tick));
|
|
||||||
|
|
||||||
const global_event_name = @as(GE, @enumFromInt(alloc.*));
|
|
||||||
try modules.callGlobal(global_event_name, &.{}, .{});
|
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
|
||||||
|
|
||||||
// Check we can use .callLocal() with a runtime-known event and module name.
|
|
||||||
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);
|
||||||
|
|
@ -1585,7 +1422,7 @@ test "event name calling" {
|
||||||
var local_event_name = @as(LE, @enumFromInt(alloc.*));
|
var local_event_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 4).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);
|
||||||
|
|
||||||
|
|
@ -1601,7 +1438,7 @@ test "event name calling" {
|
||||||
module_name = @as(M, @enumFromInt(m_alloc.*));
|
module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
try modules.callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 4).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);
|
||||||
try testing.expect(usize, 2).eql(global.renderer_updates);
|
try testing.expect(usize, 2).eql(global.renderer_updates);
|
||||||
|
|
@ -1625,10 +1462,8 @@ test "dispatch" {
|
||||||
});
|
});
|
||||||
const Physics = ModuleInterface(struct {
|
const Physics = ModuleInterface(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const global_events = .{
|
|
||||||
.tick = .{ .handler = tick },
|
|
||||||
};
|
|
||||||
pub const events = .{
|
pub const events = .{
|
||||||
|
.tick = .{ .handler = tick },
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
.update_with_struct_arg = .{ .handler = updateWithStructArg },
|
.update_with_struct_arg = .{ .handler = updateWithStructArg },
|
||||||
.calc = .{ .handler = calc },
|
.calc = .{ .handler = calc },
|
||||||
|
|
@ -1657,11 +1492,9 @@ test "dispatch" {
|
||||||
});
|
});
|
||||||
const Renderer = ModuleInterface(struct {
|
const Renderer = ModuleInterface(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const global_events = .{
|
pub const events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
.frame_done = .{ .handler = fn (i32) void },
|
.frame_done = .{ .handler = fn (i32) void },
|
||||||
};
|
|
||||||
pub const events = .{
|
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
.basic_args = .{ .handler = basicArgs },
|
.basic_args = .{ .handler = basicArgs },
|
||||||
.injected_args = .{ .handler = injectedArgs },
|
.injected_args = .{ .handler = injectedArgs },
|
||||||
|
|
@ -1696,32 +1529,11 @@ test "dispatch" {
|
||||||
try modules.init(testing.allocator);
|
try modules.init(testing.allocator);
|
||||||
defer modules.deinit(testing.allocator);
|
defer modules.deinit(testing.allocator);
|
||||||
|
|
||||||
const GE = @TypeOf(modules).GlobalEvent;
|
|
||||||
const LE = @TypeOf(modules).LocalEvent;
|
const LE = @TypeOf(modules).LocalEvent;
|
||||||
const M = ModuleName(modules2);
|
const M = ModuleName(modules2);
|
||||||
|
|
||||||
// Global events
|
|
||||||
//
|
|
||||||
// The 2nd parameter (arguments to the tick event handler) is inferred based on the `pub fn tick`
|
|
||||||
// global event handler declaration within a module. It is required that all global event handlers
|
|
||||||
// of the same name have the same standard arguments, although they can start with different
|
|
||||||
// injected arguments.
|
|
||||||
modules.sendGlobal(.engine_renderer, .tick, .{});
|
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
|
||||||
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
|
||||||
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
|
||||||
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
|
||||||
modules.sendGlobalDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
|
||||||
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
|
||||||
|
|
||||||
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
|
||||||
// within a module, which allows pre-declaring that `fooBar` is a valid global event, and enables
|
|
||||||
// its arguments to be inferred still like this:
|
|
||||||
modules.sendGlobal(.engine_renderer, .frame_done, .{1337});
|
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
modules.send(.engine_renderer, .update, .{});
|
modules.send(.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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue