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 }