1 /**
2  *
3  * The default class for managing a NodeList. This class creates the NodeList and adds and removes
4  * nodes to/from the list as the entities and the components in the engine change.
5  * 
6  * It uses the basic entity matching pattern of an entity system - entities are added to the list if
7  * they contain components matching all the public properties of the node class.
8  */
9 module ashd.core.componentMatchingFamily;
10 
11 import ashd.core.component: Component;
12 import ashd.core.engine   : Engine;
13 import ashd.core.entity   : Entity;
14 import ashd.core.ifamily  : IFamily;
15 import ashd.core.node     : Node;
16 import ashd.core.nodelist : NodeList;
17 import ashd.core.nodepool : NodePool;
18 
19 
20 import std.conv   : to;
21 import std.traits : BaseClassesTuple, CommonType, FieldTypeTuple, isInstanceOf;
22 
23 
24 
25 // The following templates taken from orange (Jacob Carlborg)
26 // https://github.com/jacob-carlborg/orange/blob/master/orange/util/Reflection.d
27 
28 /**
29  * Evaluates to an array of strings containing the names of the fields in the given type
30  */
31 template fieldsOf (T)
32 {
33     enum fieldsOf = fieldsOfImpl!(T, 0);
34 }
35 
36 /**
37  * Implementation for fieldsOf
38  *
39  * Returns: an array of strings containing the names of the fields in the given type
40  */
41 template fieldsOfImpl (T, size_t i)
42 {
43     static if (T.tupleof.length == 0)
44         enum fieldsOfImpl = [""];
45 
46     else static if (T.tupleof.length - 1 == i)
47         enum fieldsOfImpl = [nameOfFieldAt!(T, i)];
48 
49     else
50         enum fieldsOfImpl = nameOfFieldAt!(T, i) ~ fieldsOfImpl!(T, i + 1);
51 }
52 
53 /**
54  * Evaluates to a string containing the name of the field at given position in the given type.
55  *
56  * Params:
57  * T = the type of the class/struct
58  * position = the position of the field in the tupleof array
59  */
60 template nameOfFieldAt (T, size_t position)
61 {
62     static assert (position < T.tupleof.length, format!(`The given position "`, position, `" is greater than the number of fields (`, T.tupleof.length, `) in the type "`, T, `"`));
63 
64     enum nameOfFieldAt = __traits(identifier, T.tupleof[position]);
65 }
66 
67 // Default NodeList management class
68 public class ComponentMatchingFamily(Class): IFamily
69 {
70     private
71     {
72         Node[Entity]      mEntities;
73         string[ClassInfo] mComponents;
74         Engine            mEngine;
75         NodeList          mNodes;
76         NodePool!Class    mNodePool;
77     }
78 
79 
80     /**
81      * The constructor. Creates a ComponentMatchingFamily to provide a NodeList for the
82      * given node class.
83      * 
84      * @param nodeClass The type of node to create and manage a NodeList for.
85      * @param engine The engine that this family is managing teh NodeList for.
86      */
87     public this( Engine engine_a )
88     {
89         this.mEngine = engine_a;
90         this.init();
91     }
92 
93 
94     // Helper to determine if child is child of parent
95     private bool ChildInheritsFromParent( parent, child )( ) 
96     {
97         foreach ( k, t; BaseClassesTuple!child ) 
98         {
99             if( typeid(t) == typeid(parent) )
100                 return true;
101         }
102         return false;
103     }
104 
105     /**
106      * Initialises the class. Creates the nodelist and other tools. Analyses the node to determine
107      * what component types the node requires.
108      */
109     private void init()
110     {
111         mNodes = new NodeList();
112         mNodePool = new NodePool!Class( mComponents, this );
113 
114         // Collect the components that are in the node class managed by this family. 
115         auto fields = fieldsOf!Class;
116         foreach ( i, x; FieldTypeTuple!Class )
117         {
118             static if (is( x == class ))
119             {
120                 if ( ChildInheritsFromParent!(Component,x) )
121                 {
122                     mComponents[x.classinfo] = fields[i];
123                 }
124             }
125         }
126     }
127 
128     void resetNode( Node node_a )
129     {
130         Class node = to!Class(node_a);
131         foreach (i, x; FieldTypeTuple!Class )
132         {
133             static if (is( x == class ))
134             {
135                 if ( ChildInheritsFromParent!(Component,x) )
136                 {
137                     mixin( "node."~(fieldsOf!Class)[i]~" = null;" );
138                 }
139             }
140         }
141 
142     }
143 
144 
145     /**
146      * The nodelist managed by this family. This is a reference that remains valid always
147      * since it is retained and reused by Systems that use the list. i.e. we never recreate the list,
148      * we always modify it in place.
149      */
150     public NodeList nodeList()
151     {
152         return mNodes;
153     }
154 
155     /**
156      * Called by the engine when an entity has been added to it. We check if the entity should be in
157      * this family's NodeList and add it if appropriate.
158      */
159     public void newEntity( Entity entity_a )
160     {
161         if ( entity_a is null )
162             throw new Error( "null entity passed" );
163         this.addIfMatch( entity_a );
164     }
165     
166     /**
167      * Called by the engine when a component has been added to an entity. We check if the entity is not in
168      * this family's NodeList and should be, and add it if appropriate.
169      */
170     public void componentAddedToEntity( Entity entity_a, ClassInfo class_a )
171     {
172         if ( entity_a is null )
173             throw new Error( "null entity passed" );
174         this.addIfMatch( entity_a );
175     }
176 
177     /**
178      * If the entity is not in this family's NodeList, tests the components of the entity to see
179      * if it should be in this NodeList and adds it if so.
180      */
181     private void addIfMatch( Entity entity_a )
182     {
183         if ( entity_a !in mEntities )
184         {
185             foreach ( key, cpt; mComponents )
186             {
187                 if ( !entity_a.has( key ) )
188                 {
189                     return;
190                 }
191             }
192 
193             Class node = to!Class(mNodePool.get());
194             if ( node is null )
195                 throw new Error( "Null node returned !!!" );
196             node.entity = entity_a;
197 
198             // Ensure that all components in the node Class are initialised to the equivalent
199             // values of the components in the entity
200             foreach ( i, fieldtype; FieldTypeTuple!Class )
201             {
202                 static if (is( fieldtype == class ))
203                 {
204                     if ( ChildInheritsFromParent!(Component,fieldtype) )
205                     {
206                         mixin( "node."~(fieldsOf!Class)[i]~" = cast(fieldtype)entity_a.get( fieldtype.classinfo );" );
207                     }
208                 }
209             }
210 
211             mEntities[entity_a] = node;
212             mNodes.add( node );
213         }
214     }
215  
216     /**
217      * Called by the engine when a component has been removed from an entity. We check if the removed component
218      * is required by this family's NodeList and if so, we check if the entity is in this this NodeList and
219      * remove it if so.
220      */
221     public void componentRemovedFromEntity( Entity entity_a, ClassInfo class_a )
222     {
223         if ( class_a in mComponents )
224         {
225             removeIfMatch( entity_a );
226         }
227     }
228     
229     /**
230      * Called by the engine when an entity has been removed from it. Check if the entity is in
231      * this family's NodeList and remove it if so.
232      */
233     public void removeEntity( Entity entity_a )
234     {
235         removeIfMatch( entity_a );
236     }
237  
238     /**
239      * Removes the entity if it is in this family's NodeList.
240      */
241     private void removeIfMatch( Entity entity_a )
242     {
243         if ( entity_a in mEntities )
244         {
245             Node node = mEntities[entity_a];
246             mEntities.remove(entity_a);
247             mNodes.remove( node );
248             if( mEngine.updating )
249             {
250                 mNodePool.cache( node );
251                 mEngine.updateComplete.connect( &releaseNodePoolCache );
252             }
253             else
254             {
255                 mNodePool.dispose( node );
256             }
257         }
258     }
259 
260 
261     /**
262      * Releases the nodes that were added to the node pool during this engine update, so they can
263      * be reused.
264      */
265     private void releaseNodePoolCache()
266     {
267         mEngine.updateComplete.disconnect( &releaseNodePoolCache );
268         mNodePool.releaseCache();
269     }
270     
271     /**
272      * Removes all nodes from the NodeList.
273      */
274     public void cleanUp()
275     {
276         for( Node node = mNodes.head; node; node = node.next )
277         {
278             mEntities.remove( node.entity );
279         }
280         mNodes.removeAll();
281     }
282 
283  
284 } // class ComponentMatchingFamily
285 
286