161 lines
3.7 KiB
TypeScript
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;
|
|
}
|
|
}
|