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