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 }