1 /** internal engine class for optimized access of component data */
2
3 module re.ecs.storage;
4
5 import re.ecs.entity;
6 import re.ecs.component;
7 import re.ecs.manager;
8 import re.ecs.renderable;
9 import re.ecs.updatable;
10 import std.array;
11 import std.conv : to;
12 import std.string : format;
13 import std.container.array;
14 import std.algorithm;
15
16 debug {
17 import std.stdio : writefln;
18 }
19
20 /// helper class for storing components in an optimized way
21 class ComponentStorage {
22 /// basic component storage
23 public Component[] components;
24 /// components that implement Updatable
25 public Component[] updatable_components;
26 /// components that implement Renderable
27 public Component[] renderable_components;
28 /// components that implement Updatable and Renderable
29 public Component[] updatable_renderable_components;
30 /// the entity manager
31 public EntityManager manager;
32
33 /// sets up a component storage helper
34 this(EntityManager manager) {
35 this.manager = manager;
36 }
37
38 /// attaches a component to an entity
39 public ComponentId insert(Entity entity, Component component) {
40 bool is_updatable = (cast(Updatable) component) !is null;
41 bool is_renderable = (cast(Renderable) component) !is null;
42 bool is_updatable_renderable = is_updatable && is_renderable;
43
44 if (is_updatable_renderable) {
45 updatable_renderable_components ~= component;
46 return ComponentId(cast(size_t) updatable_renderable_components.length - 1,
47 entity.id, ComponentType.UpdatableRenderable);
48 } else if (is_updatable) {
49 updatable_components ~= component;
50 return ComponentId(cast(size_t) updatable_components.length - 1,
51 entity.id, ComponentType.Updatable);
52 } else if (is_renderable) {
53 renderable_components ~= component;
54 return ComponentId(cast(size_t) renderable_components.length - 1,
55 entity.id, ComponentType.Renderable);
56 } else {
57 components ~= component;
58 return ComponentId(cast(size_t) components.length - 1, entity.id, ComponentType.Base);
59 }
60 }
61
62 /// checks if an entity has a component with a matching type
63 public bool has_component(T)(Entity entity) {
64 // check all referenced components, see if any match
65 foreach (id; entity.components) {
66 auto component = get_from_id(id);
67 if (auto match = cast(T) component) {
68 return true;
69 }
70 }
71 return false;
72 }
73
74 /// get the internal buffer based on the referenced component type
75 private Component[] get_storage(ComponentId id) {
76 switch (id.type) {
77 case ComponentType.Base:
78 return components;
79 case ComponentType.Updatable:
80 return updatable_components;
81 case ComponentType.Renderable:
82 return renderable_components;
83 case ComponentType.UpdatableRenderable:
84 return updatable_renderable_components;
85 default:
86 assert(0);
87 }
88 }
89
90 private void set_storage(ComponentId id, ref Component[] buffer) {
91 switch (id.type) {
92 case ComponentType.Base:
93 components = buffer;
94 break;
95 case ComponentType.Updatable:
96 updatable_components = buffer;
97 break;
98 case ComponentType.Renderable:
99 renderable_components = buffer;
100 break;
101 case ComponentType.UpdatableRenderable:
102 updatable_renderable_components = buffer;
103 break;
104 default:
105 assert(0);
106 }
107 }
108
109 /// get a component from its id reference
110 public ref Component get_from_id(ref ComponentId id) {
111 auto storage = get_storage(id);
112 // writefln("get_from_id: %s (storage: %s)", id, storage);
113 return storage[id.index];
114 }
115
116 /// get the first component in an entity matching a type
117 public T get(T)(Entity entity) {
118 // check all referenced components, see if any match
119 foreach (id; entity.components) {
120 auto component = get_from_id(id);
121 if (auto match = cast(T) component) {
122 return match;
123 }
124 }
125 assert(0,
126 format("no matching component (%s) was found. use has_component() to ensure that the component exists.",
127 typeid(T).name));
128 }
129
130 /// get all components in an entity matching a type
131 public T[] get_all(T)(Entity entity) {
132 auto all_components = get_all(entity);
133 T[] matches;
134 for (int i = 0; i < all_components.length; i++) {
135 auto component = all_components[i];
136 // auto comp = cast(T) component;
137 // matches ~= cast(T) component;
138 // }
139 // writefln("all components 1: %s", get_all(entity));
140 if (typeid(T) is typeid(component)) {
141 auto match = (cast(T) component);
142 // writefln("all components 2: %s", get_all(entity));
143 matches ~= match;
144 // writefln("all components 3: %s", get_all(entity));
145 // writefln("match: %s", match);
146 // writefln("match ref: %s", &match);
147 // writefln("all components 4: %s", get_all(entity));
148 }
149 }
150 return matches;
151 }
152
153 /// get all components in an entity
154 public Component[] get_all(Entity entity) {
155 Component[] list;
156 foreach (ref id; entity.components) {
157 list ~= get_from_id(id);
158 }
159 return list;
160 }
161
162 /// removes a component from its owner entity
163 public void remove(Entity entity, Component to_remove) {
164 // check all referenced components, see if any match, then remove
165 foreach (id; entity.components) {
166 auto component = get_from_id(id);
167 if (component == to_remove) {
168 remove(entity, id);
169 return; // done
170 }
171 }
172 assert(0,
173 "no matching component was found. use has_component() to ensure that the component exists.");
174 }
175
176 private void remove(Entity entity, ComponentId id) {
177 // import std.stdio : writefln;
178 // writefln("\nentity (%s) components(before): %s (removing %s)", entity.name, entity.components, id);
179
180 // delete the component id from the entity
181 entity.components = entity.components.remove!(x => x == id);
182
183 // - update storage
184 auto storage = get_storage(id);
185 auto storage_type = id.type;
186 // writefln("REMOVING component_type: %s AT %d", to!string(id.type), id.index);
187 // writefln("storage[%d]: %s", storage.length, storage.array);
188
189 // empty the slot, and swap it to the end
190 assert(id.index < storage.length, format("id points to invalid position (%d) in %s storage",
191 id.index, to!string(id.type)));
192 storage[id.index].destroy(); // destroy the component
193 storage[id.index] = null; // dereference
194 auto last_slot = cast(size_t) storage.length - 1;
195 // our goal now is to make sure the last slot is null, so we can pop it off the storage
196 // check if we need to swap the slot we just nulled with the last s lot
197 // if the array is length 1, we don't need to swap: our null space is already at the end
198 // also check if the index we're removing is the last slot, in which case we've already nulled it.
199 if (storage.length > 1 && id.index != last_slot) {
200 // handle swapping our nulled slot with the last slot
201 auto tmp = storage[last_slot];
202 assert(tmp, "storage tail slot is null");
203 assert(tmp.entity, "entity in tail slot is null. this means that"
204 ~ " the component doesn't have an assocated entity");
205 storage[last_slot] = storage[id.index];
206 storage[id.index] = tmp;
207 // the entity we are removing has been swapped with the last slot
208 // writefln("swapped SLOT (%d) with TAIL (%d)", id.index, last_slot);
209 // find out who owns tmp, and tell them that their component has moved
210 auto other = tmp.entity;
211 // find the id that points to the old place
212 // to find it, it has an index pointing to the last slot (because it was swapped, so the component was previously there)
213 // we also need to be sure to be searching the correct storage type
214 auto other_id_pos = other.components.countUntil!(x => x.type == storage_type && x.index == last_slot);
215 // writefln("working with OTHER, components %s", other.components);
216 // writefln("(%s) updating COMPREF from OLDSLOT (%d) to NEWSLOT (%d)", other.name, other
217 // .components[other_id_pos].index, id.index);
218 other.components[other_id_pos].index = id.index; // point to the new place
219 // writefln("OTHER components result: %s", other.components);
220 }
221 // pop the last element off the array
222 storage = storage.remove(last_slot);
223
224 // now, we need to update the entity's componentid list
225 // so that they point to correct indices in the storage
226 // since we have shuffled around the slots, we need to update the indices
227 // in the entity's componentid list
228 for (int i = 0; i < entity.components.length; i++) {
229 }
230
231 set_storage(id, storage);
232 }
233
234 /// destroy all components attached to an entity
235 public void destroy_all(Entity entity) {
236 while (entity.components.length > 0) {
237 remove(entity, entity.components.front);
238 }
239 }
240 }
241
242 static class Thing1 : Component {
243 }
244
245 static class Thing2 : Component, Updatable {
246 void update() {
247 }
248 }
249
250 // I HAVE NO IDEA WHY THIS DOESNT WORK AND ITS FRUSTRATING ME
251
252 @("ecs-storage-basic")
253 unittest {
254 import std.string : format;
255 import std.stdio;
256
257 auto manager = new EntityManager();
258 auto storage = new ComponentStorage(manager);
259 auto nt = manager.create_entity();
260 nt.name = "testnt";
261
262 ComponentId manual_nt_add(Entity nt, Component component) {
263 auto id = storage.insert(nt, component);
264 nt.components ~= id;
265 component.entity = nt;
266 component.setup();
267 return id;
268 }
269
270 void manual_nt_remove(Entity nt, ComponentId id) {
271 storage.remove(nt, id);
272 }
273
274 // try adding stuff
275 // insert 6 things1, and 4 thing2
276 auto thing11 = manual_nt_add(nt, new Thing1());
277 auto thing12 = manual_nt_add(nt, new Thing1());
278 auto thing13 = manual_nt_add(nt, new Thing1());
279 auto thing14 = manual_nt_add(nt, new Thing1());
280 auto thing15 = manual_nt_add(nt, new Thing1());
281 auto thing16 = manual_nt_add(nt, new Thing1());
282 auto thing21 = manual_nt_add(nt, new Thing2());
283 auto thing22 = manual_nt_add(nt, new Thing2());
284 auto thing23 = manual_nt_add(nt, new Thing2());
285 auto thing24 = manual_nt_add(nt, new Thing2());
286
287 // writefln("nt comps: %s", nt.components);
288 // writefln("all components: %s", storage.get_all(nt));
289 auto thing1s = storage.get_all!Thing1(nt);
290 assert(thing1s.length == 6, format("expected 6 thing1s, got %d", thing1s.length));
291 // writefln("match thing1s passed: %s", thing1s);
292
293 auto thing2s = storage.get_all!Thing2(nt);
294 assert(thing2s.length == 4, format("expected 4 thing2s, got %d", thing2s.length));
295 // writefln("match thing2s passed: %s", thing2s);
296
297 // try removing random stuff
298 manual_nt_remove(nt, thing13);
299 manual_nt_remove(nt, thing11);
300 thing1s = storage.get_all!Thing1(nt);
301 assert(thing1s.length == 4, format("expected 4 thing1s, got %d", thing1s.length));
302 // writefln("expected-1 thing1s passed: %s", thing1s);
303
304 manual_nt_remove(nt, thing22);
305 thing2s = storage.get_all!Thing2(nt);
306 assert(thing2s.length == 3, format("expected 3 thing2s, got %d", thing2s.length));
307 // writefln("expected-2 thing2s passed: %s", thing2s);
308 }
309
310 @("ecs-storage-types")
311 unittest {
312 static class Thing1 : Component {
313 }
314
315 static class Thing2 : Component, Updatable {
316 void update() {
317 }
318 }
319
320 static class Thing3 : Component, Renderable {
321 void render() {
322 }
323
324 void debug_render() {
325 }
326 }
327
328 static class Thing4 : Component, Updatable, Renderable {
329 void update() {
330 }
331
332 void render() {
333 }
334
335 void debug_render() {
336 }
337 }
338
339 auto manager = new EntityManager();
340 auto storage = new ComponentStorage(manager);
341 auto nt = manager.create_entity();
342 nt.name = "testnt";
343
344 auto cid1 = storage.insert(nt, new Thing1());
345 auto cid2 = storage.insert(nt, new Thing2());
346 auto cid3 = storage.insert(nt, new Thing3());
347 auto cid4 = storage.insert(nt, new Thing4());
348
349 assert(cid1.type == ComponentType.Base);
350 assert(cid2.type == ComponentType.Updatable);
351 assert(cid3.type == ComponentType.Renderable);
352 assert(cid4.type == ComponentType.UpdatableRenderable);
353 }