diff --git a/examples/core-custom-entrypoint/main.zig b/examples/core-custom-entrypoint/main.zig index e04ffa45..ff1954dd 100644 --- a/examples/core-custom-entrypoint/main.zig +++ b/examples/core-custom-entrypoint/main.zig @@ -18,8 +18,7 @@ pub fn main() !void { // If desired, it is possible to observe when the app has finished starting by dispatching // systems until the app has started: - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatchUntil(stack_space, .mach_core, .started); + try mach.mods.dispatchUntil(.mach_core, .started); // On some platforms, you can drive the mach.Core main loop yourself - but this isn't // possible on all platforms. @@ -27,9 +26,8 @@ pub fn main() !void { mach.Core.non_blocking = true; while (mach.mods.mod.mach_core.state().state != .exited) { // Execute systems until a frame has been finished. - try mach.mods.dispatchUntil(stack_space, .mach_core, .frame_finished); + try mach.mods.dispatchUntil(.mach_core, .frame_finished); } - allocator.free(stack_space); } else { // On platforms where you cannot control the mach.Core main loop, the .mach_core.start // system your app schedules will block forever and the function call below will NEVER @@ -37,6 +35,6 @@ pub fn main() !void { // // In this case we can just dispatch systems until there are no more left to execute, which // conviently works even if you aren't using mach.Core in your program. - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } } diff --git a/examples/core-triangle/main.zig b/examples/core-triangle/main.zig index 0a44f62a..5b6b980f 100644 --- a/examples/core-triangle/main.zig +++ b/examples/core-triangle/main.zig @@ -19,6 +19,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/custom-renderer/main.zig b/examples/custom-renderer/main.zig index ce0af3d8..20dfbc2e 100644 --- a/examples/custom-renderer/main.zig +++ b/examples/custom-renderer/main.zig @@ -20,6 +20,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/glyphs/main.zig b/examples/glyphs/main.zig index c3ff6c07..e011735f 100644 --- a/examples/glyphs/main.zig +++ b/examples/glyphs/main.zig @@ -21,6 +21,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/hardware-check/main.zig b/examples/hardware-check/main.zig index ab9b0015..2859503a 100644 --- a/examples/hardware-check/main.zig +++ b/examples/hardware-check/main.zig @@ -22,6 +22,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/piano/main.zig b/examples/piano/main.zig index 0db36a9e..bb67ac54 100644 --- a/examples/piano/main.zig +++ b/examples/piano/main.zig @@ -20,6 +20,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/play-opus/main.zig b/examples/play-opus/main.zig index 0db36a9e..bb67ac54 100644 --- a/examples/play-opus/main.zig +++ b/examples/play-opus/main.zig @@ -20,6 +20,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/sprite/main.zig b/examples/sprite/main.zig index b82bf867..13403d7b 100644 --- a/examples/sprite/main.zig +++ b/examples/sprite/main.zig @@ -20,6 +20,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/examples/text/main.zig b/examples/text/main.zig index 5639509c..dc21d54c 100644 --- a/examples/text/main.zig +++ b/examples/text/main.zig @@ -20,6 +20,5 @@ pub fn main() !void { // Dispatch systems forever or until there are none left to dispatch. If your app uses mach.Core // then this will block forever and never return. - const stack_space = try allocator.alloc(u8, 8 * 1024 * 1024); - try mach.mods.dispatch(stack_space, .{}); + try mach.mods.dispatch(.{}); } diff --git a/src/Core.zig b/src/Core.zig index ea39f9ac..c4e8eba6 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -308,22 +308,16 @@ pub fn start(core: *Mod) !void { } // The user wants mach.Core to take control of the main loop. - - // TODO: we already have stack space since we are an executing system, so in theory we could - // deduplicate this allocation and just use 'our current stack space' - but accessing it from - // the dispatcher is tricky. - const stack_space = try core.state().allocator.alloc(u8, 8 * 1024 * 1024); - if (supports_non_blocking) { while (core.state().state != .exited) { - dispatch(stack_space); + dispatch(); } // Don't return, because Platform.run wouldn't either (marked noreturn due to underlying // platform APIs never returning.) std.process.exit(0); } else { // Platform drives the main loop. - Platform.run(platform_update_callback, .{ &mach.mods.mod.mach_core, stack_space }); + Platform.run(platform_update_callback, .{&mach.mods.mod.mach_core}); // Platform.run should be marked noreturn, so this shouldn't ever run. But just in case we // accidentally introduce a different Platform.run in the future, we put an exit here for @@ -332,16 +326,16 @@ pub fn start(core: *Mod) !void { } } -fn dispatch(stack_space: []u8) void { - mach.mods.dispatchUntil(stack_space, .mach_core, .frame_finished) catch { +fn dispatch() void { + mach.mods.dispatchUntil(.mach_core, .frame_finished) catch { @panic("Dispatch in Core failed"); }; } -fn platform_update_callback(core: *Mod, stack_space: []u8) !bool { +fn platform_update_callback(core: *Mod) !bool { // Execute systems until .mach_core.frame_finished is dispatched, signalling a frame was // finished. - try mach.mods.dispatchUntil(stack_space, .mach_core, .frame_finished); + try mach.mods.dispatchUntil(.mach_core, .frame_finished); return core.state().state != .exited; } diff --git a/src/module/main.zig b/src/module/main.zig index 1919b69a..2243eb82 100644 --- a/src/module/main.zig +++ b/src/module/main.zig @@ -102,6 +102,5 @@ test "entities DB" { world.mod.renderer.schedule(.tick); // Dispatch systems - var stack_space: [8 * 1024 * 1024]u8 = undefined; - try world.dispatch(&stack_space, .{}); + try world.dispatch(.{}); } diff --git a/src/module/module.zig b/src/module/module.zig index 6d7c210a..e3fb3f21 100644 --- a/src/module/module.zig +++ b/src/module/module.zig @@ -26,13 +26,6 @@ fn validateModule(comptime M: type, comptime systems: bool) void { } } -/// TODO: implement serialization constraints -/// For now this exists just to indicate things that we expect will be required to be serializable in -/// the future. -fn Serializable(comptime T: type) type { - return T; -} - // TODO: add runtime module support pub const ModuleID = u32; pub const SystemID = u32; @@ -129,13 +122,10 @@ pub fn Modules(comptime modules: anytype) type { const Dispatch = struct { module_name: ModuleID, system_name: SystemID, - args_slice: []u8, - args_alignment: u32, }; const DispatchQueue = std.fifo.LinearFifo(Dispatch, .Dynamic); dispatch_queue_mu: std.Thread.RwLock = .{}, - dispatch_args_queue: std.ArrayListUnmanaged(u8) = .{}, dispatch_queue: DispatchQueue, mod: ModsByName(modules), // TODO: pass mods directly instead of ComponentTypesByName? @@ -161,13 +151,10 @@ pub fn Modules(comptime modules: anytype) type { m.* = .{ .entities = entities, - // TODO(module): better default allocations - .dispatch_args_queue = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 8 * 1024 * 1024), .dispatch_queue = DispatchQueue.init(allocator), .mod = undefined, .debug_trace = debug_trace, }; - errdefer m.dispatch_args_queue.deinit(allocator); errdefer m.dispatch_queue.deinit(); try m.dispatch_queue.ensureTotalCapacity(1024); // TODO(module): better default allocations @@ -183,7 +170,7 @@ pub fn Modules(comptime modules: anytype) type { } pub fn deinit(m: *@This(), allocator: std.mem.Allocator) void { - m.dispatch_args_queue.deinit(allocator); + _ = allocator; // autofix m.dispatch_queue.deinit(); m.entities.deinit(); } @@ -215,27 +202,6 @@ pub fn Modules(comptime modules: anytype) type { comptime module_name: ModuleName(modules), // TODO(important): cleanup comptime comptime system_name: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)), - ) void { - m.scheduleWithArgs(module_name, system_name, .{}); - } - - /// Schedule the specified system to run later, passing some additional arguments. - /// - /// Today, any arguments are allowed, but in the future these will be restricted to simple - /// data types - /// , non-pointers, and you will want to ensure they are not stateful in order for - /// your program to work with future debugging tools. - /// - /// In general, scheduleWithArgs should really only be used for cross-language, cross-process, - /// or cross-network behavior. If you otherwise need to get data from one system to another - /// you should be using entities and components. - pub fn scheduleWithArgs( - m: *@This(), - // TODO: is a variant of this function where module_name/system_name is not comptime known, but asserted to be a valid enum, useful? - comptime module_name: ModuleName(modules), - // TODO(important): cleanup comptime - comptime system_name: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)), - args: SystemArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).__state), system_name), ) void { // TODO: comptime safety/debugging const system_name_g: System = comptime moduleToGlobalSystemName( @@ -245,39 +211,24 @@ pub fn Modules(comptime modules: anytype) type { SystemEnum, system_name, ); - m.sendInternal(@intFromEnum(module_name), @intFromEnum(system_name_g), args); + m.sendInternal(@intFromEnum(module_name), @intFromEnum(system_name_g)); } /// Schedule the specified system to run later, using fully dynamic parameters (i.e. to run /// a system not known to the program at compile time.) pub fn scheduleDynamic(m: *@This(), module_name: ModuleID, system_name: SystemID) void { - m.scheduleDynamicWithArgs(module_name, system_name, .{}); - } - - /// Schedule the specified system to run later, using fully dynamic parameters (i.e. to run - /// a system not known to the program at compile time.) - pub fn scheduleDynamicWithArgs(m: *@This(), module_name: ModuleID, system_name: SystemID, args: anytype) void { // TODO: runtime safety/debugging // TODO: check args do not have obviously wrong things, like comptime values - // TODO: if module_name and system_name are valid enums, can we type-check args at runtime? - m.sendInternal(module_name, system_name, args); + m.sendInternal(module_name, system_name); } - fn sendInternal(m: *@This(), module_name: ModuleID, system_name: SystemID, args: anytype) void { - // TODO: verify arguments are valid, e.g. not comptime types - _ = Serializable(@TypeOf(args)); - + fn sendInternal(m: *@This(), module_name: ModuleID, system_name: SystemID) void { // TODO: debugging m.dispatch_queue_mu.lock(); defer m.dispatch_queue_mu.unlock(); - - const args_bytes = std.mem.asBytes(&args); - m.dispatch_args_queue.appendSliceAssumeCapacity(args_bytes); m.dispatch_queue.writeItemAssumeCapacity(.{ .module_name = module_name, .system_name = system_name, - .args_slice = m.dispatch_args_queue.items[m.dispatch_args_queue.items.len - args_bytes.len .. m.dispatch_args_queue.items.len], - .args_alignment = @alignOf(@TypeOf(args)), }); } @@ -317,12 +268,11 @@ pub fn Modules(comptime modules: anytype) type { /// Use .dispatch() with a .until argument if you need to specify a runtime-known system. pub fn dispatchUntil( m: *@This(), - stack_space: []u8, comptime module_name: ModuleName(modules), // TODO(important): cleanup comptime system: SystemEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)), ) !void { - try m.dispatch(stack_space, .{ + try m.dispatch(.{ .until = .{ .module_name = m.moduleNameToID(module_name), .system = m.systemToID(module_name, system), @@ -336,7 +286,6 @@ pub fn Modules(comptime modules: anytype) type { /// which may be invoked, e.g. 8MB. It may be heap-allocated. pub fn dispatch( m: *@This(), - stack_space: []u8, options: DispatchOptions, ) !void { const Injectable = comptime blk: { @@ -357,12 +306,11 @@ pub fn Modules(comptime modules: anytype) type { } @compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type)); } - return m.dispatchInternal(stack_space, options, injectable); + return m.dispatchInternal(options, injectable); } pub fn dispatchInternal( m: *@This(), - stack_space: []u8, options: DispatchOptions, injectable: anytype, ) !void { @@ -373,27 +321,14 @@ pub fn Modules(comptime modules: anytype) type { while (true) { // Dequeue the next system - m.dispatch_queue_mu.lock(); - var d = m.dispatch_queue.readItem() orelse { - m.dispatch_queue_mu.unlock(); - return; + const d = blk: { + m.dispatch_queue_mu.lock(); + defer m.dispatch_queue_mu.unlock(); + break :blk m.dispatch_queue.readItem() orelse return; }; - // Pop the arguments off the d.args_slice stack, so we can release args_slice space. - // Otherwise when we release m.dispatch_queue_mu someone may add more system' arguments - // to the buffer which would make it tricky to find a good point-in-time to release - // argument buffer space. - const aligned_addr = std.mem.alignForward(usize, @intFromPtr(stack_space.ptr), d.args_alignment); - const align_offset = aligned_addr - @intFromPtr(stack_space.ptr); - - @memcpy(stack_space[align_offset .. align_offset + d.args_slice.len], d.args_slice); - d.args_slice = stack_space[align_offset .. align_offset + d.args_slice.len]; - - m.dispatch_args_queue.shrinkRetainingCapacity(m.dispatch_args_queue.items.len - d.args_slice.len); - m.dispatch_queue_mu.unlock(); - // Dispatch the system - try m.callSystem(@enumFromInt(d.module_name), @enumFromInt(d.system_name), d.args_slice, injectable); + try m.callSystem(@enumFromInt(d.module_name), @enumFromInt(d.system_name), &.{}, injectable); // If we only wanted to dispatch until this system, then return. if (options.until) |until| { @@ -437,6 +372,7 @@ pub fn Modules(comptime modules: anytype) type { inline fn callHandler(handler: anytype, args_data: []u8, injectable: anytype, comptime debug_name: anytype) !void { const Handler = @TypeOf(handler); const StdArgs = UninjectedArgsTuple(Handler); + if (@typeInfo(StdArgs).Struct.fields.len > 0) @compileError("mach: system may not take any arguments except injected ones: " ++ debug_name); const std_args: *StdArgs = @alignCast(@ptrCast(args_data.ptr)); const args = injectArgs(Handler, @TypeOf(injectable), injectable, std_args.*, debug_name); const Ret = @typeInfo(Handler).@"fn".return_type orelse void; @@ -636,15 +572,11 @@ pub fn ModSet(comptime modules: anytype) type { } pub inline fn schedule(m: *@This(), comptime system_name: SystemEnumM(M)) void { - m.scheduleWithArgs(system_name, .{}); - } - - pub inline fn scheduleWithArgs(m: *@This(), comptime system_name: SystemEnumM(M), args: SystemArgsM(M, system_name)) void { const ModulesT = Modules(modules); const MByName = ModsByName(modules); const mod_ptr: *MByName = @alignCast(@fieldParentPtr(@tagName(module_tag), m)); const mods: *ModulesT = @fieldParentPtr("mod", mod_ptr); - mods.scheduleWithArgs(module_tag, system_name, args); + mods.schedule(module_tag, system_name); } pub inline fn system(_: *@This(), comptime system_name: SystemEnumM(M)) AnySystem { @@ -1440,7 +1372,6 @@ test "dispatch" { var physics_updates: usize = 0; var physics_calc: usize = 0; var renderer_updates: usize = 0; - var basic_args_sum: usize = 0; }; var foo = struct { injected_args_sum: usize = 0, @@ -1455,7 +1386,6 @@ test "dispatch" { pub const systems = .{ .tick = .{ .handler = tick }, .update = .{ .handler = update }, - .update_with_struct_arg = .{ .handler = updateWithStructArg }, .calc = .{ .handler = calc }, }; @@ -1467,15 +1397,6 @@ test "dispatch" { 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 { global.physics_calc += 1; } @@ -1486,7 +1407,6 @@ test "dispatch" { .tick = .{ .handler = tick }, .frame_done = .{ .handler = fn (i32) void }, .update = .{ .handler = update }, - .basic_args = .{ .handler = basicArgs }, .injected_args = .{ .handler = injectedArgs }, }; @@ -1500,12 +1420,8 @@ test "dispatch" { global.renderer_updates += 1; } - fn basicArgs(a: u32, b: u32) void { - global.basic_args_sum = a + b; - } - - fn injectedArgs(foo_ptr: *@TypeOf(foo), a: u32, b: u32) void { - foo_ptr.*.injected_args_sum = a + b; + fn injectedArgs(foo_ptr: *@TypeOf(foo)) void { + foo_ptr.*.injected_args_sum = 1337; } }); @@ -1523,24 +1439,20 @@ test "dispatch" { const M = ModuleName(modules2); // Systems - var stack_space: [8 * 1024 * 1024]u8 = undefined; modules.schedule(.engine_renderer, .update); - try modules.dispatchInternal(&stack_space, .{}, .{&foo}); + try modules.dispatchInternal(.{}, .{&foo}); try testing.expect(usize, 1).eql(global.renderer_updates); modules.schedule(.engine_physics, .update); - modules.scheduleWithArgs(.engine_physics, .update_with_struct_arg, .{.{}}); modules.scheduleDynamic( @intFromEnum(@as(M, .engine_physics)), @intFromEnum(@as(LE, .calc)), ); - try modules.dispatchInternal(&stack_space, .{}, .{&foo}); - try testing.expect(usize, 2).eql(global.physics_updates); + try modules.dispatchInternal(.{}, .{&foo}); + try testing.expect(usize, 1).eql(global.physics_updates); try testing.expect(usize, 1).eql(global.physics_calc); // Systems - modules.scheduleWithArgs(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference - modules.scheduleWithArgs(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) }); - try modules.dispatchInternal(&stack_space, .{}, .{&foo}); - try testing.expect(usize, 3).eql(global.basic_args_sum); - try testing.expect(usize, 3).eql(foo.injected_args_sum); + modules.schedule(.engine_renderer, .injected_args); + try modules.dispatchInternal(.{}, .{&foo}); + try testing.expect(usize, 1337).eql(foo.injected_args_sum); }