From 0fc3bf6545cad244900455588097f90b5d16af70 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Wed, 20 Mar 2024 10:34:40 -0700 Subject: [PATCH] module: improve parameter injection, fix tests Signed-off-by: Stephen Gutekanst --- src/module.zig | 130 +++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/src/module.zig b/src/module.zig index 6c8d2b62..235d6b18 100644 --- a/src/module.zig +++ b/src/module.zig @@ -291,14 +291,8 @@ inline fn injectArgs( var args: std.meta.ArgsTuple(Function) = undefined; comptime var std_args_index = 0; outer: inline for (@typeInfo(std.meta.ArgsTuple(Function)).Struct.fields) |arg| { - // Injected arguments always go first, then standard (non-injected) arguments. - if (std_args_index > 0) { - @field(args, arg.name) = std_args[std_args_index]; - std_args_index += 1; - continue; - } - - // Is this argument matching the type of an argument we could inject? + // Is this a Struct or *Struct, with a `pub const IsInjectedArgument = void;` decl? If so, + // it is considered an injected argument. inline for (@typeInfo(Injectable).Struct.fields) |inject_field| { if (inject_field.type == arg.type and @alignOf(inject_field.type) == @alignOf(arg.type)) { // Inject argument @@ -322,11 +316,8 @@ fn UninjectedArgsTuple( ) type { var std_args: []const type = &[0]type{}; inline for (@typeInfo(std.meta.ArgsTuple(Function)).Struct.fields) |arg| { - // Injected arguments always go first, then standard (non-injected) arguments. - if (std_args.len > 0) { - std_args = std_args ++ [_]type{arg.type}; - continue; - } + // Is this a Struct or *Struct, with a `pub const IsInjectedArgument = void;` decl? If so, + // it is considered an injected argument. const is_injected = blk: { switch (@typeInfo(arg.type)) { .Struct => break :blk @hasDecl(arg.type, "IsInjectedArgument"), @@ -529,12 +520,11 @@ test Modules { pub const name = .engine_sprite2d; }); - const Injectable = struct {}; var modules: Modules(.{ Physics, Renderer, Sprite2D, - }, Injectable) = undefined; + }) = undefined; try modules.init(testing.allocator); defer modules.deinit(testing.allocator); testing.refAllDeclsRecursive(Physics); @@ -577,12 +567,11 @@ test EventName { }; }); - const Injectable = struct {}; const Mods = Modules(.{ Physics, Renderer, Sprite2D, - }, Injectable); + }); const info = @typeInfo(EventName(Mods.modules)).Enum; try testing.expect(type, u3).eql(info.tag_type); @@ -616,12 +605,11 @@ test ModuleName { const Sprite2D = Module(struct { pub const name = .engine_sprite2d; }); - const Injectable = struct {}; const Mods = Modules(.{ Physics, Renderer, Sprite2D, - }, Injectable); + }); const info = @typeInfo(ModuleName(Mods.modules)).Enum; try testing.expect(type, u2).eql(info.tag_type); @@ -631,6 +619,7 @@ test ModuleName { try testing.expect([]const u8, "engine_sprite2d").eql(info.fields[2].name); } +// TODO: remove this in favor of testing.expect const TupleTester = struct { fn assertTypeEqual(comptime Expected: type, comptime Actual: type) void { if (Expected != Actual) @compileError("Expected type " ++ @typeName(Expected) ++ ", but got type " ++ @typeName(Actual)); @@ -655,17 +644,28 @@ const TupleTester = struct { test injectArgs { // Injected arguments should generally be *struct types to avoid conflicts with any user-passed // parameters, though we do not require it - so we test with other types here. - var i: i32 = 1234; - const i32_ptr: *i32 = &i; - var f: f32 = 0.1234; - const f32_ptr: *f32 = &f; - const Foo = struct { foo: f32 }; - var foo: Foo = .{ .foo = 1234 }; - const foo_ptr: *Foo = &foo; + const Foo = struct { + foo: f32, + pub const IsInjectedArgument = void; + }; + const Bar = struct { + bar: i32, + pub const IsInjectedArgument = void; + }; + const Baz = struct { + baz: bool, + pub const IsInjectedArgument = void; + }; + var foo = Foo{ .foo = 0.1234 }; + var bar = Bar{ .bar = 1234 }; + var baz = Baz{ .baz = true }; + const foo_ptr = &foo; + const bar_ptr = &bar; + const baz_ptr = &baz; // No standard, no injected try testing.expect(struct {}, .{}).eql(injectArgs(fn () void, @TypeOf(.{}), .{}, .{})); - const injectable = .{ i32_ptr, f32_ptr, foo_ptr }; + const injectable = .{ foo_ptr, bar_ptr, baz_ptr }; try testing.expect(struct {}, .{}).eql(injectArgs(fn () void, @TypeOf(injectable), injectable, .{})); // Standard parameters only, no injected @@ -673,26 +673,23 @@ test injectArgs { try testing.expect(std.meta.Tuple(&.{ i32, f32 }), .{ 1, 0.5 }).eql(injectArgs(fn (a: i32, b: f32) void, @TypeOf(injectable), injectable, .{ 1, 0.5 })); // Injected parameters only, no standard - try testing.expect(std.meta.Tuple(&.{*i32}), .{i32_ptr}).eql(injectArgs(fn (a: *i32) void, @TypeOf(injectable), injectable, .{})); - try testing.expect(std.meta.Tuple(&.{*f32}), .{f32_ptr}).eql(injectArgs(fn (a: *f32) void, @TypeOf(injectable), injectable, .{})); try testing.expect(std.meta.Tuple(&.{*Foo}), .{foo_ptr}).eql(injectArgs(fn (a: *Foo) void, @TypeOf(injectable), injectable, .{})); - try testing.expect(std.meta.Tuple(&.{ *i32, *f32, *Foo }), .{ i32_ptr, f32_ptr, foo_ptr }).eql(injectArgs(fn (a: *i32, b: *f32, c: *Foo) void, @TypeOf(injectable), injectable, .{})); + try testing.expect(std.meta.Tuple(&.{ *Foo, *Bar }), .{ foo_ptr, bar_ptr }).eql(injectArgs(fn (a: *Foo, b: *Bar) void, @TypeOf(injectable), injectable, .{})); + try testing.expect(std.meta.Tuple(&.{ *Foo, *Bar, *Baz }), .{ foo_ptr, bar_ptr, baz_ptr }).eql(injectArgs(fn (a: *Foo, b: *Bar, c: *Baz) void, @TypeOf(injectable), injectable, .{})); + try testing.expect(std.meta.Tuple(&.{ *Bar, *Baz, *Foo }), .{ bar_ptr, baz_ptr, foo_ptr }).eql(injectArgs(fn (a: *Bar, b: *Baz, c: *Foo) void, @TypeOf(injectable), injectable, .{})); + try testing.expect(std.meta.Tuple(&.{ *Foo, *Foo, *Baz }), .{ foo_ptr, foo_ptr, baz_ptr }).eql(injectArgs(fn (a: *Foo, b: *Foo, c: *Baz) void, @TypeOf(injectable), injectable, .{})); - // Once a standard parameter is encountered, all parameters after that are considered standard - // and not injected. - var my_f32: f32 = 0.1337; - var my_i32: i32 = 1337; - try testing.expect(std.meta.Tuple(&.{f32}), .{1234}).eql(injectArgs(fn (a: f32) void, @TypeOf(injectable), injectable, .{1234})); - try testing.expect(std.meta.Tuple(&.{ i32, *f32 }), .{ 1234, &my_f32 }).eql(injectArgs(fn (a: i32, b: *f32) void, @TypeOf(injectable), injectable, .{ 1234, &my_f32 })); - try testing.expect(std.meta.Tuple(&.{ i32, *i32, *f32 }), .{ 1234, &my_i32, &my_f32 }).eql(injectArgs(fn (a: i32, b: *i32, c: *f32) void, @TypeOf(injectable), injectable, .{ 1234, &my_i32, &my_f32 })); + // As long as the argument is a Struct or *Struct with an IsInjectedArgument decl, it is + // considered an injected argument. + // try testing.expect(std.meta.Tuple(&.{*const Foo}), .{foo_ptr}).eql(injectArgs(fn (a: *const Foo) void, @TypeOf(injectable), injectable, .{})); + const injectable2 = .{ foo, foo_ptr, bar_ptr, baz_ptr }; + try testing.expect(std.meta.Tuple(&.{Foo}), .{foo_ptr.*}).eql(injectArgs(fn (a: Foo) void, @TypeOf(injectable2), injectable2, .{})); - // First parameter (*f32) matches an injectable parameter type, so it is injected. - try testing.expect(std.meta.Tuple(&.{ *f32, i32, *i32, *f32 }), .{ f32_ptr, 1234, &my_i32, &my_f32 }).eql(injectArgs(fn (a: *f32, b: i32, c: *i32, d: *f32) void, @TypeOf(injectable), injectable, .{ 1234, &my_i32, &my_f32 })); - - // First parameter (*f32) matches an injectable parameter type, so it is injected. 2nd - // parameter is not injectable, so all remaining parameters are not injected. - var my_foo = foo; - try testing.expect(std.meta.Tuple(&.{ *f32, i32, *Foo, *i32, *f32 }), .{ f32_ptr, 1234, &my_foo, &my_i32, &my_f32 }).eql(injectArgs(fn (a: *f32, b: i32, c: *Foo, d: *i32, e: *f32) void, @TypeOf(injectable), injectable, .{ 1234, &my_foo, &my_i32, &my_f32 })); + // Order doesn't matter, injected arguments can be placed inbetween any standard arguments, etc. + try testing.expect(std.meta.Tuple(&.{ i32, *Foo, *Foo, *Baz }), .{ 1337, foo_ptr, foo_ptr, baz_ptr }).eql(injectArgs(fn (z: i32, a: *Foo, b: *Foo, c: *Baz) void, @TypeOf(injectable), injectable, .{1337})); + try testing.expect(std.meta.Tuple(&.{ i32, *Foo, f32, *Foo, *Baz }), .{ 1337, foo_ptr, 1.337, foo_ptr, baz_ptr }).eql(injectArgs(fn (z: i32, a: *Foo, w: f32, b: *Foo, c: *Baz) void, @TypeOf(injectable), injectable, .{ 1337, 1.337 })); + try testing.expect(std.meta.Tuple(&.{ i32, f32, *Foo, *Foo, *Baz }), .{ 1337, 1.337, foo_ptr, foo_ptr, baz_ptr }).eql(injectArgs(fn (z: i32, w: f32, a: *Foo, b: *Foo, c: *Baz) void, @TypeOf(injectable), injectable, .{ 1337, 1.337 })); + try testing.expect(std.meta.Tuple(&.{ *Foo, *Foo, *Baz, i32, f32 }), .{ foo_ptr, foo_ptr, baz_ptr, 1337, 1.337 }).eql(injectArgs(fn (az: *Foo, b: *Foo, c: *Baz, z: i32, w: f32) void, @TypeOf(injectable), injectable, .{ 1337, 1.337 })); } test UninjectedArgsTuple { @@ -700,6 +697,10 @@ test UninjectedArgsTuple { foo: f32, pub const IsInjectedArgument = void; }; + const Bar = struct { + bar: bool, + pub const IsInjectedArgument = void; + }; // No standard, no injected TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn () void)); @@ -710,23 +711,24 @@ test UninjectedArgsTuple { TupleTester.assertTuple(.{ i32, f32 }, UninjectedArgsTuple(std.meta.Tuple, fn (a: i32, b: f32) void)); // Injected parameters only, no standard - TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *i32) void)); - TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *f32) void)); TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo) void)); - TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *f32, b: *Foo, c: *i32) void)); + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Bar) void)); - // Once a standard parameter is encountered, all parameters after that are considered standard - // and not injected. - TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (a: f32) void)); - TupleTester.assertTuple(.{ i32, *f32 }, UninjectedArgsTuple(std.meta.Tuple, fn (a: i32, b: *f32) void)); - TupleTester.assertTuple(.{ i32, *i32, *f32 }, UninjectedArgsTuple(std.meta.Tuple, fn (a: i32, b: *i32, c: *f32) void)); + // As long as the argument is a Struct or *Struct with an IsInjectedArgument decl, it is + // considered an injected argument. + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo, b: *Bar, c: Foo, d: Bar) void)); + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: Foo) void)); + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: Bar) void)); + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *const Foo) void)); + TupleTester.assertTuple(.{}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *const Bar) void)); - // First parameter (*f32) matches an injectable parameter type, so it is injected. - TupleTester.assertTuple(.{ i32, *i32, *f32 }, UninjectedArgsTuple(std.meta.Tuple, fn (a: *f32, b: i32, c: *i32, d: *f32) void)); - - // First parameter (*f32) matches an injectable parameter type, so it is injected. 2nd - // parameter is not injectable, so all remaining parameters are not injected. - TupleTester.assertTuple(.{ i32, *Foo, *i32, *f32 }, UninjectedArgsTuple(std.meta.Tuple, fn (a: *f32, b: i32, c: *Foo, d: *i32, e: *f32) void)); + // Order doesn't matter, injected arguments can be placed inbetween any standard arguments, etc. + TupleTester.assertTuple(.{ f32, bool }, UninjectedArgsTuple(std.meta.Tuple, fn (i: f32, a: *Foo, k: bool, b: *Bar, c: Foo, d: Bar) void)); + TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (i: f32, a: *Foo, b: *Bar, c: Foo, d: Bar) void)); + TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo, i: f32, b: *Bar, c: Foo, d: Bar) void)); + TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo, b: *Bar, i: f32, c: Foo, d: Bar) void)); + TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo, b: *Bar, c: Foo, i: f32, d: Bar) void)); + TupleTester.assertTuple(.{f32}, UninjectedArgsTuple(std.meta.Tuple, fn (a: *Foo, b: *Bar, c: Foo, d: Bar, i: f32) void)); } test "event name calling" { @@ -770,11 +772,10 @@ test "event name calling" { }; }); - const Injectable = struct {}; var modules: Modules(.{ Physics, Renderer, - }, Injectable) = undefined; + }) = undefined; try modules.init(testing.allocator); defer modules.deinit(testing.allocator); @@ -840,6 +841,8 @@ test "dispatch" { }; var foo = struct { injected_args_sum: usize = 0, + + pub const IsInjectedArgument = void; }{}; const Minimal = Module(struct { pub const name = .engine_minimal; @@ -887,12 +890,11 @@ test "dispatch" { }; }); - const injectable = .{&foo}; var modules: Modules(.{ Minimal, Physics, Renderer, - }, @TypeOf(injectable)) = undefined; + }) = undefined; try modules.init(testing.allocator); defer modules.deinit(testing.allocator); @@ -934,8 +936,8 @@ test "dispatch" { try testing.expect(usize, 1).eql(global.physics_calc); // Local events - modules.sendToModule(.engine_renderer, .basicArgs, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference - modules.sendToModule(.engine_renderer, .injectedArgs, .{ @as(u32, 1), @as(u32, 2) }); + modules.sendToModule(.engine_renderer, .basicArgs, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference + modules.sendToModule(.engine_renderer, .injectedArgs, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) }); try modules.dispatch(.{&foo}); try testing.expect(usize, 3).eql(global.basic_args_sum); try testing.expect(usize, 3).eql(foo.injected_args_sum);