1 /**
2  * The Engine class is the central point for creating and managing your game state. Add
3  * entities and systems to the engine, and fetch families of nodes from the engine.
4  *
5  * In essence, a family manages a NodeList. So whenever a System requests a collection of nodes, 
6  * the engine looks for a family that is managing such a collection. If it doesn't have one, it 
7  * creates a new one. Either way, it then passes the NodeList managed by the family back to the 
8  * System that requested it.
9  *
10  * The family is initialised with a node type. It uses reflection to determine what components 
11  * that node type requires. Whenever an entity is added to the engine it is passed to every 
12  * family in the engine, and the families determine whether that entity has the necessary components 
13  * to join that family. If it does then the family will add it to its NodeList. If the components 
14  * on an entity are changed, by adding or removing a component, then again the engine checks with 
15  * all the families and each family tests whether to add or remove the entity to/from its NodeList. 
16  * Finally, when an entity is removed form the engine the engine informs all the families and any 
17  * family that has the entity in its NodeList will remove it.
18  *
19  */
20 
21 module ashd.core.engine;
22 
23 import ashd.core.entity    : Entity;
24 import ashd.core.entitylist: EntityList;
25 import ashd.core.iengine   : IEngine;
26 import ashd.core.ifamily   : IFamily;
27 import ashd.core.node      : Node;
28 import ashd.core.nodelist  : NodeList;
29 import ashd.core.system    : System;
30 import ashd.core.systemlist: SystemList;
31 
32 import std.conv     : to;
33 import std.datetime : Duration;
34 import std.signals; // for Signal template. 
35 
36 
37 class Engine: IEngine
38 {
39     private
40     {
41         Entity[string]     mEntityNames;          // names of entities in engine...
42         EntityList         mEntityList;           
43         SystemList         mSystemList;
44         IFamily[ClassInfo] mFamilies;
45         bool               mUpdating;
46     }
47 
48     @property public bool updating() { return mUpdating; }
49 
50 
51     /**
52      * Dispatched when the update loop ends. If you want to add and remove systems from the
53      * engine it is usually best not to do so during the update loop. To avoid this you can
54      * listen for this signal and make the change when the signal is dispatched.
55      */
56     mixin Signal!() updateComplete;
57     
58     
59     public this()
60     {
61         mEntityList = new EntityList();
62         mSystemList = new SystemList();
63     }
64 
65 
66     /**
67      * Add an entity to the engine.
68      * 
69      * Params:
70      *   entity_a = The entity to add.
71      */
72     public void addEntity( Entity entity_a )
73     {
74         if ( entity_a.name() in mEntityNames )
75         {
76             throw new Error( "The entity name " ~ entity_a.name ~ " is already in use by another entity." );
77         }
78         mEntityList.add( entity_a );
79         mEntityNames[ entity_a.name ] = entity_a;
80         entity_a.componentAdded.connect( &this.componentAdded );
81         entity_a.componentRemoved.connect( &this.componentRemoved );
82         entity_a.nameChanged.connect( &this.entityNameChanged );
83 
84         foreach( family; mFamilies )
85         {
86             family.newEntity( entity_a );
87         }
88     }
89     
90     /**
91      * Remove an entity from the engine.
92      *
93      * Params:
94      *  entity_a =  The entity to remove.
95      */
96     public void removeEntity( Entity entity_a )
97     {
98         entity_a.componentAdded.disconnect( &this.componentAdded );
99         entity_a.componentRemoved.disconnect( &this.componentRemoved );
100         entity_a.nameChanged.disconnect( &this.entityNameChanged );
101 
102         foreach( family; mFamilies )
103         {
104             family.removeEntity( entity_a );
105         }
106 
107         mEntityNames.remove( entity_a.name );
108         mEntityList.remove( entity_a );
109     }
110 
111     // callback received when entity name changed....   
112     private void entityNameChanged( Entity entity_a, string oldName_a )
113     {
114         if ( oldName_a in mEntityNames )
115         {
116             if ( mEntityNames[ oldName_a ] == entity_a )
117             {
118                 mEntityNames.remove( oldName_a );
119                 mEntityNames[ entity_a.name ] = entity_a;
120             }
121             else
122             {
123                 throw new Error( "Entity name '" ~ oldName_a ~ "' does not match entity !" );
124             }
125         }
126         else
127         {
128             throw new Error( "Entity Name '" ~ oldName_a ~ "'does not exist !" );
129         }
130     }
131    
132     /**
133      * Get an entity based n its name.
134      * 
135      * @param name The name of the entity
136      * @return The entity, or null if no entity with that name exists on the engine
137      */
138     public Entity getEntityByName( string name_a )
139     {
140         if ( name_a in mEntityNames )
141             return mEntityNames[ name_a ];
142         else
143             return null;
144     }
145     
146     /**
147      * Remove all entities from the engine.
148      */
149     public void removeAllEntities()
150     {
151         while( mEntityList.head )
152         {
153             this.removeEntity( mEntityList.head );
154         }
155     }
156 
157 
158     /**
159      * Returns a vector containing all the entities in the engine.
160      */
161     public Entity[] entities()
162     {
163         Entity[] entities;
164         for ( Entity entity = mEntityList.head; entity; entity = entity.next )
165         {
166             entities ~= entity;
167         }
168         return entities;
169     }
170 
171 
172     /*
173      *  Callback received whenever component added to entity
174      */
175     private void componentAdded( Entity entity_a, ClassInfo cpt_a )
176     {
177         foreach( family; mFamilies )
178             family.componentAddedToEntity( entity_a, cpt_a );
179     }
180     
181     /*
182      * Callback received whenever component removed from an entity
183      */
184     private void componentRemoved( Entity entity_a, ClassInfo cpt_a )
185     {
186         foreach( family; mFamilies )
187             family.componentRemovedFromEntity( entity_a, cpt_a );
188     }
189    
190     public void registerFamily(N)( IFamily family_a )
191     {
192         if (N.classinfo in mFamilies)
193             throw new Error( "Family '" ~ N.stringof ~ "' already registered" );
194 
195         mFamilies[N.classinfo] = family_a;
196     }
197 
198     /**
199      * Get a collection of nodes from the engine, based on the type of the node required.
200      * 
201      * <p>The engine will create the appropriate NodeList if it doesn't already exist and 
202      * will keep its contents up to date as entities are added to and removed from the
203      * engine.</p>
204      * 
205      * <p>If a NodeList is no longer required, release it with the releaseNodeList method.</p>
206      * 
207      * @param nodeClass The type of node required.
208      * RETURNS:
209      *  A linked list of all nodes of this type from all entities in the engine.
210      */
211     public NodeList getNodeList( ClassInfo nodeType_a, IFamily delegate() createInstance_a=null )
212     {
213         if ( nodeType_a in mFamilies )
214         {
215             return mFamilies[nodeType_a].nodeList;
216         }
217 
218         if (createInstance_a is null)
219             throw new Error( "no family registered for '" ~ to!string(nodeType_a) ~ "'");
220         IFamily family = createInstance_a();
221 
222         mFamilies[nodeType_a] = family;
223 
224         for( Entity entity = mEntityList.head; entity; entity = entity.next )
225         {
226             family.newEntity( entity );
227         }
228         return family.nodeList;
229 
230     }
231     ///
232     public NodeList getNodeList(N)( IFamily delegate() createInstance_a=null )
233     {
234         return this.getNodeList( N.classinfo, createInstance_a );
235     }
236    
237     /**
238      * If a NodeList is no longer required, this method will stop the engine updating
239      * the list and will release all references to the list within the framework
240      * classes, enabling it to be garbage collected.
241      * 
242      * <p>It is not essential to release a list, but releasing it will free
243      * up memory and processor resources.</p>
244      * 
245      * @param nodeClass The type of the node class if the list to be released.
246      */
247     public void releaseNodeList(N)()
248     {
249         if ( N.classinfo in mFamilies )
250         {
251             mFamilies[N.classinfo].cleanUp();
252             mFamilies.remove(N.classinfo);
253         }
254     }
255 
256     /**
257      * Add a system to the engine, and set its priority for the order in which the
258      * systems are updated by the engine update loop.
259      * 
260      * The priority dictates the order in which the systems are updated by the engine update 
261      * loop. Lower numbers for priority are updated first. i.e. a priority of 1 is 
262      * updated before a priority of 2.
263      *
264      * Params:
265      *  system_a =  The system to add to the engine.
266      *  priority_a = priority The priority for updating the systems during the engine loop. A 
267      * lower number means the system is updated sooner.
268      */
269     public void addSystem(T)( ref T system_a, int priority_a )
270     {
271         system_a.priority = priority_a;
272         system_a.type = T.classinfo;
273         system_a.addToEngine( this );
274         mSystemList.add( system_a );
275     }
276 
277     public void addSystem( ref System system_a, ClassInfo class_a, int priority_a )
278     {
279         system_a.priority = priority_a;
280         system_a.type = class_a;
281         system_a.addToEngine( this );
282         mSystemList.add( system_a );
283     }
284 
285 
286 
287     /**
288      * Get the system instance of a particular type from within the engine.
289      * 
290      * Params:
291      *  T = system type
292      * Returns:
293      *  The instance of the system type that is in the engine, or
294      *  null if no systems of this type are in the engine.
295      */
296     public System getSystem(T)()
297     {
298         return mSystemList.get!T();
299     }
300 
301     /**
302      * Returns a vector containing all the systems in the engine.
303      */
304     @property public System[] systems()
305     {
306         System[] systems;
307         for ( System system = mSystemList.head; system; system = system.next )
308         {
309             systems ~= system;
310         }
311 
312         return systems;
313     }
314 
315 
316     /**
317      * Remove a system from the engine.
318      * 
319      * @param system The system to remove from the engine.
320      */
321     public void removeSystem( System system_a )
322     {
323         mSystemList.remove( system_a );
324         system_a.removeFromEngine( this );
325     }
326     
327     /**
328      * Remove all systems from the engine.
329      */
330     public void removeAllSystems()
331     {
332         while( mSystemList.head )
333         {
334             removeSystem( mSystemList.head );
335         }
336     }
337 
338     /**
339      * Update the engine. This causes the engine update loop to run, calling update on all the
340      * systems in the engine.
341      * 
342      * Params:
343      *  time_a = The duration of this update step.
344      */
345     public void update( Duration time_a )
346     {
347         mUpdating = true;
348         for ( System system = mSystemList.head; system; system = system.next )
349         {
350             system.update( time_a );
351         }
352         mUpdating = false;
353         updateComplete.emit();
354     }
355 } // class Engine