d3/server/engine.ts
2024-04-09 04:25:34 +02:00

161 lines
3.7 KiB
TypeScript

import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
import { Awaitable, log_from, Prototyped, Vec2 } from "../common/utils.ts";
import { ClassMap } from "../common/utils.ts";
import { wait } from "../common/utils.ts";
import { Constructible } from "../common/utils.ts";
import { first } from "../common/utils.ts";
const log = log_from(import.meta);
export class Entity {
identifier;
components;
engine;
constructor(identifier: number, engine: Engine) {
this.identifier = identifier;
this.engine = engine;
this.components = new ClassMap();
}
insert(...components: Prototyped[]) {
for (const c of components) this.components.insert(c);
return this;
}
get<I>(class_: Constructible<I>) {
return this.components.get(class_);
}
get_force<I>(class_: Constructible<I>) {
const result = this.get(class_);
if (result === null) throw new Error();
return result;
}
}
export class Engine {
next_identifier;
entities;
constructor() {
this.next_identifier = 0;
this.entities = new Map<number, Entity>();
}
start() {
log("Started engine.");
}
spawn(op: (entity: Entity) => unknown) {
const identifier = this.next_identifier++;
const entity = new Entity(identifier, this);
op(entity);
this.entities.set(identifier, entity);
return entity;
}
run<T>(sys: (engine: Engine) => T) {
return sys(this);
}
async global_system_loop(sys: (engine: Engine) => Awaitable, interval: number) {
while (true) {
await sys(this);
await wait(interval);
}
}
*query_all(query: Query) {
yield* query.run(this.entities.values());
}
query_one(query: Query) {
return first(this.query_all(query));
}
get(identifier: number) {
const value = this.entities.get(identifier);
if (value === undefined) return null;
return value;
}
get_force(identifier: number) {
const entity = this.get(identifier);
if (entity === null) throw new Error();
return entity;
}
delete(identifier: number) {
return this.entities.delete(identifier);
}
}
function* displacement_steps(position: Vec2, displacement: Vec2) {
let pos = position;
let left = displacement;
while (left.len() >= 1) {
if (left.x() >= 1) {
pos = pos.add(new Vec2(1, 0));
left = left.sub(new Vec2(1, 0));
}
if (left.x() <= -1) {
pos = pos.add(new Vec2(-1, 0));
left = left.sub(new Vec2(-1, 0));
}
if (left.y() >= 1) {
pos = pos.add(new Vec2(0, 1));
left = left.sub(new Vec2(0, 1));
}
if (left.y() <= -1) {
pos = pos.add(new Vec2(0, -1));
left = left.sub(new Vec2(0, -1));
}
yield pos;
}
}
Deno.test("test_displacement", () => {
assertEquals(
[...displacement_steps(new Vec2(1, 1), new Vec2(4, 6))],
[new Vec2(2, 2), new Vec2(3, 3), new Vec2(4, 4), new Vec2(5, 5), new Vec2(5, 6), new Vec2(5, 7)],
);
});
export class Query {
private test;
constructor(test: (entity: Entity) => boolean) {
this.test = test;
}
and(query: Query) {
return new Query((entity) => this.test(entity) && query.test(entity));
}
static with(...components: Constructible[]) {
return new Query((entity) => {
for (const comp of components) if (!entity.components.has(comp)) return false;
return true;
});
}
with(...components: Constructible[]) {
return this.and(Query.with(...components));
}
static filter<I>(component: Constructible<I>, filter: (comp: I, entity: Entity) => boolean) {
return new Query((entity) => {
const comp = entity.get(component);
if (comp === null) return false;
return filter(comp, entity);
});
}
filter<I>(component: Constructible<I>, filter: (comp: I, entity: Entity) => boolean) {
return this.and(Query.filter(component, filter));
}
*run(entities: Iterable<Entity>) {
for (const entity of entities) if (this.test(entity)) yield entity;
}
}