sylveos

Toy Operating System
Log | Files | Refs

commit 34d09bf786776f2e1bdc91cddf3c41aae11439d7
parent a64232b210714ac96724eef3165db3aac6857cd6
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu,  5 Feb 2026 14:59:24 -0800

Add basic Publisher

Diffstat:
Ashared/pubsub.zig | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mshared/root.zig | 3+++
2 files changed, 130 insertions(+), 0 deletions(-)

diff --git a/shared/pubsub.zig b/shared/pubsub.zig @@ -0,0 +1,127 @@ +const std = @import("std"); + +const ListenerFn = *const fn (*const anyopaque) void; +const Listener = struct { + callback: ListenerFn, + node: std.SinglyLinkedList.Node, +}; + +fn validate_listener_fn(comptime ptr: anytype) type { + const P = @TypeOf(ptr); + const ptr_info = @typeInfo(P); + if (ptr_info != .pointer) { + @compileError("expected *fn, found " ++ @typeName(ptr_info)); + } + const L = ptr_info.pointer.child; + + const listener_info = @typeInfo(L); + if (listener_info != .@"fn") { + @compileError("expected *fn, found " ++ @typeName(P)); + } + + const listener_fn = listener_info.@"fn"; + if (listener_fn.params.len != 1) { + @compileError("expected 1 parameter, found " ++ listener_fn.params.len); + } + const msg_type = listener_fn.params[0].type orelse unreachable; + const msg_type_info = @typeInfo(msg_type); + if (msg_type_info != .pointer) { + @compileError("expected *const, found " ++ @typeName(msg_type)); + } + if (!msg_type_info.pointer.is_const) { + @compileError("expected *const, found " ++ @typeName(msg_type)); + } + + // Questionable legality + // return @ptrCast(ptr); + return msg_type; +} + +// pub fn create_listener_static(comptime ptr: anytype) Listener { +// return .{ +// .node = undefined, +// .callback = @ptrCast(ptr), +// .callback_fn_param = validate_listener_fn(ptr), +// }; +// } + +pub fn Publisher(comptime M: type, max_listeners: comptime_int) type { + const msg_info = @typeInfo(M); + if (msg_info != .@"union") { + @compileError("expected tagged union, found " ++ @typeName(M)); + } + + const msg_union = msg_info.@"union"; + const msg_tag_type = msg_union.tag_type orelse @compileError("expected tagged union, found untagged union"); + + const Slot = struct { []const u8, std.SinglyLinkedList }; + + var slots: [msg_union.fields.len]Slot = undefined; + + comptime for (msg_union.fields, 0..) |field, index| { + slots[index] = .{ field.name, undefined }; + }; + + const event_map: std.StaticStringMap(std.SinglyLinkedList) = .initComptime(slots); + + return struct { + const Self = @This(); + + listeners: std.StaticStringMap(std.SinglyLinkedList), + buffer: [max_listeners * @sizeOf(Listener)]u8, + fba: std.heap.FixedBufferAllocator, + + pub fn init() Self { + const buffer = undefined; + + return .{ + .listeners = event_map, + .buffer = buffer, + .fba = .init(buffer), + }; + } + + pub fn listen(self: *Self, comptime event: msg_tag_type, comptime listener: anytype) !void { + comptime if (max_listeners == 0) @compileError("must use listen_alloc on this Publisher"); + + try self.listen_alloc(self.fba.allocator(), event, listener); + } + + // TODO; this leaks + pub fn listen_alloc(self: *Self, gpa: std.mem.Allocator, comptime event: msg_tag_type, comptime listener: anytype) !void { + comptime { + const param_type = validate_listener_fn(listener); + for (msg_union.fields) |f| { + if (std.mem.eql(u8, f.name, @tagName(event))) { + // We know that param_type is a pointer so its safe to index into + if (f.type != @typeInfo(param_type).pointer.child) { + @compileError("expected " ++ @typeName(f.type) ++ ", found " ++ @typeName(param_type)); + } + + break; + } + } + } + + var node = try gpa.create(Listener); + node.callback = @ptrCast(listener); + + const name = @tagName(event); + var ev = self.listeners.get(name) orelse unreachable; + + ev.prepend(&node.node); + } + + pub fn publish(self: *Self, comptime event: msg_tag_type, data: anytype) void { + const name = @tagName(event); + const ev = self.listeners.get(name) orelse unreachable; + + var head = ev.first; + while (head) |node| { + const listener: *Listener = @fieldParentPtr("node", node); + listener.callback(@ptrCast(&data)); + head = node.next; + } + } + }; +} diff --git a/shared/root.zig b/shared/root.zig @@ -1,4 +1,7 @@ pub const bootloader_protocol = @import("./net.zig"); pub const lists = @import("./lists.zig"); +pub const pubsub = @import("./pubsub.zig"); pub const StackRingBuffer = lists.StackRingBuffer; + +pub const Publisher = pubsub.Publisher;