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