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