--- title: ECS Systems --- # ECS Systems ECS Systems are what actually run the simulations and provide the behaviors to the entity components. Systems are extended from the base `System` class and exported from "/server/src/systems/index.ts" in the specific order that they are supposed to run. The base `System` class is designed to keep a cache of the entities associated with it so it doesn't have to iterate over the entire entity list every time it runs. The intention is to extend any of the several abstract methods provided by the `System` class. These include `test`, `preUpdate`, `update`, `postUpdate`, `enter`, and `exit`. --- ``` test(entity:Entity):boolean ``` This is called whenever a system is added to the ECS world, whenever an entity is added, or whenever a component is added or removed from an entity. It should return `true` if the system should operate on the entity and `false` if it should not. --- ``` preUpdate(elapsed:number) ``` Called once every update before any entities are updated. It is passed the amount of time that elapsed since the last update. --- ``` update(entity:Entity, elapsed:number) ``` Called for every entity associated with this system. This is where the bulk of the logic should be. Any mutations to entities should happen here. --- ``` postUpdate(elapsed:number) ``` Called once every update after all of the entities have been updated. It is passed the amount of time that elapsed since the last update. This is where any `pubsub.publish` calls should be placed. --- ``` enter(entity:Entity) ``` Called whenever an entity is added to the system. --- ``` exit(entity:Entity) ``` Called whenever an entity is removed from the system. ## Example This code can be found in "/server/src/systems/TimerSystem.ts" ```ts import { Duration } from "luxon"; import { Entity, System } from "@thorium/utils/ecs"; import { pubsub } from "../utils/pubsub"; export class TimerSystem extends System { timeCount = 0; test(entity: Entity) { return !!entity.components.timer; } preUpdate(elapsed: number) { this.timeCount += elapsed; } update(entity: Entity, elapsed: number) { if (entity.components.timer && this.timeCount >= 1000 && !entity.components.timer?.paused) { try { entity.updateComponent("timer", { time: subtractTimer(entity.components.timer.time), }); if (entity.components.timer.time === "00:00:00") { this.ecs.removeEntityById(entity.id); } } catch (err) { if (err?.message === "Seconds has gone negative") { // Remove the entity this.ecs.removeEntityById(entity.id); } } } } postUpdate() { if (this.timeCount >= 1000) { this.timeCount = 0; pubsub.publish("timers", { entities: this.ecs.entities.filter((e) => e.components.timer), }); } } } function subtractTimer(timer: string) { const [hours = "0", minutes = "0", seconds = "0"] = timer.split(":"); const dur = Duration.fromObject({ hours: parseInt(hours, 10), minutes: parseInt(minutes, 10), seconds: parseInt(seconds, 10), }) .minus(1000) .normalize() .toFormat("hh:mm:ss"); if (parseInt(seconds, 10) < 0) { throw new Error("Seconds has gone negative"); } return dur; } ```