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 }