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 }