diff --git a/README.md b/README.md index c2c9544..057f46b 100644 --- a/README.md +++ b/README.md @@ -1,112 +1,110 @@ -**Includes** -- Plugins -- Scheduler -- Resources -- Events -- ECS (Archetype) +# NYXApp -**Plugin** -``` zig -pub const plugin: App.plugin.Plugin = .{ - .name = "Renderer", - .description = "Vulkan Renderer", +Low cortisol **General Purpose App** + +# App +```zig +// --- APP --- +var app: App = .init(std.testing.allocator); +defer app.deinit(); + +// --- ADD PLUGINS --- +try app.plugins.addMany(&.{ + window_plugin, +}); +try app.plugins.build(&app); +``` + +# Plugin +```zig +pub const window_plugin: Plugin = .{ + .name = "Window", + .description = "SDL3 Window", .build = build, }; -``` -**System** -``` zig -pub fn render(app: *App) !void { - // --- GET RESOURCES --- - const res = try app.resources.getMany(struct { - window: *Window, - device: *Device, - render_frame: ?*RenderFrame, - }); - - if (res.render_frame) |rf| { - _ = rf; - } +fn build( + self: *Plugin, + _: *App, +) !void { + std.debug.print( + "[WINDOW_PLUGIN] {s} - {s}\n", + .{ + self.name, + self.description, + }, + ); } ``` -**Simple Events** -``` zig -// --- SEND MANY EVENTS --- -try app.events.sendMany(&.{ - event.AppExit{ - .reason = "Yes", +# Schedules + +**addMany** +```zig +try app.schedules.addMany(stage.init, &.{ + ensureStuff, + otherSystem, +}); +``` + +**runMany** +```zig +try app.schedules.runMany( + &.{ + App.stage.init, + App.stage.deinit, }, + &app, +); +``` + +# Resources + +**set** +```zig +const Window = struct { raw: u32 }; +try app.resources.set(Window{ .raw = 0 }); +``` + +**get** +```zig +if (app.resources.getPtr(Window)) |win| { + std.debug.print("[WINDOW] {}\n", .{win.raw}); +} +``` + +**getMany** +```zig +const res = try app.resources.getMany(struct { + window: *Window, + renderer: ?*Renderer, }); -// --- MAIN GAME LOOP --- -while (true) { - // --- USE AND EAT EVENT --- - if (app.events.consume(event.AppExit)) |app_exit| { - std.debug.print("Exit Reason: {s}\n", .{ - app_exit.reason, - }); - break; - } +if (res.renderer) |ren| { + std.debug.print("[RENDERER] {}\n", .{ren.raw}); } ``` -**Event Queue** -``` zig -const Register = struct { - username: []const u8, -}; +# Events -// --- QUEUE --- -if (app.events.get(event.Register)) |queue| { - // --- GET EVERY EVENT --- - for (queue.items()) |register| { - // --- DEBUG --- - std.debug.print("New Register: Username(s)\n", .{ - register.username - }); - // ... - } - // --- CLEAR EVENT QUEUE --- - queue.clear(); +**send** +```zig +try app.events.send( + AppExit{ + .reason = "Why Not?", + }, +); +``` + +**consume** +```zig +if (app.events.consume(AppExit)) |app_exit| { + std.debug.print("[APP_EXIT] {s}\n", .{app_exit.reason}); } ``` -**Simple** -``` zig -test "Main" { - // --- APP --- - var app: Self = .init(std.testing.allocator); - defer app.deinit(); - - { // PLUGINS - // --- ADD PLUGINS --- - try app.plugins.addMany(&.{ - example.simple.plugin, - }); - - // --- BUILD PLUGINS --- - try app.plugins.build(.{ - .app = &app, - }); - } - - { // SYSTEMS - try app.schedules.runMany(&.{ - example.simple.stage.Init, - example.simple.stage.Inputs, - example.simple.stage.Update, - example.simple.stage.Draw, - example.simple.stage.Deinit, - }, .{ - .app = &app, - }); - } -} -``` - -**Default App Stages** -``` zig +# Default Stages +```zig pub const stage = struct { // --- LIFETIME --- pub const init = struct {}; diff --git a/src/app.zig b/src/app.zig index c10e2e1..31352a8 100644 --- a/src/app.zig +++ b/src/app.zig @@ -1,21 +1,25 @@ //! ---------------------------------------------------- //! ---------------------------------------------------- -// --- LIBS --- +// +// PRIVATE +// + const std = @import("std"); -const root = @import("root"); +const template = @import("template/root.zig"); +const Self = @This(); + +// +// PUBLIC +// pub const event = @import("event/root.zig"); pub const plugin = @import("plugin/root.zig"); pub const resource = @import("resource/root.zig"); pub const scheduler = @import("scheduler/root.zig"); -pub const ecs = @import("world/root.zig"); -pub const Entity = ecs.Entity; pub const Plugin = plugin.Plugin; - -const template = @import("template/root.zig"); -const Self = @This(); +pub const AppExit = event.AppExit; /// ---------------------------------------------------- /// Default app stages @@ -36,16 +40,6 @@ pub const stage = struct { pub const draw = struct {}; }; -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub const Options = struct { - seed: u64 = 8490694, -}; -pub const options: Options = if (@hasDecl( - root, - "app_options", -)) root.app_options else .{}; - // // FIELDS // @@ -54,7 +48,6 @@ events: event.Storage, plugins: plugin.Storage, resources: resource.Storage, schedules: scheduler.Storage, -world: ecs.World, alloc: std.mem.Allocator, /// ---------------------------------------------------- @@ -65,7 +58,6 @@ pub fn init(alloc: std.mem.Allocator) Self { .plugins = .init(alloc), .resources = .init(alloc), .schedules = .init(alloc), - .world = .init(alloc), .alloc = alloc, }; } @@ -73,7 +65,6 @@ pub fn init(alloc: std.mem.Allocator) Self { /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - self.world.deinit(); self.events.deinit(); self.plugins.deinit(); self.resources.deinit(); @@ -81,60 +72,80 @@ pub fn deinit(self: *Self) void { } // -// EXAMPLES +// TESTS // -const example = @import("example/root.zig"); +const Window = struct { raw: u32 }; +const Renderer = struct { raw: u32 }; -// ---------------------------------------------------- -// ---------------------------------------------------- -test "Main" { +const window_plugin: Plugin = .{ + .name = "Window", + .description = "SDL3 Window", + .build = build, +}; + +fn ensureStuff(app: *Self) !void { + try app.resources.set(Window{ .raw = 0 }); + try app.resources.set(Renderer{ .raw = 0 }); + + if (app.resources.getPtr(Window)) |win| { + std.debug.print("[WINDOW] {}\n", .{win.raw}); + } + + const res = try app.resources.getMany(struct { + window: *Window, + renderer: ?*Renderer, + }); + + if (res.renderer) |ren| { + std.debug.print("[RENDERER] {}\n", .{ren.raw}); + } + + try app.events.send( + AppExit{ + .reason = "Why Not?", + }, + ); + + if (app.events.consume(AppExit)) |app_exit| { + std.debug.print("[APP_EXIT] {s}\n", .{app_exit.reason}); + } +} + +fn build( + self: *Plugin, + app: *Self, +) !void { + std.debug.print( + "[WINDOW_PLUGIN] {s} - {s}\n", + .{ + self.name, + self.description, + }, + ); + + try app.schedules.addMany(stage.init, &.{ + ensureStuff, + }); +} + +test "main" { // --- APP --- var app: Self = .init(std.testing.allocator); defer app.deinit(); - // --- SEND MANY EVENTS --- - try app.events.sendMany(&.{ - event.AppExit{ - .reason = "Yes", - }, + // --- ADD PLUGINS --- + try app.plugins.addMany(&.{ + window_plugin, }); + try app.plugins.build(&app); - // --- MAIN GAME LOOP --- - while (true) { - // --- USE AND EAT EVENT --- - if (app.events.consume(event.AppExit)) |app_exit| { - std.debug.print("| {s}\n", .{ - app_exit.reason, - }); - break; - } - } - - { // PLUGINS - // --- ADD PLUGINS --- - try app.plugins.addMany(&.{ - example.simple.plugin, - }); - - // --- BUILD PLUGINS --- - try app.plugins.build(&app); - } - - { // SYSTEMS - // try app.schedules.runAll(&app); - - try app.schedules.runMany(&.{ - example.simple.stage.Init, - example.simple.stage.Inputs, - example.simple.stage.Update, - example.simple.stage.Draw, - example.simple.stage.Deinit, - }, &app); - - try app.schedules.runReversed( - example.simple.stage.Deinit, - &app, - ); - } + // --- RUN SYSTEMS --- + try app.schedules.runMany( + &.{ + stage.init, + stage.deinit, + }, + &app, + ); } diff --git a/src/example/root.zig b/src/example/root.zig deleted file mode 100644 index 1f1879c..0000000 --- a/src/example/root.zig +++ /dev/null @@ -1 +0,0 @@ -pub const simple = @import("simple/root.zig"); diff --git a/src/example/simple/component.zig b/src/example/simple/component.zig deleted file mode 100644 index c10ab4a..0000000 --- a/src/example/simple/component.zig +++ /dev/null @@ -1,45 +0,0 @@ -const std = @import("std"); - -pub const Transform = struct { - pos: @Vector(2, f32) = .{ 0, 0 }, - size: @Vector(2, f32) = .{ 1, 1 }, - - pub fn format( - self: @This(), - writer: *std.Io.Writer, - ) !void { - try writer.print("pos=({}, {}) size=({}, {})", .{ - self.pos[0], self.pos[1], - self.size[0], self.size[1], - }); - } -}; - -pub const Material = struct { - albedo: @Vector(4, u8) = .{ 255, 255, 255, 255 }, - - pub fn format( - self: @This(), - writer: *std.Io.Writer, - ) std.Io.Writer.Error!void { - try writer.print("rgba({}, {}, {}, {})", .{ - self.albedo[0], - self.albedo[1], - self.albedo[2], - self.albedo[3], - }); - } -}; - -pub const Player = struct { - name: []const u8, - - pub fn format( - self: @This(), - writer: *std.Io.Writer, - ) std.Io.Writer.Error!void { - try writer.print("\"{s}\"", .{self.name}); - } -}; - -pub const Dead = struct { value: bool = false }; diff --git a/src/example/simple/event.zig b/src/example/simple/event.zig deleted file mode 100644 index 59b69a6..0000000 --- a/src/example/simple/event.zig +++ /dev/null @@ -1,18 +0,0 @@ -pub const Login = struct { - username: []const u8, - password: []const u8, -}; - -pub const Register = struct { - username: []const u8, - password: []const u8, -}; - -pub const Connect = struct { - ip: []const u8 = "localhost", - port: u16 = 4444, -}; - -pub const ConnectionAccepted = struct { - id: u32, -}; diff --git a/src/example/simple/plugin.zig b/src/example/simple/plugin.zig deleted file mode 100644 index 2e0273c..0000000 --- a/src/example/simple/plugin.zig +++ /dev/null @@ -1,71 +0,0 @@ -const std = @import("std"); -const App = @import("../../app.zig"); -const system = @import("system.zig"); -const stage = @import("stage.zig"); - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub const plugin: App.plugin.Plugin = .{ - .name = "Simple", - .description = "Simple plugin example", - .dependencies = &.{}, - .build = build, -}; - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn build( - self: *App.plugin.Plugin, - app: *App, -) !void { - // --- DEBUG --- - std.debug.print( - "\x1b[33mPLUGIN\x1b[0m\n" ++ - \\| Name: {s} - \\| Description: {s} - \\ - , - .{ - self.name, - self.description, - }, - ); - - // --- ADD SYSTEMS --- - try app.schedules.addMany(stage.Init, &.{ - system.test_resources, - system.test_events, - system.test_ecs, - }); - - // --- RUN BEFORE --- - try app.schedules.addBefore( - stage.Init, - stage.Init, - system.draw, - ); - - try app.schedules.addMany(stage.Deinit, &.{ - deinit_one, - deinit_two, - deinit_three, - }); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn deinit_one(_: *App) !void { - std.debug.print("---> One\n", .{}); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn deinit_two(_: *App) !void { - std.debug.print("---> Two\n", .{}); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn deinit_three(_: *App) !void { - std.debug.print("---> Three\n", .{}); -} diff --git a/src/example/simple/resource.zig b/src/example/simple/resource.zig deleted file mode 100644 index 5948d6a..0000000 --- a/src/example/simple/resource.zig +++ /dev/null @@ -1,2 +0,0 @@ -pub const Server = struct { id: u32 = 0 }; -pub const Client = struct { id: u32 = 0 }; diff --git a/src/example/simple/root.zig b/src/example/simple/root.zig deleted file mode 100644 index 30b184a..0000000 --- a/src/example/simple/root.zig +++ /dev/null @@ -1,3 +0,0 @@ -pub const plugin = @import("plugin.zig").plugin; -pub const system = @import("system.zig"); -pub const stage = @import("stage.zig"); diff --git a/src/example/simple/stage.zig b/src/example/simple/stage.zig deleted file mode 100644 index ccb11de..0000000 --- a/src/example/simple/stage.zig +++ /dev/null @@ -1,7 +0,0 @@ -pub const Init = struct {}; -pub const Deinit = struct {}; -pub const Inputs = struct {}; -pub const Update = struct {}; -pub const Draw = struct {}; - -pub const MainPass = struct {}; diff --git a/src/example/simple/system.zig b/src/example/simple/system.zig deleted file mode 100644 index 1e968db..0000000 --- a/src/example/simple/system.zig +++ /dev/null @@ -1,241 +0,0 @@ -const std = @import("std"); -const App = @import("../../app.zig"); - -const event = @import("event.zig"); -const resource = @import("resource.zig"); -const component = @import("component.zig"); - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn test_resources( - app: *App, -) !void { - // --- SET NEW --- - try app.resources.set(resource.Server{}); - try app.resources.set(resource.Client{}); - - // --- CONTAINS DEBUG --- - std.debug.print( - "\x1b[34mRESOURCES EXIST\x1b[0m\n" ++ - \\| Server: {} - \\| Client: {} - \\ - , - .{ - app.resources.contains(resource.Server), - app.resources.contains(resource.Client), - }, - ); - - // --- GET READONLY RESOURCES --- - _ = try app.resources.getMany(struct { - server: ?*const resource.Server, - client: ?*const resource.Client, - }); -} - -const User = struct { - id: u32 = 0, - username: []const u8, - password: []const u8, -}; - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn test_events( - app: *App, -) !void { - // --- DEBUG --- - std.debug.print( - "\x1b[35mEVENTS\x1b[0m\n", - .{}, - ); - - // --- CLEAR EVENTS --- - defer app.events.clear(); - - // --- USER LIST --- - var users: std.ArrayList(User) = .empty; - defer users.deinit(app.alloc); - - // --- ID`S --- - var next_register_id: u32 = 0; - var next_connected_id: u32 = 0; - - // --- ADD ADMIN USER --- - try users.append(app.alloc, .{ - .id = next_register_id, - .username = "admin", - .password = "1234", - }); - - { // NEW USER - try app.events.sendMany(.{ - event.Register{ - .username = "abux", - .password = "qwerty", - }, - event.Login{ - .username = "abux", - .password = "qwerty", - }, - }); - } - - // --- REGISTER LOOP --- - if (app.events.get(event.Register)) |queue| { - for (queue.items()) |reg| { - next_register_id += 1; - try users.append(app.alloc, .{ - .id = next_register_id, - .username = reg.username, - .password = reg.password, - }); - } - queue.clear(); - } - - // --- LOGIN LOOP --- - if (app.events.get(event.Login)) |queue| { - for (queue.items()) |login| { - for (users.items) |user| { - // --- CHECK CREDS --- - if (!std.mem.eql(u8, user.username, login.username)) continue; - if (!std.mem.eql(u8, user.password, login.password)) continue; - - // --- SEND ACCEPTED CONNECTION --- - next_connected_id += 1; - try app.events.send(event.ConnectionAccepted{ - .id = next_connected_id, - }); - } - } - queue.clear(); - } - - // --- CONNECTED --- - if (app.events.consume(event.ConnectionAccepted)) |ca| { - std.debug.print( - \\ | Connected: - \\ | ID: {} - \\ - , - .{ - ca.id, - }, - ); - } - - // --- DEBUG --- - std.debug.print( - "\x1b[31mUSERS\x1b[0m\n", - .{}, - ); - for (users.items) |user| { - std.debug.print( - \\ | ID: {} - \\ | Username: {s} - \\ | Password: {s} - \\ - , - .{ - user.id, - user.username, - user.password, - }, - ); - } -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn test_ecs( - app: *App, -) !void { - // --- SPAWN ENTITY --- - const player = try app.world.spawnEntity(.{ - component.Player{ .name = "abux" }, - component.Material{}, - component.Transform{}, - }); - try app.world.spawn(.{ - component.Material{}, - component.Transform{}, - }); - - // --- DEBUG --- - std.debug.print( - "\x1b[35mENTITIES\x1b[0m\n", - .{}, - ); - - // --- QUERY --- - var query = app.world.query(struct { - player: ?*component.Player, - material: *component.Material, - transform: *component.Transform, - }, .{}); - while (query.next()) |e| { - // --- UPDATE POS --- - e.transform.pos[0] += 1; - e.transform.pos[1] += 1; - - // --- CHANGE PLAYER'S COLOR --- - if (e.player) |_| { - e.material.albedo = .{ 20, 200, 20, 255 }; - } - - // --- ENTITIES DEBUG --- - std.debug.print("\x1b[36m", .{}); - std.debug.print( - \\| ID: {f} - \\| Components: - \\ | Player: {?f} - \\ | Material: {f} - \\ | Transform: {f} - \\ - , - .{ - query.entity(), - e.player, - e.material, - e.transform, - }, - ); - std.debug.print("\x1b[0m", .{}); - } - - if (app.world.getComponent(component.Dead, player)) |_| { - std.debug.print("------------------>\n", .{}); - } - - // try app.world.addComponents(player, .{component.Dead{}}); - try app.world.addComponents(player, .{ - component.Dead{}, - }); - - const cmps = try app.world.getComponents(struct { - mat: *component.Material, - dead: ?*component.Dead, - }, player); - - if (cmps.dead) |_| {} -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn draw( - app: *App, -) !void { - _ = app; - std.debug.print("----> DRAW\n", .{}); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn mainPass( - app: *App, -) !void { - _ = app; - std.debug.print("----> MAIN_PASS\n", .{}); -} diff --git a/src/plugin/plugin.zig b/src/plugin/plugin.zig index ba33c19..96ae82f 100644 --- a/src/plugin/plugin.zig +++ b/src/plugin/plugin.zig @@ -1,3 +1,6 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + const App = @import("../app.zig"); const Self = @This(); diff --git a/src/template/type_id.zig b/src/template/type_id.zig index 1b0df09..3ccba62 100644 --- a/src/template/type_id.zig +++ b/src/template/type_id.zig @@ -1,9 +1,8 @@ const std = @import("std"); -const root = @import("../app.zig"); pub fn of(comptime T: type) u64 { return std.hash.Wyhash.hash( - root.options.seed, + 57691, @typeName(T), ); } diff --git a/src/world/archetype.zig b/src/world/archetype.zig deleted file mode 100644 index f180657..0000000 --- a/src/world/archetype.zig +++ /dev/null @@ -1,144 +0,0 @@ -// --- LIBS --- -const std = @import("std"); -const Signature = @import("signature.zig"); -const Column = @import("column.zig"); -const Entity = @import("entity.zig"); -const TypeID = @import("../template/type_id.zig").of; -const Self = @This(); - -// -// FIELDS -// - -signature: Signature, -type_map: std.AutoHashMap(u64, usize), -entities: std.ArrayList(Entity), -columns: std.ArrayList(Column), -alloc: std.mem.Allocator, - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn init( - signature: Signature, - alloc: std.mem.Allocator, -) Self { - return .{ - .signature = signature, - .type_map = .init(alloc), - .entities = .empty, - .columns = .empty, - .alloc = alloc, - }; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn deinit(self: *Self) void { - for (self.columns.items) |*col| { - col.deinit(); - } - - self.columns.deinit(self.alloc); - self.entities.deinit(self.alloc); - self.type_map.deinit(); - - self.alloc.free(self.signature.ids); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn addColumn( - self: *Self, - comptime T: type, -) !void { - const idx = self.columns.items.len; - - try self.columns.append( - self.alloc, - try .init(T, self.alloc), - ); - - try self.type_map.put( - TypeID(T), - idx, - ); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn addColumns( - self: *Self, - components: anytype, -) !void { - inline for (components) |T| { - try self.addColumn(T); - } -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn add( - self: *Self, - entity: Entity, - components: anytype, -) !void { - const fields = std.meta.fields(@TypeOf(components)); - - if (fields.len != self.columns.items.len) { - return error.MissingComponent; - } - - // const row = self.entities.items.len; - try self.entities.append(self.alloc, entity); - - inline for (fields) |field| { - const component = @field(components, field.name); - - const T = @TypeOf(component); - - const idx = self.type_map.get(TypeID(T)) orelse { - return error.ComponentNotFound; - }; - - try self.columns.items[idx].append(&component); - } - - // for (self.columns.items) |*col| { - // if (col. != row + 1) { - // return error.RowDesync; - // } - // } -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn get( - self: *Self, - comptime T: type, - row: usize, -) ?*T { - const idx = self.type_map.get(TypeID(T)) orelse return null; - const ptr = self.columns.items[idx].getPtr(row) orelse return null; - return @ptrCast(@alignCast(ptr)); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn remove( - self: *Self, - row: usize, -) ?Entity { - const last_entity = self.entities.items[self.entities.items.len - 1]; - - _ = self.entities.swapRemove(row); - - for (self.columns.items) |*col| { - col.swapRemove(row); - } - - if (row < self.entities.items.len) { - return last_entity; - } - - return null; -} diff --git a/src/world/column.zig b/src/world/column.zig deleted file mode 100644 index 8b1bce3..0000000 --- a/src/world/column.zig +++ /dev/null @@ -1,182 +0,0 @@ -const std = @import("std"); -const TypeID = @import("../template/type_id.zig").of; -const TypedColumn = @import("column_typed.zig").TypedColumn; -const Self = @This(); - -// -// FIELDS -// - -storage: *anyopaque, - -type_id: u64, - -elem_size: usize, -alignment: usize, - -alloc: std.mem.Allocator, - -append_fn: *const fn ( - storage: *anyopaque, - value: *const anyopaque, - alloc: std.mem.Allocator, -) anyerror!void, - -swap_remove_fn: *const fn ( - storage: *anyopaque, - idx: usize, -) void, - -get_ptr_fn: *const fn ( - storage: *anyopaque, - idx: usize, -) ?*anyopaque, - -deinit_fn: *const fn ( - storage: *anyopaque, - alloc: std.mem.Allocator, -) void, - -init_fn: *const fn ( - alloc: std.mem.Allocator, -) anyerror!Self, - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn init( - comptime T: type, - alloc: std.mem.Allocator, -) !Self { - const typed = try alloc.create(TypedColumn(T)); - - typed.* = TypedColumn(T).init(); - - return .{ - .storage = typed, - - .type_id = TypeID(T), - - .elem_size = @sizeOf(T), - .alignment = @alignOf(T), - - .alloc = alloc, - - .append_fn = struct { - fn f( - storage: *anyopaque, - value: *const anyopaque, - allocator: std.mem.Allocator, - ) !void { - const col: *TypedColumn(T) = @ptrCast(@alignCast(storage)); - try col.append(value, allocator); - } - }.f, - - .swap_remove_fn = struct { - fn f( - storage: *anyopaque, - idx: usize, - ) void { - const col: *TypedColumn(T) = @ptrCast(@alignCast(storage)); - col.swapRemove(idx); - } - }.f, - - .get_ptr_fn = struct { - fn f( - storage: *anyopaque, - idx: usize, - ) ?*anyopaque { - const col: *TypedColumn(T) = @ptrCast(@alignCast(storage)); - if (idx >= col.items.items.len) { - return null; - } - return &col.items.items[idx]; - } - }.f, - - .deinit_fn = struct { - fn f( - storage: *anyopaque, - allocator: std.mem.Allocator, - ) void { - const col: *TypedColumn(T) = @ptrCast(@alignCast(storage)); - col.deinit(allocator); - allocator.destroy(col); - } - }.f, - - .init_fn = struct { - fn f(a: std.mem.Allocator) !Self { - return init(T, a); - } - }.f, - }; -} - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn deinit(self: *Self) void { - self.deinit_fn( - self.storage, - self.alloc, - ); -} - -/// ------------------------------------------ -/// Create a new empty Column of the same type -/// ------------------------------------------ -pub fn cloneEmpty(self: *Self) !Self { - return self.init_fn(self.alloc); -} - -/// ------------------------------------------ -/// Copy a single element from another column -/// Both columns must store the same concrete type -/// ------------------------------------------ -pub fn copyElement( - self: *Self, - other: *Self, - idx: usize, -) !void { - if (other.getPtr(idx)) |ptr| { - try self.append(ptr); - } -} - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn append( - self: *Self, - value: *const anyopaque, -) !void { - try self.append_fn( - self.storage, - value, - self.alloc, - ); -} - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn swapRemove( - self: *Self, - idx: usize, -) void { - self.swap_remove_fn( - self.storage, - idx, - ); -} - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn getPtr( - self: *Self, - idx: usize, -) ?*anyopaque { - return self.get_ptr_fn( - self.storage, - idx, - ); -} diff --git a/src/world/column_typed.zig b/src/world/column_typed.zig deleted file mode 100644 index 844453a..0000000 --- a/src/world/column_typed.zig +++ /dev/null @@ -1,61 +0,0 @@ -const std = @import("std"); - -/// ------------------------------------------ -/// ------------------------------------------ -pub fn TypedColumn(comptime T: type) type { - return struct { - const Self = @This(); - - // - // FIELDS - // - - items: std.ArrayList(T), - - /// ------------------------------------------ - /// ------------------------------------------ - pub fn init() @This() { - return .{ - .items = .empty, - }; - } - - /// ------------------------------------------ - /// ------------------------------------------ - pub fn deinit( - self: *Self, - alloc: std.mem.Allocator, - ) void { - self.items.deinit(alloc); - } - - /// ------------------------------------------ - /// ------------------------------------------ - pub fn append( - self: *Self, - value: *const anyopaque, - alloc: std.mem.Allocator, - ) !void { - const typed: *const T = @ptrCast(@alignCast(value)); - try self.items.append(alloc, typed.*); - } - - /// ------------------------------------------ - /// ------------------------------------------ - pub fn swapRemove( - self: *Self, - idx: usize, - ) void { - _ = self.items.swapRemove(idx); - } - - /// ------------------------------------------ - /// ------------------------------------------ - pub fn getPtr( - self: *Self, - idx: usize, - ) *anyopaque { - return &self.items.items[idx]; - } - }; -} diff --git a/src/world/entity.zig b/src/world/entity.zig deleted file mode 100644 index a5696f1..0000000 --- a/src/world/entity.zig +++ /dev/null @@ -1,12 +0,0 @@ -const std = @import("std"); - -pub const IDType = u64; - -id: IDType, - -pub fn format( - self: @This(), - writer: *std.Io.Writer, -) !void { - try writer.print("{d}", .{self.id}); -} diff --git a/src/world/entity_record.zig b/src/world/entity_record.zig deleted file mode 100644 index b2ef352..0000000 --- a/src/world/entity_record.zig +++ /dev/null @@ -1,2 +0,0 @@ -archetype_idx: usize, -row: usize, diff --git a/src/world/query.zig b/src/world/query.zig deleted file mode 100644 index 13d9bfb..0000000 --- a/src/world/query.zig +++ /dev/null @@ -1,141 +0,0 @@ -const std = @import("std"); -const World = @import("world.zig"); -const Archetype = @import("archetype.zig"); -const Entity = @import("entity.zig"); -const QueryFilters = @import("query_filters.zig"); -const TypeID = @import("../template/type_id.zig").of; - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn Query( - comptime Result: type, - comptime Filters: QueryFilters, -) type { - return struct { - const Self = @This(); - - const result_fields = std.meta.fields(Result); - - // - // FIELDS - // - - world: *World, - arch_cursor: usize, - row_cursor: usize, - - current_arch: ?*Archetype, - current_row: usize, - - /// ---------------------------------------------------- - /// ---------------------------------------------------- - pub fn init(world: *World) Self { - return .{ - .world = world, - .arch_cursor = 0, - .row_cursor = 0, - - .current_arch = null, - .current_row = 0, - }; - } - - /// ---------------------------------------------------- - /// ---------------------------------------------------- - fn matches(arch: *Archetype) bool { - // --- REQUIRED COMPONENTS --- - inline for (result_fields) |field| { - // TODO: Clean this shit up - const FieldT = field.type; - const is_optional = @typeInfo(FieldT) == .optional; - - const PtrT = if (is_optional) @typeInfo(FieldT).optional.child else FieldT; - const T = std.meta.Child(PtrT); - - if (!arch.type_map.contains(TypeID(T))) { - // --- SKIP OPTIONALS --- - if (!is_optional) { - return false; - } - } - } - - // --- WITH --- - inline for (Filters.with) |T| { - if (!arch.type_map.contains(TypeID(T))) { - return false; - } - } - - // --- WITHOUT --- - inline for (Filters.without) |T| { - if (arch.type_map.contains(TypeID(T))) { - return false; - } - } - - return true; - } - - /// ---------------------------------------------------- - /// ---------------------------------------------------- - pub fn next(self: *Self) ?Result { - while (self.arch_cursor < self.world.archetypes.items.len) { - var arch = &self.world.archetypes.items[self.arch_cursor]; - - // --- SKIP ARCHETYPES --- - if (!matches(arch)) { - self.arch_cursor += 1; - self.row_cursor = 0; - continue; - } - - // --- END ROWS --- - if (self.row_cursor >= arch.entities.items.len) { - self.arch_cursor += 1; - self.row_cursor = 0; - continue; - } - - const row = self.row_cursor; - - self.row_cursor += 1; - self.current_arch = arch; - self.current_row = row; - - var result: Result = undefined; - - inline for (result_fields) |field| { - // TODO: Clean this shit up - const FieldT = field.type; - - const is_optional = @typeInfo(FieldT) == .optional; - - const PtrT = if (is_optional) @typeInfo(FieldT).optional.child else FieldT; - - const T = std.meta.Child(PtrT); - - const ptr = arch.get(T, row); - - if (is_optional) { - @field(result, field.name) = ptr; - } else { - @field(result, field.name) = ptr orelse return null; - } - } - - return result; - } - - return null; - } - - /// ---------------------------------------------------- - /// ---------------------------------------------------- - pub fn entity(self: *const Self) Entity { - return self.current_arch.?.entities.items[ - self.current_row - ]; - } - }; -} diff --git a/src/world/query_filters.zig b/src/world/query_filters.zig deleted file mode 100644 index 4f28ecd..0000000 --- a/src/world/query_filters.zig +++ /dev/null @@ -1,2 +0,0 @@ -with: []const type = &.{}, -without: []const type = &.{}, diff --git a/src/world/root.zig b/src/world/root.zig deleted file mode 100644 index 41dc228..0000000 --- a/src/world/root.zig +++ /dev/null @@ -1,3 +0,0 @@ -pub const World = @import("world.zig"); -pub const Entity = @import("entity.zig"); -pub const Archetype = @import("archetype.zig"); diff --git a/src/world/signature.zig b/src/world/signature.zig deleted file mode 100644 index 17e98ad..0000000 --- a/src/world/signature.zig +++ /dev/null @@ -1,26 +0,0 @@ -// --- LIBS --- -const std = @import("std"); -const root = @import("../app.zig"); -const TypeID = @import("../template/type_id.zig").of; -const Self = @This(); - -// -// FIELDS -// - -ids: []u64, - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn eql(a: Self, b: Self) bool { - return std.mem.eql(u64, a.ids, b.ids); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn hash(self: Self) u64 { - return std.hash.Wyhash.hash( - root.options.seed, - std.mem.sliceAsBytes(self.ids), - ); -} diff --git a/src/world/world.zig b/src/world/world.zig deleted file mode 100644 index cfd22f3..0000000 --- a/src/world/world.zig +++ /dev/null @@ -1,401 +0,0 @@ -// --- LIBS --- -const std = @import("std"); -const Archetype = @import("archetype.zig"); -const Signature = @import("signature.zig"); -const Entity = @import("entity.zig"); -const EntityRecord = @import("entity_record.zig"); -const QueryFilters = @import("query_filters.zig"); -const Query = @import("query.zig").Query; -const TypeID = @import("../template/type_id.zig").of; -const Self = @This(); - -// -// FIELDS -// - -archetypes: std.ArrayList(Archetype), -entity_map: std.AutoHashMap(Entity, EntityRecord), -signature_map: std.AutoHashMap(u64, usize), -next_entity: Entity.IDType, -alloc: std.mem.Allocator, - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn init(alloc: std.mem.Allocator) Self { - return .{ - .archetypes = .empty, - .entity_map = .init(alloc), - .signature_map = .init(alloc), - .next_entity = 1, - .alloc = alloc, - }; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn deinit(self: *Self) void { - // --- ARCHETYPES --- - for (self.archetypes.items) |*arch| { - arch.deinit(); - } - self.entity_map.deinit(); - self.archetypes.deinit(self.alloc); - self.signature_map.deinit(); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn spawn( - self: *Self, - components: anytype, -) !void { - _ = try self.spawnEntity(components); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn addComponents( - self: *Self, - entity: Entity, - components: anytype, -) !void { - const record = self.entity_map.get(entity) orelse return; - const new_fields = std.meta.fields(@TypeOf(components)); - const old_count = self.archetypes.items[record.archetype_idx].signature.ids.len; - - // --- BUILD COMBINED SIGNATURE (dedup against existing) --- - var ids = try self.alloc.alloc(u64, old_count + new_fields.len); - defer self.alloc.free(ids); - - for (self.archetypes.items[record.archetype_idx].signature.ids, 0..) |id, i| { - ids[i] = id; - } - - var total: usize = old_count; - inline for (new_fields) |field| { - const component = @field(components, field.name); - const id = TypeID(@TypeOf(component)); - var dominated = false; - - for (ids[0..total]) |existing| { - if (existing == id) { - dominated = true; - break; - } - } - - if (!dominated) { - ids[total] = id; - total += 1; - } - } - - if (total == old_count) return; - - std.mem.sort(u64, ids[0..total], {}, std.sort.asc(u64)); - const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ids[0..total])); - - // --- FIND OR CREATE NEW ARCHETYPE --- - const new_arch_idx = - self.signature_map.get(signature_hash) orelse blk: { - const old = &self.archetypes.items[record.archetype_idx]; - const sig_ids = try self.alloc.dupe(u64, ids[0..total]); - var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc); - - // --- CLONE OLD COLUMNS --- - for (old.columns.items) |*col| { - try arch.columns.append(self.alloc, try col.cloneEmpty()); - } - - // --- COPY OLD TYPE MAPPINGS --- - var old_iter = old.type_map.iterator(); - while (old_iter.next()) |entry| { - try arch.type_map.put(entry.key_ptr.*, entry.value_ptr.*); - } - - // --- ADD NEW COLUMNS --- - inline for (new_fields) |field| { - const component = @field(components, field.name); - const T = @TypeOf(component); - var already = false; - for (old.signature.ids) |old_id| { - if (old_id == TypeID(T)) { - already = true; - break; - } - } - if (!already) try arch.addColumn(T); - } - - try self.archetypes.append(self.alloc, arch); - const idx = self.archetypes.items.len - 1; - try self.signature_map.put(signature_hash, idx); - break :blk idx; - }; - - // --- RE-FETCH --- - const old_arch = &self.archetypes.items[record.archetype_idx]; - const new_arch = &self.archetypes.items[new_arch_idx]; - - // --- COPY OLD COMPONENT VALUES --- - try new_arch.entities.append( - self.alloc, - entity, - ); - - for (old_arch.columns.items, 0..) |*old_col, i| { - if (old_col.getPtr(record.row)) |ptr| { - try new_arch.columns.items[i].append(ptr); - } - } - - // --- ADD NEW COMPONENT VALUES --- - inline for (new_fields) |field| { - const component = @field(components, field.name); - const T = @TypeOf(component); - - var already = false; - for (old_arch.signature.ids) |old_id| { - if (old_id == TypeID(T)) { - already = true; - break; - } - } - if (!already) { - const idx = new_arch.type_map.get(TypeID(T)) orelse return error.ComponentNotFound; - try new_arch.columns.items[idx].append(&component); - } - } - - // --- REMOVE FROM OLD ARCHETYPE --- - if (old_arch.remove(record.row)) |moved| { - self.entity_map.getPtr(moved).?.row = record.row; - } - - // --- UPDATE ENTITY MAP --- - self.entity_map.getPtr(entity).?.archetype_idx = new_arch_idx; - self.entity_map.getPtr(entity).?.row = new_arch.entities.items.len - 1; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn getComponent( - self: *Self, - comptime T: type, - entity: Entity, -) ?*T { - const record = self.entity_map.get(entity) orelse return null; - const arch = &self.archetypes.items[record.archetype_idx]; - return arch.get(T, record.row); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn getComponents( - self: *Self, - comptime Result: type, - entity: Entity, -) error{ComponentNotRegistered}!Result { - // --- RESULT --- - var result: Result = undefined; - - // --- GET FIELDS --- - inline for (std.meta.fields(Result)) |field| { - // --- FIELD INFO --- - const FieldType = field.type; - - switch (@typeInfo(FieldType)) { - .optional => |opt| { - const ptr_info = @typeInfo(opt.child).pointer; - @field(result, field.name) = self.getComponent(ptr_info.child, entity); - }, - - .pointer => |ptr| { - @field(result, field.name) = - self.getComponent(ptr.child, entity) orelse - return error.ComponentNotRegistered; - }, - - else => @compileError( - "getComponents fields must be *T or ?*T; field '" ++ field.name ++ "' has type '" ++ - @typeName(FieldType) ++ "'", - ), - } - } - - return result; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn hasComponent( - self: *Self, - comptime T: type, - entity: Entity, -) bool { - return self.getComponent(T, entity) != null; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn despawn( - self: *Self, - entity: Entity, -) void { - const record = self.entity_map.get(entity) orelse return; - if (self.archetypes.items[record.archetype_idx].remove(record.row)) |moved| { - self.entity_map.getPtr(moved).?.row = record.row; - } - _ = self.entity_map.remove(entity); -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -pub fn spawnEntity( - self: *Self, - components: anytype, -) !Entity { - const Components = @TypeOf(components); - - const fields = std.meta.fields(Components); - - // --- SIGNATURE --- - var ids: [fields.len]u64 = undefined; - inline for (fields, 0..) |field, i| { - const component = @field( - components, - field.name, - ); - ids[i] = TypeID(@TypeOf(component)); - } - - // --- SORT TYPE ID'S --- - std.mem.sort( - u64, - &ids, - {}, - std.sort.asc(u64), - ); - - const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(&ids)); - - // --- FIND OR CREATE ARCHETYPE --- - const archetype_idx = - self.signature_map.get(signature_hash) orelse blk: { - const sig_ids = try self.alloc.dupe(u64, &ids); - - var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc); - - // --- NEW COLUMNS --- - inline for (fields) |field| { - const component = @field(components, field.name); - try arch.addColumn(@TypeOf(component)); - } - - // --- ADD ARCHETYPE --- - try self.archetypes.append( - self.alloc, - arch, - ); - - // --- ADD SIGNATURE --- - const idx = self.archetypes.items.len - 1; - try self.signature_map.put( - signature_hash, - idx, - ); - - break :blk idx; - }; - - // --- NEW ENTITY --- - const entity = self.createEntity(); - - // --- ADD ROW --- - var arch = &self.archetypes.items[archetype_idx]; - - const row = arch.entities.items.len; - - try arch.add( - entity, - components, - ); - - // --- STORE ENTITY LOCATION --- - try self.entity_map.put(entity, .{ - .archetype_idx = archetype_idx, - .row = row, - }); - - return entity; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn createEntity(self: *Self) Entity { - const entity = Entity{ .id = self.next_entity }; - self.next_entity += 1; - return entity; -} - -/// ---------------------------------------------------- -/// ---------------------------------------------------- -fn createArchetype( - self: *Self, - comptime Components: type, -) !usize { - const fields = - std.meta.fields(Components); - - var ids = try self.alloc.alloc( - u64, - fields.len, - ); - - inline for (fields, 0..) |field, i| { - ids[i] = TypeID(field.type); - } - - const sig: Signature = .{ - .ids = ids, - }; - - var arch: Archetype = .init( - sig, - self.alloc, - ); - - inline for (fields) |field| { - try arch.addColumn( - field.type, - ); - } - - try self.archetypes.append( - self.alloc, - arch, - ); - - return self.archetypes.items.len - 1; -} - -/// ---------------------------------------------------- -/// Example: -/// ``` -/// var query = world.query(struct { -/// player: ?*Player, -/// pos: *Position, -/// vel: *Velocity, -/// }, .{}); -/// while (query.next()) |entity| { -/// if (entity.player) |player| {} -/// } -/// ``` -/// ---------------------------------------------------- -pub fn query( - self: *Self, - comptime Result: type, - comptime Filters: QueryFilters, -) Query(Result, Filters) { - return .init(self); -}