import { Entity } from './Entity' ; import { System } from './System' ; import { Class } from '../utils/Class' ; import { Query } from './Query' ; import { Subscription } from './Subscription' ; import { Signal } from '../utils/Signal' ; /**
 * Engine represents game state, and provides entities update loop on top of systems.
 */ export class Engine { /**
     * Signal dispatches when new entity were added to engine
     */ public onEntityAdded : Signal < ( entity : Entity ) => void > = new Signal ( ) ; /**
     * Signal dispatches when entity was removed from engine
     */ public onEntityRemoved : Signal < ( entity : Entity ) => void > = new Signal ( ) ; private _entityMap : Map < number , Entity > = new Map ( ) ; private _entities : Entity [ ] = [ ] ; private _systems : System [ ] = [ ] ; private _queries : Query [ ] = [ ] ; private _subscriptions : Subscription < any > [ ] = [ ] ; private _sharedConfig : Entity = new Entity ( ) ; /**
     * Gets a list of entities added to engine
     */ public get entities ( ) : ReadonlyArray < Entity > { return Array . from ( this . _entities ) ; } /**
     * Gets a list of systems added to engine
     */ public get systems ( ) : ReadonlyArray < System > { return this . _systems ; } /**
     * Gets a list of queries added to engine
     */ public get queries ( ) : ReadonlyArray < Query > { return this . _queries ; } public constructor ( ) { this . connectEntity ( this . _sharedConfig ) ; } internal public get subscriptions ( ) : ReadonlyArray < Subscription < any > > { return this . _subscriptions ; } return { Entity } public get sharedConfig ( ) : Entity { return this . _sharedConfig ; } param entity see onEntityAdded public addEntity ( entity ) { if ( this . _entityMap . has ( entity . id ) ) return this ; this . _entities . push ( entity ) ; this . _entityMap . set ( entity . id , entity ) ; this . onEntityAdded . emit ( entity ) ; this . connectEntity ( entity ) ; return this ; } param entity see onEntityRemoved public removeEntity ( entity ) { if ( ! this . _entityMap . has ( entity . id ) ) return this ; const index = this . _entities . indexOf ( entity ) ; this . _entities . splice ( index , 1 ) ; this . _entityMap . delete ( entity . id ) ; this . onEntityRemoved . emit ( entity ) ; this . disconnectEntity ( entity ) ; return this ; } /**
     * Removes a system from engine
     * Avoid remove the system during update cycle, do it only if your sure what your are doing.
     * Note:  IterativeSystem has aware guard during update loop, if system removed - updating is being stopped.
     *
     *  param system public removeSystem ( system ) { const index = this . _systems . indexOf ( system ) ; if ( index === - 1 ) return this ; this . _systems . splice ( index , 1 ) ; system . onRemovedFromEngine ( ) ; system . setEngine ( undefined ) ; return this ; } param { number } id return { Entity | undefined } public getEntityById ( id ) { return this . _entityMap . get ( id ) ; } param systemClass public getSystem < T extends System > ( systemClass ) { return this . _systems . find ( value => value instanceof systemClass ) as T ; } /**
     * Remove all systems
     */ public removeAllSystems ( ) { const systems = this . _systems ; this . _systems = [ ] ; for ( const system of systems ) { system . onRemovedFromEngine ( ) ; } } /**
     * Remove all queries.
     * After remove all queries will be cleared.
     */ public removeAllQueries ( ) { const queries = this . _queries ; this . _queries = [ ] ; for ( const query of queries ) { this . disconnectQuery ( query ) ; query . clear ( ) ; } } /**
     * Remove all entities.
     * onEntityRemoved will be fired for every entity.
     */ public removeAllEntities ( ) { this . removeAllEntitiesInternal ( false ) ; } /**
     * Removes all entities, queries and systems.
     * All entities will be removed silently,  onEntityRemoved event will not be fired.
     * Queries will be cleared.
      public clear ( ) { this . removeAllEntitiesInternal ( true ) ; this . removeAllSystems ( ) ; this . removeAllQueries ( ) ; } param dt public update ( dt ) { for ( const system of this . _systems ) { system . update ( dt ) ; } } param query public addQuery ( query ) { this . connectQuery ( query ) ; query . matchEntities ( this . entities ) ; this . _queries [ this . _queries . length ] = query ; return this ; } param system param priority public addSystem ( system , priority = 0 ) { system . setPriority ( priority ) ; if ( this . _systems . length === 0 ) { this . _systems [ 0 ] = system ; } else { const index = this . _systems . findIndex ( value => value . priority > priority ) ; if ( index === - 1 ) { this . _systems [ this . _systems . length ] = system ; } else { this . _systems . splice ( index , 0 , system ) ; } } system . setEngine ( this ) ; system . onAddedToEngine ( ) ; return this ; } param query public removeQuery ( query ) { const index = this . _queries . indexOf ( query ) ; if ( index == - 1 ) return undefined ; this . _queries . splice ( index , 1 ) ; this . disconnectQuery ( query ) ; query . clear ( ) ; return this ; } /**
     * Subscribe to any message of the  messageType .
     * Those messages can be dispatched from any system attached to the engine
     *
     *  param { Class < T > | T } messageType param { ( value : T ) => void } handler public subscribe < T > ( messageType , handler ) { this . addSubscription ( messageType , handler ) ; } param { Class < T > } messageType param { ( value : T ) => void } handler public unsubscribe < T > ( messageType , handler ? ) { this . removeSubscription ( messageType , handler ) ; } /**
     * Unsubscribe from all type of messages
     */ public unsubscribeAll ( ) { this . _subscriptions . length = 0 ; } internal public addSubscription < T > ( messageType , handler ) { for ( const subscription of this . _subscriptions ) { if ( subscription . equals ( messageType , handler ) ) return subscription ; } const subscription = new Subscription < T > ( messageType , handler ) ; this . _subscriptions . push ( subscription ) ; return subscription ; } internal public removeSubscription < T > ( messageType , handler ) { let i = this . _subscriptions . length ; while ( -- i >= 0 ) { const subscription = this . _subscriptions [ i ] ; if ( subscription . equals ( messageType , handler ) ) { this . _subscriptions . splice ( i , 1 ) ; if ( handler !== undefined ) return ; } } } internal public dispatch < T > ( message ) { for ( const subscription of this . _subscriptions ) { if ( ( typeof subscription . messageType === 'function' && message instanceof subscription . messageType ) || message === subscription . messageType ) { subscription . handler ( message ) ; } } } private connectEntity ( entity ) { entity . onComponentAdded . connect ( this . onComponentAdded , Number . POSITIVE_INFINITY ) ; entity . onComponentRemoved . connect ( this . onComponentRemoved , Number . POSITIVE_INFINITY ) ; entity . onInvalidationRequested . connect ( this . onInvalidationRequested , Number . NEGATIVE_INFINITY ) ; } private disconnectEntity ( entity ) { entity . onComponentAdded . disconnect ( this . onComponentAdded ) ; entity . onComponentRemoved . disconnect ( this . onComponentRemoved ) ; entity . onInvalidationRequested . disconnect ( this . onInvalidationRequested ) ; } private connectQuery ( query ) { this . onEntityAdded . connect ( query . entityAdded ) ; this . onEntityRemoved . connect ( query . entityRemoved ) ; } private disconnectQuery ( query ) { this . onEntityAdded . disconnect ( query . entityAdded ) ; this . onEntityRemoved . disconnect ( query . entityRemoved ) ; } private removeAllEntitiesInternal ( silently ) { const entities = this . _entities ; this . _entities = [ ] ; this . _entityMap . clear ( ) ; for ( const entity of entities ) { if ( ! silently ) { this . onEntityRemoved . emit ( entity ) ; } this . disconnectEntity ( entity ) ; } } private onComponentAdded = < T > ( entity , component , componentClass ? ) => { this . _queries . forEach ( value => value . entityComponentAdded ( entity , component , componentClass ) ) ; } ; private onInvalidationRequested = ( entity ) => { this . _queries . forEach ( value => value . validateEntity ( entity ) ) ; } ; private onComponentRemoved = < T > ( entity , component , componentClass ? ) => { this . _queries . forEach ( value => value . entityComponentRemoved ( entity , component , componentClass ) ) ; } ; }