import { getComponentClass , getComponentId } from './ComponentId' ; import { Entity , EntitySnapshot } from './Entity' ; import { isTag , Tag } from './Tag' ; import { Signal } from '../utils/Signal' ; import { Class } from '../utils/Class' ; import { isLinkedComponent } from './LinkedComponent' ; /**
 * Query Predicate is the type that describes a function that compares Entities with the conditions it sets.
 * In other words, it's a function that determines whether Entities meets the right conditions to get into a
 * given Query or not.
 */ export type QueryPredicate = ( entity : Entity ) => boolean ; see QueryBuilder export class Query { /**
     * Signal dispatches if new matched entity were added
     */ public onEntityAdded : Signal < ( snapshot : EntitySnapshot ) => void > = new Signal ( ) ; /**
     * Signal dispatches if entity stops matching query
     */ public onEntityRemoved : Signal < ( snapshot : EntitySnapshot ) => void > = new Signal ( ) ; private readonly _helper : Entity = new Entity ( ) ; private readonly _snapshot : EntitySnapshot = new EntitySnapshot ( ) ; private readonly _predicate : QueryPredicate ; private _entities : Entity [ ] = [ ] ; param predicate public constructor ( predicate ) { this . _predicate = predicate ; } /**
     * Entities list which matches the query
     */ public get entities ( ) : ReadonlyArray < Entity > { return this . _entities ; } returns { Entity | undefined } public get first ( ) : Entity | undefined { if ( this . _entities . length === 0 ) return undefined ; return this . _entities [ 0 ] ; } returns { Entity | undefined } public get last ( ) : Entity | undefined { if ( this . _entities . length === 0 ) return undefined ; return this . _entities [ this . _entities . length - 1 ] ; } returns { Entity | undefined } public get length ( ) : number { return this . _entities . length ; } param { ( entity : Entity ) => boolean } predicate returns { number } public countBy ( predicate ) { let result = 0 ; for ( const entity of this . _entities ) { if ( predicate ( entity ) ) result ++ ; } return result ; } param { ( entity : Entity ) => boolean } predicate returns { Entity | undefined } public find ( predicate ) { return this . _entities . find ( predicate ) ; } param { ( entity : Entity ) => boolean } predicate returns { Entity [ ] } public filter ( predicate ) { return this . _entities . filter ( predicate ) ; } param { Entity } entity returns { boolean } public has ( entity ) { return this . _entities . indexOf ( entity ) !== - 1 ; } /**
     * This method is matching passed list of entities with predicate of the query to determine
     * if entities are the part of query or not.
     *
     * Entities that will pass testing will become a part of the query
     */ public matchEntities ( entities ) { entities . forEach ( ( entity ) => this . entityAdded ( entity ) ) ; } /**
     * Gets a value indicating that query is empty
     */ public get isEmpty ( ) : boolean { return this . entities . length == 0 ; } /**
     * Clears the list of entities of the query
     */ public clear ( ) { this . _entities = [ ] ; } internal public validateEntity ( entity ) { const index = this . entities . indexOf ( entity ) ; const isMatch = this . _predicate ( entity ) ; if ( index !== - 1 && ! isMatch ) { this . entityRemoved ( entity ) ; } else { this . entityAdded ( entity ) ; } } internal public entityAdded = ( entity ) => { const index = this . _entities . indexOf ( entity ) ; if ( index === - 1 && this . _predicate ( entity ) ) { this . _entities . push ( entity ) ; if ( this . onEntityAdded . hasHandlers ) { entity . takeSnapshot ( this . _snapshot ) ; this . onEntityAdded . emit ( this . _snapshot ) ; } } } ; internal public entityRemoved = ( entity ) => { const index = this . _entities . indexOf ( entity ) ; if ( index !== - 1 ) { this . _entities . splice ( index , 1 ) ; if ( this . onEntityRemoved . hasHandlers ) { entity . takeSnapshot ( this . _snapshot ) ; this . onEntityRemoved . emit ( this . _snapshot ) ; } } } ; internal public entityComponentAdded = < T > ( entity , componentOrTag , componentClass ? ) => { const hasAddedHandlers = this . onEntityAdded . hasHandlers ; const hasRemovedHandlers = this . onEntityRemoved . hasHandlers ; this . updateHelper ( entity , componentOrTag , componentClass ) ; const index = this . _entities . indexOf ( entity ) ; const isMatch = this . _predicate ( this . _helper ) ; if ( index === - 1 && isMatch ) { this . _entities . push ( entity ) ; if ( hasAddedHandlers ) { entity . takeSnapshot ( this . _snapshot , componentOrTag , componentClass ) ; this . onEntityAdded . emit ( this . _snapshot ) ; } } else if ( index !== - 1 && ! isMatch ) { this . _entities . splice ( index , 1 ) ; if ( hasRemovedHandlers ) { entity . takeSnapshot ( this . _snapshot , componentOrTag , componentClass ) ; this . onEntityRemoved . emit ( this . _snapshot ) ; } } } ; internal public entityComponentRemoved = < T > ( entity , component , componentClass ? ) => { const hasAddedHandlers = this . onEntityAdded . hasHandlers ; const hasRemovedHandlers = this . onEntityRemoved . hasHandlers ; this . updateHelper ( entity , component , componentClass ) ; const index = this . _entities . indexOf ( entity ) ; if ( index !== - 1 && this . _predicate ( this . _helper ) && ! this . _predicate ( entity ) ) { this . _entities . splice ( index , 1 ) ; if ( hasRemovedHandlers ) { entity . takeSnapshot ( this . _snapshot , component , componentClass ) ; this . onEntityRemoved . emit ( this . _snapshot ) ; } } else if ( index === - 1 && this . _predicate ( entity ) && ! this . _predicate ( this . _helper ) ) { this . _entities . push ( entity ) ; if ( hasAddedHandlers ) { entity . takeSnapshot ( this . _snapshot , component , componentClass ) ; this . onEntityAdded . emit ( this . _snapshot ) ; } } } ; private updateHelper < T > ( entity , component , resolveClass ? ) { this . _helper . clear ( ) ; this . _helper . copyFrom ( entity ) ; if ( ! isLinkedComponent ( component ) ) { this . _helper . add ( component ) ; } else if ( ! this . _helper . has ( getComponentClass ( component ! , resolveClass ) ) ) { this . _helper . append ( component ) ; } } } function hasAll ( entity , components , tags ) { if ( components . size > 0 ) { for ( const componentId of components ) { if ( entity . components [ componentId ] === undefined ) { return false ; } } } if ( tags . size > 0 ) { for ( const tag of tags ) { if ( ! entity . tags . has ( tag ) ) { return false ; } } } return true ; } example export class QueryBuilder { private readonly _components : Set < number > = new Set ( ) ; private readonly _tags : Set < Tag > = new Set ( ) ; param componentsOrTags public contains ( ... componentsOrTags ) { for ( const componentOrTag of componentsOrTags ) { if ( isTag ( componentOrTag ) ) { if ( ! this . _tags . has ( componentOrTag ) ) { this . _tags . add ( componentOrTag ) ; } } else { const componentId = getComponentId ( componentOrTag , true ) ! ; if ( ! this . _components . has ( componentId ) ) { this . _components . add ( componentId ) ; } } } return this ; } /**
     * Build query
     */ public build ( ) { return new Query ( ( entity ) => hasAll ( entity , this . _components , this . _tags ) ) ; } internal public getComponents ( ) { return this . _components ; } internal public getTags ( ) { return this . _tags ; } } internal export function isQueryPredicate ( item ) { return typeof item === 'function' ; } internal export function isQueryBuilder ( item ) { return item instanceof QueryBuilder ; }