ecs: begin reworking queries
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
366abd1b26
commit
5e2ee3fed5
3 changed files with 119 additions and 81 deletions
|
|
@ -4,6 +4,7 @@ const Allocator = mem.Allocator;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const query = @import("query.zig");
|
||||||
|
|
||||||
const is_debug = builtin.mode == .Debug;
|
const is_debug = builtin.mode == .Debug;
|
||||||
|
|
||||||
|
|
@ -332,87 +333,8 @@ pub fn Entities(comptime all_components: anytype) type {
|
||||||
row_index: u32,
|
row_index: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Query = Query: {
|
/// A complex query for entities matching a given criteria
|
||||||
const namespaces = std.meta.fields(@TypeOf(all_components));
|
pub const Query = query.Query(all_components);
|
||||||
var fields: [namespaces.len]std.builtin.Type.UnionField = undefined;
|
|
||||||
inline for (namespaces, 0..) |namespace, i| {
|
|
||||||
const component_enum = std.meta.FieldEnum(namespace.type);
|
|
||||||
fields[i] = .{
|
|
||||||
.name = namespace.name,
|
|
||||||
.type = component_enum,
|
|
||||||
.alignment = @alignOf(component_enum),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// need type_info variable (rather than embedding in @Type() call)
|
|
||||||
// to work around stage 1 bug
|
|
||||||
const type_info = std.builtin.Type{
|
|
||||||
.Union = .{
|
|
||||||
.layout = .Auto,
|
|
||||||
.tag_type = std.meta.FieldEnum(@TypeOf(all_components)),
|
|
||||||
.fields = &fields,
|
|
||||||
.decls = &.{},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break :Query @Type(type_info);
|
|
||||||
};
|
|
||||||
|
|
||||||
fn fullComponentName(comptime q: Query) []const u8 {
|
|
||||||
return @tagName(q) ++ "." ++ @tagName(@field(q, @tagName(std.meta.activeTag(q))));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Iter(comptime components: []const Query) type {
|
|
||||||
return struct {
|
|
||||||
entities: *Self,
|
|
||||||
archetype_index: usize = 0,
|
|
||||||
row_index: u32 = 0,
|
|
||||||
|
|
||||||
const Iterator = @This();
|
|
||||||
|
|
||||||
pub const Entry = struct {
|
|
||||||
entity: EntityID,
|
|
||||||
|
|
||||||
pub fn unlock(e: Entry) void {
|
|
||||||
_ = e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn next(iter: *Iterator) ?Entry {
|
|
||||||
const entities = iter.entities;
|
|
||||||
|
|
||||||
// If the archetype table we're looking at does not contain the components we're
|
|
||||||
// querying for, keep searching through tables until we find one that does.
|
|
||||||
var archetype = entities.archetypes.entries.get(iter.archetype_index).value;
|
|
||||||
while (!hasComponents(archetype, components) or iter.row_index >= archetype.len) {
|
|
||||||
iter.archetype_index += 1;
|
|
||||||
iter.row_index = 0;
|
|
||||||
if (iter.archetype_index >= entities.archetypes.count()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
archetype = entities.archetypes.entries.get(iter.archetype_index).value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const row_entity_id = archetype.get(iter.entities.allocator, iter.row_index, "id", EntityID).?;
|
|
||||||
iter.row_index += 1;
|
|
||||||
return Entry{ .entity = row_entity_id };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hasComponents(storage: ArchetypeStorage, comptime components: []const Query) bool {
|
|
||||||
var archetype = storage;
|
|
||||||
if (components.len == 0) return false;
|
|
||||||
inline for (components) |component| {
|
|
||||||
if (!archetype.hasComponent(fullComponentName(component))) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query(entities: *Self, comptime components: []const Query) Iter(components) {
|
|
||||||
return Iter(components){
|
|
||||||
.entities = entities,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) !Self {
|
pub fn init(allocator: Allocator) !Self {
|
||||||
var entities = Self{ .allocator = allocator };
|
var entities = Self{ .allocator = allocator };
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub const World = @import("systems.zig").World;
|
||||||
|
|
||||||
test "inclusion" {
|
test "inclusion" {
|
||||||
std.testing.refAllDeclsRecursive(@This());
|
std.testing.refAllDeclsRecursive(@This());
|
||||||
|
std.testing.refAllDeclsRecursive(@import("query.zig"));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "example" {
|
test "example" {
|
||||||
|
|
@ -87,5 +88,7 @@ test "example" {
|
||||||
try physics.set(player2, .id, 1234);
|
try physics.set(player2, .id, 1234);
|
||||||
try physics.set(player3, .id, 1234);
|
try physics.set(player3, .id, 1234);
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Send events to modules
|
||||||
try world.send(.tick);
|
try world.send(.tick);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
113
libs/ecs/src/query.zig
Normal file
113
libs/ecs/src/query.zig
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
pub const QueryType = enum {
|
||||||
|
any,
|
||||||
|
all,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A complex query for entities matching a given criteria
|
||||||
|
pub fn Query(comptime all_components: anytype) type {
|
||||||
|
return union(QueryType) {
|
||||||
|
/// Enum matching a namespace. e.g. `.game` or `.physics2d`
|
||||||
|
pub const Namespace = std.meta.FieldEnum(@TypeOf(all_components));
|
||||||
|
|
||||||
|
/// Enum matching a component within a namespace
|
||||||
|
/// e.g. `var a: Component(.physics2d) = .location`
|
||||||
|
pub fn Component(comptime namespace: Namespace) type {
|
||||||
|
return std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slice of enums matching a component within a namespace
|
||||||
|
/// e.g. `&.{.location, .rotation}`
|
||||||
|
pub fn ComponentList(comptime namespace: Namespace) type {
|
||||||
|
return []const Component(namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tagged union of namespaces matching lists of components
|
||||||
|
/// e.g. `.physics2d = .{ .location, .rotation }`
|
||||||
|
pub const NamespaceComponent = T: {
|
||||||
|
const namespaces = std.meta.fields(Namespace);
|
||||||
|
var fields: [namespaces.len]std.builtin.Type.UnionField = undefined;
|
||||||
|
inline for (namespaces, 0..) |namespace, i| {
|
||||||
|
const ns = std.meta.stringToEnum(Namespace, namespace.name).?;
|
||||||
|
fields[i] = .{
|
||||||
|
.name = namespace.name,
|
||||||
|
.type = ComponentList(ns),
|
||||||
|
.alignment = @alignOf(ComponentList(ns)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :T @Type(.{ .Union = .{
|
||||||
|
.layout = .Auto,
|
||||||
|
.tag_type = Namespace,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = &.{},
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Matches entities with any of these components
|
||||||
|
any: []const NamespaceComponent,
|
||||||
|
|
||||||
|
/// Matches entities with all of these components
|
||||||
|
all: []const NamespaceComponent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "query" {
|
||||||
|
const Location = struct {
|
||||||
|
x: f32 = 0,
|
||||||
|
y: f32 = 0,
|
||||||
|
z: f32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Rotation = struct { degrees: f32 };
|
||||||
|
|
||||||
|
const all_components = .{
|
||||||
|
.game = .{
|
||||||
|
.name = []const u8,
|
||||||
|
},
|
||||||
|
.physics = .{
|
||||||
|
.location = Location,
|
||||||
|
.rotation = Rotation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Q = Query(all_components);
|
||||||
|
|
||||||
|
// Namespace type lets us select a single namespace.
|
||||||
|
try testing.expectEqual(@as(Q.Namespace, .game), .game);
|
||||||
|
try testing.expectEqual(@as(Q.Namespace, .physics), .physics);
|
||||||
|
|
||||||
|
// Component type lets us select a single component within a namespace.
|
||||||
|
try testing.expectEqual(@as(Q.Component(.physics), .location), .location);
|
||||||
|
try testing.expectEqual(@as(Q.Component(.game), .name), .name);
|
||||||
|
|
||||||
|
// ComponentList type lets us select multiple components within a namespace.
|
||||||
|
var x: Q.ComponentList(.physics) = &.{
|
||||||
|
.location,
|
||||||
|
.rotation,
|
||||||
|
};
|
||||||
|
_ = x;
|
||||||
|
|
||||||
|
// NamespaceComponent lets us select multiple components within multiple namespaces.
|
||||||
|
var y: []const Q.NamespaceComponent = &.{
|
||||||
|
.{ .physics = &.{ .location, .rotation } },
|
||||||
|
.{ .game = &.{.name} },
|
||||||
|
};
|
||||||
|
_ = y;
|
||||||
|
|
||||||
|
// Query matching entities with *any* of these components
|
||||||
|
var z: Q = .{ .any = &.{
|
||||||
|
.{ .physics = &.{ .location, .rotation } },
|
||||||
|
.{ .game = &.{.name} },
|
||||||
|
} };
|
||||||
|
_ = z;
|
||||||
|
|
||||||
|
// Query matching entities with *all* of these components.
|
||||||
|
var w: Q = .{ .all = &.{
|
||||||
|
.{ .physics = &.{ .location, .rotation } },
|
||||||
|
.{ .game = &.{.name} },
|
||||||
|
} };
|
||||||
|
_ = w;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue