Skip to content

Instantly share code, notes, and snippets.

@TimVosch
Created May 21, 2022 09:45
Show Gist options
  • Save TimVosch/7175118bb37fc04b0d427dd299971e95 to your computer and use it in GitHub Desktop.
Save TimVosch/7175118bb37fc04b0d427dd299971e95 to your computer and use it in GitHub Desktop.
/*
- Entities are simple IDs
- An entity has zero or more components
- Components are just data
- Systems are executed on components
*/
export type Entity = number;
export type Component = Function;
export type ComponentInstance = Object;
export abstract class System {
public ecs!: ECS;
abstract requiredComponents: Component[];
abstract update(entities: Set<Entity>, dt: number): void;
}
class ComponentContainer {
private components = new Map<Component, ComponentInstance>();
public get<T extends ComponentInstance>(component: Component): T {
return this.components.get(component) as T;
}
public add<T extends ComponentInstance>(componentInstance: T): void {
this.components.set(componentInstance.constructor, componentInstance);
}
public remove(component: Component): void {
this.components.delete(component);
}
public has(...component: Component[]): boolean {
for (const c of component) {
if (!this.components.has(c)) {
return false;
}
}
return true;
}
}
export class ECS {
private entities = new Map<Entity, ComponentContainer>();
private systems = new Map<System, Set<Entity>>();
private ID = 0;
public createEntity(): Entity {
const entity = this.ID++;
this.entities.set(entity, new ComponentContainer());
return entity;
}
public addComponent(entity: Entity, ...component: ComponentInstance[]): void {
const cc = this.entities.get(entity);
if (!cc) return;
for (const c of component) {
cc.add(c);
}
this.checkEntity(entity);
}
public removeComponent(entity: Entity, ...component: Component[]): void {
const cc = this.entities.get(entity);
if (!cc) return;
for (const c of component) {
cc.remove(c);
}
this.checkEntity(entity);
}
public getComponent(entity: Entity, component: Component): ComponentInstance | undefined {
return this.entities.get(entity)?.get(component);
}
public addSystem(system: System): void {
if (system.requiredComponents.length === 0) {
console.warn(`System ${system.constructor.name} has no required components`);
return;
}
system.ecs = this;
this.systems.set(system, new Set<Entity>());
this.checkSystem(system);
}
public removeSystem(system: System): void {
this.systems.delete(system);
}
public update(): void {
for (const [system, entitySet] of this.systems) {
system.update(entitySet, 1.0);
}
}
private checkEntity(entity: Entity): void {
for (const system of this.systems.keys()) {
this.verifyEntitySystem(entity, system);
}
}
private checkSystem(system: System): void {
for (const entity of this.entities.keys()) {
this.verifyEntitySystem(entity, system);
}
}
private verifyEntitySystem(entity: Entity, system: System): void {
const cc = this.entities.get(entity);
const sys = this.systems.get(system);
if (!cc || !sys) return;
if (cc.has(...system.requiredComponents)) {
sys.add(entity);
} else {
sys.delete(entity);
}
}
}
export class ActiveEntity {
public constructor(private readonly id: Entity, private readonly ecs: ECS) {}
public get(component: Component): ComponentInstance | undefined {
return this.ecs.getComponent(this.id, component);
}
public add(...component: ComponentInstance[]): void {
this.ecs.addComponent(this.id, ...component);
}
public remove(...component: Component[]): void {
this.ecs.removeComponent(this.id, ...component);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment