let NOW ; if ( typeof ( self ) === 'undefined' && typeof ( process ) !== 'undefined' && process . hrtime ) { NOW = function ( ) { var time = process . hrtime ( ) ; return time [ 0 ] * 1000 + time [ 1 ] / 1000000 ; } ; } else if ( typeof ( self ) !== 'undefined' && self . performance !== undefined && self . performance . now !== undefined ) { NOW = self . performance . now . bind ( self . performance ) ; } else if ( Date . now !== undefined ) { NOW = Date . now ; } else { NOW = function ( ) { return new Date ( ) . getTime ( ) ; } ; } let SEQ_SYSTEM = 1 ; let SEQ_ENTITY = 1 ; let SEQ_COMPONENT = 1 ; /**
 * Utility class for asynchronous access to a list
 */ export class Iterator < T > { private end = false ; private cache : T [ ] = [ ] ; private next : ( i : number ) => T | void ; constructor ( next ) { this . next = next ; } param cb each ( cb ) { let index = 0 ; while ( true ) { let value ; if ( this . cache . length <= index ) { if ( this . end ) { break ; } value = this . next ( index ++ ) ; if ( value === undefined ) { this . end = true ; break ; } this . cache . push ( value ) ; } else { value = this . cache [ index ++ ] ; } if ( cb ( value ) === false ) { break ; } } } param test find ( test ) { let out = undefined ; this . each ( ( item ) => { if ( test ( item ) ) { out = item ; return false ; } } ) ; return out ; } param test filter ( test ) { let list = [ ] ; this . each ( ( item ) => { if ( test ( item ) ) { list . push ( item ) ; } } ) ; return list ; } param cb map < P > ( cb ) { let list = [ ] ; this . each ( ( item ) => { list . push ( cb ( item ) ) ; } ) ; return list ; } } export type Susbcription = ( entity : Entity , added ? : Component < any > , removed ? : Component < any > ) => void ; /**
 * Representation of an entity in ECS
 */ export abstract class Entity { /**
     * Lista de interessados sobre a atualiação dos componentes
     */ private subscriptions : Array < Susbcription > = [ ] ; /**
     * Components by type
     */ private components : { [ key ] : Component < any > [ ] ; } = { } ; public id : number ; /**
     * Informs if the entity is active
     */ public active : boolean = true ; constructor ( ) { this . id = SEQ_ENTITY ++ ; } param handler public subscribe ( handler ) { this . subscriptions . push ( handler ) ; return ( ) => { const idx = this . subscriptions . indexOf ( handler ) ; if ( idx >= 0 ) { this . subscriptions . splice ( idx , 1 ) ; } return this ; } ; } param component public add ( component ) { const type = component . type ; if ( ! this . components [ type ] ) { this . components [ type ] = [ ] ; } if ( this . components [ type ] . indexOf ( component ) >= 0 ) { return ; } this . components [ type ] . push ( component ) ; this . subscriptions . forEach ( cb => cb ( this , component , undefined ) ) ; } param component public remove ( component ) { const type = component . type ; if ( ! this . components [ type ] ) { return ; } const idx = this . components [ type ] . indexOf ( component ) ; if ( idx >= 0 ) { this . components [ type ] . splice ( idx , 1 ) ; if ( this . components [ type ] . length < 1 ) { delete this . components [ type ] ; } this . subscriptions . forEach ( cb => cb ( this , undefined , component ) ) ; } } } /**
 * Force typing
 */ export type ComponentClassType < P > = ( new ( data ) => Component < P > ) & { /**
     * Static reference to type id
     */ type : number ; param entity allFrom ( entity ) : Component < P > [ ] ; param entity oneFrom ( entity ) : Component < P > ; } ; /**
 * Representation of a component in ECS
 */ export abstract class Component < T > { /**
     * Register a new component class
     */ public static register < P > ( ) { const typeID = SEQ_COMPONENT ++ ; class ComponentImpl extends Component < P > { static type = typeID ; static allFrom ( entity ) { let components = ( entity as any ) . components [ typeID ] ; return components || [ ] ; } static oneFrom ( entity ) { let components = ComponentImpl . allFrom ( entity ) ; if ( components && components . length > 0 ) { return components [ 0 ] ; } } param data constructor ( data ) { super ( typeID , data ) ; } } return ( ComponentImpl as any ) as ComponentClassType < P > ; } public type : number ; public data : T ; /**
     * A component can have attributes. Attributes are secondary values used to save miscellaneous data required by some
     * specialized systems.
     */ public attr : { [ key ] : any ; } = { } ; constructor ( type , data ) { this . type = type ; this . data = data ; } } /**
 * System callback
 */ export type EventCallback = ( data : any , entities : Iterator < Entity > ) => void ; /**
 * Represents the logic that transforms component data of an entity from its current state to its next state. A system
 * runs on entities that have a specific set of component types.
 */ export abstract class System { /**
     * IDs of the types of components this system expects the entity to have before it can act on. If you want to
     * create a system that acts on all entities, enter [-1]
     */ private readonly componentTypes : number [ ] = [ ] ; private readonly callbacks : { [ key ] : Array < EventCallback > ; } = { } ; /**
     * Unique identifier of an instance of this system
     */ public readonly id : number ; /**
     * The maximum times per second this system should be updated
     */ public frequence : number ; /**
     * Reference to the world, changed at runtime during interactions.
     */ protected world : ECS = undefined as any ; param event param data protected trigger : ( event : string , data : any ) => void = undefined as any ; param time public beforeUpdateAll ? ( time ) ; param time param delta param entity public update ? ( time , delta , entity ) ; param time public afterUpdateAll ? ( time , entities ) ; param entity param added param removed public change ? ( entity , added ? , removed ? ) ; param entity public enter ? ( entity ) ; param entity public exit ? ( entity ) ; param componentTypes param frequence constructor ( componentTypes , frequence = 0 ) { this . id = SEQ_SYSTEM ++ ; this . componentTypes = componentTypes ; this . frequence = frequence ; } param componentTypes protected query ( componentTypes ) { return this . world . query ( componentTypes ) ; } param event param callback param once protected listenTo ( event , callback , once ? ) { if ( ! this . callbacks . hasOwnProperty ( event ) ) { this . callbacks [ event ] = [ ] ; } if ( once ) { let tmp = callback . bind ( this ) ; callback = ( data , entities ) => { tmp ( data , entities ) ; let idx = this . callbacks [ event ] . indexOf ( callback ) ; if ( idx >= 0 ) { this . callbacks [ event ] . splice ( idx , 1 ) ; } if ( this . callbacks [ event ] . length === 0 ) { delete this . callbacks [ event ] ; } } ; } this . callbacks [ event ] . push ( callback ) ; } } /**
 * The very definition of the ECS. Also called Admin or Manager in other implementations.
 */ export default class ECS { public static System = System ; public static Entity = Entity ; public static Component = Component ; /**
     * All systems in this world
     */ private systems : System [ ] = [ ] ; /**
     * All entities in this world
     */ private entities : Entity [ ] = [ ] ; /**
     * Indexes the systems that must be run for each entity
     */ private entitySystems : { [ key ] : System [ ] ; } = { } ; /**
     * Records the last instant a system was run in this world for an entity, using real time
     */ private entitySystemLastUpdate : { [ key ] : { [ key ] : number ; } ; } = { } ; /**
     * Records the last instant a system was run in this world for an entity, using game time
     */ private entitySystemLastUpdateGame : { [ key ] : { [ key ] : number ; } ; } = { } ; /**
     * Saves subscriptions made to entities
     */ private entitySubscription : { [ key ] : ( ) => void ; } = { } ; param event param data private systemTrigger = ( event , data ) => { this . systems . forEach ( system => { let callbacks = ( system as any ) . callbacks ; if ( callbacks . hasOwnProperty ( event ) && callbacks [ event ] . length > 0 ) { this . inject ( system ) ; let entitiesIterator = this . query ( ( system as any ) . componentTypes ) ; callbacks [ event ] . forEach ( callback => { callback ( data , entitiesIterator ) ; } ) ; } } ) ; } ; /**
     * Allows you to apply slow motion effect on systems execution. When timeScale is 1, the timestamp and delta
     * parameters received by the systems are consistent with the actual timestamp. When timeScale is 0.5, the values
     * received by systems will be half of the actual value.
     *
     * ATTENTION! The systems continue to be invoked obeying their normal frequencies, what changes is only the values
     * received in the timestamp and delta parameters.
     */ public timeScale : number = 1 ; /**
     * Last execution of update method
     */ private lastUpdate : number = NOW ( ) ; /**
     * The timestamp of the game, different from the real world, is updated according to timeScale. If at no time does
     * the timeScale change, the value is the same as the current timestamp.
     *
     * This value is sent to the systems update method.
     */ private gameTime : number = 0 ; constructor ( systems ? ) { if ( systems ) { systems . forEach ( system => { this . addSystem ( system ) ; } ) ; } } /**
     * Remove all entities and systems
     */ public destroy ( ) { this . entities . forEach ( ( entity ) => { this . removeEntity ( entity ) ; } ) ; this . systems . forEach ( system => { this . removeSystem ( system ) ; } ) ; } param id public getEntity ( id ) { return this . entities . find ( entity => entity . id === id ) ; } param entity public addEntity ( entity ) { if ( ! entity || this . entities . indexOf ( entity ) >= 0 ) { return ; } this . entities . push ( entity ) ; this . entitySystemLastUpdate [ entity . id ] = { } ; this . entitySystemLastUpdateGame [ entity . id ] = { } ; if ( this . entitySubscription [ entity . id ] ) { this . entitySubscription [ entity . id ] ( ) ; } this . entitySubscription [ entity . id ] = entity . subscribe ( ( entity , added , removed ) => { this . onEntityUpdate ( entity , added , removed ) ; this . indexEntity ( entity ) ; } ) ; this . indexEntity ( entity ) ; } param idOrInstance public removeEntity ( idOrInstance ) { let entity = idOrInstance as Entity ; if ( typeof idOrInstance === 'number' ) { entity = this . getEntity ( idOrInstance ) as Entity ; } if ( ! entity ) { return ; } const idx = this . entities . indexOf ( entity ) ; if ( idx >= 0 ) { this . entities . splice ( idx , 1 ) ; } if ( this . entitySubscription [ entity . id ] ) { this . entitySubscription [ entity . id ] ( ) ; } let systems = this . entitySystems [ entity . id ] ; if ( systems ) { systems . forEach ( system => { if ( system . exit ) { this . inject ( system ) ; system . exit ( entity as Entity ) ; } } ) ; } delete this . entitySystems [ entity . id ] ; delete this . entitySystemLastUpdate [ entity . id ] ; delete this . entitySystemLastUpdateGame [ entity . id ] ; } param system public addSystem ( system ) { if ( ! system ) { return ; } if ( this . systems . indexOf ( system ) >= 0 ) { return ; } this . systems . push ( system ) ; this . entities . forEach ( entity => { this . indexEntity ( entity , system ) ; } ) ; this . entities . forEach ( entity => { if ( entity . active ) { let systems = this . entitySystems [ entity . id ] ; if ( systems && systems . indexOf ( system ) >= 0 ) { if ( system . enter ) { this . inject ( system ) ; system . enter ( entity ) ; } } } } ) ; } param system public removeSystem ( system ) { if ( ! system ) { return ; } const idx = this . systems . indexOf ( system ) ; if ( idx >= 0 ) { this . entities . forEach ( entity => { if ( entity . active ) { let systems = this . entitySystems [ entity . id ] ; if ( systems && systems . indexOf ( system ) >= 0 ) { if ( system . exit ) { this . inject ( system ) ; system . exit ( entity ) ; } } } } ) ; this . systems . splice ( idx , 1 ) ; if ( ( system as any ) . world === this ) { ( system as any ) . world = undefined ; ( system as any ) . trigger = undefined ; } this . entities . forEach ( entity => { this . indexEntity ( entity , system ) ; } ) ; } } param componentTypes public query ( componentTypes ) { let index = 0 ; let listAll = componentTypes . indexOf ( - 1 ) >= 0 ; return new Iterator < Entity > ( ( ) => { outside : for ( let l = this . entities . length ; index < l ; index ++ ) { let entity = this . entities [ index ] ; if ( listAll ) { return entity ; } const entityComponentIDs = [ - 1 ] . concat ( Object . keys ( ( entity as any ) . components ) . map ( v => Number . parseInt ( v , 10 ) ) ) ; for ( var a = 0 , j = componentTypes . length ; a < j ; a ++ ) { if ( entityComponentIDs . indexOf ( componentTypes [ a ] ) < 0 ) { continue outside ; } } return entity ; } } ) ; } /**
     * Invokes the "update" method of the systems in this world.
     */ public update ( ) { let now = NOW ( ) ; this . gameTime += ( now - this . lastUpdate ) * this . timeScale ; this . lastUpdate = now ; let toCallAfterUpdateAll = { } ; this . entities . forEach ( entity => { if ( ! entity . active ) { return this . removeEntity ( entity ) ; } let systems = this . entitySystems [ entity . id ] ; if ( ! systems ) { return ; } const entityLastUpdates = this . entitySystemLastUpdate [ entity . id ] ; const entityLastUpdatesGame = this . entitySystemLastUpdateGame [ entity . id ] ; let elapsed , elapsedScaled , interval ; systems . forEach ( system => { if ( system . update ) { this . inject ( system ) ; elapsed = now - entityLastUpdates [ system . id ] ; elapsedScaled = this . gameTime - entityLastUpdatesGame [ system . id ] ; if ( system . frequence > 0 ) { interval = 1000 / system . frequence ; if ( elapsed < interval ) { return ; } entityLastUpdates [ system . id ] = now - ( elapsed % interval ) ; entityLastUpdatesGame [ system . id ] = this . gameTime ; } else { entityLastUpdates [ system . id ] = now ; entityLastUpdatesGame [ system . id ] = this . gameTime ; } let id = `_` + system . id ; if ( ! toCallAfterUpdateAll [ id ] ) { if ( system . beforeUpdateAll ) { system . beforeUpdateAll ( this . gameTime ) ; } toCallAfterUpdateAll [ id ] = { system : system , entities : [ ] } ; } toCallAfterUpdateAll [ id ] . entities . push ( entity ) ; system . update ( this . gameTime , elapsedScaled , entity ) ; } } ) ; } ) ; for ( var attr in toCallAfterUpdateAll ) { if ( ! toCallAfterUpdateAll . hasOwnProperty ( attr ) ) { continue ; } let system = toCallAfterUpdateAll [ attr ] . system ; if ( system . afterUpdateAll ) { this . inject ( system ) ; system . afterUpdateAll ( this . gameTime , toCallAfterUpdateAll [ attr ] . entities ) ; } } toCallAfterUpdateAll = { } ; } param system private inject ( system ) { ( system as any ) . world = this ; ( system as any ) . trigger = this . systemTrigger ; return system ; } param entity private onEntityUpdate ( entity , added ? , removed ? ) { if ( ! this . entitySystems [ entity . id ] ) { return ; } const toNotify = this . entitySystems [ entity . id ] . slice ( 0 ) ; outside : for ( var idx = toNotify . length - 1 ; idx >= 0 ; idx -- ) { let system = toNotify [ idx ] ; if ( system . change ) { let systemComponentTypes = ( system as any ) . componentTypes ; if ( systemComponentTypes . indexOf ( - 1 ) >= 0 ) { continue ; } if ( added && systemComponentTypes . indexOf ( added . type ) >= 0 ) { continue outside ; } if ( removed && systemComponentTypes . indexOf ( removed . type ) >= 0 ) { continue outside ; } } toNotify . splice ( idx , 1 ) ; } toNotify . forEach ( system => { system = this . inject ( system ) ; const systemComponentTypes = ( system as any ) . componentTypes ; const all = systemComponentTypes . indexOf ( - 1 ) >= 0 ; ( system . change as any ) ( entity , all ? added : ( added && systemComponentTypes . indexOf ( added . type ) >= 0 ? added : undefined ) , all ? removed : ( removed && systemComponentTypes . indexOf ( removed . type ) >= 0 ? removed : undefined ) ) ; } ) ; } private indexEntitySystem = ( entity , system ) => { const idx = this . entitySystems [ entity . id ] . indexOf ( system ) ; if ( this . systems . indexOf ( system ) < 0 ) { if ( idx >= 0 ) { this . entitySystems [ entity . id ] . splice ( idx , 1 ) ; delete this . entitySystemLastUpdate [ entity . id ] [ system . id ] ; delete this . entitySystemLastUpdateGame [ entity . id ] [ system . id ] ; } return ; } const systemComponentTypes = ( system as any ) . componentTypes ; for ( var a = 0 , l = systemComponentTypes . length ; a < l ; a ++ ) { let entityComponentIDs = [ - 1 ] . concat ( Object . keys ( ( entity as any ) . components ) . map ( v => Number . parseInt ( v , 10 ) ) ) ; if ( entityComponentIDs . indexOf ( systemComponentTypes [ a ] ) < 0 ) { if ( idx >= 0 ) { if ( system . exit ) { this . inject ( system ) ; system . exit ( entity ) ; } this . entitySystems [ entity . id ] . splice ( idx , 1 ) ; delete this . entitySystemLastUpdate [ entity . id ] [ system . id ] ; delete this . entitySystemLastUpdateGame [ entity . id ] [ system . id ] ; } return ; } } if ( idx < 0 ) { this . entitySystems [ entity . id ] . push ( system ) ; this . entitySystemLastUpdate [ entity . id ] [ system . id ] = NOW ( ) ; this . entitySystemLastUpdateGame [ entity . id ] [ system . id ] = this . gameTime ; if ( system . enter ) { this . inject ( system ) ; system . enter ( entity ) ; } } } ; param entity private indexEntity ( entity , system ? ) { if ( ! this . entitySystems [ entity . id ] ) { this . entitySystems [ entity . id ] = [ ] ; } if ( system ) { this . indexEntitySystem ( entity , system ) ; } else { this . systems . forEach ( ( system ) => { this . indexEntitySystem ( entity , system ) ; } ) ; } } }