d3/server/engine.ts

143 lines
3.1 KiB
TypeScript

import { Awaitable, ClassMap, Constructible, first, log_from, Prototyped, wait } from "../common/utils.ts";
const log = log_from(import.meta);
export class Entity {
components;
engine;
constructor(engine: Engine) {
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 CompId {
public readonly id;
constructor(id: number) {
this.id = id;
}
}
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(this);
entity.insert(new CompId(identifier));
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) {
// TODO : Wait for start.
while (true) {
await sys(this);
await wait(interval);
}
}
*all<O extends QueryRes>(query: Query<O>) {
yield* query.run(this.entities.values());
}
one<O extends QueryRes>(query: Query<O>) {
return first(this.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);
}
}
type QueryRes<T = unknown> = readonly T[];
export class Query<O extends QueryRes> {
extractor;
constructor(extractor: (entity: Entity) => O | null) {
this.extractor = extractor;
}
*run(entities: Iterable<Entity>) {
for (const entity of entities) {
const extracted = this.extractor(entity);
if (extracted !== null) yield extracted;
}
}
and<Q extends QueryRes>(other: Query<Q>) {
const this_extractor = this.extractor;
return new Query((entity) => {
const this_extracted = this_extractor(entity);
if (this_extracted === null) return null;
const other_extracted = other.extractor(entity);
if (other_extracted === null) return null;
return [...this_extracted, ...other_extracted] as const;
});
}
static with<I>(comp: Constructible<I>) {
function extract(entity: Entity) {
const extracted = entity.get(comp);
if (extracted === null) return null;
return [extracted] as const;
}
return new Query(extract);
}
with<I>(comp: Constructible<I>) {
return this.and(Query.with(comp));
}
filter(predicate: (comps: O) => boolean) {
const this_extract = this.extractor;
return new Query((entity) => {
const extracted = this_extract(entity);
if (extracted === null) return null;
if (predicate(extracted)) return extracted;
else return null;
});
}
}