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 /// helper class for storing components in an optimized way
15 class ComponentStorage {
16     /// basic component storage
17     public Array!Component components;
18     /// components that implement Updatable
19     public Array!Component updatable_components;
20     /// components that implement Renderable
21     public Array!Component renderable_components;
22     /// the entity manager
23     public EntityManager manager;
24 
25     /// sets up a component storage helper
26     this(EntityManager manager) {
27         this.manager = manager;
28     }
29 
30     /// attaches a component to an entity
31     public ComponentId insert(Entity entity, Component component) {
32         if (auto updatable = cast(Updatable) component) {
33             updatable_components ~= component;
34             return ComponentId(cast(size_t) updatable_components.length - 1,
35                     entity.id, ComponentType.Updatable);
36         } else if (auto renderable = cast(Renderable) component) {
37             renderable_components ~= component;
38             return ComponentId(cast(size_t) renderable_components.length - 1,
39                     entity.id, ComponentType.Renderable);
40         } else {
41             components ~= component;
42             return ComponentId(cast(size_t) components.length - 1, entity.id, ComponentType.Base);
43         }
44     }
45 
46     /// checks if an entity has a component with a matching type
47     public bool has_component(T)(Entity entity) {
48         // check all referenced components, see if any match
49         foreach (id; entity.components) {
50             auto component = get(id);
51             if (auto match = cast(T) component) {
52                 return true;
53             }
54         }
55         return false;
56     }
57 
58     /// get the internal buffer based on the referenced component type
59     private ref Array!Component get_storage(ComponentId id) {
60         switch (id.type) {
61         case ComponentType.Base:
62             return components;
63         case ComponentType.Updatable:
64             return updatable_components;
65         case ComponentType.Renderable:
66             return renderable_components;
67         default:
68             assert(0);
69         }
70     }
71 
72     /// get a component from its id reference
73     public Component get(ComponentId id) {
74         auto storage = get_storage(id);
75         return storage[id.index];
76     }
77 
78     /// get the first component in an entity matching a type
79     public T get(T)(Entity entity) {
80         // check all referenced components, see if any match
81         foreach (id; entity.components) {
82             auto component = get(id);
83             if (auto match = cast(T) component) {
84                 return match;
85             }
86         }
87         assert(0,
88                 format("no matching component (%s) was found. use has_component() to ensure that the component exists.",
89                     typeid(T).name));
90     }
91 
92     /// get all components in an entity matching a type
93     public T[] get_all(T)(Entity entity) {
94         // check all referenced components, return all matches
95         auto matches = appender!(T[]);
96         foreach (id; entity.components) {
97             auto component = get(id);
98             if (auto match = cast(T) component) {
99                 matches ~= match;
100             }
101         }
102         return matches.data;
103     }
104 
105     /// get all components in an entity
106     public Component[] get_all(Entity entity) {
107         auto list = appender!(Component[]);
108         foreach (id; entity.components) {
109             list ~= get(id);
110         }
111         return list.data;
112     }
113 
114     /// removes a component from its owner entity
115     public void remove(Entity entity, Component to_remove) {
116         // check all referenced components, see if any match, then remove
117         foreach (id; entity.components) {
118             auto component = get(id);
119             if (component == to_remove) {
120                 remove(entity, id);
121                 return; // done
122             }
123         }
124         assert(0,
125                 "no matching component was found. use has_component() to ensure that the component exists.");
126     }
127 
128     private void remove(Entity entity, ComponentId id) {
129         // import std.stdio : writefln;
130         // writefln("\nentity (%s) components(before): %s (removing %s)", entity.name, entity.components, id);
131 
132         // delete the component id from the entity
133         entity.components = entity.components.remove!(x => x == id);
134 
135         // - update storage
136         auto storage = get_storage(id);
137         // writefln("REMOVING component_type: %s AT %d", to!string(id.type), id.index);
138         // writefln("storage[%d]: %s", storage.length, storage.array);
139 
140         // empty the slot, and swap it to the end
141         assert(id.index < storage.length, format("id points to invalid position (%d) in %s storage",
142                 id.index, to!string(id.type)));
143         storage[id.index].destroy(); // cleanup
144         storage[id.index] = null; // dereference
145         auto last_slot = cast(size_t) storage.length - 1;
146         // our goal now is to make sure the last slot is null, so we can pop it off the storage
147         // check if we need to swap the slot we just nulled with the last s lot
148         // if the array is length 1, we don't need to swap: our null space is already at the end
149         // also check if the index we're removing is the last slot, in which case we've already nulled it.
150         if (storage.length > 1 && id.index != last_slot) {
151             // handle swapping our nulled slot with the last slot
152             auto tmp = storage[last_slot];
153             assert(tmp, "storage tail slot is null");
154             assert(tmp.entity, "entity in tail slot is null");
155             storage[last_slot] = storage[id.index];
156             storage[id.index] = tmp;
157             // writefln("swapped SLOT (%d) with TAIL (%d)", id.index, last_slot);
158             // find out who owns tmp, and tell them that their component has moved
159             auto other = tmp.entity;
160             // find the id that points to the old place
161             auto other_id_pos = other.components.countUntil!(x => x.index == last_slot);
162             // writefln("working with OTHER, components %s", other.components);
163             // writefln("(%s) updating COMPREF from OLDSLOT (%d) to NEWSLOT (%d)", other.name, other.components[other_id_pos].index, id.index);
164             other.components[other_id_pos].index = id.index; // point to the new place
165         }
166         // pop the last element off the array
167         storage.removeBack();
168     }
169 
170     /// destroy all components attached to an entity
171     public void destroy_all(Entity entity) {
172         while (entity.components.length > 0) {
173             remove(entity, entity.components.front);
174         }
175     }
176 }