Lightweight functional library implementation of the Entity-Component-System pattern with typescript.
Import the package with deno:
import { engine as darkerEngine } from "https://deno.land/x/darker_engine/mod.ts";
Install the package with npm:
npm install darker-engine
This engine implements an action queue with three levels of priority: Priority.HIGH
, Priority.MEDIUM
, Priority.LOW
.
Actions are added to the queue and processed based on their priority.
With Engine.load
config we can specify how many ticks we want per second. By default, is 60
await Engine.load({
ticksPerSecond: 40,
})
With Engine.onTick
we can sets a callback function that is run on each iteration of the loop.
The callback function receives an object with the result of the last processed action, the time (ms
) the iteration took, and the % usage of the tick.
Engine.onTick(({usage, ms, status}) => {
console.log({ms, usage, actionId: status?.id})
})
// -> { ms: 2, usage: 0.02, actionId: 1 }
With Engine.pause
you can pause the entire engine loop. All systems will call onPause
function before.
await Engine.pause()
With Engine.resume
you can start again the engine loop. All systems will call onResume
function after the loop restarts.
await Engine.resume()
When we use addEntity
, removeEntity
, entity.updateComponent
and entity.removeComponent
we can specify if we want to perform the action immediately or assign it a priority.
By default, they are added to the queue and assigned a medium priority (Priority.MEDIUM
)
// Action added to HIGH priority queue
await Engine.addEntity({
priority: Priority.HIGH,
entities: [exampleEntity()]
})
// Action that is executed immediately without depending on the queue
await Engine.addEntity({
force: true,
entities: [exampleEntity()]
})
import { engine } from "darker-engine";
export const Engine = engine<IEntities, IComponents, ComponentData>();
Engine.setSystems(...[]);
Engine.load({
ticksPerSecond: 40
});
enum EntityType {
EXAMPLE_ENTITY,
}
enum Components {
EXAMPLE_COMPONENT,
OTHER_COMPONENT
}
type ComponentData = {
[Components.EXAMPLE_COMPONENT]: {
foo: string;
},
[Components.OTHER_COMPONENT]: {
bar: number;
};
};
import { EntityTypeFunction } from "darker-engine";
const exampleEntity: EntityTypeFunction<IEntities, IComponents, ComponentData, any> = () => ({
type: Entities.EXAMPLE_ENTITY,
data: {
[Components.EXAMPLE_COMPONENT]: {
foo: "faa",
}
},
components: [Components.EXAMPLE_COMPONENT],
})
import { SystemFunction } from "darker-engine";
const exampleSystem: SystemFunction<Components> = async () => {
const onAdd = async (entityId: number) => {};
const onUpdate = async (entityId: number, component: string) => {};
const onRemove = async (entityId: number) => {};
return {
components: [],
onAdd,
onUpdate,
onRemove,
};
};
import {engine, EntityTypeFunction, SystemFunction} from "darker-engine";
enum IEntities {
EXAMPLE_ENTITY,
}
enum IComponents {
EXAMPLE_COMPONENT,
OTHER_COMPONENT,
}
type ComponentData = {
[IComponents.EXAMPLE_COMPONENT]: {
foo: string;
},
[IComponents.OTHER_COMPONENT]: {
bar: number;
};
};
export const Engine = engine<IEntities, IComponents, ComponentData>()
const exampleEntity: EntityTypeFunction<IEntities, IComponents, ComponentData, void> = () => ({
type: IEntities.EXAMPLE_ENTITY,
data: {
[IComponents.EXAMPLE_COMPONENT]: {
foo: "faa",
}
},
components: [IComponents.EXAMPLE_COMPONENT],
})
const exampleSystem: SystemFunction<IComponents> = async () => {
let interval: number
const onLoad = async () => {
console.log("welcome!");
await Engine.addEntity({
entities: [exampleEntity()]
})
interval = setInterval(() => {
const entityList = Engine.getEntityList();
const entityListByType = Engine.getEntityListByType(IEntities.EXAMPLE_ENTITY);
const entityListByComponents = Engine.getEntityListByComponents(
IComponents.EXAMPLE_COMPONENT,
);
console.log(`Entities`);
console.log(` - total: ${entityList.length}`);
console.log(` - type: ${entityListByType.length}`);
console.log(` - component: ${entityListByComponents.length}`);
}, 5000);
}
const onDestroy = async () => {
clearInterval(interval);
console.log("bye!");
}
const onAdd = async (id: number) => {
const entity = Engine.getEntity(id);
if(entity) {
await entity.updateComponent({
component: IComponents.EXAMPLE_COMPONENT,
data: {foo: 'fii2'}
})
}
}
const onUpdate = async (id: number, component?: IComponents) => {
const entity = Engine.getEntity(id);
if (!entity || component !== IComponents.EXAMPLE_COMPONENT) return;
const { foo } = entity.getComponent(IComponents.EXAMPLE_COMPONENT);
if (foo === "fii" && !entity.hasComponent(IComponents.OTHER_COMPONENT)) {
await entity.removeComponent({
component: IComponents.EXAMPLE_COMPONENT
})
}
}
const onRemove = async (entityId: number) => {
await Engine.removeEntity({ids: [entityId]})
};
const onResume = async () => {
console.log('system wake up')
}
const onPause = async () => {
console.log('system paused')
}
return {
components: [IComponents.EXAMPLE_COMPONENT],
onLoad,
onResume,
onPause,
onDestroy,
onAdd,
onUpdate,
onRemove,
}
}
await Engine.setSystems(exampleSystem);
await Engine.load({
ticksPerSecond: 2,
})
Engine.onTick(({usage, ms, status, tickCount}) => {
console.log({ ms, usage, status, tickCount})
})