diff --git a/src/Ecs/Data.ts b/src/Ecs/Data.ts index 2443ba4..eadc634 100644 --- a/src/Ecs/Data.ts +++ b/src/Ecs/Data.ts @@ -19,10 +19,22 @@ export type Store<T> = (T & HasGeneration)[]; export type SparseStore<T> = Record<number, T & HasGeneration>; export class Data { entity: Store<EntityState> = []; - - [name: string]: Store<{}> | SparseStore<{}>; } +// Ergonomic Lookup typings +type StoreKeysOf<DATA> = { + [K in keyof DATA]: DATA[K] extends Record<number, infer T & HasGeneration> ? K : never; +}; +type StoreKeys<DATA extends Data> = StoreKeysOf<DATA>[keyof DATA]; +type ItemType<S> = S extends Record<number, infer T & HasGeneration> ? T : never; +type StoreType<DATA extends Data, K> = K extends keyof DATA ? ItemType<DATA[K]> : never; +type StoreTypes<DATA extends Data, K> = { + [I in keyof K]: StoreType<DATA, K[I]>; +}; +type MaybeStoreTypes<DATA extends Data, K> = { + [I in keyof K]: StoreType<DATA, K[I]> | null; +}; + /** * Create an entity in the store * @param data store @@ -31,7 +43,7 @@ export class Data { * @returns the new entity's ID and generation */ type Assigner<DATA extends Data> = { - [S in keyof DATA]?: Pick<DATA[S][number], Exclude<keyof DATA[S][number], "generation">> + [S in StoreKeys<DATA>]?: StoreType<DATA, S> }; export function Create<DATA extends Data>(data: DATA, assign: Assigner<DATA>, state = Liveness.ALIVE): Id { const entities = data.entity; @@ -57,7 +69,8 @@ export function Create<DATA extends Data>(data: DATA, assign: Assigner<DATA>, st }; for(const key in assign) { - data[key][freeId] = {...(assign[key] as {}), generation}; + const store = data[key as keyof Data] as Store<{}>|SparseStore<{}>; + store[freeId] = {...(assign as Record<string, HasGeneration>)[key] as {}, generation}; } return [freeId, generation]; @@ -76,16 +89,6 @@ export function Remove<DATA extends Data>(data: DATA, [id, generation]: Id, stat } } -// Ergonomic Lookup typings -type ItemType<S> = S extends Record<number, infer T> ? T : never; -type StoreType<DATA extends Data, K> = K extends keyof DATA ? ItemType<DATA[K]> : never; -type StoreTypes<DATA extends Data, K extends (keyof DATA)[]> = { - [I in keyof K]: StoreType<DATA, K[I]>; -}; -type MaybeStoreTypes<DATA extends Data, K extends (keyof DATA)[]> = { - [I in keyof K]: StoreType<DATA, K[I]> | null; -}; - /** * Look up components that may or may not exist for an entity * @param data store @@ -93,12 +96,13 @@ type MaybeStoreTypes<DATA extends Data, K extends (keyof DATA)[]> = { * @param components names of components to look for * @returns the cooresponding components, with unfound ones replaced by nulls */ -export function Lookup<DATA extends Data, K extends (keyof DATA)[]>(data: DATA, [id, generation]: Id, ...components: K): MaybeStoreTypes<DATA, K> { +export function Lookup<DATA extends Data, K extends StoreKeys<DATA>[]>(data: DATA, [id, generation]: Id, ...components: K): MaybeStoreTypes<DATA, K> { const entity = data.entity[id]; // inactive entities are fine to lookup, but dead ones are not if(entity && entity.generation == generation && entity.alive != Liveness.DEAD) { return components.map(storeName => { - const component = data[storeName][id]; + const store = data[storeName] as unknown as Store<{}>|SparseStore<{}>; + const component = store[id]; if(component && component.generation == generation) { return component; } else { @@ -113,32 +117,32 @@ export function Lookup<DATA extends Data, K extends (keyof DATA)[]>(data: DATA, // Ergonomic Join typings export function Join< DATA extends Data, - A extends keyof DATA, + A extends StoreKeys<DATA>, > ( data: DATA, a: A, ): [ Id, - DATA[A][number] + StoreType<DATA, A> & {} ][]; export function Join< DATA extends Data, - A extends keyof DATA, - B extends keyof DATA, + A extends StoreKeys<DATA>, + B extends StoreKeys<DATA>, > ( data: DATA, a: A, b: B, ): [ Id, - DATA[A][number], - DATA[B][number] + StoreType<DATA, A> & {}, + StoreType<DATA, B> & {}, ][]; export function Join< DATA extends Data, - A extends keyof DATA, - B extends keyof DATA, - C extends keyof DATA, + A extends StoreKeys<DATA>, + B extends StoreKeys<DATA>, + C extends StoreKeys<DATA>, > ( data: DATA, a: A, @@ -146,16 +150,16 @@ export function Join< c: C, ): [ Id, - DATA[A][number], - DATA[B][number], - DATA[C][number] + StoreType<DATA, A> & {}, + StoreType<DATA, B> & {}, + StoreType<DATA, C> & {}, ][]; export function Join< DATA extends Data, - A extends keyof DATA, - B extends keyof DATA, - C extends keyof DATA, - D extends keyof DATA, + A extends StoreKeys<DATA>, + B extends StoreKeys<DATA>, + C extends StoreKeys<DATA>, + D extends StoreKeys<DATA>, > ( data: DATA, a: A, @@ -164,18 +168,18 @@ export function Join< d: D, ): [ Id, - DATA[A][number], - DATA[B][number], - DATA[C][number], - DATA[D][number] + StoreType<DATA, A> & {}, + StoreType<DATA, B> & {}, + StoreType<DATA, C> & {}, + StoreType<DATA, D> & {}, ][]; /** * Query a Data collection for all Alive entities possessing the named set of Components. * @returns an array of tuples containing the matching entity [ID, generation]s & associated Components */ -export function Join<DATA extends Data, K extends keyof DATA>(data: DATA, ...components: K[]): [Id, ...{}[]][] { +export function Join<DATA extends Data, K extends StoreKeys<DATA>[]>(data: DATA, ...components: K): [Id, ...{}[]][] { const entities = data.entity; - const stores: (Store<{}>|SparseStore<{}>)[] = components.map(name => data[name]); + const stores = components.map(name => data[name] as unknown as (Store<{}>|SparseStore<{}>)); const results: [Id, ...{}[]][] = []; const firstStore = stores[0];