143 lines
3.1 KiB
TypeScript
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;
|
|
});
|
|
}
|
|
}
|