1 /** 2 Copyright: Copyright (c) 2016-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module datadriven.entityman; 7 8 import cbor; 9 import voxelman.log; 10 import datadriven.api; 11 import datadriven.storage; 12 import datadriven.query; 13 import voxelman.container.buffer; 14 import voxelman.container.hash.set; 15 import voxelman.serialization; 16 17 private struct ComponentInfo 18 { 19 IoKey ioKey; 20 bool serializeToDb; 21 bool serializeToNet; 22 void delegate(EntityId) remove; 23 void delegate() removeAll; 24 void delegate(Buffer!ubyte*) serialize; 25 void delegate(Buffer!ubyte*, HashSet!EntityId) serializePartial; 26 void delegate(ubyte[]) deserialize; 27 void* storage; 28 29 bool isSerialized(IoStorageType storageType) { 30 final switch(storageType) { 31 case IoStorageType.database: return serializeToDb; 32 case IoStorageType.network: return serializeToNet; 33 } 34 } 35 36 ComponentStorage!C* getTypedStorage(C)() { 37 return cast(ComponentStorage!C*)storage; 38 } 39 } 40 41 enum bool isTagComponent(C) = C.tupleof.length == 0; 42 43 private struct _Totally_empty_struct {} 44 static assert(isTagComponent!_Totally_empty_struct); 45 46 template ComponentStorage(C) 47 { 48 static if(isTagComponent!C) 49 alias ComponentStorage = EntitySet; 50 else 51 alias ComponentStorage = HashmapComponentStorage!C; 52 } 53 54 struct EntityIdManager 55 { 56 private EntityId lastEntityId; // 0 is reserved 57 IoKey ioKey = IoKey("voxelman.entity.lastEntityId"); 58 59 EntityId nextEntityId() 60 { 61 return ++lastEntityId; 62 } 63 64 void load(ref PluginDataLoader loader) 65 { 66 loader.readEntryDecoded(ioKey, lastEntityId); 67 } 68 69 void save(ref PluginDataSaver saver) 70 { 71 saver.writeEntryEncoded(ioKey, lastEntityId); 72 } 73 } 74 75 /// Convenience type for centralized storage and management of entity components. 76 struct EntityManager 77 { 78 private ComponentInfo*[TypeInfo] componentInfoMap; 79 private ComponentInfo*[] componentInfoArray; 80 EntityIdManager* eidMan; 81 82 /// Before using component type in every other method, register it here. 83 /// name is used for (de)serialization. 84 void registerComponent(C)() 85 { 86 assert(typeid(C) !in componentInfoMap); 87 auto storage = new ComponentStorage!C; 88 auto info = new ComponentInfo( 89 IoKey(componentUda!C.key), 90 cast(bool)(componentUda!C.replication & Replication.toDb), 91 cast(bool)(componentUda!C.replication & Replication.toClient), 92 &storage.remove, 93 &storage.removeAll, 94 &storage.serialize, 95 &storage.serializePartial, 96 &storage.deserialize, 97 storage); 98 componentInfoMap[typeid(C)] = info; 99 componentInfoArray ~= info; 100 101 //tracef("Register component %s", *info); 102 } 103 104 auto getIoKeys() { 105 static struct IoKeyRange 106 { 107 ComponentInfo*[] array; 108 int opApply(scope int delegate(ref IoKey) del) { 109 foreach(info; array) 110 { 111 if (auto ret = del(info.ioKey)) 112 return ret; 113 } 114 return 0; 115 } 116 } 117 118 return IoKeyRange(componentInfoArray); 119 } 120 121 /// Returns pointer to the storage of components C. 122 /// Storage type depends on component type (tag or not). 123 auto getComponentStorage(C)() 124 { 125 ComponentInfo* untyped = componentInfoMap[typeid(C)]; 126 return untyped.getTypedStorage!C(); 127 } 128 129 /// Add or set list of components for entity eid. 130 void set(Components...)(EntityId eid, Components components) 131 { 132 foreach(i, C; Components) 133 { 134 static if(isTagComponent!C) 135 getComponentStorage!C().set(eid); 136 else 137 getComponentStorage!C().set(eid, components[i]); 138 } 139 } 140 141 /// Returns pointer to the component of type C. 142 /// Returns null if entity has no such component. 143 /// Works only with non-tag components. 144 C* get(C)(EntityId eid) 145 { 146 static assert (!isTagComponent!C, "Cannot use get for tag component, use has method"); 147 return getComponentStorage!C().get(eid); 148 } 149 150 /// Returns pointer to the component of type C. 151 /// Creates component first if entity had no such component. 152 /// Works only with non-tag components. 153 C* getOrCreate(C)(EntityId eid, C defVal = C.init) 154 { 155 static assert (!isTagComponent!C, "Cannot use getOrCreate for tag component"); 156 return getComponentStorage!C().getOrCreate(eid, defVal); 157 } 158 159 /// Used to check for presence of given component or tag. 160 bool has(C)(EntityId eid) 161 { 162 return cast(bool)getComponentStorage!C().get(eid); 163 } 164 165 /// Removes one component for given eid. 166 void remove(C)(EntityId eid) 167 { 168 getComponentStorage!C().remove(eid); 169 } 170 171 /// Removes all components for given eid. 172 void remove(EntityId eid) 173 { 174 foreach(info; componentInfoArray) 175 { 176 info.remove(eid); 177 } 178 } 179 180 /// Removes all components of all types. 181 void removeAll() 182 { 183 foreach(info; componentInfoArray) 184 { 185 info.removeAll(); 186 } 187 } 188 189 /// Returns query object for given set of component types for iteration with foreach. 190 /// Will pass EntityId, followed by pointers to components. Flag components are omitted. 191 /// Example: 192 /// --- 193 /// auto query = eman.query!(Position, Velocity, IsMovable); 194 /// foreach(EntityId id, Position* position, Velocity* velocity; query) 195 /// { 196 /// position.vector += velocity.vector; 197 /// } 198 /// --- 199 auto query(Components...)() 200 { 201 // generate variables for typed storages 202 mixin(genTempComponentStorages!Components); 203 // populate variables 204 foreach(i, C; Components) 205 { 206 mixin(genComponentStorageName!(ComponentStorage!C, i)) = 207 getComponentStorage!C(); 208 } 209 // construct query with storages 210 return mixin(genQueryCall!Components); 211 } 212 213 /// Serializes all component storages with given saver. 214 void save(Saver)(ref Saver saver) 215 { 216 foreach(info; componentInfoArray) { 217 if(!info.isSerialized(saver.storageType)) continue; 218 info.serialize(saver.beginWrite()); 219 saver.endWrite(info.ioKey); 220 } 221 } 222 223 void savePartial(Saver, E)(ref Saver saver, E entities) 224 { 225 foreach(info; componentInfoArray) { 226 if(!info.isSerialized(saver.storageType)) continue; 227 info.serializePartial(saver.beginWrite(), entities); 228 saver.endWrite(info.ioKey); 229 } 230 } 231 232 /// Deserializes all component storages from given loader. 233 void load(Loader)(ref Loader loader, bool removeBeforeRead = true) 234 { 235 foreach(info; componentInfoArray) { 236 if(!info.isSerialized(loader.storageType)) continue; 237 auto data = loader.readEntryRaw(info.ioKey); 238 if (removeBeforeRead) 239 info.removeAll(); 240 info.deserialize(data); 241 } 242 } 243 244 void removeSerializedComponents(IoStorageType storageType) 245 { 246 foreach(info; componentInfoArray) { 247 if(info.isSerialized(storageType)) 248 info.removeAll(); 249 } 250 } 251 } 252 253 private string genTempComponentStorages(Components...)() 254 { 255 import std.conv : to; 256 string result; 257 258 foreach(i, C; Components) 259 { 260 result ~= "ComponentStorage!(Components[" ~ i.to!string ~ "])* " ~ 261 genComponentStorageName!(ComponentStorage!C, i) ~ ";\n"; 262 } 263 264 return result; 265 } 266 267 private string genQueryCall(Components...)() 268 { 269 string result = "componentQuery("; 270 271 foreach(i, C; Components) 272 { 273 result ~= "*" ~ genComponentStorageName!(ComponentStorage!C, i) ~ ","; 274 } 275 result ~= ")"; 276 277 return result; 278 }