module: make global event argument types always known
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
135e9b8121
commit
fcf6be9e6d
1 changed files with 73 additions and 48 deletions
|
|
@ -81,26 +81,22 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
|
||||||
|
|
||||||
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
||||||
/// global event handler requires.
|
/// global event handler requires.
|
||||||
///
|
fn Args(event_name: EventName(mods)) type {
|
||||||
/// If the returned type would differ from EventArgs, a compile-time error will occur.
|
|
||||||
///
|
|
||||||
/// If no module currently has a global event handler of this name, then its argument type
|
|
||||||
/// is currently undefined and assumed to be EventArgs.
|
|
||||||
fn Args(comptime EventArgs: type, event_name: EventName(mods)) type {
|
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
|
// TODO: enforce any defined event handlers of the same name have the same argument types
|
||||||
if (@hasDecl(M, @tagName(event_name))) {
|
if (@hasDecl(M, @tagName(event_name))) {
|
||||||
switch (@typeInfo(@TypeOf(@field(M, @tagName(event_name))))) {
|
const Handler = switch (@typeInfo(@TypeOf(@field(M, @tagName(event_name))))) {
|
||||||
.Fn => {
|
.Fn => @TypeOf(@field(M, @tagName(event_name))),
|
||||||
const handler = @field(M, @tagName(event_name));
|
.Type => switch (@typeInfo(@field(M, @tagName(event_name)))) {
|
||||||
// TODO: worth checking if the return type is == EventArgs here? Could
|
.Fn => @field(M, @tagName(event_name)),
|
||||||
// that lead to better UX?
|
else => continue,
|
||||||
return UninjectedArgsTuple(@TypeOf(handler), Injectable);
|
|
||||||
},
|
},
|
||||||
else => {},
|
else => continue,
|
||||||
|
};
|
||||||
|
return UninjectedArgsTuple(Handler, Injectable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
@compileError("No global event handler " ++ @tagName(event_name) ++ " is defined in any module.");
|
||||||
return EventArgs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a global event
|
/// Send a global event
|
||||||
|
|
@ -108,8 +104,7 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime event_name: EventName(mods),
|
comptime event_name: EventName(mods),
|
||||||
comptime EventArgs: type,
|
args: Args(event_name),
|
||||||
args: Args(EventArgs, event_name),
|
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
m.sendInternal(null, @intFromEnum(event_name), args);
|
m.sendInternal(null, @intFromEnum(event_name), args);
|
||||||
|
|
@ -157,7 +152,7 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
|
||||||
m.events.writeItemAssumeCapacity(.{
|
m.events.writeItemAssumeCapacity(.{
|
||||||
.module_name = module_name,
|
.module_name = module_name,
|
||||||
.event_name = event_name,
|
.event_name = event_name,
|
||||||
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. args_bytes.len],
|
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. m.args_queue.items.len],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,15 +290,22 @@ fn UninjectedArgsTuple(comptime Function: type, comptime Injectable: type) type
|
||||||
return std.meta.Tuple(std_args);
|
return std.meta.Tuple(std_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enum describing every possible comptime-known global event name
|
/// enum describing every possible comptime-known global event name.
|
||||||
fn GlobalEvent(comptime mods: anytype) type {
|
fn GlobalEvent(comptime mods: 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 (mods) |M| {
|
for (mods) |M| {
|
||||||
// Global event handlers
|
// Global event handlers
|
||||||
for (@typeInfo(M).Struct.decls) |decl| {
|
for (@typeInfo(M).Struct.decls) |decl| {
|
||||||
switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
const is_event_handler = switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
||||||
.Fn => {
|
.Fn => true,
|
||||||
|
.Type => switch (@typeInfo(@field(M, decl.name))) {
|
||||||
|
.Fn => true,
|
||||||
|
else => false,
|
||||||
|
},
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
if (is_event_handler) {
|
||||||
const exists_already = blk2: {
|
const exists_already = blk2: {
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
||||||
break :blk2 false;
|
break :blk2 false;
|
||||||
|
|
@ -312,11 +314,10 @@ fn GlobalEvent(comptime mods: anytype) type {
|
||||||
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return @Type(.{
|
return @Type(.{
|
||||||
.Enum = .{
|
.Enum = .{
|
||||||
.tag_type = std.math.IntFittingRange(0, enum_fields.len - 1),
|
.tag_type = std.math.IntFittingRange(0, enum_fields.len - 1),
|
||||||
|
|
@ -334,8 +335,15 @@ fn EventName(comptime mods: anytype) type {
|
||||||
for (mods) |M| {
|
for (mods) |M| {
|
||||||
// Global event handlers
|
// Global event handlers
|
||||||
for (@typeInfo(M).Struct.decls) |decl| {
|
for (@typeInfo(M).Struct.decls) |decl| {
|
||||||
switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
const is_event_handler = switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
||||||
.Fn => {
|
.Fn => true,
|
||||||
|
.Type => switch (@typeInfo(@field(M, decl.name))) {
|
||||||
|
.Fn => true,
|
||||||
|
else => false,
|
||||||
|
},
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
if (is_event_handler) {
|
||||||
const exists_already = blk2: {
|
const exists_already = blk2: {
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
||||||
break :blk2 false;
|
break :blk2 false;
|
||||||
|
|
@ -344,8 +352,6 @@ fn EventName(comptime mods: anytype) type {
|
||||||
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,6 +579,9 @@ test EventName {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
|
||||||
|
pub const fooUnused = fn (f32, i32) void;
|
||||||
|
pub const barUnused = fn (i32, f32) void;
|
||||||
|
|
||||||
pub fn tick() !void {}
|
pub fn tick() !void {}
|
||||||
pub fn foo() !void {} // same .foo name as .engine_physics.foo
|
pub fn foo() !void {} // same .foo name as .engine_physics.foo
|
||||||
pub fn bar() !void {} // same .bar name as .engine_physics.bar
|
pub fn bar() !void {} // same .bar name as .engine_physics.bar
|
||||||
|
|
@ -596,20 +605,24 @@ test EventName {
|
||||||
const info = @typeInfo(EventName(Mods.modules)).Enum;
|
const info = @typeInfo(EventName(Mods.modules)).Enum;
|
||||||
|
|
||||||
try testing.expect(type, u3).eql(info.tag_type);
|
try testing.expect(type, u3).eql(info.tag_type);
|
||||||
try testing.expect(usize, 6).eql(info.fields.len);
|
try testing.expect(usize, 8).eql(info.fields.len);
|
||||||
try testing.expect([]const u8, "foo").eql(info.fields[0].name);
|
try testing.expect([]const u8, "foo").eql(info.fields[0].name);
|
||||||
try testing.expect([]const u8, "bar").eql(info.fields[1].name);
|
try testing.expect([]const u8, "bar").eql(info.fields[1].name);
|
||||||
try testing.expect([]const u8, "baz").eql(info.fields[2].name);
|
try testing.expect([]const u8, "baz").eql(info.fields[2].name);
|
||||||
try testing.expect([]const u8, "bam").eql(info.fields[3].name);
|
try testing.expect([]const u8, "bam").eql(info.fields[3].name);
|
||||||
try testing.expect([]const u8, "tick").eql(info.fields[4].name);
|
try testing.expect([]const u8, "fooUnused").eql(info.fields[4].name);
|
||||||
try testing.expect([]const u8, "foobar").eql(info.fields[5].name);
|
try testing.expect([]const u8, "barUnused").eql(info.fields[5].name);
|
||||||
|
try testing.expect([]const u8, "tick").eql(info.fields[6].name);
|
||||||
|
try testing.expect([]const u8, "foobar").eql(info.fields[7].name);
|
||||||
|
|
||||||
const global_info = @typeInfo(GlobalEvent(Mods.modules)).Enum;
|
const global_info = @typeInfo(GlobalEvent(Mods.modules)).Enum;
|
||||||
try testing.expect(type, u2).eql(global_info.tag_type);
|
try testing.expect(type, u3).eql(global_info.tag_type);
|
||||||
try testing.expect(usize, 3).eql(global_info.fields.len);
|
try testing.expect(usize, 5).eql(global_info.fields.len);
|
||||||
try testing.expect([]const u8, "foo").eql(global_info.fields[0].name);
|
try testing.expect([]const u8, "foo").eql(global_info.fields[0].name);
|
||||||
try testing.expect([]const u8, "bar").eql(global_info.fields[1].name);
|
try testing.expect([]const u8, "bar").eql(global_info.fields[1].name);
|
||||||
try testing.expect([]const u8, "tick").eql(global_info.fields[2].name);
|
try testing.expect([]const u8, "fooUnused").eql(global_info.fields[2].name);
|
||||||
|
try testing.expect([]const u8, "barUnused").eql(global_info.fields[3].name);
|
||||||
|
try testing.expect([]const u8, "tick").eql(global_info.fields[4].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
test ModuleName {
|
test ModuleName {
|
||||||
|
|
@ -871,6 +884,8 @@ test "dispatch" {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
|
||||||
|
pub const frameDone = fn (i32) void;
|
||||||
|
|
||||||
pub fn tick() void {
|
pub fn tick() void {
|
||||||
global.ticks += 1;
|
global.ticks += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -902,7 +917,12 @@ test "dispatch" {
|
||||||
const M = ModuleName(@TypeOf(modules).modules);
|
const M = ModuleName(@TypeOf(modules).modules);
|
||||||
|
|
||||||
// Global events
|
// Global events
|
||||||
modules.send(.tick, struct {}, .{});
|
//
|
||||||
|
// 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.send(.tick, .{});
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
try testing.expect(usize, 0).eql(global.ticks);
|
||||||
modules.dispatch(.{&foo});
|
modules.dispatch(.{&foo});
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
try testing.expect(usize, 2).eql(global.ticks);
|
||||||
|
|
@ -911,6 +931,11 @@ test "dispatch" {
|
||||||
modules.dispatch(.{&foo});
|
modules.dispatch(.{&foo});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
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.send(.frameDone, .{1337});
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.sendToModule(.engine_renderer, .update, .{});
|
modules.sendToModule(.engine_renderer, .update, .{});
|
||||||
modules.dispatch(.{&foo});
|
modules.dispatch(.{&foo});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue