Handler map
Description
An event loop picks a handler by type id.
When you need it
The loop gets items of many types, as Anys.
It should not have to know every type.
It keeps one handler per type, in a map keyed by TypeId.
What it does
- Register one handler per type, under its
typeId. - Receive a
Message, aJoband aPingasAnys. - Find the handler by
type_id. - Call it with
ptr. - Count the
Pingas unhandled. It has no handler. - Get a
Messageback from anAnywithfromAny.
What to notice
The dispatch needs only the type id.
The handler casts ptr to its own type. The map matched the type id, so the type is right.
fromAny checks the type id itself. Another type gives null.
Source
const Message: type = struct {
text: []const u8,
tnode: paternitas.SinglyTypedNode = .{},
};
const TypedMessage: type = paternitas.Typed(Message);
const Job: type = struct {
tnode: paternitas.DoublyTypedNode = .{},
id: u32,
};
const TypedJob: type = paternitas.Typed(Job);
const Ping: type = struct {
tnode: paternitas.SinglyTypedNode = .{},
};
const TypedPing: type = paternitas.Typed(Ping);
const Counts: type = struct {
messages: usize = 0,
jobs: usize = 0,
unhandled: usize = 0,
};
const Handler: type = *const fn (parent: *anyopaque, counts: *Counts) void;
pub fn handler_map(allocator: std.mem.Allocator, io: std.Io) !void {
_ = io;
var handlers: std.AutoHashMap(paternitas.TypeId, Handler) = .init(allocator);
defer handlers.deinit();
// Register the handlers. The dispatch code does not use your struct types.
try handlers.put(TypedMessage.typeId(), onMessage);
try handlers.put(TypedJob.typeId(), onJob);
var message: Message = .{ .text = "hello" };
TypedMessage.setTypeId(&message);
var job: Job = .{ .id = 42 };
TypedJob.setTypeId(&job);
var ping: Ping = .{};
TypedPing.setTypeId(&ping);
const received: [3]paternitas.Any = .{
TypedMessage.toAny(&message),
TypedJob.toAny(&job),
TypedPing.toAny(&ping),
};
var counts: Counts = .{};
for (received) |any| dispatch(&handlers, any, &counts);
if (counts.messages != 1 or counts.jobs != 1 or counts.unhandled != 1) return error.WrongCount;
try checkFromAny(&message);
}
/// Picks the handler by type id. No struct type appears here.
fn dispatch(handlers: *const std.AutoHashMap(paternitas.TypeId, Handler), any: paternitas.Any, counts: *Counts) void {
const h: Handler = handlers.get(any.type_id) orelse {
std.log.info("no handler for this type", .{});
counts.*.unhandled += 1;
return;
};
h(any.ptr, counts);
}
/// `fromAny` checks the type id first. Asking for another type gives null.
fn checkFromAny(message: *Message) !void {
const any: paternitas.Any = TypedMessage.toAny(message);
if (TypedMessage.fromAny(any) != message) return error.WrongParent;
if (TypedJob.fromAny(any) != null) return error.WrongParent;
}
// The cast has no check. The map matched the type id, so the type is
// right.
fn onMessage(parent: *anyopaque, counts: *Counts) void {
const m: *Message = @ptrCast(@alignCast(parent));
std.log.info("message: {s}", .{m.*.text});
counts.*.messages += 1;
}
fn onJob(parent: *anyopaque, counts: *Counts) void {
const j: *Job = @ptrCast(@alignCast(parent));
std.log.info("job: {d}", .{j.*.id});
counts.*.jobs += 1;
}
const paternitas = @import("paternitas");
const std = @import("std");