module: initial runtime event dispatch implementation
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
5b6020508c
commit
6f23812fa6
1 changed files with 137 additions and 4 deletions
141
src/module.zig
141
src/module.zig
|
|
@ -30,14 +30,78 @@ pub fn Modules(comptime mods: anytype) type {
|
|||
|
||||
// TODO: add runtime module support
|
||||
|
||||
pub const ModuleID = u32;
|
||||
pub const EventID = u32;
|
||||
|
||||
const Event = struct {
|
||||
module_name: ?ModuleID,
|
||||
event_name: EventID,
|
||||
args_slice: []u8,
|
||||
};
|
||||
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
||||
|
||||
events_mu: std.Thread.RwLock = .{},
|
||||
args_queue: std.ArrayListUnmanaged(u8) = .{},
|
||||
events: EventQueue,
|
||||
|
||||
pub fn init(m: *@This(), allocator: std.mem.Allocator) !void {
|
||||
m.* = .{};
|
||||
_ = allocator;
|
||||
// TODO: custom event queue allocation sizes
|
||||
m.* = .{
|
||||
.args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024),
|
||||
.events = EventQueue.init(allocator),
|
||||
};
|
||||
errdefer m.args_queue.deinit(allocator);
|
||||
errdefer m.events.deinit();
|
||||
try m.events.ensureTotalCapacity(1024);
|
||||
}
|
||||
|
||||
pub fn deinit(m: *@This(), allocator: std.mem.Allocator) void {
|
||||
_ = m;
|
||||
_ = allocator;
|
||||
m.args_queue.deinit(allocator);
|
||||
m.events.deinit();
|
||||
}
|
||||
|
||||
// TODO: API variation for global/local events, rather than `null` parameter
|
||||
|
||||
// Send a module-specific or global event, using comptime-known module and event names.
|
||||
pub fn send(m: *@This(), module_name: ?ModuleName(mods), event_name: EventName(mods), args: anytype) void {
|
||||
// TODO: debugging
|
||||
m.sendDynamic(if (module_name) |v| @intFromEnum(v) else null, @intFromEnum(event_name), args);
|
||||
}
|
||||
|
||||
// Send a module-specific or global event, using runtime-known module and event names.
|
||||
pub fn sendDynamic(m: *@This(), module_name: ?ModuleID, event_name: EventID, args: anytype) void {
|
||||
// TODO: debugging
|
||||
m.events_mu.lock();
|
||||
defer m.events_mu.unlock();
|
||||
|
||||
const args_bytes = std.mem.asBytes(&args);
|
||||
m.args_queue.appendSliceAssumeCapacity(args_bytes);
|
||||
|
||||
m.events.writeItemAssumeCapacity(.{
|
||||
.module_name = module_name,
|
||||
.event_name = event_name,
|
||||
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. args_bytes.len],
|
||||
});
|
||||
}
|
||||
|
||||
pub fn dispatch(m: *@This()) void {
|
||||
// TODO: optimize to reduce send contention
|
||||
// TODO: parallel / multi-threaded dispatch
|
||||
// TODO: PGO
|
||||
m.events_mu.lock();
|
||||
defer m.events_mu.unlock();
|
||||
|
||||
while (m.events.readItem()) |ev| {
|
||||
_ = ev.args_slice; // TODO: dispatch arguments
|
||||
if (ev.module_name) |module_name| {
|
||||
// TODO: dispatch arguments
|
||||
@This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), .{});
|
||||
} else {
|
||||
// TODO: dispatch arguments
|
||||
@This().call(@enumFromInt(ev.event_name), .{});
|
||||
}
|
||||
}
|
||||
m.args_queue.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
// Call global event handler with the specified name in all modules
|
||||
|
|
@ -154,6 +218,7 @@ fn ModuleName(comptime mods: anytype) type {
|
|||
});
|
||||
}
|
||||
|
||||
/// Struct like .{.foo = FooMod, .bar = BarMod}
|
||||
fn NamespacedModules(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
inline for (modules) |M| {
|
||||
|
|
@ -174,6 +239,7 @@ fn NamespacedModules(comptime modules: anytype) type {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: reconsider components concept
|
||||
fn NamespacedComponents(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
|
|
@ -484,3 +550,70 @@ test "event name calling" {
|
|||
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||
try testing.expect(usize, 2).eql(global.renderer_updates);
|
||||
}
|
||||
|
||||
test "dispatch" {
|
||||
const global = struct {
|
||||
var ticks: usize = 0;
|
||||
var physics_updates: usize = 0;
|
||||
var physics_calc: usize = 0;
|
||||
var renderer_updates: usize = 0;
|
||||
};
|
||||
const Physics = Module(struct {
|
||||
pub const name = .engine_physics;
|
||||
pub const components = struct {};
|
||||
|
||||
pub fn tick() void {
|
||||
global.ticks += 1;
|
||||
}
|
||||
|
||||
pub const local = struct {
|
||||
pub fn update() void {
|
||||
global.physics_updates += 1;
|
||||
}
|
||||
|
||||
pub fn calc() void {
|
||||
global.physics_calc += 1;
|
||||
}
|
||||
};
|
||||
});
|
||||
const Renderer = Module(struct {
|
||||
pub const name = .engine_renderer;
|
||||
pub const components = struct {};
|
||||
|
||||
pub fn tick() void {
|
||||
global.ticks += 1;
|
||||
}
|
||||
|
||||
pub const local = struct {
|
||||
pub fn update() void {
|
||||
global.renderer_updates += 1;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var modules: Modules(.{
|
||||
Physics,
|
||||
Renderer,
|
||||
}) = undefined;
|
||||
try modules.init(testing.allocator);
|
||||
defer modules.deinit(testing.allocator);
|
||||
|
||||
// Global events
|
||||
modules.send(null, .tick, .{});
|
||||
try testing.expect(usize, 0).eql(global.ticks);
|
||||
modules.dispatch();
|
||||
try testing.expect(usize, 2).eql(global.ticks);
|
||||
modules.send(null, .tick, .{});
|
||||
modules.dispatch();
|
||||
try testing.expect(usize, 4).eql(global.ticks);
|
||||
|
||||
// Local events
|
||||
modules.send(.engine_renderer, .update, .{});
|
||||
modules.dispatch();
|
||||
try testing.expect(usize, 1).eql(global.renderer_updates);
|
||||
modules.send(.engine_physics, .update, .{});
|
||||
modules.send(.engine_physics, .calc, .{});
|
||||
modules.dispatch();
|
||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue