From 65e2168b9fd8cfb169affce77b7af70844c4a21d Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Tue, 7 May 2024 14:14:31 -0700 Subject: [PATCH] module: injected mach.Entity.Mod for global entity operations Signed-off-by: Stephen Gutekanst --- examples/custom-renderer/App.zig | 6 +++-- examples/glyphs/App.zig | 8 ++++--- examples/piano/App.zig | 26 +++++++++++---------- examples/play-opus/App.zig | 23 +++++++++++------- examples/sprite/App.zig | 8 ++++--- examples/text/App.zig | 14 ++++++----- src/Core.zig | 4 ++-- src/main.zig | 2 +- src/module/entities.zig | 8 +++---- src/module/main.zig | 11 +++++---- src/module/module.zig | 40 ++++++++++++++++++++++++-------- 11 files changed, 94 insertions(+), 56 deletions(-) diff --git a/examples/custom-renderer/App.zig b/examples/custom-renderer/App.zig index 3ed90f22..30b93726 100644 --- a/examples/custom-renderer/App.zig +++ b/examples/custom-renderer/App.zig @@ -48,6 +48,7 @@ fn init( // These are injected dependencies - as long as these modules were registered in the top-level // of the program we can have these types injected here, letting us work with other modules in // our program seamlessly and with a type-safe API: + entity: *mach.Entity.Mod, core: *mach.Core.Mod, renderer: *Renderer.Mod, game: *Mod, @@ -55,7 +56,7 @@ fn init( renderer.send(.init, .{}); // Create our player entity. - const player = try core.newEntity(); + const player = try entity.new(); // Give our player entity a .renderer.position and .renderer.scale component. Note that these // are defined by the Renderer module, so we use `renderer: *Renderer.Mod` to interact with @@ -81,6 +82,7 @@ fn init( // TODO(important): remove need for returning an error here fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, renderer: *Renderer.Mod, game: *Mod, @@ -134,7 +136,7 @@ fn tick( _ = game.state().spawn_timer.lap(); // Reset the timer for (0..5) |_| { // Spawn a new entity at the same position as the player, but smaller in scale. - const new_entity = try core.newEntity(); + const new_entity = try entity.new(); try renderer.set(new_entity, .position, player_pos); try renderer.set(new_entity, .scale, 1.0 / 6.0); diff --git a/examples/glyphs/App.zig b/examples/glyphs/App.zig index 94ee5eaa..a0132da2 100644 --- a/examples/glyphs/App.zig +++ b/examples/glyphs/App.zig @@ -60,6 +60,7 @@ fn init(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: } fn afterInit( + entity: *mach.Entity.Mod, sprite: *gfx.Sprite.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, @@ -67,7 +68,7 @@ fn afterInit( ) !void { // Create a sprite rendering pipeline const texture = glyphs.state().texture; - const pipeline = try sprite_pipeline.newEntity(); + const pipeline = try entity.new(); try sprite_pipeline.set(pipeline, .texture, texture); sprite_pipeline.send(.update, .{}); @@ -76,7 +77,7 @@ fn afterInit( // type than the `.physics2d` module's `.location` component if you desire. const r = glyphs.state().regions.get('?').?; - const player = try sprite.newEntity(); + const player = try entity.new(); try sprite.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0))); try sprite.set(player, .pipeline, pipeline); try sprite.set(player, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height))); @@ -97,6 +98,7 @@ fn afterInit( } fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, sprite: *gfx.Sprite.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, @@ -150,7 +152,7 @@ fn tick( const rand_index = game.state().rand.random().intRangeAtMost(usize, 0, glyphs.state().regions.count() - 1); const r = glyphs.state().regions.entries.get(rand_index).value; - const new_entity = try core.newEntity(); + const new_entity = try entity.new(); try sprite.set(new_entity, .transform, Mat4x4.translate(new_pos).mul(&Mat4x4.scaleScalar(0.3))); try sprite.set(new_entity, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height))); try sprite.set(new_entity, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y)))); diff --git a/examples/piano/App.zig b/examples/piano/App.zig index a353bc78..bb3fff66 100644 --- a/examples/piano/App.zig +++ b/examples/piano/App.zig @@ -65,6 +65,7 @@ fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void { } fn audioStateChange( + entity: *mach.Entity.Mod, audio: *mach.Audio.Mod, app: *Mod, ) !void { @@ -81,20 +82,21 @@ fn audioStateChange( if (app.get(id, .play_after)) |frequency| { // Play a new sound - const entity = try audio.newEntity(); - try audio.set(entity, .samples, try fillTone(audio, frequency)); - try audio.set(entity, .channels, @intCast(audio.state().player.channels().len)); - try audio.set(entity, .playing, true); - try audio.set(entity, .index, 0); + const e = try entity.new(); + try audio.set(e, .samples, try fillTone(audio, frequency)); + try audio.set(e, .channels, @intCast(audio.state().player.channels().len)); + try audio.set(e, .playing, true); + try audio.set(e, .index, 0); } // Remove the entity for the old sound - try audio.removeEntity(id); + try entity.remove(id); } } } fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod, @@ -121,16 +123,16 @@ fn tick( // Piano keys else => { // Play a new sound - const entity = try audio.newEntity(); - try audio.set(entity, .samples, try fillTone(audio, keyToFrequency(ev.key))); - try audio.set(entity, .channels, @intCast(audio.state().player.channels().len)); - try audio.set(entity, .playing, true); - try audio.set(entity, .index, 0); + const e = try entity.new(); + try audio.set(e, .samples, try fillTone(audio, keyToFrequency(ev.key))); + try audio.set(e, .channels, @intCast(audio.state().player.channels().len)); + try audio.set(e, .playing, true); + try audio.set(e, .index, 0); if (app.state().ghost_key_mode) { // After that sound plays, we'll chain on another sound that is one semi-tone higher. const one_semi_tone_higher = keyToFrequency(ev.key) * math.pow(f32, 2.0, (1.0 / 12.0)); - try app.set(entity, .play_after, one_semi_tone_higher); + try app.set(e, .play_after, one_semi_tone_higher); } }, } diff --git a/examples/play-opus/App.zig b/examples/play-opus/App.zig index 3b7853bf..f69365e6 100644 --- a/examples/play-opus/App.zig +++ b/examples/play-opus/App.zig @@ -32,7 +32,12 @@ pub const components = .{ sfx: Opus, -fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) !void { +fn init( + entity: *mach.Entity.Mod, + core: *mach.Core.Mod, + audio: *mach.Audio.Mod, + app: *Mod, +) !void { audio.send(.init, .{}); app.send(.after_init, .{}); @@ -48,7 +53,7 @@ fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) !void { // Initialize module state app.init(.{ .sfx = sfx }); - const bgm_entity = try audio.newEntity(); + const bgm_entity = try entity.new(); try app.set(bgm_entity, .is_bgm, {}); try audio.set(bgm_entity, .samples, bgm.samples); try audio.set(bgm_entity, .channels, bgm.channels); @@ -75,6 +80,7 @@ fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void { } fn audioStateChange( + entity: *mach.Entity.Mod, audio: *mach.Audio.Mod, app: *Mod, ) !void { @@ -93,13 +99,14 @@ fn audioStateChange( try audio.set(id, .playing, true); } else { // Remove the entity for the old sound - try audio.removeEntity(id); + try entity.remove(id); } } } } fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod, @@ -121,11 +128,11 @@ fn tick( }, else => { // Play a new SFX - const entity = try audio.newEntity(); - try audio.set(entity, .samples, app.state().sfx.samples); - try audio.set(entity, .channels, app.state().sfx.channels); - try audio.set(entity, .index, 0); - try audio.set(entity, .playing, true); + const e = try entity.new(); + try audio.set(e, .samples, app.state().sfx.samples); + try audio.set(e, .channels, app.state().sfx.channels); + try audio.set(e, .index, 0); + try audio.set(e, .playing, true); }, }, .close => core.send(.exit, .{}), diff --git a/examples/sprite/App.zig b/examples/sprite/App.zig index 12ae57e8..ab1cb41c 100644 --- a/examples/sprite/App.zig +++ b/examples/sprite/App.zig @@ -52,6 +52,7 @@ fn deinit( } fn init( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, sprite: *gfx.Sprite.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, @@ -65,12 +66,12 @@ fn init( // Create a sprite rendering pipeline const allocator = gpa.allocator(); - const pipeline = try core.newEntity(); + const pipeline = try entity.new(); try sprite_pipeline.set(pipeline, .texture, try loadTexture(core, allocator)); sprite_pipeline.send(.update, .{}); // Create our player sprite - const player = try core.newEntity(); + const player = try entity.new(); try sprite.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0))); try sprite.set(player, .size, vec2(32, 32)); try sprite.set(player, .uv_transform, Mat3x3.translate(vec2(0, 0))); @@ -94,6 +95,7 @@ fn init( } fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, sprite: *gfx.Sprite.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, @@ -143,7 +145,7 @@ fn tick( new_pos.v[0] += game.state().rand.random().floatNorm(f32) * 25; new_pos.v[1] += game.state().rand.random().floatNorm(f32) * 25; - const new_entity = try core.newEntity(); + const new_entity = try entity.new(); try sprite.set(new_entity, .transform, Mat4x4.translate(new_pos).mul(&Mat4x4.scale(Vec3.splat(0.3)))); try sprite.set(new_entity, .size, vec2(32, 32)); try sprite.set(new_entity, .uv_transform, Mat3x3.translate(vec2(0, 0))); diff --git a/examples/text/App.zig b/examples/text/App.zig index 689d8fce..5dadddb6 100644 --- a/examples/text/App.zig +++ b/examples/text/App.zig @@ -63,6 +63,7 @@ fn deinit( } fn init( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, text: *gfx.Text.Mod, text_pipeline: *gfx.TextPipeline.Mod, @@ -73,21 +74,21 @@ fn init( // TODO: a better way to initialize entities with default values // TODO(text): most of these style options are not respected yet. - const style1 = try core.newEntity(); + const style1 = try entity.new(); try text_style.set(style1, .font_name, "Roboto Medium"); // TODO try text_style.set(style1, .font_size, 48 * gfx.px_per_pt); // 48pt try text_style.set(style1, .font_weight, gfx.font_weight_normal); try text_style.set(style1, .italic, false); try text_style.set(style1, .color, vec4(0.6, 1.0, 0.6, 1.0)); - const style2 = try core.newEntity(); + const style2 = try entity.new(); try text_style.set(style2, .font_name, "Roboto Medium"); // TODO try text_style.set(style2, .font_size, 48 * gfx.px_per_pt); // 48pt try text_style.set(style2, .font_weight, gfx.font_weight_normal); try text_style.set(style2, .italic, true); try text_style.set(style2, .color, vec4(0.6, 1.0, 0.6, 1.0)); - const style3 = try core.newEntity(); + const style3 = try entity.new(); try text_style.set(style3, .font_name, "Roboto Medium"); // TODO try text_style.set(style3, .font_size, 48 * gfx.px_per_pt); // 48pt try text_style.set(style3, .font_weight, gfx.font_weight_bold); @@ -95,12 +96,12 @@ fn init( try text_style.set(style3, .color, vec4(0.6, 1.0, 0.6, 1.0)); // Create a text rendering pipeline - const pipeline = try core.newEntity(); + const pipeline = try entity.new(); try text_pipeline.set(pipeline, .is_pipeline, {}); text_pipeline.send(.update, .{}); // Create some text - const player = try core.newEntity(); + const player = try entity.new(); try text.set(player, .pipeline, pipeline); try text.set(player, .transform, Mat4x4.scaleScalar(upscale).mul(&Mat4x4.translate(vec3(0, 0, 0)))); @@ -132,6 +133,7 @@ fn init( } fn tick( + entity: *mach.Entity.Mod, core: *mach.Core.Mod, text: *gfx.Text.Mod, text_pipeline: *gfx.TextPipeline.Mod, @@ -181,7 +183,7 @@ fn tick( new_pos.v[0] += game.state().rand.random().floatNorm(f32) * 50; new_pos.v[1] += game.state().rand.random().floatNorm(f32) * 50; - const new_entity = try core.newEntity(); + const new_entity = try entity.new(); try text.set(new_entity, .pipeline, game.state().pipeline); try text.set(new_entity, .transform, Mat4x4.scaleScalar(upscale).mul(&Mat4x4.translate(new_pos))); diff --git a/src/Core.zig b/src/Core.zig index 5fd8ae7a..0a46498d 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -106,7 +106,7 @@ fn start(core: *Mod) !void { core.state().run_state = .running; } -fn init(core: *Mod) !void { +fn init(entity: *mach.Entity.Mod, core: *Mod) !void { mach.core.allocator = gpa.allocator(); // TODO: banish this global allocator // Initialize GPU implementation @@ -116,7 +116,7 @@ fn init(core: *Mod) !void { try mach.core.init(.{}); // TODO(important): update this information upon framebuffer resize events - const main_window = try core.newEntity(); + const main_window = try entity.new(); try core.set(main_window, .framebuffer_format, mach.core.descriptor.format); try core.set(main_window, .framebuffer_width, mach.core.descriptor.width); try core.set(main_window, .framebuffer_height, mach.core.descriptor.height); diff --git a/src/main.zig b/src/main.zig index cc04dc35..23ea0173 100644 --- a/src/main.zig +++ b/src/main.zig @@ -40,7 +40,7 @@ pub const EventID = @import("module/main.zig").EventID; pub const AnyEvent = @import("module/main.zig").AnyEvent; pub const merge = @import("module/main.zig").merge; pub const builtin_modules = @import("module/main.zig").builtin_modules; -pub const EntityModule = @import("module/main.zig").EntityModule; +pub const Entity = @import("module/main.zig").Entity; /// To use experimental sysgpu graphics API, you can write this in your main.zig: /// diff --git a/src/module/entities.zig b/src/module/entities.zig index f89b3232..f4be45dc 100644 --- a/src/module/entities.zig +++ b/src/module/entities.zig @@ -9,7 +9,7 @@ const StringTable = @import("StringTable.zig"); const ComponentTypesByName = @import("module.zig").ComponentTypesByName; const merge = @import("main.zig").merge; const builtin_modules = @import("main.zig").builtin_modules; -const EntityModule = @import("main.zig").EntityModule; +const Entity = @import("main.zig").Entity; const ModuleName = @import("module.zig").ModuleName; const ComponentNameM = @import("module.zig").ComponentNameM; const ComponentName = @import("module.zig").ComponentName; @@ -130,7 +130,7 @@ pub fn Entities(comptime modules: anytype) type { .component_names = component_names, .buckets = buckets, }; - entities.id_name = entities.componentName(EntityModule.name, .id); + entities.id_name = entities.componentName(Entity.name, .id); const columns = try allocator.alloc(Archetype.Column, 1); columns[0] = .{ @@ -1076,7 +1076,7 @@ test "example" { // Query for all entities that have all of the given components const W = @TypeOf(world); var q = try world.query(.{ - .ids = W.ComponentQuery{ .read = W.ModuleComponentName{ .module = EntityModule.name, .component = .id } }, + .ids = W.ComponentQuery{ .read = W.ModuleComponentName{ .module = Entity.name, .component = .id } }, .rotations = W.ComponentQuery{ .write = W.ModuleComponentName{ .module = Game.name, .component = .rotation } }, }); while (q.next()) |v| { @@ -1090,7 +1090,7 @@ test "example" { // Dynamic queries (e.g. issued from another programming language without comptime) var q2 = try world.queryDynamic(.{ .op_and = &.{ - .{ .read = world.componentName(EntityModule.name, .id) }, + .{ .read = world.componentName(Entity.name, .id) }, .{ .read = world.componentName(Game.name, .rotation) }, }, }); diff --git a/src/module/main.zig b/src/module/main.zig index 2a093af3..0784f086 100644 --- a/src/module/main.zig +++ b/src/module/main.zig @@ -13,10 +13,10 @@ pub const AnyEvent = @import("module.zig").AnyEvent; pub const Merge = @import("module.zig").Merge; pub const merge = @import("module.zig").merge; -pub const builtin_modules = .{EntityModule}; +pub const builtin_modules = .{Entity}; /// Builtin .entity module -pub const EntityModule = struct { +pub const Entity = struct { pub const name = .entity; pub const Mod = mach.Mod(@This()); @@ -82,14 +82,15 @@ test "entities DB" { defer world.deinit(allocator); // Initialize module state. + var entity = &world.mod.entity; var physics = &world.mod.physics; var renderer = &world.mod.renderer; physics.init(.{ .pointer = 123 }); _ = physics.state().pointer; // == 123 - const player1 = try physics.newEntity(); - const player2 = try physics.newEntity(); - const player3 = try physics.newEntity(); + const player1 = try entity.new(); + const player2 = try entity.new(); + const player3 = try entity.new(); try physics.set(player1, .id, 1001); try renderer.set(player1, .id, 1001); diff --git a/src/module/module.zig b/src/module/module.zig index ae86b7ca..fd8b5462 100644 --- a/src/module/module.zig +++ b/src/module/module.zig @@ -574,6 +574,36 @@ pub fn ModSet(comptime modules: anytype) type { pub fn Mod(comptime M: anytype) type { const module_tag = M.name; const components = ComponentTypesM(M){}; + + if (M.name == .entity) { + // The .entity module is a special builtin module, with its own unique API. + return struct { + /// Private/internal fields + __entities: *Entities(modules), + __is_initialized: bool, + __state: void, + + pub const IsInjectedArgument = void; + + pub inline fn read(comptime component_name: ComponentNameM(M)) Entities(modules).ComponentQuery { + return .{ .read = .{ + .module = M.name, + .component = comptime stringToEnum(ComponentName(modules), @tagName(component_name)).?, + } }; + } + + /// Returns a new entity. + pub inline fn new(m: *@This()) !EntityID { + return m.__entities.new(); + } + + /// Removes an entity. + pub inline fn remove(m: *@This(), entity: EntityID) !void { + try m.__entities.remove(entity); + } + }; + } + return struct { /// Private/internal fields __entities: *Entities(modules), @@ -622,16 +652,6 @@ pub fn ModSet(comptime modules: anytype) type { return &m.__state; } - /// Returns a new entity. - pub inline fn newEntity(m: *@This()) !EntityID { - return m.__entities.new(); - } - - /// Removes an entity. - pub inline fn removeEntity(m: *@This(), entity: EntityID) !void { - try m.__entities.remove(entity); - } - /// Sets the named component to the specified value for the given entity, /// moving the entity from it's current archetype table to the new archetype /// table if required.