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