diff --git a/src/abi.zig b/src/abi.zig index dcb6c13..5909c4a 100644 --- a/src/abi.zig +++ b/src/abi.zig @@ -97,7 +97,7 @@ pub fn stats() Stats { /// Drops the handles nobody kept. This calls into the host, which is not /// allowed from a post-return function, so it can't be left to `endCall`. -fn releaseHandles() void { +pub fn releaseHandles() void { while (tracked.pop()) |item| item.drop(@bitCast(item.handle)); } @@ -419,6 +419,37 @@ pub fn liftFlat(comptime T: type, slots: []const u64) T { } } +// A plugin only ever looks at a few cases of the big variants (there are +// hundreds of events). These let it lift and lower one case without pulling +// in code for all the others. + +pub fn loadTag(comptime U: type, ptr: [*]const u8) std.meta.Tag(U) { + return @enumFromInt(get(Discriminant(caseCount(U)), ptr)); +} + +pub fn loadCase(comptime U: type, comptime tag: std.meta.Tag(U), ptr: [*]const u8) @FieldType(U, @tagName(tag)) { + return load(@FieldType(U, @tagName(tag)), ptr + payloadOffset(U)); +} + +pub fn storeCase(comptime U: type, comptime tag: std.meta.Tag(U), value: @FieldType(U, @tagName(tag)), ptr: [*]u8) void { + put(Discriminant(caseCount(U)), ptr, @intFromEnum(tag)); + store(@FieldType(U, @tagName(tag)), value, ptr + payloadOffset(U)); +} + +/// Offset of field `index` inside the record `T` in linear memory. +pub fn offsetOf(comptime T: type, comptime index: usize) comptime_int { + return fieldOffset(T, index); +} + +/// Whether parameters of this shape arrive as a pointer instead of flat values. +pub fn passedInMemory(comptime Args: type) bool { + return flatten(Args).len > max_flat_params; +} + +pub fn alloc(comptime T: type) [*]u8 { + return scratch(T); +} + fn put(comptime Int: type, ptr: [*]u8, value: Int) void { std.mem.writeInt(Int, ptr[0..@sizeOf(Int)], value, .little); } diff --git a/src/pumpkin.zig b/src/pumpkin.zig index b49f710..6c1fb33 100644 --- a/src/pumpkin.zig +++ b/src/pumpkin.zig @@ -122,15 +122,25 @@ pub fn on( comptime handler: fn (Server, *EventData(event_type)) void, options: EventOptions, ) void { + const tag = comptime std.meta.stringToEnum(std.meta.Tag(Event), @tagName(event_type)).?; const glue = struct { - fn dispatch(srv: Server, ev: *Event) void { - handler(srv, &@field(ev, @tagName(event_type))); + // Only this event's payload gets lifted. Going through the whole + // `Event` union would compile code for every event there is. + fn dispatch(srv: Server, in: [*]const u8, out: [*]u8) void { + if (abi.loadTag(Event, in) != tag) { + @memcpy(out[0..abi.sizeOf(Event)], in[0..abi.sizeOf(Event)]); + return; + } + var data = abi.loadCase(Event, tag, in); + handler(srv, &data); + abi.storeCase(Event, tag, data, out); } }; ctx.registerEvent(handlerId(&glue.dispatch), event_type, options.priority, options.blocking); } -pub const EventHandler = fn (Server, *Event) void; +const EventDispatch = fn (Server, in: [*]const u8, out: [*]u8) void; + pub const CommandHandler = fn (CommandSender, Server, ConsumedArgs) CommandResult; pub const SuggestionHandler = fn (CommandSender, Server, command.SuggestionRequest) command.CommandSuggestions; pub const TaskHandler = fn (Server) void; @@ -223,10 +233,23 @@ pub fn register(comptime Plugin: type) void { return .ok; } - fn handleEvent(id: u32, srv: Server, ev: Event) Event { - var out = ev; - handlerFromId(EventHandler, id)(srv, &out); - return out; + // Written by hand instead of going through `exports.handleEvent`, see `on`. + fn handleEvent(raw_args: i32) callconv(.c) i32 { + const Args = struct { u32, Server, Event }; + comptime std.debug.assert(abi.passedInMemory(Args)); + + const args: [*]const u8 = @ptrFromInt(@as(u32, @bitCast(raw_args))); + const id = abi.load(u32, args + abi.offsetOf(Args, 0)); + const srv = abi.load(Server, args + abi.offsetOf(Args, 1)); + const out = abi.alloc(Event); + + handlerFromId(EventDispatch, id)(srv, args + abi.offsetOf(Args, 2), out); + abi.releaseHandles(); + return @bitCast(@as(u32, @intFromPtr(out))); + } + + fn postHandleEvent(_: i32) callconv(.c) void { + abi.endCall(); } fn handleCommand(id: u32, sender: CommandSender, srv: Server, args: ConsumedArgs) CommandResult { @@ -281,7 +304,8 @@ pub fn register(comptime Plugin: type) void { exports.getMetadata(glue.getMetadata); exports.onLoad(glue.onLoad); exports.onUnload(glue.onUnload); - exports.handleEvent(glue.handleEvent); + @export(&glue.handleEvent, .{ .name = "handle-event" }); + @export(&glue.postHandleEvent, .{ .name = "cabi_post_handle-event" }); exports.handleCommand(glue.handleCommand); exports.handleCommandSuggestion(glue.handleCommandSuggestion); exports.handleTask(glue.handleTask);