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                 "no matching component was found. use has_component() to ensure that the component exists.");
89     }
90 
91     /// get all components in an entity matching a type
92     public T[] get_all(T)(Entity entity) {
93         // check all referenced components, return all matches
94         auto matches = appender!(T[]);
95         foreach (id; entity.components) {
96             auto component = get(id);
97             if (auto match = cast(T) component) {
98                 matches ~= match;
99             }
100         }
101         return matches.data;
102     }
103 
104     /// get all components in an entity
105     public Component[] get_all(Entity entity) {
106         auto list = appender!(Component[]);
107         foreach (id; entity.components) {
108             list ~= get(id);
109         }
110         return list.data;
111     }
112 
113     /// removes a component from its owner entity
114     public void remove(Entity entity, Component to_remove) {
115         // check all referenced components, see if any match, then remove
116         foreach (id; entity.components) {
117             auto component = get(id);
118             if (component == to_remove) {
119                 remove(entity, id);
120                 return; // done
121             }
122         }
123         assert(0,
124                 "no matching component was found. use has_component() to ensure that the component exists.");
125     }
126 
127     private void remove(Entity entity, ComponentId id) {
128         // import std.stdio : writefln;
129         // writefln("\nentity (%s) components(before): %s (removing %s)", entity.name, entity.components, id);
130 
131         // delete the component id from the entity
132         entity.components = entity.components.remove!(x => x == id);
133 
134         // - update storage
135         auto storage = get_storage(id);
136         // writefln("REMOVING component_type: %s AT %d", to!string(id.type), id.index);
137         // writefln("storage[%d]: %s", storage.length, storage.array);
138 
139         // empty the slot, and swap it to the end
140         assert(id.index < storage.length,
141                 format("id points to invalid position (%d) in %s storage", id.index, to!string(id.type)));
142         storage[id.index].destroy(); // cleanup
143         storage[id.index] = null; // dereference
144         auto last_slot = cast(size_t) storage.length - 1;
145         // our goal now is to make sure the last slot is null, so we can pop it off the storage
146         // check if we need to swap the slot we just nulled with the last s lot
147         // if the array is length 1, we don't need to swap: our null space is already at the end
148         // also check if the index we're removing is the last slot, in which case we've already nulled it.
149         if (storage.length > 1 && id.index != last_slot) {
150             // handle swapping our nulled slot with the last slot
151             auto tmp = storage[last_slot];
152             assert(tmp, "storage tail slot is null");
153             assert(tmp.entity, "entity in tail slot is null");
154             storage[last_slot] = storage[id.index];
155             storage[id.index] = tmp;
156             // writefln("swapped SLOT (%d) with TAIL (%d)", id.index, last_slot);
157             // find out who owns tmp, and tell them that their component has moved
158             auto other = tmp.entity;
159             // find the id that points to the old place
160             auto other_id_pos = other.components.countUntil!(x => x.index == last_slot);
161             // writefln("working with OTHER, components %s", other.components);
162             // writefln("(%s) updating COMPREF from OLDSLOT (%d) to NEWSLOT (%d)", other.name, other.components[other_id_pos].index, id.index);
163             other.components[other_id_pos].index = id.index; // point to the new place
164         }
165         // pop the last element off the array
166         storage.removeBack();
167     }
168 
169     /// destroy all components attached to an entity
170     public void destroy_all(Entity entity) {
171         while (entity.components.length > 0) {
172             remove(entity, entity.components.front);
173         }
174     }
175 }