import { getComponentClass , getComponentId } from './ComponentId' ; import { Class } from '../utils/Class' ; import { Signal } from '../utils/Signal' ; import { isTag , Tag } from './Tag' ; import { ILinkedComponent , isLinkedComponent } from './LinkedComponent' ; import { LinkedComponentList } from './LinkedComponentList' ; /**
 * Entity readonly interface
 */ export interface ReadonlyEntity { /**
     * The signal dispatches if new component or tag was added to the entity
     */ readonly onComponentAdded : Signal < ComponentUpdateHandler > ; /**
     * The signal dispatches if component was removed from the entity
     */ readonly onComponentRemoved : Signal < ComponentUpdateHandler > ; see getComponentId ,  Entity . getComponents readonly components : Readonly < Record < number , unknown > > ; see getComponentId readonly tags : ReadonlySet < Tag > ; param { Class | Tag } componentClassOrTag example has < T > ( componentClassOrTag ) : boolean ; param { T } component param { Class < K > } resolveClass example contains < T extends K , K > ( component , resolveClass ? ) : boolean ; param component example hasComponent < T > ( component ) : boolean ; param tag example hasTag ( tag ) : boolean ; param { Class < unknown > | Tag } componentClassOrTag returns { boolean } example hasAny ( ... componentClassOrTag ) : boolean ; param { Class < unknown > | Tag } componentClassOrTag returns { boolean } example hasAll ( ... componentClassOrTag ) : boolean ; returns { unknown [ ] } getComponents ( ) : unknown [ ] ; /**
     * Returns an array of tags applied to the entity
     */ getTags ( ) : Tag [ ] ; /**
     * Gets a component instance if it's exists in the entity, otherwise returns `undefined`
     * - If you want to check presence of the tag then use  has instead.
     *
     *  param componentClass get < T > ( componentClass ) : T | undefined ; param { Class < T > } componentClass param { ( component : T ) => void } action example iterate < T > ( componentClass , action ) : void ; param { Class < T > } componentClass example getAll < T > ( componentClass ) : Generator < T , void , T > ; param { Class < T > } componentClass param { ( component : T ) => boolean } predicate return { T | undefined } find < T > ( componentClass , predicate ) : T | undefined ; param { Class < T > } componentClass return { number } lengthOf < T > ( componentClass ) : number ; } example export class Entity implements ReadonlyEntity { /**
     * The signal dispatches if new component or tag was added to the entity. Works for every linked component as well.
     */ public readonly onComponentAdded : Signal < ComponentUpdateHandler > = new Signal ( ) ; /**
     * The signal dispatches if component was removed from the entity. Works for every linked component as well.
     */ public readonly onComponentRemoved : Signal < ComponentUpdateHandler > = new Signal ( ) ; /**
     * The signal dispatches that invalidation requested for this entity.
     * Which means that if the entity attached to the engine — its queries will be updated.
     *
     * Use  Entity . invalidate method in case if in query test function is using component properties or complex
     * logic.
     *
     * Only adding/removing components and tags are tracked by Engine. So you need to request queries invalidation
     * manually, if some of your queries depends on logic or component`s properties.
      public readonly onInvalidationRequested : Signal < ( entity : Entity ) => void > = new Signal ( ) ; /**
     * Unique id identifier
     */ public readonly id = entityId ++ ; private _components : Record < number , unknown > = { } ; private _linkedComponents : Record < number , LinkedComponentList < ILinkedComponent > > = { } ; private _tags : Set < Tag > = new Set ( ) ; see getComponentId ,  Entity . getComponents public get components ( ) : Readonly < Record < number , unknown > > { return this . _components ; } see getComponentId public get tags ( ) : ReadonlySet < Tag > { return this . _tags ; } /**
     * Adds a component or tag to the entity.
     * It's a unified shorthand for  addComponent and  addTag .
     *
     * - If a component of the same type already exists in entity, it will be replaced by the passed one (only if
     *  component itself is not the same, in this case - no actions will be done).
     * - If the tag is already present in the entity - no actions will be done.
     * - During components replacement  onComponentRemoved and  onComponentAdded are will be triggered
     *  sequentially.
     * - If there is no component of the same type, or the tag is not present in the entity - then only
     * - If the passed component is an instance of ILinkedComponent then all existing instances will be removed, and the
     *  passed instance will be added to the Entity.  onComponentRemoved will be triggered for every removed
     *  instance and  onComponentAdded will be triggered for the passed component.
     * - Linked component always replaces all existing instances. Even if the passed instance already exists in the
     *  Entity - all existing linked components will be removed anyway, and replaced with the passed one.
     *
     *  throws param { T | Tag } componentOrTag param { K } resolveClass returns { Entity } see addComponent ,  addTag example public add < T extends K , K extends unknown > ( componentOrTag , resolveClass ? ) { if ( isTag ( componentOrTag ) ) { this . addTag ( componentOrTag ) ; } else { this . addComponent ( componentOrTag , resolveClass ) ; } return this ; } /**
     * Appends a linked component to the entity.
     *
     * - If linked component is not exists, then it will be added to the Entity and  onComponentAdded * will be triggered.
     * - If component already exists in the entity, then passed one will be appended to the tail.  onComponentAdded *  will be triggered as well.
     *
     * It's a shorthand to  appendComponent throws param { T | Tag } component param { K } resolveClass returns { Entity } see addComponent see appendComponent example public append < T extends K , K extends ILinkedComponent > ( component , resolveClass ? ) { return this . appendComponent ( component , resolveClass ) ; } /**
     * Removes first appended linked component instance of the specified type.
     * Unlike  remove and  removeComponent remaining linked components stays in the Entity.
     *
     * - If linked component exists in the Entity, then it will be removed from Entity and  onComponentRemoved * will be triggered.
     *
     *  param { Class < T > } componentClass return { T | undefined } example public withdraw < T > ( componentClass ) { const component = this . get ( componentClass ) ; if ( component !== undefined ) { return this . withdrawComponent ( component as NonNullable < T > , componentClass ) ; } return undefined ; } /**
     * Removes particular linked component instance from the Entity.
     *
     * - If linked component instance exists in the Entity, then it will be removed from Entity and
     *  onComponentRemoved will be triggered.
     *
     *  param { NonNullable < T > } component param { Class < K > } resolveClass return { T | undefined } public pick < T > ( component , resolveClass ? ) { return this . withdrawComponent ( component , resolveClass ) ; } /**
     * Adds a component to the entity.
     *
     * - If a component of the same type already exists in entity, it will be replaced by the passed one (only if
     *  component itself is not the same, in this case - no actions will be done).
     * - During components replacement  onComponentRemoved and  onComponentAdded are will be triggered
     *  sequentially.
     * - If there is no component of the same type - then only  onComponentAdded will be triggered.
     *
     *  throws param { T } component param { K } resolveClass returns { Entity } see add ,  addTag example public addComponent < T extends K , K extends unknown > ( component , resolveClass ? ) { const componentClass = getComponentClass ( component , resolveClass ) ; const id = getComponentId ( componentClass , true ) ! ; const linkedComponent = isLinkedComponent ( component ) ; if ( this . _components [ id ] !== undefined ) { if ( ! linkedComponent && component === this . _components [ id ] ) { return ; } this . remove ( componentClass ) ; } if ( linkedComponent ) { this . append ( component as ILinkedComponent , resolveClass as Class < ILinkedComponent > ) ; } else { this . _components [ id ] = component ; this . dispatchOnComponentAdded ( component ) ; } } /**
     * Appends a linked component to the entity.
     *
     * - If linked component is not exists, then it will be added via `addComponent` method and  onComponentAdded * will be triggered.
     * - If component already exists in the entity, then passed one will be appended to the tail.  {@link
      * onComponentAdded} wont be triggered.
     *
     *  throws param { T | Tag } component param { K } resolveClass returns { Entity } see append see addComponent example public appendComponent < T extends K , K extends ILinkedComponent > ( component , resolveClass ? ) { const componentClass = getComponentClass ( component , resolveClass ) ; const componentId = getComponentId ( componentClass , true ) ! ; const componentList = this . getLinkedComponentList ( componentId ) ! ; componentList . add ( component ) ; if ( this . _components [ componentId ] === undefined ) { this . _components [ componentId ] = componentList . head ; } this . dispatchOnComponentAdded ( component ) ; return this ; } /**
     * Adds a tag to the entity.
     *
     * - If the tag is already present in the entity - no actions will be done.
     * - If there is such tag in the entity then  onComponentAdded will be triggered.
     *
     *  param { Tag } tag returns { Entity } see add ,  addComponent example public addTag ( tag ) { if ( ! this . _tags . has ( tag ) ) { this . _tags . add ( tag ) ; this . dispatchOnComponentAdded ( tag ) ; } } param componentClassOrTag example public has < T > ( componentClassOrTag ) { if ( isTag ( componentClassOrTag ) ) { return this . hasTag ( componentClassOrTag ) ; } return this . hasComponent ( componentClassOrTag ) ; } param { NonNullable < T > } component param { Class < K > } resolveClass example public contains < T extends K , K > ( component , resolveClass ? ) { const componentClass = getComponentClass ( component , resolveClass ) ; if ( isLinkedComponent ( component ) ) { return this . find ( componentClass , ( value ) => value === component ) !== undefined ; } return this . get ( componentClass ) === component ; } param component example public hasComponent < T > ( component ) { const id = getComponentId ( component ) ; if ( id === undefined ) return false ; return this . _components [ id ] !== undefined ; } param tag example public hasTag ( tag ) { return this . _tags . has ( tag ) ; } param { Class < unknown > | Tag } componentClassOrTag returns { boolean } example public hasAny ( ... componentClassOrTag ) { return componentClassOrTag . some ( value => this . has ( value ) ) ; } param { Class < unknown > | Tag } componentClassOrTag returns { boolean } example public hasAll ( ... componentClassOrTag ) { return componentClassOrTag . every ( value => this . has ( value ) ) ; } /**
     * Gets a component instance if it's exists in the entity, otherwise returns `undefined`
     * - If you want to check presence of the tag then use  has instead.
     *
     *  param componentClass public get < T > ( componentClass ) { const id = getComponentId ( componentClass ) ; if ( id === undefined ) return undefined ; return this . _components [ id ] as T ; } returns { unknown [ ] } public getComponents ( ) { return Array . from ( Object . values ( this . _components ) ) ; } /**
     * Returns an array of tags applied to the entity
     */ public getTags ( ) { return Array . from ( this . _tags ) ; } /**
     * Removes a component or tag from the entity.
     *  In case if the component or tag is present - then  onComponentRemoved will be
     *  dispatched after removing it from the entity.
     *
     * If linked component type provided:
     * - For each instance of linked component  onComponentRemoved will be called
     * - Only head of the linked list will be returned.
     *
     * If you need to get all instances use  withdraw or  pick instead, or check  iterate ,
     *  getAll *
     * It's a shorthand for  removeComponent param componentClassOrTag returns see withdraw see pick public remove < T > ( componentClassOrTag ) { if ( isTag ( componentClassOrTag ) ) { this . removeTag ( componentClassOrTag ) ; return undefined ; } return this . removeComponent ( componentClassOrTag ) ; } /**
     * Removes a component from the entity.
     *  In case if the component or tag is present - then  onComponentRemoved will be
     *  dispatched after removing it from the entity.
     *
     * If linked component type provided:
     * - For each instance of linked component  onComponentRemoved will be called
     * - Only head of the linked list will be returned.
     *
     * If you need to get all instances use  withdraw or  pick instead, or check  iterate ,
     *  getAll param componentClassOrTag returns public removeComponent < T > ( componentClassOrTag ) { const id = getComponentId ( componentClassOrTag ) ; if ( id === undefined || this . _components [ id ] === undefined ) { return undefined ; } let value = this . _components [ id ] ; if ( isLinkedComponent ( value ) ) { const list = this . getLinkedComponentList ( componentClassOrTag ) ! ; while ( ! list . isEmpty ) { this . withdraw ( componentClassOrTag ) ; } } else { delete this . _components [ id ] ; this . dispatchOnComponentRemoved ( value ) ; } return value as T ; } /**
     * Removes a tag from the entity.
     *  In case if the component tag is present - then  onComponentRemoved will be
     *  dispatched after removing it from the entity
     *
     *  param { Tag } tag returns { void } public removeTag ( tag ) { if ( this . _tags . has ( tag ) ) { this . _tags . delete ( tag ) ; this . dispatchOnComponentRemoved ( tag ) ; } } /**
     * Removes all components and tags from entity
     */ public clear ( ) { this . _components = { } ; this . _linkedComponents = { } ; this . _tags . clear ( ) ; } param { Entity } entity return { this } public copyFrom ( entity ) { this . _components = Object . assign ( { } , entity . _components ) ; this . _linkedComponents = Object . assign ( { } , entity . _linkedComponents ) ; this . _tags = new Set ( entity . _tags ) ; return this ; } param { Class < T > } componentClass param { ( component : T ) => void } action example public iterate < T > ( componentClass , action ) { if ( ! this . hasComponent ( componentClass ) ) return ; this . getLinkedComponentList ( componentClass ) ?. iterate ( action ) ; } param { Class < T > } componentClass example public * getAll < T > ( componentClass ) { if ( ! this . hasComponent ( componentClass ) ) return ; const list = this . getLinkedComponentList ( componentClass , false ) ; if ( list === undefined ) return undefined ; yield * list . nodes ( ) ; } param { Class < T > } componentClass param { ( component : T ) => boolean } predicate return { T | undefined } public find < T > ( componentClass , predicate ) { const componentIdToFind = getComponentId ( componentClass , false ) ; if ( componentIdToFind === undefined ) return undefined ; const component = this . _components [ componentIdToFind ] ; if ( component === undefined ) return undefined ; if ( isLinkedComponent ( component ) ) { let linkedComponent = component ; while ( linkedComponent !== undefined ) { if ( predicate ( linkedComponent as T ) ) return linkedComponent as T ; linkedComponent = linkedComponent . next ; } } else return predicate ( component as T ) ? component as T : undefined ; } param { Class < T > } componentClass return { number } public lengthOf < T > ( componentClass ) { let result = 0 ; this . iterate ( componentClass , ( ) => { result ++ ; } ) ; return result ; } /**
     * Use this method to dispatch that entity component properties were changed, in case if
     * queries predicates are depends on them.
     * Components properties are not tracking by Engine itself, because it's too expensive.
     */ public invalidate ( ) { this . onInvalidationRequested . emit ( this ) ; } internal param { EntitySnapshot } result param { T } changedComponentOrTag param { Class < T > } resolveClass public takeSnapshot < T > ( result , changedComponentOrTag ? , resolveClass ? ) { const previousState = result . previous as Entity ; if ( result . current !== this ) { result . current = this ; previousState . copyFrom ( this ) ; } if ( changedComponentOrTag === undefined ) { return ; } if ( isTag ( changedComponentOrTag ) ) { const previousTags = previousState . _tags ; if ( this . has ( changedComponentOrTag ) ) { previousTags . delete ( changedComponentOrTag ) ; } else { previousTags . add ( changedComponentOrTag ) ; } } else { const componentClass = resolveClass ?? Object . getPrototypeOf ( changedComponentOrTag ) . constructor ; const componentId = getComponentId ( componentClass ! , true ) ! ; const previousComponents = previousState . _components ; if ( this . has ( componentClass ) ) { delete previousComponents [ componentId ] ; } else { previousComponents [ componentId ] = changedComponentOrTag ; } } } internal public getLinkedComponentList ( componentClassOrId , createIfNotExists = true ) { if ( typeof componentClassOrId !== 'number' ) { componentClassOrId = getComponentId ( componentClassOrId ) ! ; } if ( this . _linkedComponents [ componentClassOrId ] !== undefined || ! createIfNotExists ) { return this . _linkedComponents [ componentClassOrId ] ; } else { return this . _linkedComponents [ componentClassOrId ] = new LinkedComponentList < ILinkedComponent > ( ) ; } } private withdrawComponent < T extends K , K extends ILinkedComponent > ( component , resolveClass ? ) { const componentClass = getComponentClass ( component , resolveClass ) ; if ( ! isLinkedComponent ( component ) ) { return this . remove ( componentClass ) ; } const componentList = this . getLinkedComponentList ( componentClass , false ) ; if ( ! this . hasComponent ( componentClass ) || componentList === undefined ) return undefined ; const result = componentList . remove ( component ) ? component : undefined ; const componentId = getComponentId ( componentClass , true ) ! ; if ( componentList . isEmpty ) { delete this . _components [ componentId ] ; delete this . _linkedComponents [ componentId ] ; } else { this . _components [ componentId ] = componentList . head ; } if ( result !== undefined ) { this . dispatchOnComponentRemoved ( result ) ; } return result ; } private dispatchOnComponentAdded < T > ( component ) { if ( this . onComponentAdded . hasHandlers ) { this . onComponentAdded . emit ( this , component ) ; } } private dispatchOnComponentRemoved < T > ( value ) { if ( this . onComponentRemoved . hasHandlers ) { this . onComponentRemoved . emit ( this , value ) ; } } } /**
 * EntitySnapshot is a content container that displays the difference between the current state of Entity and its
 * previous state.
 *
 * The  EntitySnapshot . current property always reflects the current state, and  EntitySnapshot . previous -
 * previous one. So you can understand which components have been added and which have been removed.
 *
 * <p>It is important to note that changes in the data of the same entity components will not be reflected in the
 * snapshot, even if a manual invalidation of the entity has been triggered.</p>
  export class EntitySnapshot { private _current ? : Entity ; private _previous : ReadonlyEntity = new Entity ( ) ; returns { Entity } public get current ( ) : Entity { return this . _current ! ; } internal public set current ( value ) { this . _current = value ; } /**
     * Gets an instance of the previous state of entity
     */ public get previous ( ) : ReadonlyEntity { return this . _previous ; } } see Entity . onComponentAdded see Entity . onComponentRemoved export type ComponentUpdateHandler = ( entity : Entity , componentOrTag : unknown , componentClass ? : Class < unknown > ) => void ; /**
 * Entity ids enumerator
 */ let entityId = 1 ;