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 }