entix-ecs
Version:
A Class based ECS Package.
95 lines (89 loc) • 4.53 kB
text/typescript
export type EntityId = number;// A entityId type
export type ComponentClass<T> = new (...args: any[]) => T
type Query = 'All' | 'Any' | 'None';
export class EntityManager {// A class to manage all the entities
private entityCount: number = 0;//A counter for id's
private entities: EntityId[] = [];//A array of entities
private componentMap: Map<EntityId, Map<ComponentClass<any>, any>> = new Map();/* A nested map,
contains EntityId with a another map containing components.*/
createEntity(): EntityId {// create a entity
const id: EntityId = this.entityCount;
this.entities.push(id);
this.componentMap.set(id, new Map());
this.entityCount++;
return id;
};
addComponent<T>(id: EntityId, componentClass: ComponentClass<T>, componentInstance: T):void {/* adds a component to entity,
doenst add a already added*/
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
const components = this.componentMap.get(id);
if (!components?.has(componentClass)) {
components?.set(componentClass, componentInstance);
}
};
removeComponent<T>(id: EntityId, componentClass: ComponentClass<T>):void {//removes a component from entity
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
const components = this.componentMap.get(id);
if (components?.has(componentClass)) {
components.delete(componentClass);
}
};
getComponent<T>(id: EntityId, componentClass: ComponentClass<T>): T | undefined;
getComponent<T>(id: EntityId, componentClass: ComponentClass<T>,strict:true): T;
getComponent<T>(id: EntityId, componentClass: ComponentClass<T>,strict:false): T | undefined;
getComponent<T>(id: EntityId, componentClass: ComponentClass<T>,strict?:boolean): T | undefined {// used to get a component
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
const components = this.componentMap.get(id);
const requestedComponent = components?.get(componentClass) as T | undefined;
if(strict && !requestedComponent){
throw new Error(`'${componentClass.name}'` + ' COMPONENT NOT FOUND ON ENTITY: ' + id);
}
return requestedComponent;
};
getAllComponents(id: EntityId): any[] {
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
const components = this.componentMap.get(id);
if (!components) return [];
return Array.from(components.values());
};
hasComponent<T>(id:EntityId,componentClass:ComponentClass<T>):boolean {
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
return this.componentMap.get(id)?.has(componentClass) ?? false;
};
removeEntity(id: EntityId) {
if (!this.hasEntity(id)) throw new Error(`Entity ${id} does not exist!`);
this.entities = this.entities.filter(e => e !== id);
this.componentMap.delete(id);
};
query<T extends Record<string,ComponentClass<any>>>(
filterType: Query,
componentsToMatch: T,
callback: (id: EntityId, matched: {[K in keyof T]:InstanceType<T[K]>}) => void
) {
const entitiesSnapshot = [...this.entities];
for (const entity of entitiesSnapshot) {
if(!this.hasEntity(entity)) continue;
const matched = {} as {[K in keyof T]: InstanceType<T[K]>};
const results: boolean[] = [];
for (const key in componentsToMatch) {
const compClass = componentsToMatch[key];
const instance = this.getComponent(entity, compClass);
if (instance) matched[key] = instance;
results.push(!!instance);
}
let shouldCall = false;
if (filterType === 'All') shouldCall = results.every(Boolean);
else if (filterType === 'Any') shouldCall = results.some(Boolean);
else if (filterType === 'None') shouldCall = results.every(r => !r);
else throw new Error(`Unknown filter type: ${filterType}`);
if (shouldCall) callback(entity, matched);
}
};
getAllEntities():EntityId[]{
return this.entities;
};
hasEntity(id:EntityId):boolean{
if(this.entities.includes(id)) return true;
return false;
};
};