module: enable Module to analyze event handler signatures
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
ce0b764a6d
commit
b3663f7899
4 changed files with 148 additions and 107 deletions
|
|
@ -53,7 +53,7 @@ test "example" {
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
fn tick(physics: *Modules(modules).Mod(Physics)) void {
|
fn tick(physics: *mach.ModSet(modules).Mod(Physics)) void {
|
||||||
_ = physics;
|
_ = physics;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -68,8 +68,8 @@ test "example" {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn tick(
|
fn tick(
|
||||||
physics: *Modules(modules).Mod(Physics),
|
physics: *mach.ModSet(modules).Mod(Physics),
|
||||||
renderer: *Modules(modules).Mod(Renderer),
|
renderer: *mach.ModSet(modules).Mod(Renderer),
|
||||||
) void {
|
) void {
|
||||||
_ = renderer;
|
_ = renderer;
|
||||||
_ = physics;
|
_ = physics;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
/// The main Mach engine ECS module.
|
/// The main Mach engine ECS module.
|
||||||
|
// TODO: move this to Engine.zig
|
||||||
pub const Engine = struct {
|
pub const Engine = struct {
|
||||||
device: *gpu.Device,
|
device: *gpu.Device,
|
||||||
queue: *gpu.Queue,
|
queue: *gpu.Queue,
|
||||||
|
|
@ -17,7 +18,7 @@ pub const Engine = struct {
|
||||||
encoder: *gpu.CommandEncoder,
|
encoder: *gpu.CommandEncoder,
|
||||||
|
|
||||||
pub const name = .engine;
|
pub const name = .engine;
|
||||||
pub const Mod = Modules.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const global_events = .{
|
pub const global_events = .{
|
||||||
.init = .{ .handler = fn () void },
|
.init = .{ .handler = fn () void },
|
||||||
|
|
@ -102,7 +103,7 @@ pub const Engine = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const App = struct {
|
pub const App = struct {
|
||||||
modules: Modules,
|
modules: mach.Modules,
|
||||||
|
|
||||||
pub fn init(app: *@This()) !void {
|
pub fn init(app: *@This()) !void {
|
||||||
app.* = .{ .modules = undefined };
|
app.* = .{ .modules = undefined };
|
||||||
|
|
@ -127,10 +128,3 @@ pub const App = struct {
|
||||||
return app.modules.mod.engine.state.should_exit;
|
return app.modules.mod.engine.state.should_exit;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Modules = module.Modules(blk: {
|
|
||||||
if (!@hasDecl(@import("root"), "modules")) {
|
|
||||||
@compileError("expected `pub const modules = .{};` in root file");
|
|
||||||
}
|
|
||||||
break :blk @import("root").modules;
|
|
||||||
});
|
|
||||||
|
|
|
||||||
13
src/main.zig
13
src/main.zig
|
|
@ -21,8 +21,17 @@ pub const sysgpu = if (build_options.want_sysgpu) @import("sysgpu/main.zig") els
|
||||||
// Engine exports
|
// Engine exports
|
||||||
pub const App = @import("engine.zig").App;
|
pub const App = @import("engine.zig").App;
|
||||||
pub const Engine = @import("engine.zig").Engine;
|
pub const Engine = @import("engine.zig").Engine;
|
||||||
pub const Modules = @import("engine.zig").Modules;
|
pub const ModSet = @import("module.zig").ModSet;
|
||||||
pub const Mod = Modules.Mod;
|
|
||||||
|
// TODO: perhaps this could be a comptime var rather than @import("root")?
|
||||||
|
const modules = blk: {
|
||||||
|
if (!@hasDecl(@import("root"), "modules")) {
|
||||||
|
@compileError("expected `pub const modules = .{};` in root file");
|
||||||
|
}
|
||||||
|
break :blk @import("root").modules;
|
||||||
|
};
|
||||||
|
pub const Modules = @import("module.zig").Modules(modules);
|
||||||
|
pub const Mod = ModSet(modules).Mod;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
|
||||||
118
src/module.zig
118
src/module.zig
|
|
@ -5,15 +5,15 @@ const testing = @import("testing.zig");
|
||||||
const Entities = @import("ecs/entities.zig").Entities;
|
const Entities = @import("ecs/entities.zig").Entities;
|
||||||
const EntityID = @import("ecs/entities.zig").EntityID;
|
const EntityID = @import("ecs/entities.zig").EntityID;
|
||||||
|
|
||||||
|
// TODO: make sendToModule the default name for sending events? and sendGlobal always secondary? Or vice-versa?
|
||||||
|
|
||||||
/// Verifies that M matches the basic layout of a Mach module
|
/// Verifies that M matches the basic layout of a Mach module
|
||||||
fn ModuleInterface(comptime M: type) type {
|
fn ModuleInterface(comptime M: type) type {
|
||||||
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
||||||
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`");
|
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`");
|
||||||
if (@typeInfo(@TypeOf(M.name)) != .EnumLiteral) @compileError("mach: module must have `pub const name = .foobar;`, found type:" ++ @typeName(M.name));
|
if (@typeInfo(@TypeOf(M.name)) != .EnumLiteral) @compileError("mach: module must have `pub const name = .foobar;`, found type:" ++ @typeName(M.name));
|
||||||
|
if (@hasDecl(M, "global_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " global_events ", M.global_events);
|
||||||
// TODO: enable once parameter dependency loop has been resolved
|
if (@hasDecl(M, "local_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " local_events ", M.global_events);
|
||||||
// if (@hasDecl(M, "global_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " global_events ", M.global_events);
|
|
||||||
// if (@hasDecl(M, "local_events")) validateEvents("mach: module ." ++ @tagName(M.name) ++ " local_events ", M.global_events);
|
|
||||||
_ = ComponentTypesM(M);
|
_ = ComponentTypesM(M);
|
||||||
return M;
|
return M;
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,6 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
/// e.g. @field(@field(ComponentTypesByName, "module_name"), "component_name")
|
/// e.g. @field(@field(ComponentTypesByName, "module_name"), "component_name")
|
||||||
pub const component_types_by_name = ComponentTypesByName(modules){};
|
pub const component_types_by_name = ComponentTypesByName(modules){};
|
||||||
|
|
||||||
const ModulesT = @This();
|
|
||||||
const Event = struct {
|
const Event = struct {
|
||||||
module_name: ?ModuleID,
|
module_name: ?ModuleID,
|
||||||
event_name: EventID,
|
event_name: EventID,
|
||||||
|
|
@ -58,15 +57,10 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
events_mu: std.Thread.RwLock = .{},
|
events_mu: std.Thread.RwLock = .{},
|
||||||
args_queue: std.ArrayListUnmanaged(u8) = .{},
|
args_queue: std.ArrayListUnmanaged(u8) = .{},
|
||||||
events: EventQueue,
|
events: EventQueue,
|
||||||
mod: ModsByName(modules, ModulesT),
|
mod: ModsByName(modules),
|
||||||
// TODO: pass mods directly instead of ComponentTypesByName?
|
// TODO: pass mods directly instead of ComponentTypesByName?
|
||||||
entities: Entities(component_types_by_name),
|
entities: Entities(component_types_by_name),
|
||||||
|
|
||||||
pub fn Mod(comptime M: type) type {
|
|
||||||
const NSComponents = ComponentTypesByName(ModulesT.modules);
|
|
||||||
return Module(M, ModulesT, NSComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(m: *@This(), allocator: std.mem.Allocator) !void {
|
pub fn init(m: *@This(), allocator: std.mem.Allocator) !void {
|
||||||
// TODO: switch Entities to stack allocation like Modules is
|
// TODO: switch Entities to stack allocation like Modules is
|
||||||
var entities = try Entities(component_types_by_name).init(allocator);
|
var entities = try Entities(component_types_by_name).init(allocator);
|
||||||
|
|
@ -110,16 +104,36 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn moduleToGlobalEvent(
|
||||||
|
comptime M: type,
|
||||||
|
comptime EventEnumM: anytype,
|
||||||
|
comptime EventEnum: anytype,
|
||||||
|
comptime event_name: EventEnumM(M),
|
||||||
|
) EventEnum(modules) {
|
||||||
|
for (@typeInfo(EventEnum(modules)).Enum.fields) |gfield| {
|
||||||
|
if (std.mem.eql(u8, @tagName(event_name), gfield.name)) {
|
||||||
|
return @enumFromInt(gfield.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a global event which the specified module defines
|
/// Send a global event which the specified module defines
|
||||||
pub fn sendGlobal(
|
pub fn sendGlobal(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime module_name: ModuleName(modules),
|
comptime module_name: ModuleName(modules),
|
||||||
comptime event_name: GlobalEvent,
|
comptime event_name: GlobalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).state)),
|
||||||
args: GlobalArgs(module_name, event_name),
|
args: GlobalArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).state), event_name),
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
m.sendInternal(null, @intFromEnum(event_name), args);
|
const event_name_g: GlobalEvent = comptime moduleToGlobalEvent(
|
||||||
|
@TypeOf(@field(m.mod, @tagName(module_name)).state),
|
||||||
|
GlobalEventEnumM,
|
||||||
|
GlobalEventEnum,
|
||||||
|
event_name,
|
||||||
|
);
|
||||||
|
m.sendInternal(null, @intFromEnum(event_name_g), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an event to a specific module
|
/// Send an event to a specific module
|
||||||
|
|
@ -127,11 +141,17 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where module_name/event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where module_name/event_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime module_name: ModuleName(modules),
|
comptime module_name: ModuleName(modules),
|
||||||
comptime event_name: LocalEvent,
|
comptime event_name: LocalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).state)),
|
||||||
args: LocalArgs(module_name, event_name),
|
args: LocalArgsM(@TypeOf(@field(m.mod, @tagName(module_name)).state), event_name),
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
m.sendInternal(@intFromEnum(module_name), @intFromEnum(event_name), args);
|
const event_name_g: LocalEvent = comptime moduleToGlobalEvent(
|
||||||
|
@TypeOf(@field(m.mod, @tagName(module_name)).state),
|
||||||
|
LocalEventEnumM,
|
||||||
|
LocalEventEnum,
|
||||||
|
event_name,
|
||||||
|
);
|
||||||
|
m.sendInternal(@intFromEnum(module_name), @intFromEnum(event_name_g), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a global event, using a dynamic (not known to the compiled program) event name.
|
/// Send a global event, using a dynamic (not known to the compiled program) event name.
|
||||||
|
|
@ -171,7 +191,7 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
pub fn dispatch(m: *@This()) !void {
|
pub fn dispatch(m: *@This()) !void {
|
||||||
const Injectable = comptime blk: {
|
const Injectable = comptime blk: {
|
||||||
var types: []const type = &[0]type{};
|
var types: []const type = &[0]type{};
|
||||||
for (@typeInfo(ModsByName(modules, ModulesT)).Struct.fields) |field| {
|
for (@typeInfo(ModsByName(modules)).Struct.fields) |field| {
|
||||||
const ModPtr = @TypeOf(@as(*field.type, undefined));
|
const ModPtr = @TypeOf(@as(*field.type, undefined));
|
||||||
types = types ++ [_]type{ModPtr};
|
types = types ++ [_]type{ModPtr};
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +199,7 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
};
|
};
|
||||||
var injectable: Injectable = undefined;
|
var injectable: Injectable = undefined;
|
||||||
outer: inline for (@typeInfo(Injectable).Struct.fields) |field| {
|
outer: inline for (@typeInfo(Injectable).Struct.fields) |field| {
|
||||||
inline for (@typeInfo(ModsByName(modules, ModulesT)).Struct.fields) |injectable_field| {
|
inline for (@typeInfo(ModsByName(modules)).Struct.fields) |injectable_field| {
|
||||||
if (*injectable_field.type == field.type) {
|
if (*injectable_field.type == field.type) {
|
||||||
@field(injectable, field.name) = &@field(m.mod, injectable_field.name);
|
@field(injectable, field.name) = &@field(m.mod, injectable_field.name);
|
||||||
|
|
||||||
|
|
@ -291,17 +311,16 @@ pub fn Modules(comptime modules2: anytype) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ModsByName(comptime modules: anytype, comptime ModulesT: type) type {
|
pub fn ModsByName(comptime modules: anytype) type {
|
||||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||||
for (modules) |M| {
|
for (modules) |M| {
|
||||||
const NSComponents = ComponentTypesByName(modules);
|
const ModT = ModSet(modules).Mod(M);
|
||||||
const Mod = Module(M, ModulesT, NSComponents);
|
|
||||||
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
||||||
.name = @tagName(M.name),
|
.name = @tagName(M.name),
|
||||||
.type = Mod,
|
.type = ModT,
|
||||||
.default_value = null,
|
.default_value = null,
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = @alignOf(Mod),
|
.alignment = @alignOf(ModT),
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
return @Type(.{
|
return @Type(.{
|
||||||
|
|
@ -314,11 +333,25 @@ pub fn ModsByName(comptime modules: anytype, comptime ModulesT: type) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Module(
|
// Note: Modules() causes analysis of event handlers' function signatures, whose parameters include
|
||||||
comptime M: anytype,
|
// references to ModSet(modules).Mod(). As a result, the type returned here may never invoke Modules()
|
||||||
comptime ModulesT: type,
|
// or depend on its result. However, it can analyze the global set of modules on its own, since no
|
||||||
comptime NSComponents: type,
|
// module's type should embed the result of Modules().
|
||||||
) type {
|
//
|
||||||
|
// In short, these calls are fine:
|
||||||
|
//
|
||||||
|
// Modules() -> ModSet()
|
||||||
|
// Modules() -> ModSet() -> Mod()
|
||||||
|
//
|
||||||
|
// But these are never permissible:
|
||||||
|
//
|
||||||
|
// ModSet() -> Modules()
|
||||||
|
// Mod() -> Modules()
|
||||||
|
//
|
||||||
|
pub fn ModSet(comptime modules: anytype) type {
|
||||||
|
const NSComponents = ComponentTypesByName(modules);
|
||||||
|
return struct {
|
||||||
|
pub fn Mod(comptime M: anytype) type {
|
||||||
const module_tag = M.name;
|
const module_tag = M.name;
|
||||||
const components = ComponentTypesM(M){};
|
const components = ComponentTypesM(M){};
|
||||||
return struct {
|
return struct {
|
||||||
|
|
@ -373,26 +406,31 @@ pub fn Module(
|
||||||
try m.entities.removeComponent(entity, module_tag, component_name);
|
try m.entities.removeComponent(entity, module_tag, component_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn send(m: *@This(), comptime event_name: ModulesT.LocalEvent, args: LocalArgsM(M, event_name)) void {
|
pub inline fn send(m: *@This(), comptime event_name: LocalEventEnumM(M), args: LocalArgsM(M, event_name)) void {
|
||||||
const MByName = ModsByName(ModulesT.modules, ModulesT);
|
const ModulesT = Modules(modules);
|
||||||
|
const MByName = ModsByName(ModulesT.modules);
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
const modules = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
modules.sendToModule(module_tag, event_name, args);
|
mods.sendToModule(module_tag, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn sendGlobal(m: *@This(), comptime event_name: ModulesT.GlobalEvent, args: GlobalArgsM(M, event_name)) void {
|
pub inline fn sendGlobal(m: *@This(), comptime event_name: GlobalEventEnumM(M), args: GlobalArgsM(M, event_name)) void {
|
||||||
const MByName = ModsByName(ModulesT.modules, ModulesT);
|
const ModulesT = Modules(modules);
|
||||||
|
const MByName = ModsByName(ModulesT.modules);
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
const modules = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
modules.sendGlobal(module_tag, event_name, args);
|
mods.sendGlobal(module_tag, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: eliminate this
|
// TODO: eliminate this
|
||||||
pub fn dispatchNoError(m: *@This()) void {
|
pub fn dispatchNoError(m: *@This()) void {
|
||||||
const MByName = ModsByName(ModulesT.modules, ModulesT);
|
const ModulesT = Modules(modules);
|
||||||
|
const MByName = ModsByName(ModulesT.modules);
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
const modules = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
modules.dispatch() catch |err| @panic(@errorName(err));
|
mods.dispatch() catch |err| @panic(@errorName(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue