mach/src/ecs/main.zig
Stephen Gutekanst 56fc29743f move mach.ecs.Module -> mach.Module
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
2024-04-06 15:18:38 -07:00

117 lines
3.9 KiB
Zig

//! mach/ecs is an Entity component system implementation.
//!
//! ## Design principles:
//!
//! * Initially a 100% clean-room implementation, working from first-principles. Later informed by
//! research into how other ECS work, with advice from e.g. Bevy and Flecs authors at different
//! points (thank you!)
//! * Solve the problems ECS solves, in a way that is natural to Zig and leverages Zig comptime.
//! * Fast. Optimal for CPU caches, multi-threaded, leverage comptime as much as is reasonable.
//! * Simple. Small API footprint, should be natural and fun - not like you're writing boilerplate.
//! * Enable other libraries to provide tracing, editors, visualizers, profilers, etc.
//!
const std = @import("std");
const mach = @import("../main.zig");
const testing = std.testing;
pub const EntityID = @import("entities.zig").EntityID;
pub const Entities = @import("entities.zig").Entities;
pub const Archetype = @import("Archetype.zig");
pub const World = @import("systems.zig").World;
// TODO:
// * Iteration
// * Querying
// * Multi threading
// * Multiple entities having one value
// * Sparse storage?
test "inclusion" {
std.testing.refAllDeclsRecursive(@This());
std.testing.refAllDeclsRecursive(@import("Archetype.zig"));
std.testing.refAllDeclsRecursive(@import("entities.zig"));
std.testing.refAllDeclsRecursive(@import("query.zig"));
std.testing.refAllDeclsRecursive(@import("StringTable.zig"));
std.testing.refAllDeclsRecursive(@import("systems.zig"));
}
test "example" {
const allocator = testing.allocator;
comptime var Renderer = type;
comptime var Physics = type;
Physics = mach.Module(struct {
pointer: u8,
pub const name = .physics;
pub const components = struct {
pub const id = u32;
};
pub fn tick(physics: *World(.{ Renderer, Physics }).Mod(Physics)) !void {
_ = physics;
}
});
Renderer = mach.Module(struct {
pub const name = .renderer;
pub const components = struct {
pub const id = u16;
};
pub fn tick(
physics: *World(.{ Renderer, Physics }).Mod(Physics),
renderer: *World(.{ Renderer, Physics }).Mod(Renderer),
) !void {
_ = renderer;
_ = physics;
}
});
//-------------------------------------------------------------------------
// Create a world.
var world = try World(.{ Renderer, Physics }).init(allocator);
defer world.deinit();
// Initialize module state.
var physics = &world.mod.physics;
var renderer = &world.mod.renderer;
physics.state = .{ .pointer = 123 };
_ = physics.state.pointer; // == 123
const player1 = try physics.newEntity();
const player2 = try physics.newEntity();
const player3 = try physics.newEntity();
try physics.set(player1, .id, 1001);
try renderer.set(player1, .id, 1001);
try physics.set(player2, .id, 1002);
try physics.set(player3, .id, 1003);
//-------------------------------------------------------------------------
// Querying
var iter = world.entities.query(.{ .all = &.{
.{ .physics = &.{.id} },
} });
var archetype = iter.next().?;
var ids = archetype.slice(.physics, .id);
try testing.expectEqual(@as(usize, 2), ids.len);
try testing.expectEqual(@as(usize, 1002), ids[0]);
try testing.expectEqual(@as(usize, 1003), ids[1]);
archetype = iter.next().?;
ids = archetype.slice(.physics, .id);
try testing.expectEqual(@as(usize, 1), ids.len);
try testing.expectEqual(@as(usize, 1001), ids[0]);
// TODO: can't write @as type here easily due to generic parameter, should be exposed
// ?comp.ArchetypeSlicer(all_components)
try testing.expectEqual(iter.next(), null);
//-------------------------------------------------------------------------
// Send events to modules
try world.send(null, .tick, .{});
}