Skip to content

Commit

Permalink
feat(ecs): add generics for Component & Group related types & methods
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Oct 30, 2019
1 parent e8c72d5 commit 82e3e92
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 78 deletions.
27 changes: 14 additions & 13 deletions packages/ecs/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
Fn0,
IID,
IObjectOf,
IRelease,
Type,
TypedArray
TypedArray,
TypedArrayTypeMap
} from "@thi.ng/api";

export const EVENT_ADDED = "added";
Expand All @@ -13,22 +13,23 @@ export const EVENT_CHANGED = "changed";

export type ComponentDefaultValue = ArrayLike<number> | Fn0<ArrayLike<number>>;

export type ComponentTuple = IObjectOf<TypedArray> & IID<number>;
export type ComponentTuple<K extends string> = Record<K, TypedArray> &
IID<number>;

export interface ComponentOpts {
id: string;
type: Type;
buf: ArrayBuffer;
byteOffset: number;
size: number;
stride: number;
default: ComponentDefaultValue;
cache: ICache<TypedArray>;
export interface ComponentOpts<ID extends string, T extends Type = Type.F32> {
id: ID;
type?: T;
buf?: ArrayBuffer;
byteOffset?: number;
size?: number;
stride?: number;
default?: ComponentDefaultValue;
cache?: ICache<TypedArrayTypeMap[T]>;
}

export interface GroupOpts {
id: string;
cache: ICache<ComponentTuple>;
cache: ICache<ComponentTuple<string>>;
}

export interface ComponentInfo {
Expand Down
55 changes: 30 additions & 25 deletions packages/ecs/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
INotify,
INotifyMixin,
Type,
TypedArray,
typedArray,
TypedArrayTypeMap,
UIntArray
} from "@thi.ng/api";
import { isFunction } from "@thi.ng/checks";
Expand All @@ -19,15 +19,14 @@ import {
ICache
} from "./api";

let NEXT_ID = 0;

@INotifyMixin
export class Component<V extends TypedArray> implements IID<string>, INotify {
readonly id: string;
export class Component<ID extends string, T extends Type = Type.F32>
implements IID<ID>, INotify {
readonly id: ID;

sparse: UIntArray;
dense: UIntArray;
vals: V;
vals: TypedArrayTypeMap[T];
n: number;

readonly size: number;
Expand All @@ -36,36 +35,34 @@ export class Component<V extends TypedArray> implements IID<string>, INotify {

owner?: IID<string>;

cache?: ICache<V>;
cache?: ICache<TypedArrayTypeMap[T]>;

constructor(
sparse: UIntArray,
dense: UIntArray,
opts: Partial<ComponentOpts> = {}
opts: ComponentOpts<ID, T>
) {
this.sparse = sparse;
this.dense = dense;
opts = {
type: Type.F32,
type: <any>Type.F32,
size: 1,
byteOffset: 0,
...opts
};
this.id = opts.id || `comp${NEXT_ID++}`;
this.id = opts.id;
this.size = opts.size!;
this.stride = opts.stride || this.size;
this.default = opts.default; // || zeroes(this.size);
this.vals = opts.buf
? <V>(
typedArray(
opts.type!,
opts.buf,
opts.byteOffset!,
dense.length * this.stride
)
? typedArray(
opts.type!,
opts.buf,
opts.byteOffset!,
dense.length * this.stride
)
: <V>typedArray(opts.type!, dense.length * this.stride);
this.cache = <ICache<V>>opts.cache;
: typedArray(opts.type!, dense.length * this.stride);
this.cache = opts.cache;
this.n = 0;
}

Expand All @@ -75,12 +72,14 @@ export class Component<V extends TypedArray> implements IID<string>, INotify {

*values() {
for (let i = this.n; --i >= 0; ) {
yield this.getIndex(i);
yield this.getIndex(i)!;
}
}

packedValues() {
return <V>this.vals.subarray(0, this.n * this.stride);
return <TypedArrayTypeMap[T]>(
this.vals.subarray(0, this.n * this.stride)
);
}

// TODO add version support via IDGen
Expand Down Expand Up @@ -132,9 +131,12 @@ export class Component<V extends TypedArray> implements IID<string>, INotify {
? this.cache
? this.cache.getSet(i, () => {
i *= this.stride;
return <V>this.vals.subarray(i, i + this.size);
return <TypedArrayTypeMap[T]>(
this.vals.subarray(i, i + this.size)
);
})
: ((i *= this.stride), <V>this.vals.subarray(i, i + this.size))
: ((i *= this.stride),
<TypedArrayTypeMap[T]>this.vals.subarray(i, i + this.size))
: undefined;
}

Expand All @@ -143,9 +145,12 @@ export class Component<V extends TypedArray> implements IID<string>, INotify {
? this.cache
? this.cache.getSet(i, () => {
i *= this.stride;
return <V>this.vals.subarray(i, i + this.size);
return <TypedArrayTypeMap[T]>(
this.vals.subarray(i, i + this.size)
);
})
: ((i *= this.stride), <V>this.vals.subarray(i, i + this.size))
: ((i *= this.stride),
<TypedArrayTypeMap[T]>this.vals.subarray(i, i + this.size))
: undefined;
}

Expand Down
66 changes: 48 additions & 18 deletions packages/ecs/src/ecs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
assert,
IObjectOf,
Type,
TypedArray,
typedArray,
TypedArrayTypeMap
} from "@thi.ng/api";
import { assert, Type, typedArray } from "@thi.ng/api";
import { isArray, isString } from "@thi.ng/checks";
import { ReadonlyVec } from "@thi.ng/vectors";
import { ComponentOpts, GroupOpts } from "./api";
Expand All @@ -15,20 +8,20 @@ import { IDGen } from "./id";

export class ECS {
idgen: IDGen;
components: Map<string, Component<TypedArray>>;
groups: Map<string, Group>;
components: Map<string, Component<string, Type>>;
groups: Map<string, Group<string>>;

constructor(capacity = 1000) {
this.idgen = new IDGen(capacity);
this.components = new Map();
this.groups = new Map();
}

defEntity(
defEntity<K extends string>(
comps?:
| string[]
| Component<TypedArray>[]
| IObjectOf<ReadonlyVec | undefined>
| Component<K, Type>[]
| Record<K, ReadonlyVec | undefined>
) {
const id = this.idgen.next();
assert(
Expand All @@ -54,10 +47,12 @@ export class ECS {
return id!;
}

defComponent<T extends Type = Type.F32>(opts: Partial<ComponentOpts>) {
defComponent<K extends string, T extends Type = Type.F32>(
opts: ComponentOpts<K, T>
) {
const cap = this.idgen.capacity;
const utype = uintType(cap);
const comp = new Component<TypedArrayTypeMap[T]>(
const comp = new Component<K, T>(
typedArray(utype, cap),
typedArray(utype, cap),
opts
Expand All @@ -67,11 +62,46 @@ export class ECS {
return comp;
}

defGroup<A extends string>(
comps: [Component<A, Type>],
owned?: Component<A, Type>[],
opts?: Partial<GroupOpts>
): Group<A>;
defGroup<A extends string, B extends string>(
comps: [Component<A, Type>, Component<B, Type>],
owned?: Component<A | B, Type>[],
opts?: Partial<GroupOpts>
): Group<A | B>;
defGroup<A extends string, B extends string, C extends string>(
comps: [Component<A, Type>, Component<B, Type>, Component<C, Type>],
owned?: Component<A | B | C, Type>[],
opts?: Partial<GroupOpts>
): Group<A | B | C>;
defGroup<
A extends string,
B extends string,
C extends string,
D extends string
>(
comps: [
Component<A, Type>,
Component<B, Type>,
Component<C, Type>,
Component<D, Type>
],
owned?: Component<A | B | C | D, Type>[],
opts?: Partial<GroupOpts>
): Group<A | B | C | D>;
defGroup(
comps: Component<string, Type>[],
owned?: Component<string, Type>[],
opts?: Partial<GroupOpts>
): Group<string>;
defGroup(
comps: Component<TypedArray>[],
owned: Component<TypedArray>[] = comps,
comps: Component<string, Type>[],
owned: Component<string, Type>[] = comps,
opts: Partial<GroupOpts> = {}
) {
): Group<string> {
const g = new Group(comps, owned, opts);
// TODO add exist check
this.groups.set(g.id, g);
Expand Down
48 changes: 26 additions & 22 deletions packages/ecs/src/group.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {
assert,
Event,
Fn3,
FnO2,
FnO3,
IID,
IObjectOf,
TypedArray
Type
} from "@thi.ng/api";
import { intersection } from "@thi.ng/associative";
import {
Expand All @@ -21,20 +20,20 @@ import { UnboundedCache } from "./unbounded";

let NEXT_ID = 0;

export class Group implements IID<string> {
export class Group<K extends string> implements IID<string> {
readonly id: string;

components: Component<TypedArray>[];
owned: Component<TypedArray>[];
components: Component<K, Type>[];
owned: Component<K, Type>[];
ids: Set<number>;
n: number;

info: IObjectOf<ComponentInfo>;
cache: ICache<ComponentTuple>;
info: Record<K, ComponentInfo>;
cache: ICache<ComponentTuple<K>>;

constructor(
comps: Component<TypedArray>[],
owned: Component<TypedArray>[] = comps,
comps: Component<K, Type>[],
owned: Component<K, Type>[] = comps,
opts: Partial<GroupOpts> = {}
) {
this.components = comps;
Expand All @@ -43,10 +42,13 @@ export class Group implements IID<string> {
this.id = opts.id || `group${NEXT_ID++}`;
this.cache = opts.cache || new UnboundedCache();

this.info = comps.reduce((acc: IObjectOf<ComponentInfo>, c) => {
acc[c.id] = { buffer: c.vals, size: c.size, stride: c.stride };
return acc;
}, {});
this.info = comps.reduce(
(acc: Record<K, ComponentInfo>, c) => {
acc[c.id] = { buffer: c.vals, size: c.size, stride: c.stride };
return acc;
},
<any>{}
);

// update ownerships
owned.forEach((c) => {
Expand Down Expand Up @@ -100,35 +102,37 @@ export class Group implements IID<string> {

getEntityUnsafe(id: number) {
return this.cache.getSet(id, () => {
const tuple = <ComponentTuple>{ id: id };
const tuple = <ComponentTuple<K>>{ id: id };
const comps = this.components;
for (let j = comps.length; --j >= 0; ) {
const c = comps[j];
tuple[c.id] = c.getIndex(c.sparse[id])!;
tuple[c.id] = <any>c.getIndex(c.sparse[id])!;
}
return tuple;
});
}

run(fn: FnO2<IObjectOf<ComponentInfo>, number, void>, ...xs: any[]) {
run(fn: FnO2<Record<K, ComponentInfo>, number, void>, ...xs: any[]) {
this.ensureFullyOwning();
fn(this.info, this.n, ...xs);
}

forEachRaw(fn: Fn3<IObjectOf<ComponentInfo>, number, number, void>) {
forEachRaw(
fn: FnO3<Record<K, ComponentInfo>, number, number, void>,
...xs: any[]
) {
this.ensureFullyOwning();
const info = this.info;
const ref = this.components[0].dense;
for (let i = 0, n = this.n; i < n; i++) {
fn(info, ref[i], i);
fn(info, ref[i], i, ...xs);
}
}

forEach(fn: Fn3<IObjectOf<ComponentInfo>, ComponentTuple, number, void>) {
forEach(fn: FnO2<ComponentTuple<K>, number, void>, ...xs: any[]) {
let i = 0;
const info = this.info;
for (let id of this.ids) {
fn(info, this.getEntityUnsafe(id), i++);
fn(this.getEntityUnsafe(id), i++, ...xs);
}
}

Expand Down

0 comments on commit 82e3e92

Please sign in to comment.