aboutsummaryrefslogtreecommitdiff
path: root/src/ecs/ecs.zig
blob: d1341bffe5f4bcfe3fd019a72b301fd5dc7802ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
const std = @import("std");
const component = @import("component.zig");
const ComponentType = component.ComponentType;

pub const EntityComponentSystem = struct {
    allocator: std.mem.Allocator,
    components: std.MultiArrayList(component.Components),
    available_ids: std.ArrayList(usize),
    next_id: usize,

    fn nullEntity(self: *EntityComponentSystem, entity: usize) void {
        inline for (std.enums.valuesFromFields(ComponentType, std.meta.fields(ComponentType))) |comp| {
            self.componentRemoveEntity(comp, entity);
        }
    }

    pub fn init(allocator: std.mem.Allocator) EntityComponentSystem {
        return EntityComponentSystem{
            .allocator = allocator,
            .components = .{},
            .available_ids = std.ArrayList(usize).init(allocator),
            .next_id = 0,
        };
    }

    pub fn deinit(self: *EntityComponentSystem) void {
        self.available_ids.deinit();
        self.components.deinit(self.allocator);
    }

    pub fn createEntity(self: *EntityComponentSystem) !usize {
        const id = self.available_ids.popOrNull() orelse noroom: {
            const id = self.next_id;
            try self.components.append(self.allocator, undefined);
            self.next_id += 1;
            break :noroom id;
        };
        self.nullEntity(id);
        return id;
    }

    pub fn componentAddEntity(
        self: *EntityComponentSystem,
        comp: component.ComponentType,
        entity: usize,
        args: anytype,
    ) !void {
        switch (comp) {
            ComponentType.component_stub => self.components.items(.component_stub)[entity] = component.ComponentStub.init(args),
        }
    }

    pub fn deleteEntity(self: *EntityComponentSystem, entity: usize) !void {
        self.nullEntity(entity);
        try self.available_ids.append(entity);
    }

    fn componentRemoveEntityGeneric(comp: anytype, entity: usize) void {
        var fixed_comp = comp[entity] orelse return;
        fixed_comp.deinit();
        comp[entity] = null;
    }

    pub fn componentRemoveEntity(
        self: *EntityComponentSystem,
        comp: component.ComponentType,
        entity: usize,
    ) void {
        var components = self.components;
        switch (comp) {
            ComponentType.component_stub => componentRemoveEntityGeneric(components.items(.component_stub), entity),
        }
    }
};

test "add entities to components" {
    var ecs = EntityComponentSystem.init(std.testing.allocator);
    defer ecs.deinit();

    for (0..100) |i| {
        const entity = try ecs.createEntity();
        try std.testing.expectEqual(i, entity);
        try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
        try std.testing.expect(ecs.components.items(.component_stub)[entity] != null);
    }
}

test "remove entities from components" {
    var ecs = EntityComponentSystem.init(std.testing.allocator);
    defer ecs.deinit();

    const entity = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
    const entity2 = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});

    try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
    ecs.componentRemoveEntity(ComponentType.component_stub, entity2);
    try std.testing.expect(ecs.components.items(.component_stub)[entity2] == null);
}

test "delete entities" {
    var ecs = EntityComponentSystem.init(std.testing.allocator);
    defer ecs.deinit();

    const entity = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
    const entity2 = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});

    try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
    try ecs.deleteEntity(entity2);
    try std.testing.expect(ecs.components.items(.component_stub)[entity2] == null);
    try std.testing.expectEqual(1, ecs.available_ids.items.len);
}

test "recycle entities" {
    var ecs = EntityComponentSystem.init(std.testing.allocator);
    defer ecs.deinit();

    const entity = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
    const entity2 = try ecs.createEntity();
    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});

    try ecs.deleteEntity(entity2);
    try std.testing.expectEqual(1, ecs.available_ids.items.len);

    const entity3 = try ecs.createEntity();
    try std.testing.expectEqual(1, entity3);
    try std.testing.expectEqual(0, ecs.available_ids.items.len);

    const entity4 = try ecs.createEntity();
    try std.testing.expectEqual(2, entity4);

    try ecs.deleteEntity(entity);
    try std.testing.expectEqual(1, ecs.available_ids.items.len);

    const entity5 = try ecs.createEntity();
    try std.testing.expectEqual(0, entity5);
    try std.testing.expectEqual(0, ecs.available_ids.items.len);

    try ecs.deleteEntity(entity5);
    try ecs.deleteEntity(entity4);
    try ecs.deleteEntity(entity3);
    try std.testing.expectEqual(3, ecs.available_ids.items.len);
}