1 module re.ecs.manager;
2 
3 import re.ecs.entity;
4 import re.ecs.component;
5 import re.ecs.storage;
6 import std.algorithm;
7 import std.array;
8 
9 /// manages the entity/component system
10 class EntityManager {
11     /// list of all entities
12     public Entity[] entities;
13     /// helper to store components in an optimized way
14     public ComponentStorage storage;
15     private size_t[] entities_to_remove;
16     private size_t entity_counter;
17 
18     /// sets up the ECS
19     this() {
20         storage = new ComponentStorage(this);
21     }
22 
23     /// create a fresh entity
24     public Entity create_entity() {
25         auto nt = new Entity(this);
26         nt.initialize();
27         nt.id = entity_counter++;
28         entities ~= nt;
29         return nt;
30     }
31 
32     public Entity get_entity(string name) {
33         auto list = entities.find!(x => x.name == name);
34         if (list.length == 0) {
35             assert(0, "no matching entity was found");
36         }
37         return list[0];
38     }
39 
40     public bool has_entity(string name) {
41         return entities.any!(x => x.name == name);
42     }
43 
44     /// remove an entity
45     public void remove_entity(Entity entity) {
46         entities.remove!(x => x == entity);
47         // TODO: entity pooling
48     }
49 
50     /// keeps all the ducks in line
51     public void update() {
52         entities_to_remove = [];
53         for (size_t i = 0; i < entities.length; i++) {
54             auto nt = entities[i];
55             if (!nt.alive) {
56                 entities_to_remove ~= i;
57             }
58         }
59 
60         // remove entities
61         foreach (to_remove; entities_to_remove) {
62             entities = remove(entities, to_remove);
63         }
64     }
65 
66     /// destroy all entities and components and clean up
67     public void destroy() {
68         foreach (entity; entities) {
69             entity.destroy();
70         }
71     }
72 }
73 
74 @("ecs-basic")
75 unittest {
76     class Food : Component {
77         public bool tasty = true;
78     }
79 
80     auto ecs = new EntityManager();
81     auto nt = ecs.create_entity();
82     auto food = new Food();
83     nt.add_component(food);
84     assert(nt.has_component!Food, "component was not properly added");
85     assert(nt.get_component!Food == food, "component cannot be retrieved");
86     nt.remove_component!Food();
87     assert(!nt.has_component!Food, "component cannot be removed");
88     nt.destroy();
89     assert(!nt.alive);
90 }
91 
92 @("ecs-destroy")
93 unittest {
94     static class Thing1 : Component {
95     }
96 
97     static class Thing2 : Component {
98     }
99 
100     auto ecs = new EntityManager();
101     auto nt1 = ecs.create_entity();
102     nt1.add_component!Thing1();
103     auto nt2 = ecs.create_entity();
104     nt2.add_component!Thing2();
105 
106     ecs.destroy();
107 }
108 
109 @("ecs-test1")
110 unittest {
111     class Butter : Component {
112         public bool tasty = true;
113     }
114 
115     class Jelly : Component {
116         public int rank = 4;
117     }
118 
119     auto ecs = new EntityManager();
120     auto sandwich1 = ecs.create_entity();
121     auto sandwich2 = ecs.create_entity();
122     auto sandwich3 = ecs.create_entity();
123 
124     sandwich1.add_component(new Butter());
125     sandwich1.add_component(new Jelly());
126     assert(sandwich1.has_component!Butter);
127     assert(sandwich1.has_component!Jelly);
128 
129     sandwich2.add_component(new Butter());
130     assert(sandwich2.has_component!Butter);
131 
132     sandwich3.add_component(new Jelly());
133     assert(sandwich3.has_component!Jelly);
134 
135     sandwich1.remove_component!Butter;
136 
137     // make sure everything else is still good
138     enum msg = "component storage is unstable";
139     assert(!sandwich1.has_component!Butter, msg);
140     assert(sandwich1.has_component!Jelly, msg);
141     assert(sandwich2.has_component!Butter, msg);
142     assert(sandwich3.has_component!Jelly, msg);
143 
144     ecs.destroy();
145 }
146 
147 @("ecs-test2")
148 unittest {
149     import re.ecs : Renderable, Updatable;
150 
151     static class Brush : Component, Renderable {
152         void render() {
153         }
154 
155         void debug_render() {
156         }
157     }
158 
159     static class Control : Component {
160     }
161 
162     static class Paint : Component, Updatable {
163         void update() {
164         }
165     }
166 
167     auto ecs = new EntityManager();
168 
169     auto a1 = ecs.create_entity();
170     a1.add_component!Brush(); // R
171     a1.add_component!Control(); // B
172     a1.add_component!Paint(); // U
173 
174     ecs.destroy();
175 }