1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.entity; 13 14 import retrograde.stringid; 15 import retrograde.option; 16 import retrograde.messaging; 17 import retrograde.stringid; 18 19 import std.string; 20 import std.exception; 21 22 import poodinis; 23 24 class Entity { 25 private string _name; 26 private bool _isFinalized; 27 public uint id = 0; 28 29 public Entity parent; 30 31 private EntityComponent[StringId] _components; 32 33 public @property name() { 34 return _name; 35 } 36 37 public @property components() { 38 return _components.values; 39 } 40 41 public @property isFinalized() { 42 return _isFinalized; 43 } 44 45 this() { 46 this("Undefined"); 47 } 48 49 this(string name) { 50 this._name = name; 51 } 52 53 private void enforceNonFinalized(string message) { 54 if (isFinalized) { 55 throw new EntityIsFinalizedException(this, message); 56 } 57 } 58 59 public void addComponent(EntityComponent component) { 60 enforce!Exception(component !is null, "Passed component reference is null."); 61 enforceNonFinalized("Cannot add new component, entity is finalized. Entities will become finalized when they are added to the entity manager or by calling Entity.finalize()."); 62 auto type = component.getComponentType(); 63 _components[type] = component; 64 } 65 66 public void addComponent(EntityComponentType : EntityComponent)() { 67 TypeInfo_Class typeInfo = typeid(EntityComponentType); 68 auto component = cast(EntityComponentType) typeInfo.create(); 69 enforce!Exception(component !is null, 70 format("Error creating component of type %s. Does the component have a default constructor?", 71 typeInfo)); 72 addComponent(component); 73 } 74 75 public void removeComponent(EntityComponent component) { 76 enforce!Exception(component !is null, "Passed component reference is null."); 77 auto type = component.getComponentType(); 78 removeComponent(type); 79 } 80 81 public void removeComponent(StringId componentType) { 82 enforceNonFinalized("Cannot remove components, entity is finalized. Entities will become finalized when they are added to the entity manager or by calling Entity.finalize()."); 83 _components.remove(componentType); 84 } 85 86 public void removeComponent(EntityComponentType : EntityComponent)() { 87 StringId componentType = EntityComponentType.componentType; 88 removeComponent(componentType); 89 } 90 91 public bool hasComponent(EntityComponent component) { 92 enforce!Exception(component !is null, "Passed component reference is null."); 93 return hasComponent(component.getComponentType()); 94 } 95 96 public bool hasComponent(EntityComponentType : EntityComponent)() { 97 StringId componentType = EntityComponentType.componentType; 98 return hasComponent(componentType); 99 } 100 101 public bool hasComponent(StringId componentType) { 102 return (componentType in _components) !is null; 103 } 104 105 public void maybeWithComponent(EntityComponentType : EntityComponent)( 106 void delegate(EntityComponentType) fn) { 107 if (hasComponent!EntityComponentType) { 108 withComponent(fn); 109 } 110 } 111 112 public void withComponent(EntityComponentType : EntityComponent)( 113 void delegate(EntityComponentType) fn) { 114 fn(getComponent!EntityComponentType); 115 } 116 117 public ReturnType getFromComponent(EntityComponentType : EntityComponent, ReturnType)( 118 ReturnType delegate(EntityComponentType) fn) { 119 return fn(getComponent!EntityComponentType); 120 } 121 122 public ReturnType getFromComponent(EntityComponentType : EntityComponent, ReturnType)( 123 ReturnType delegate(EntityComponentType) fn, lazy ReturnType defaultValue) { 124 return hasComponent!EntityComponentType ? getFromComponent(fn) : defaultValue(); 125 } 126 127 public EntityComponentType getComponent(EntityComponentType : EntityComponent)() { 128 StringId componentType = EntityComponentType.componentType; 129 return cast(EntityComponentType) getComponent(componentType); 130 } 131 132 public EntityComponent getComponent(StringId componentType) { 133 auto component = componentType in _components; 134 if (component is null) { 135 throw new ComponentNotFoundException(componentType, this); 136 } 137 138 return *component; 139 } 140 141 public void clearComponents() { 142 enforceNonFinalized("Cannot clear components."); 143 _components.destroy(); 144 } 145 146 public void finalize() { 147 _isFinalized = true; 148 } 149 } 150 151 class ComponentNotFoundException : Exception { 152 this(StringId componentType, Entity sourceEntity) { 153 super(format("Component of type %s not added to entity %s(%s)", 154 componentType, sourceEntity.name, sourceEntity.id)); 155 } 156 } 157 158 class EntityIsFinalizedException : Exception { 159 this(Entity entity, string additionalMessage) { 160 super(format("Entity %s with id %s is finalized. %s", entity.name, 161 entity.id, additionalMessage)); 162 } 163 } 164 165 interface EntityComponent { 166 StringId getComponentType(); 167 string getComponentTypeString(); 168 } 169 170 interface Snapshotable { 171 string[string] getSnapshotData(); 172 } 173 174 mixin template EntityComponentIdentity(string ComponentType) { 175 import retrograde.stringid; 176 177 public static const StringId componentType = sid(ComponentType); 178 179 public StringId getComponentType() { 180 return componentType; 181 } 182 183 public string getComponentTypeString() { 184 return ComponentType; 185 } 186 } 187 188 class HierarchialEntityCollection : EntityCollection { 189 190 private Entity[][Entity] entityChildren; 191 private Entity[uint] _rootEntities; 192 193 private bool entityHierarchyChanged = false; 194 195 public @property Entity[] rootEntities() { 196 return _rootEntities.values; 197 } 198 199 public Entity[] getChildrenOfEntity(Entity entity) { 200 auto children = entity in entityChildren; 201 if (children) { 202 return *children; 203 } 204 205 return []; 206 } 207 208 public override void addEntity(Entity entity) { 209 entityHierarchyChanged = true; 210 super.addEntity(entity); 211 } 212 213 public override void removeEntity(Entity entity) { 214 entityHierarchyChanged = true; 215 super.removeEntity(entity); 216 } 217 218 public override void removeEntity(uint entityId) { 219 entityHierarchyChanged = true; 220 super.removeEntity(entityId); 221 } 222 223 public void updateHierarchy() { 224 if (entityHierarchyChanged) { 225 recreateEntityHierarchy(); 226 entityHierarchyChanged = false; 227 } 228 } 229 230 private void recreateEntityHierarchy() { 231 _rootEntities.destroy(); 232 entityChildren.destroy(); 233 foreach (entity; getAll()) { 234 if (entity.parent !is null) { 235 entityChildren[entity.parent] ~= entity; 236 if (entity.parent.parent is null) { 237 _rootEntities[entity.parent.id] = entity.parent; 238 } 239 } else { 240 _rootEntities[entity.id] = entity; 241 } 242 } 243 } 244 245 public void forEachChild(void delegate(Entity entity) fn) { 246 foreach (entity; _rootEntities.values) { 247 forEachChild(entity, fn); 248 } 249 } 250 251 private void forEachChild(Entity entity, void delegate(Entity entity) fn) { 252 auto children = entity in entityChildren; 253 if (children) { 254 foreach (child; *children) { 255 fn(child); 256 forEachChild(child, fn); 257 } 258 } 259 } 260 261 public void forEachRootEntity(void delegate(Entity entity) fn) { 262 foreach (entity; _rootEntities.values) { 263 fn(entity); 264 } 265 } 266 } 267 268 class EntityCollection { 269 private Entity[uint] entities; 270 271 public @property size_t length() { 272 return entities.length; 273 } 274 275 public void addEntity(Entity entity) { 276 assert(entity.id > 0, "Entity id has not been set (entity ids cannot be 0)"); 277 entities[entity.id] = entity; 278 } 279 280 public void removeEntity(Entity entity) { 281 removeEntity(entity.id); 282 } 283 284 public void removeEntity(uint entityId) { 285 entities.remove(entityId); 286 } 287 288 public Entity getEntity(uint entityId) { 289 return this[entityId]; 290 } 291 292 Entity opIndex(uint entityId) { 293 return entities[entityId]; 294 } 295 296 public void clearEntities() { 297 entities.destroy(); 298 } 299 300 public bool hasEntity(Entity entity) { 301 assert(entity !is null); 302 if (hasEntity(entity.id)) { 303 auto myEntity = this[entity.id]; 304 return myEntity is entity; 305 } 306 307 return false; 308 } 309 310 public bool hasEntity(uint entityId) { 311 Entity* entity = entityId in entities; 312 return entity !is null; 313 } 314 315 public Entity[] getAll() { 316 return entities.values; 317 } 318 } 319 320 class EntityManager { 321 private EntityCollection _entities = new EntityCollection(); 322 private EntityProcessor[] _processors; 323 private uint nextAvailableId = 1; 324 325 public @property entities() { 326 return _entities.getAll(); 327 } 328 329 public @property processors() { 330 return _processors; 331 } 332 333 public void addEntity(Entity entity) { 334 if (!entity.isFinalized) { 335 entity.finalize(); 336 } 337 338 entity.id = nextAvailableId++; 339 _entities.addEntity(entity); 340 foreach (processor; _processors) { 341 processor.addEntity(entity); 342 } 343 } 344 345 public void addEntityProcessors(EntityProcessor[] processors) { 346 foreach (processor; processors) { 347 addEntityProcessor(processor); 348 } 349 } 350 351 public void addEntityProcessor(EntityProcessor processor) { 352 _processors ~= processor; 353 foreach (entity; _entities.getAll()) { 354 processor.addEntity(entity); 355 } 356 } 357 358 public void initializeEntityProcessors() { 359 foreach (processor; _processors) { 360 processor.initialize(); 361 } 362 } 363 364 public void cleanupEntityProcessors() { 365 foreach (processor; _processors) { 366 processor.cleanup(); 367 } 368 } 369 370 public bool hasEntity(uint entityId) { 371 return _entities.hasEntity(entityId); 372 } 373 374 public bool hasEntity(Entity entity) { 375 return hasEntity(entity.id); 376 } 377 378 public void removeEntity(uint entityId) { 379 _entities.removeEntity(entityId); 380 foreach (processor; _processors) { 381 if (processor.hasEntity(entityId)) { 382 processor.removeEntity(entityId); 383 } 384 } 385 } 386 387 public void removeEntity(Entity entity) { 388 removeEntity(entity.id); 389 } 390 391 public void update() { 392 foreach (processor; _processors) { 393 processor.update(); 394 } 395 } 396 397 public void draw() { 398 foreach (processor; _processors) { 399 processor.draw(); 400 } 401 } 402 } 403 404 abstract class EntityProcessor { 405 protected EntityCollection _entities; 406 407 abstract public bool acceptsEntity(Entity entity); 408 409 public void initialize() { 410 } 411 412 public void update() { 413 } 414 415 public void draw() { 416 } 417 418 public void cleanup() { 419 } 420 421 public @property entityCount() { 422 return _entities.length(); 423 } 424 425 public @property entities() { 426 return _entities.getAll(); 427 } 428 429 public this() { 430 _entities = new EntityCollection(); 431 } 432 433 public void addEntity(Entity entity) { 434 enforce!Exception(entity.isFinalized, 435 "An unfinalized entity was added to entity processor. Finalize entities first using Entity.finalize()"); 436 if (!acceptsEntity(entity)) 437 return; 438 439 _entities.addEntity(entity); 440 processAcceptedEntity(entity); 441 } 442 443 protected void processAcceptedEntity(Entity entity) { 444 } 445 446 public bool hasEntity(uint entityId) { 447 return _entities.hasEntity(entityId); 448 } 449 450 public bool hasEntity(Entity entity) { 451 return hasEntity(entity.id); 452 } 453 454 public void removeEntity(uint entityId) { 455 auto entity = _entities[entityId]; 456 _entities.removeEntity(entityId); 457 processRemovedEntity(entity); 458 } 459 460 protected void processRemovedEntity(Entity entity) { 461 } 462 } 463 464 interface CreationParameters { 465 } 466 467 abstract class EntityFactory { 468 private string _entityName; 469 470 public @property entityName() { 471 return this._entityName; 472 } 473 474 this(string entityName) { 475 this._entityName = entityName; 476 } 477 478 protected Entity createBlankEntity() { 479 return new Entity(entityName); 480 } 481 482 public bool createsEntity(string entityName) { 483 return entityName == this.entityName; 484 } 485 486 public abstract Entity createEntity(CreationParameters parameters); 487 } 488 489 class EntityLifecycleMessageData : MessageData { 490 private Entity _entity; 491 492 public @property Entity entity() { 493 return _entity; 494 } 495 496 this(Entity entity) { 497 this._entity = entity; 498 } 499 } 500 501 class EntityCreationMessageData : MessageData { 502 private CreationParameters _creationParameters; 503 private string _entityName; 504 505 public @property creationParameters() { 506 return this._creationParameters; 507 } 508 509 public @property entityName() { 510 return this._entityName; 511 } 512 513 this(string entityName, CreationParameters creationParameters = null) { 514 this._entityName = entityName; 515 this._creationParameters = creationParameters; 516 } 517 } 518 519 class EntityLifecycleCommandChannel : CommandChannel { 520 public void createEntity(string entityName, CreationParameters creationParameters = null) { 521 emit(Command(EntityLifecycleCommand.createEntity, 0, 522 new EntityCreationMessageData(entityName, creationParameters))); 523 } 524 525 public void addEntity(Entity entity) { 526 emit(Command(EntityLifecycleCommand.addEntity, 0, new EntityLifecycleMessageData(entity))); 527 } 528 529 public void removeEntity(Entity entity) { 530 emit(Command(EntityLifecycleCommand.removeEntity, 0, 531 new EntityLifecycleMessageData(entity))); 532 } 533 } 534 535 class EntityLifecycleEventChannel : EventChannel { 536 } 537 538 enum EntityLifecycleCommand : StringId { 539 addEntity = sid("cmd_add_entity"), 540 removeEntity = sid("cmd_remove_entity"), 541 createEntity = sid("cmd_create_entity") 542 } 543 544 enum EntityLifecycleEvent : StringId { 545 entityAdded = sid("ev_entity_added"), 546 entityRemoved = sid("ev_entity_removed") 547 } 548 549 public void registerLifecycleDebugSids(SidMap sidMap) { 550 sidMap.add("cmd_add_entity"); 551 sidMap.add("cmd_remove_entity"); 552 sidMap.add("cmd_create_entity"); 553 sidMap.add("ev_entity_added"); 554 sidMap.add("ev_entity_removed"); 555 } 556 557 class EntityChannelManager : MessageProcessor { 558 @Autowire private EntityLifecycleEventChannel eventChannel; 559 560 @Autowire private EntityManager entityManager; 561 562 @Autowire @OptionalDependency private EntityFactory[] entitityFactories; 563 564 private EntityFactory[string] entitityFactoriesByName; 565 566 this(EntityLifecycleCommandChannel sourceChannel) { 567 super(sourceChannel); 568 } 569 570 public override void initialize() { 571 super.initialize(); 572 foreach (entityFactory; entitityFactories) { 573 entitityFactoriesByName[entityFactory.entityName] = entityFactory; 574 } 575 entitityFactories.destroy(); 576 } 577 578 protected override void handleMessage(const Command command) { 579 switch (command.type) { 580 case EntityLifecycleCommand.addEntity: 581 auto data = cast(EntityLifecycleMessageData) command.data; 582 if (data is null || data.entity is null) { 583 return; 584 } 585 586 entityManager.addEntity(data.entity); 587 eventChannel.emit(Event(EntityLifecycleEvent.entityAdded, 1, data)); 588 break; 589 590 case EntityLifecycleCommand.removeEntity: 591 auto data = cast(EntityLifecycleMessageData) command.data; 592 if (data is null || data.entity is null) { 593 return; 594 } 595 596 entityManager.removeEntity(data.entity); 597 eventChannel.emit(Event(EntityLifecycleEvent.entityRemoved, 1, data)); 598 break; 599 600 case EntityLifecycleCommand.createEntity: 601 auto data = cast(EntityCreationMessageData) command.data; 602 if (data is null || data.entityName is null) { 603 return; 604 } 605 606 auto factory = data.entityName in entitityFactoriesByName; 607 if (!factory) { 608 return; 609 } 610 611 auto entity = factory.createEntity(data.creationParameters); 612 entityManager.addEntity(entity); 613 eventChannel.emit(Event(EntityLifecycleEvent.entityAdded, 1, 614 new EntityLifecycleMessageData(entity))); 615 break; 616 617 default: 618 break; 619 } 620 } 621 }