module: event handlers can return inferred error set

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-03-16 22:09:08 -07:00 committed by Stephen Gutekanst
parent 03454decdf
commit e24d09cd62

View file

@ -157,7 +157,7 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
}
/// Dispatches pending events, invoking their event handlers.
pub fn dispatch(m: *@This(), injectable: Injectable) void {
pub fn dispatch(m: *@This(), injectable: Injectable) !void {
// TODO: verify injectable arguments are valid, e.g. not comptime types
// TODO: optimize to reduce send contention
@ -166,20 +166,22 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
m.events_mu.lock();
defer m.events_mu.unlock();
// TODO: this is wrong
defer m.args_queue.clearRetainingCapacity();
while (m.events.readItem()) |ev| {
if (ev.module_name) |module_name| {
// TODO: dispatch arguments
@This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
try @This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
} else {
// TODO: dispatch arguments
@This().call(@enumFromInt(ev.event_name), ev.args_slice, injectable);
try @This().call(@enumFromInt(ev.event_name), ev.args_slice, injectable);
}
}
m.args_queue.clearRetainingCapacity();
}
/// Call global event handler with the specified name in all modules
inline fn call(event_name: EventName(mods), args: []u8, injectable: anytype) void {
inline fn call(event_name: EventName(mods), args: []u8, injectable: anytype) !void {
switch (event_name) {
inline else => |name| {
inline for (modules) |M| {
@ -187,7 +189,7 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
switch (@typeInfo(@TypeOf(@field(M, @tagName(name))))) {
.Fn => {
const handler = @field(M, @tagName(name));
callHandler(handler, args, injectable);
try callHandler(handler, args, injectable);
},
else => {},
}
@ -198,7 +200,7 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
}
/// Call local event handler with the specified name in the specified module
inline fn callLocal(module_name: ModuleName(mods), event_name: EventName(mods), args: []u8, injectable: anytype) void {
inline fn callLocal(module_name: ModuleName(mods), event_name: EventName(mods), args: []u8, injectable: anytype) !void {
// TODO: invert switch case for hypothetically better branch prediction
switch (module_name) {
inline else => |mod_name| {
@ -206,11 +208,11 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
inline else => |ev_name| {
const M = @field(NamespacedModules(@This().modules){}, @tagName(mod_name));
// TODO: no need for hasDecl, assertion should be event can be sent at send() time.
if (@hasDecl(M.local, @tagName(ev_name))) {
if (@hasDecl(M, "local") and @hasDecl(M.local, @tagName(ev_name))) {
const handler = @field(M.local, @tagName(ev_name));
switch (@typeInfo(@TypeOf(handler))) {
.Fn => {
callHandler(handler, args, injectable);
try callHandler(handler, args, injectable);
},
else => {},
}
@ -222,11 +224,16 @@ pub fn Modules(comptime mods: anytype, comptime Injectable: type) type {
}
/// Invokes an event handler with optionally injected arguments.
inline fn callHandler(handler: anytype, args_data: []u8, injectable: Injectable) void {
const StdArgs = UninjectedArgsTuple(@TypeOf(handler), Injectable);
inline fn callHandler(handler: anytype, args_data: []u8, injectable: anytype) !void {
const Handler = @TypeOf(handler);
const StdArgs = UninjectedArgsTuple(Handler, @TypeOf(injectable));
const std_args: *StdArgs = @alignCast(@ptrCast(args_data.ptr));
const args = injectArgs(@TypeOf(handler), Injectable, injectable, std_args.*);
@call(.auto, handler, args);
const args = injectArgs(Handler, @TypeOf(injectable), injectable, std_args.*);
const Ret = @typeInfo(Handler).Fn.return_type orelse void;
switch (@typeInfo(Ret)) {
.ErrorUnion => try @call(.auto, handler, args),
else => @call(.auto, handler, args),
}
}
};
}
@ -799,7 +806,7 @@ test "event name calling" {
try modules.init(testing.allocator);
defer modules.deinit(testing.allocator);
@TypeOf(modules).call(.tick, &.{}, .{});
try @TypeOf(modules).call(.tick, &.{}, .{});
try testing.expect(usize, 2).eql(global.ticks);
// Check we can use .call() with a runtime-known event name.
@ -809,13 +816,13 @@ test "event name calling" {
alloc.* = @intFromEnum(@as(E, .tick));
var event_name = @as(E, @enumFromInt(alloc.*));
@TypeOf(modules).call(event_name, &.{}, .{});
try @TypeOf(modules).call(event_name, &.{}, .{});
try testing.expect(usize, 4).eql(global.ticks);
// Check call() behavior with a valid event name enum, but not a valid global event handler name
alloc.* = @intFromEnum(@as(E, .update));
event_name = @as(E, @enumFromInt(alloc.*));
@TypeOf(modules).call(event_name, &.{}, .{});
try @TypeOf(modules).call(event_name, &.{}, .{});
try testing.expect(usize, 4).eql(global.ticks);
try testing.expect(usize, 0).eql(global.physics_updates);
try testing.expect(usize, 0).eql(global.renderer_updates);
@ -827,8 +834,8 @@ test "event name calling" {
m_alloc.* = @intFromEnum(@as(M, .engine_renderer));
alloc.* = @intFromEnum(@as(E, .update));
var module_name = @as(M, @enumFromInt(m_alloc.*));
@TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
@TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try testing.expect(usize, 4).eql(global.ticks);
try testing.expect(usize, 0).eql(global.physics_updates);
try testing.expect(usize, 2).eql(global.renderer_updates);
@ -837,14 +844,14 @@ test "event name calling" {
alloc.* = @intFromEnum(@as(E, .update));
module_name = @as(M, @enumFromInt(m_alloc.*));
event_name = @as(E, @enumFromInt(alloc.*));
@TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try testing.expect(usize, 1).eql(global.physics_updates);
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
alloc.* = @intFromEnum(@as(E, .calc));
module_name = @as(M, @enumFromInt(m_alloc.*));
event_name = @as(E, @enumFromInt(alloc.*));
@TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
try testing.expect(usize, 4).eql(global.ticks);
try testing.expect(usize, 1).eql(global.physics_calc);
try testing.expect(usize, 1).eql(global.physics_updates);
@ -924,11 +931,11 @@ test "dispatch" {
// injected arguments.
modules.send(.tick, .{});
try testing.expect(usize, 0).eql(global.ticks);
modules.dispatch(.{&foo});
try modules.dispatch(.{&foo});
try testing.expect(usize, 2).eql(global.ticks);
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
modules.sendDynamic(@intFromEnum(@as(E, .tick)), .{});
modules.dispatch(.{&foo});
try modules.dispatch(.{&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;`
@ -938,7 +945,7 @@ test "dispatch" {
// Local events
modules.sendToModule(.engine_renderer, .update, .{});
modules.dispatch(.{&foo});
try modules.dispatch(.{&foo});
try testing.expect(usize, 1).eql(global.renderer_updates);
modules.sendToModule(.engine_physics, .update, .{});
modules.sendToModuleDynamic(
@ -946,14 +953,14 @@ test "dispatch" {
@intFromEnum(@as(E, .calc)),
.{},
);
modules.dispatch(.{&foo});
try modules.dispatch(.{&foo});
try testing.expect(usize, 1).eql(global.physics_updates);
try testing.expect(usize, 1).eql(global.physics_calc);
// Local events
modules.sendToModule(.engine_renderer, .basicArgs, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
modules.sendToModule(.engine_renderer, .injectedArgs, .{ @as(u32, 1), @as(u32, 2) });
modules.dispatch(.{&foo});
try modules.dispatch(.{&foo});
try testing.expect(usize, 3).eql(global.basic_args_sum);
try testing.expect(usize, 3).eql(foo.injected_args_sum);
}