1 module mutils.entity; 2 3 import std.algorithm : clamp, max, min; 4 import std.format : format; 5 import std.meta; 6 import std.traits; 7 8 import mutils.container.buckets_chain; 9 import mutils.container.hash_map; 10 import mutils.container.string_intern; 11 import mutils.time : useconds; 12 import mutils.container.hash_map_tow_way; 13 14 /** 15 * EntityId No Reference 16 * Struct representing EntityId but without compile time information of EntityManager 17 * Used to bypass forward reference problems 18 **/ 19 struct EntityIdNR { 20 @disable this(this); 21 22 private StringIntern name; 23 private StableId id; 24 StringIntern triggerEventOnDestroy; 25 bool doSerialization = true; 26 int type = int.max; 27 28 StableId stableIdNoAutoAdd() { 29 return id; 30 } 31 } 32 33 bool hasComponent(Entity, Components...)() { 34 bool has = true; 35 foreach (Component; Components) { 36 enum componentNum = staticIndexOf!(Component, Fields!Entity); 37 has = has & (componentNum != -1); 38 } 39 return has; 40 } 41 42 Component* getComponent(Component, Entity)(ref Entity ent) if (!isPointer!Entity) { 43 enum componentNum = staticIndexOf!(Component, Fields!Entity); 44 static assert(componentNum != -1, "Entity don't have this component."); 45 return &ent.tupleof[componentNum]; 46 } 47 48 Component* getComponent(Component, Entity)(Entity* ent) { 49 enum componentNum = staticIndexOf!(Component, Fields!Entity); 50 static assert(componentNum != -1, "Entity don't have this component."); 51 return &ent.tupleof[componentNum]; 52 } 53 54 struct StableId { 55 private @("noserialize") ulong id; 56 } 57 58 struct EntityManager(ENTS) { 59 alias Entities = ENTS.Entities; 60 alias FromEntities = Entities; 61 alias UniqueComponents = NoDuplicates!(staticMap!(Fields, Entities)); 62 template EntitiesWithComponents(Components...) { 63 template EntityHasComponents(EEE) { 64 alias EntityHasComponents = hasComponent!(EEE, Components); 65 } 66 67 alias EntitiesWithComponents = Filter!(EntityHasComponents, FromEntities); 68 } 69 70 //Enum elements are well defined, 0->Entities[0], 1->Entities[1], 71 //Enum EntityEnumM {...} // form mixin 72 mixin(createEnumCode()); 73 alias EntityEnum = EntityEnumM; //Alias for autocompletion 74 75 enum memoryDtId = 32; 76 77 private __gshared static HashMapTwoWay!(StringIntern, EntityId*) nameIdMap; 78 private __gshared static HashMapTwoWay!(StableId, EntityId*) stableIdEntityIdMap; 79 private __gshared static ulong lastStableId = 0; 80 81 // Keep this struct in sync with EntityIdNR 82 static struct EntityId { 83 @disable this(this); 84 85 private StringIntern entityName; 86 private StableId id; 87 StringIntern triggerEventOnDestroy; 88 bool doSerialization = true; 89 EntityEnum type = EntityEnum.none; 90 91 StableId stableId() { 92 if (id.id != 0) { 93 return id; 94 } 95 lastStableId++; 96 id = StableId(lastStableId); 97 stableIdEntityIdMap.add(id, &this); 98 return id; 99 } 100 101 StableId stableIdNoAutoAdd() { 102 return id; 103 } 104 105 StringIntern name() { 106 return entityName; 107 } 108 109 void name(StringIntern newName) { 110 if (newName == entityName) { 111 return; 112 } 113 if (newName.length != 0) { 114 EntityId* otherEnt = nameIdMap.get(newName, null); 115 if (otherEnt !is null) { 116 otherEnt.entityName = StringIntern(); 117 //nameIdMap.remove(newName); 118 } 119 } 120 entityName = newName; 121 nameIdMap.add(newName, &this); 122 } 123 124 auto get(EntityType)() { 125 enum int entityEnumIndex = staticIndexOf!(EntityType, FromEntities); 126 static assert(entityEnumIndex != -1, "There is no entity like: " ~ EntityType.stringof); 127 128 assert(type == entityEnumIndex); 129 return cast(EntityType*)(cast(void*)&this + memoryDtId); 130 } 131 132 Return apply(alias fun, Return = void)() { 133 final switch (type) { 134 foreach (i, Ent; Entities) { 135 case cast(EntityEnum) i: 136 Ent* el = get!Ent; 137 if (is(Return == void)) { 138 fun(el); 139 break; 140 } else { 141 return fun(el); 142 } 143 } 144 } 145 assert(0); 146 } 147 148 Component* getComponent(Component)() { 149 static assert(staticIndexOf!(Component, UniqueComponents) != -1, 150 "No entity has such component"); 151 switch (type) { 152 foreach (uint i, Entity; Entities) { 153 enum componentNum = staticIndexOf!(Component, Fields!Entity); 154 static if (componentNum != -1) { 155 case cast(EntityEnumM) i: 156 Entity* el = cast(Entity*)(cast(void*)&this + memoryDtId); // Inline get!Entity for debug performance 157 return &el.tupleof[componentNum]; 158 } 159 } 160 default: 161 break; 162 } 163 164 assert(0, "This entity does not have this component."); 165 } 166 167 auto hasComponent(Components...)() { 168 foreach (C; Components) { 169 static assert(staticIndexOf!(C, UniqueComponents) != -1, 170 "No entity has such component"); 171 } 172 switch (type) { 173 foreach (uint i, Entity; Entities) { 174 case cast(EntityEnumM) i: 175 enum has = mutils.entity.hasComponent!(Entity, Components); 176 return has; 177 } 178 default: 179 break; 180 } 181 182 assert(0, "There is no entity represented by this EntityId enum."); //TODO log 183 //return false; 184 } 185 186 } 187 188 static struct EntityData(Ent) { 189 EntityId entityId; 190 Ent entity; 191 static assert(entity.offsetof == memoryDtId); 192 193 alias entity this; 194 } 195 196 template getEntityContainer(T) { 197 alias getEntityContainer = BucketsChain!(EntityData!(T), 64, false); 198 } 199 200 alias EntityContainers = staticMap!(getEntityContainer, Entities); 201 EntityContainers entityContainers; 202 203 // Check compile time Entites requirements 204 void checkEntities() { 205 foreach (Entity; Entities) { 206 alias Components = Fields!Entity; 207 // No duplicate components 208 static assert(Components.length == NoDuplicates!(Components) 209 .length, "Entities should have unique components."); 210 } 211 } 212 213 @disable this(this); 214 215 void initialize() { 216 foreach (Comp; UniqueComponents) { 217 static if (hasStaticMember!(Comp, "staticInitialize")) { 218 Comp.staticInitialize(); 219 } 220 } 221 } 222 223 void destroy() { 224 foreach (Comp; UniqueComponents) { 225 static if (hasStaticMember!(Comp, "staticDestroy")) { 226 Comp.staticDestroy(); 227 } 228 } 229 } 230 231 size_t length() { 232 size_t len; 233 foreach (ref con; entityContainers) { 234 len += con.length; 235 } 236 return len; 237 } 238 239 void clear() { 240 foreach (ref con; entityContainers) { 241 con.clear(); 242 } 243 stableIdEntityIdMap.clear(); 244 nameIdMap.clear(); 245 } 246 247 ref auto getContainer(EntityType)() { 248 enum int entityEnumIndex = staticIndexOf!(EntityType, FromEntities); 249 static assert(entityEnumIndex != -1, "There is no entity like: " ~ EntityType.stringof); 250 return entityContainers[entityEnumIndex]; 251 } 252 253 void update() { 254 import mutils.benchmark; 255 256 auto timeThis = TimeThis.time(); 257 foreach (i, ref con; entityContainers) { 258 foreach (ref con.ElementType el; con) { 259 el.update(); 260 } 261 } 262 263 foreach (i, ref con; entityContainers) { 264 alias EntityType = typeof(con.ElementType.entity); 265 alias TFields = Fields!EntityType; 266 foreach (Type; TFields) { 267 static if (hasMember!(Type, "updateTimely")) { 268 updateTimely!(Type)(con); 269 } 270 } 271 272 } 273 274 } 275 276 void updateTimely(Component, Container)(ref Container container) { 277 static assert(hasMember!(Component, "updateTimely")); 278 alias Entity = typeof(Container.ElementType.entity); 279 280 static size_t startTime = 0; 281 static size_t lastIndex = 0; 282 static size_t lastUnitsPerFrame = 100; 283 //static size_t sumUnitsOfWork=0; 284 285 if (startTime == 0) { 286 startTime = useconds(); 287 } 288 289 size_t currentWork; 290 auto range = getRange!(Entity)(lastIndex, container.length); 291 foreach (ref Entity ent; range) { 292 Component* comp = ent.getComponent!Component; 293 currentWork += comp.updateTimely(ent); 294 lastIndex += 1; 295 if (currentWork > lastUnitsPerFrame) { 296 break; 297 } 298 } 299 //sumUnitsOfWork+=currentWork; 300 if (lastIndex < container.length || startTime > useconds()) { 301 return; 302 } 303 size_t endTime = useconds(); 304 size_t dt = endTime - startTime; 305 306 startTime = endTime + Component.updateTimelyPeriod; 307 308 float mul = cast(float) Component.updateTimelyPeriod / dt; 309 mul = (mul - 1) * 0.5 + 1; 310 311 lastUnitsPerFrame = cast(size_t)(lastUnitsPerFrame / mul); 312 lastUnitsPerFrame = max(10, lastUnitsPerFrame); 313 314 //sumUnitsOfWork=0; 315 lastIndex = 0; 316 317 } 318 319 // Adds enitiy without calling initialize on it, the user has to do it himself 320 EntityType* addNoInitialize(EntityType, Components...)(Components components) { 321 EntityData!(EntityType)* entD = getContainer!(EntityType).add(); 322 entD.entityId.type = getEnum!EntityType; 323 324 foreach (ref comp; components) { 325 auto entCmp = getComponent!(typeof(comp))(entD.entity); 326 *entCmp = comp; 327 } 328 329 return &entD.entity; 330 } 331 332 EntityType* add(EntityType, Components...)(Components components) { 333 EntityType* ent = addNoInitialize!(EntityType)(components); 334 ent.initialize(); 335 return ent; 336 } 337 338 void remove(EntityType)(EntityType* entity) { 339 EntityId* entId = entityToEntityId(entity); 340 entity.destroy(); 341 342 if (entId.id.id != 0) { 343 stableIdEntityIdMap.remove(entId); 344 } else { 345 assert(stableIdEntityIdMap.get(entId, StableId()) == StableId()); 346 } 347 348 if (entId.entityName != StringIntern()) { 349 nameIdMap.remove(entId); 350 } else { 351 assert(nameIdMap.get(entId, StringIntern()) == StringIntern()); 352 353 } 354 getContainer!(EntityType).remove( 355 cast(EntityData!(EntityType)*)(cast(void*) entity - memoryDtId)); 356 357 } 358 359 void remove(EntityId* entityId) { 360 foreach (i, Entity; Entities) { 361 if (entityId.type == i) { 362 Entity* ent = entityId.get!Entity; 363 remove(ent); 364 return; 365 } 366 } 367 assert(0); 368 369 } 370 371 // Based on pointer of component checks its base type 372 EntityId* getEntityFromComponent(Component)(ref Component c) { 373 alias EntsWithComp = EntitiesWithComponents!(Component); 374 static assert(EntsWithComp.length != 0, "There are no entities with this component."); 375 376 foreach (Entity; EntsWithComp) { 377 auto container = &getContainer!(Entity)(); 378 foreach (ref bucket; container.buckets[]) { 379 if (!bucket.isIn(cast(container.ElementType*)&c)) { 380 continue; 381 } 382 enum componentNum = staticIndexOf!(Component, Fields!Entity); 383 Entity el; 384 enum ptrDt = el.tupleof[componentNum].offsetof; 385 Entity* ent = cast(Entity*)(cast(void*)&c - ptrDt); 386 return entityToEntityId(ent); 387 } 388 } 389 assert(0); 390 } 391 392 // When (id == 0 && makeDefault !is null ) new id is assigned and Entity is created by makeDefault function 393 EntityId* getEntityByStableId(ref StableId id, EntityId* function() makeDefault = null) { 394 assert(id.id <= lastStableId); 395 EntityId* ent = stableIdEntityIdMap.get(id, null); 396 397 if (ent == null && makeDefault !is null) { 398 ent = makeDefault(); 399 id = ent.stableId; 400 } 401 return ent; 402 } 403 404 EntityId* getEntityByName(StringIntern name) { 405 EntityId* ent = nameIdMap.get(name, null); 406 return ent; 407 } 408 409 void removeByStableId(StableId id) { 410 if (id.id == 0) { 411 return; 412 } 413 EntityId* ent = stableIdEntityIdMap.get(id, null); 414 if (ent !is null) { 415 remove(ent); 416 } 417 } 418 419 auto getRange(Entity)(size_t start, size_t end) { 420 auto container = &getContainer!Entity(); 421 assert(end <= container.length); 422 return Range!(Entity)(container, start, end); 423 } 424 425 struct Range(Entity) { 426 getEntityContainer!Entity* container; 427 size_t start; 428 size_t end; 429 430 size_t length() { 431 return end - start; 432 } 433 434 int opApply(Dg)(scope Dg dg) { 435 int result; 436 // Can be improved: skip whole containers 437 int i; 438 foreach (ref EntityData!(Entity) el; *container) { 439 scope (exit) 440 i++; 441 if (i < start) { 442 continue; 443 } 444 if (i >= end) { 445 break; 446 } 447 result = dg(el.entity); 448 if (result) 449 break; 450 } 451 return result; 452 } 453 454 } 455 456 static EntityId* entityToEntityId(EntityType)(EntityType* el) { 457 static assert(!isPointer!(EntityType), 458 "Wrong type passed. Maybe pointer to pointer was passed?"); 459 static assert(staticIndexOf!(EntityType, FromEntities) != -1, 460 "There is no entity like: " ~ EntityType.stringof); 461 EntityId* id = cast(EntityId*)(cast(void*) el - memoryDtId); 462 assert(id.type < Entities.length); 463 return id; 464 } 465 466 static string getEntityEnumName(EntityEnum type) { 467 foreach (i, Entity; Entities) { 468 if (type == i) 469 return Entity.stringof; 470 } 471 return "!unknown"; 472 } 473 ///////////////////////// 474 /////// Enum code ////// 475 ///////////////////////// 476 static EntityEnum getEnum(EntityType)() { 477 enum int entityEnumIndex = staticIndexOf!(EntityType, FromEntities); 478 static assert(entityEnumIndex != -1, "There is no entity like: " ~ EntityType.stringof); 479 return cast(EntityEnum) entityEnumIndex; 480 481 } 482 483 // Order if enum is important, indexing of objects is made out of it 484 static string createEnumCode() { 485 string code = "enum EntityEnumM:int{"; 486 foreach (i, Entity; Entities) { 487 code ~= format("_%d=%d,", i, i); 488 } 489 code ~= format("none"); 490 code ~= "}"; 491 return code; 492 } 493 494 } 495 496 unittest { 497 static int entitiesNum = 0; 498 499 static struct EntityTurrent { 500 int a; 501 502 void update() { 503 } 504 505 void initialize() { 506 entitiesNum++; 507 } 508 509 void destroy() { 510 entitiesNum--; 511 } 512 } 513 514 static struct EntityTurrent2 { 515 int a; 516 517 void update() { 518 } 519 520 void initialize() { 521 entitiesNum++; 522 } 523 524 void destroy() { 525 entitiesNum--; 526 } 527 } 528 529 static struct EntitySomething { 530 int a; 531 532 void update() { 533 534 } 535 536 void initialize() { 537 entitiesNum++; 538 } 539 540 void destroy() { 541 entitiesNum--; 542 } 543 } 544 545 static struct ENTS { 546 alias Entities = AliasSeq!(EntityTurrent, EntityTurrent2, EntitySomething); 547 } 548 549 alias TetstEntityManager = EntityManager!(ENTS); 550 551 TetstEntityManager entitiesManager; 552 entitiesManager.initialize; 553 assert(entitiesManager.getContainer!(EntityTurrent).length == 0); 554 assert(entitiesManager.getContainer!(EntityTurrent2).length == 0); 555 556 EntityTurrent* ret1 = entitiesManager.add!(EntityTurrent)(3); 557 EntityTurrent2* ret2 = entitiesManager.add!(EntityTurrent2)(); 558 559 assert(*ret1.getComponent!int == 3); 560 assert(*ret2.getComponent!int == 0); 561 562 assert(entitiesManager.getContainer!(EntityTurrent).length == 1); 563 assert(entitiesManager.getContainer!(EntityTurrent2).length == 1); 564 assert(entitiesManager.getEntityFromComponent(ret1.a) 565 .type == entitiesManager.getEnum!EntityTurrent); 566 assert(entitiesNum == 2); 567 568 entitiesManager.remove(ret1); 569 entitiesManager.remove(ret2); 570 571 assert(entitiesManager.getContainer!(EntityTurrent).length == 0); 572 assert(entitiesManager.getContainer!(EntityTurrent2).length == 0); 573 assert(entitiesNum == 0); 574 }