Darker-Engine
Lightweight functional library implementation of the Entity-Component-System pattern with typescript.
Installation
Deno
Import the package with deno:
import { engine as darkerEngine } from "https://deno.land/x/darker_engine/mod.ts";npm
Install the package with npm:
npm install darker-engineConcepts
Action Queue System
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.
Config
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 }Add Actions To Queue
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()]
})Code Example
Declaration
import { engine } from "darker-engine";
export const Engine = engine<IEntities, IComponents, ComponentData>();
Engine.setSystems(...[]);
Engine.load({
ticksPerSecond: 40
});Enums
enum EntityType {
EXAMPLE_ENTITY,
}
enum Components {
EXAMPLE_COMPONENT,
OTHER_COMPONENT
}
type ComponentData = {
[Components.EXAMPLE_COMPONENT]: {
foo: string;
},
[Components.OTHER_COMPONENT]: {
bar: number;
};
};Entity
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],
})Systems
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,
};
};Full code
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]})
};
return {
components: [IComponents.EXAMPLE_COMPONENT],
onLoad,
onDestroy,
onAdd,
onUpdate,
onRemove,
}
}
await Engine.setSystems(exampleSystem);
await Engine.load({
ticksPerSecond: 2,
})
Engine.onTick(({usage, ms, status}) => {
console.log({ms, usage, actionId: status?.id})
})