module: correct alignment of dispatched arguments; move stack space to caller
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
09c01a79b0
commit
2bc17a33fb
3 changed files with 41 additions and 17 deletions
|
|
@ -10,13 +10,15 @@ const platform = @import("platform.zig");
|
||||||
const mach = @import("../main.zig");
|
const mach = @import("../main.zig");
|
||||||
pub var mods: mach.Modules = undefined;
|
pub var mods: mach.Modules = undefined;
|
||||||
|
|
||||||
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
|
|
||||||
pub fn initModule() !void {
|
pub fn initModule() !void {
|
||||||
// Initialize the global set of Mach modules used in the program.
|
// Initialize the global set of Mach modules used in the program.
|
||||||
try mods.init(std.heap.c_allocator);
|
try mods.init(std.heap.c_allocator);
|
||||||
mods.mod.mach_core.send(.init, .{});
|
mods.mod.mach_core.send(.init, .{});
|
||||||
|
|
||||||
// Dispatch events until this .mach_core.init_done is sent
|
// Dispatch events until this .mach_core.init_done is sent
|
||||||
try mods.dispatch(.{ .until = .{
|
try mods.dispatch(&stack_space, .{ .until = .{
|
||||||
.module_name = mods.moduleNameToID(.mach_core),
|
.module_name = mods.moduleNameToID(.mach_core),
|
||||||
.local_event = mods.localEventToID(.mach_core, .init_done),
|
.local_event = mods.localEventToID(.mach_core, .init_done),
|
||||||
} });
|
} });
|
||||||
|
|
@ -29,7 +31,7 @@ pub fn tick() !bool {
|
||||||
mods.mod.mach_core.send(.main_thread_tick, .{});
|
mods.mod.mach_core.send(.main_thread_tick, .{});
|
||||||
|
|
||||||
// Dispatch events until this .mach_core.main_thread_tick_done is sent
|
// Dispatch events until this .mach_core.main_thread_tick_done is sent
|
||||||
try mods.dispatch(.{ .until = .{
|
try mods.dispatch(&stack_space, .{ .until = .{
|
||||||
.module_name = mods.moduleNameToID(.mach_core),
|
.module_name = mods.moduleNameToID(.mach_core),
|
||||||
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
|
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
|
||||||
} });
|
} });
|
||||||
|
|
|
||||||
|
|
@ -102,5 +102,6 @@ test "entities DB" {
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Send events to modules
|
// Send events to modules
|
||||||
world.mod.renderer.sendGlobal(.tick, .{});
|
world.mod.renderer.sendGlobal(.tick, .{});
|
||||||
try world.dispatch(.{});
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
|
try world.dispatch(&stack_space, .{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
module_name: ?ModuleID,
|
module_name: ?ModuleID,
|
||||||
event_name: EventID,
|
event_name: EventID,
|
||||||
args_slice: []u8,
|
args_slice: []u8,
|
||||||
|
args_alignment: u32,
|
||||||
};
|
};
|
||||||
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
||||||
|
|
||||||
|
|
@ -142,9 +143,9 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
break :blk std.ascii.eqlIgnoreCase(s, "true");
|
break :blk std.ascii.eqlIgnoreCase(s, "true");
|
||||||
} else false;
|
} else false;
|
||||||
|
|
||||||
// TODO: custom event queue allocation sizes
|
|
||||||
m.* = .{
|
m.* = .{
|
||||||
.entities = entities,
|
.entities = entities,
|
||||||
|
// TODO(module): better default allocations
|
||||||
.args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024),
|
.args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024),
|
||||||
.events = EventQueue.init(allocator),
|
.events = EventQueue.init(allocator),
|
||||||
.mod = undefined,
|
.mod = undefined,
|
||||||
|
|
@ -152,7 +153,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
};
|
};
|
||||||
errdefer m.args_queue.deinit(allocator);
|
errdefer m.args_queue.deinit(allocator);
|
||||||
errdefer m.events.deinit();
|
errdefer m.events.deinit();
|
||||||
try m.events.ensureTotalCapacity(1024);
|
try m.events.ensureTotalCapacity(1024); // TODO(module): better default allocations
|
||||||
|
|
||||||
// Default initialize m.mod
|
// Default initialize m.mod
|
||||||
inline for (@typeInfo(@TypeOf(m.mod)).Struct.fields) |field| {
|
inline for (@typeInfo(@TypeOf(m.mod)).Struct.fields) |field| {
|
||||||
|
|
@ -274,6 +275,7 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
.module_name = module_name,
|
.module_name = module_name,
|
||||||
.event_name = event_name,
|
.event_name = event_name,
|
||||||
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. m.args_queue.items.len],
|
.args_slice = m.args_queue.items[m.args_queue.items.len - args_bytes.len .. m.args_queue.items.len],
|
||||||
|
.args_alignment = @alignOf(@TypeOf(args)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,8 +307,12 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Dispatches pending events, invoking their event handlers.
|
/// Dispatches pending events, invoking their event handlers.
|
||||||
|
///
|
||||||
|
/// Stack space must be large enough to fit the uninjected arguments of any event handler
|
||||||
|
/// which may be invoked, e.g. 8MB. It may be heap-allocated.
|
||||||
pub fn dispatch(
|
pub fn dispatch(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
|
stack_space: []u8,
|
||||||
options: DispatchOptions,
|
options: DispatchOptions,
|
||||||
) !void {
|
) !void {
|
||||||
const Injectable = comptime blk: {
|
const Injectable = comptime blk: {
|
||||||
|
|
@ -327,11 +333,12 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
@compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type));
|
@compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type));
|
||||||
}
|
}
|
||||||
return m.dispatchInternal(options, injectable);
|
return m.dispatchInternal(stack_space, options, injectable);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatchInternal(
|
pub fn dispatchInternal(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
|
stack_space: []u8,
|
||||||
options: DispatchOptions,
|
options: DispatchOptions,
|
||||||
injectable: anytype,
|
injectable: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -339,8 +346,6 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
// TODO: parallel / multi-threaded dispatch
|
// TODO: parallel / multi-threaded dispatch
|
||||||
// TODO: PGO
|
// TODO: PGO
|
||||||
|
|
||||||
// TODO(important): we may exceed stack space here, consider moving to heap.
|
|
||||||
var buf: [8 * 1024 * 1024]u8 = undefined;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Dequeue the next event
|
// Dequeue the next event
|
||||||
m.events_mu.lock();
|
m.events_mu.lock();
|
||||||
|
|
@ -349,12 +354,16 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pop the arguments off the stack, so we can release args_slice space.
|
// Pop the arguments off the ev.args_slice stack, so we can release args_slice space.
|
||||||
// Otherwise when we release m.events_mu someone may add more events' arguments
|
// Otherwise when we release m.events_mu someone may add more events' arguments
|
||||||
// to the buffer which would make it tricky to find a good point-in-time to release
|
// to the buffer which would make it tricky to find a good point-in-time to release
|
||||||
// argument buffer space.
|
// argument buffer space.
|
||||||
@memcpy(buf[0..ev.args_slice.len], ev.args_slice);
|
const aligned_addr = std.mem.alignForward(usize, @intFromPtr(stack_space.ptr), ev.args_alignment);
|
||||||
ev.args_slice = buf[0..ev.args_slice.len];
|
const align_offset = aligned_addr - @intFromPtr(stack_space.ptr);
|
||||||
|
|
||||||
|
@memcpy(stack_space[align_offset .. align_offset + ev.args_slice.len], ev.args_slice);
|
||||||
|
ev.args_slice = stack_space[align_offset .. align_offset + ev.args_slice.len];
|
||||||
|
|
||||||
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
||||||
m.events_mu.unlock();
|
m.events_mu.unlock();
|
||||||
|
|
||||||
|
|
@ -1428,6 +1437,7 @@ test "dispatch" {
|
||||||
};
|
};
|
||||||
pub const local_events = .{
|
pub const local_events = .{
|
||||||
.update = .{ .handler = update },
|
.update = .{ .handler = update },
|
||||||
|
.update_with_struct_arg = .{ .handler = updateWithStructArg },
|
||||||
.calc = .{ .handler = calc },
|
.calc = .{ .handler = calc },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1439,6 +1449,15 @@ test "dispatch" {
|
||||||
global.physics_updates += 1;
|
global.physics_updates += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MyStruct = extern struct {
|
||||||
|
x: [4]extern struct { x: @Vector(4, f32) } = undefined,
|
||||||
|
y: [4]extern struct { x: @Vector(4, f32) } = undefined,
|
||||||
|
};
|
||||||
|
fn updateWithStructArg(arg: MyStruct) void {
|
||||||
|
_ = arg;
|
||||||
|
global.physics_updates += 1;
|
||||||
|
}
|
||||||
|
|
||||||
fn calc() void {
|
fn calc() void {
|
||||||
global.physics_calc += 1;
|
global.physics_calc += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -1495,11 +1514,12 @@ test "dispatch" {
|
||||||
// injected arguments.
|
// injected arguments.
|
||||||
modules.sendGlobal(.engine_renderer, .tick, .{});
|
modules.sendGlobal(.engine_renderer, .tick, .{});
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
try testing.expect(usize, 0).eql(global.ticks);
|
||||||
try modules.dispatchInternal(.{}, .{&foo});
|
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||||
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
try testing.expect(usize, 2).eql(global.ticks);
|
||||||
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
||||||
modules.sendGlobalDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
modules.sendGlobalDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
||||||
try modules.dispatchInternal(.{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
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;`
|
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
||||||
|
|
@ -1509,22 +1529,23 @@ test "dispatch" {
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.send(.engine_renderer, .update, .{});
|
modules.send(.engine_renderer, .update, .{});
|
||||||
try modules.dispatchInternal(.{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 1).eql(global.renderer_updates);
|
try testing.expect(usize, 1).eql(global.renderer_updates);
|
||||||
modules.send(.engine_physics, .update, .{});
|
modules.send(.engine_physics, .update, .{});
|
||||||
|
modules.send(.engine_physics, .update_with_struct_arg, .{.{}});
|
||||||
modules.sendDynamic(
|
modules.sendDynamic(
|
||||||
@intFromEnum(@as(M, .engine_physics)),
|
@intFromEnum(@as(M, .engine_physics)),
|
||||||
@intFromEnum(@as(LE, .calc)),
|
@intFromEnum(@as(LE, .calc)),
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
try modules.dispatchInternal(.{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 2).eql(global.physics_updates);
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.send(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
modules.send(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
||||||
modules.send(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
modules.send(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
||||||
try modules.dispatchInternal(.{}, .{&foo});
|
try modules.dispatchInternal(&stack_space, .{}, .{&foo});
|
||||||
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
||||||
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue