Change typings to distinguish dense and sparse stores, and have the container add the "generation" field instead of the container

This commit is contained in:
Tangent Wantwight 2020-01-15 22:49:52 -05:00
parent e8415da145
commit 8434e7046c
5 changed files with 36 additions and 37 deletions

View file

@ -5,7 +5,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "webpack", "build": "webpack",
"buildProduction": "webpack --mode=production" "buildProduction": "webpack --mode=production",
"tsc:check": "tsc --noEmit"
}, },
"author": "Tangent 128", "author": "Tangent 128",
"license": "ISC", "license": "ISC",

View file

@ -1,5 +1,5 @@
import { Data as CoreData, Store } from "./Data"; import { Data as CoreData, Store, SparseStore } from "./Data";
import { Layer, SpriteSheet } from "../Applet/Render"; import { Layer, SpriteSheet } from "../Applet/Render";
export class Box { export class Box {
@ -68,8 +68,8 @@ export class RenderSprite {
export class Data extends CoreData { export class Data extends CoreData {
location: Store<Location> = []; location: Store<Location> = [];
bounds: Store<Polygon> = []; bounds: Store<Polygon> = [];
renderBounds: Store<RenderBounds> = {}; renderBounds: SparseStore<RenderBounds> = {};
renderSprite: Store<RenderSprite> = {}; renderSprite: SparseStore<RenderSprite> = {};
collisionSourceClass: Store<CollisionClass> = {}; collisionSourceClass: SparseStore<CollisionClass> = {};
collisionTargetClass: Store<CollisionClass> = {}; collisionTargetClass: SparseStore<CollisionClass> = {};
} }

View file

@ -1,25 +1,26 @@
export interface Component {
generation: number;
}
export type Id = [number, number]; export type Id = [number, number];
export enum Liveness { export const enum Liveness {
DEAD = 0, DEAD = 0,
ALIVE = 1, ALIVE = 1,
INACTIVE = 2 INACTIVE = 2
} }
export interface EntityState extends Component { export interface HasGeneration {
generation: number;
}
export interface EntityState {
alive: Liveness; alive: Liveness;
} }
export type Store<T> = (T & Component)[] | Record<number, T & Component>; export type Store<T> = (T & HasGeneration)[];
export type SparseStore<T> = Record<number, T & HasGeneration>;
export class Data { export class Data {
entity: EntityState[] = []; entity: Store<EntityState> = [];
[name: string]: Store<{}>; [name: string]: Store<{}> | SparseStore<{}>;
} }
/** /**
@ -29,11 +30,8 @@ export class Data {
* @param state Liveness state, allows creating an inactive entity * @param state Liveness state, allows creating an inactive entity
* @returns the new entity's ID and generation * @returns the new entity's ID and generation
*/ */
type StripKeys<T, N> = {
[P in keyof T]: P extends N ? never : P
}[keyof T];
type Assigner<DATA extends Data> = { type Assigner<DATA extends Data> = {
[S in keyof DATA]?: Pick<DATA[S][number], StripKeys<DATA[S][number], "generation">> [S in keyof DATA]?: Pick<DATA[S][number], Exclude<keyof DATA[S][number], "generation">>
}; };
export function Create<DATA extends Data>(data: DATA, assign: Assigner<DATA>, state = Liveness.ALIVE): Id { export function Create<DATA extends Data>(data: DATA, assign: Assigner<DATA>, state = Liveness.ALIVE): Id {
const entities = data.entity; const entities = data.entity;
@ -126,7 +124,7 @@ export function Lookup<
* @param components names of components to look for * @param components names of components to look for
* @returns the cooresponding components, with unfound ones replaced by nulls * @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[]): (Component | null)[] { export function Lookup<DATA extends Data, K extends keyof DATA>(data: DATA, [id, generation]: Id, ...components: K[]): ({} | null)[] {
const entity = data.entity[id]; const entity = data.entity[id];
// inactive entities are fine to lookup, but dead ones are not // inactive entities are fine to lookup, but dead ones are not
if(entity && entity.generation == generation && entity.alive != Liveness.DEAD) { if(entity && entity.generation == generation && entity.alive != Liveness.DEAD) {
@ -206,11 +204,11 @@ export function Join<
* Query a Data collection for all Alive entities possessing the named set of Components. * 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 * @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, ...Component[]][] { export function Join<DATA extends Data, K extends keyof DATA>(data: DATA, ...components: K[]): [Id, ...{}[]][] {
const entities = data.entity; const entities = data.entity;
const stores: Store<{}>[] = components.map(name => data[name]); const stores: (Store<{}>|SparseStore<{}>)[] = components.map(name => data[name]);
const results: [Id, ...Component[]][] = []; const results: [Id, ...{}[]][] = [];
const firstStore = stores[0]; const firstStore = stores[0];
if(Array.isArray(firstStore)) { if(Array.isArray(firstStore)) {
for(let id = 0; id < firstStore.length; id++) { for(let id = 0; id < firstStore.length; id++) {
@ -223,8 +221,8 @@ export function Join<DATA extends Data, K extends keyof DATA>(data: DATA, ...com
} }
return results; return results;
} }
function JoinLoop(id: number, entities: Store<EntityState>, stores: Store<{}>[], results: [Id, ...Component[]][]) { function JoinLoop(id: number, entities: Store<EntityState>, stores: (Store<{}>|SparseStore<{}>)[], results: [Id, ...{}[]][]) {
const result: [Id, ...Component[]] = [[id, -1]]; const result: [Id, ...{}[]] = [[id, -1]];
let generation = -1; let generation = -1;
for (const store of stores) { for (const store of stores) {

View file

@ -4,15 +4,15 @@ import { Loop } from "../Applet/Loop";
import { Layer, DrawSet } from "../Applet/Render"; import { Layer, DrawSet } from "../Applet/Render";
import { Data, Location, Polygon, RenderBounds, CollisionClass } from "./Components"; import { Data, Location, Polygon, RenderBounds, CollisionClass } from "./Components";
import { FindCollisions } from "./Collision"; import { FindCollisions } from "./Collision";
import { Component, Join, Liveness, Remove, Create, Lookup } from "./Data"; import { Join, Liveness, Remove, Create, Lookup, Store, SparseStore } from "./Data";
import { DumbMotion } from "./Location"; import { DumbMotion } from "./Location";
import { RunRenderBounds } from "./Renderers"; import { RunRenderBounds } from "./Renderers";
interface Apple extends Component {} interface Apple {}
interface Banana extends Component { interface Banana {
peeled: boolean peeled: boolean
} }
interface Carrot extends Component { interface Carrot {
cronch: number cronch: number
} }
@ -25,7 +25,7 @@ class TestData extends Data {
{generation: 5, alive: Liveness.INACTIVE}, {generation: 5, alive: Liveness.INACTIVE},
{generation: 5, alive: Liveness.ALIVE}, {generation: 5, alive: Liveness.ALIVE},
]; ];
apple: Apple[] = [ apple: Store<Apple> = [
{generation: 5}, {generation: 5},
{generation: 5}, {generation: 5},
{generation: -1}, {generation: -1},
@ -33,11 +33,11 @@ class TestData extends Data {
{generation: 5}, {generation: 5},
{generation: 5}, {generation: 5},
]; ];
banana: Record<number, Banana> = { banana: SparseStore<Banana> = {
3: {generation: 5, peeled: false}, 3: {generation: 5, peeled: false},
4: {generation: 5, peeled: true}, 4: {generation: 5, peeled: true},
}; };
carrot: Record<number, Carrot> = { carrot: SparseStore<Carrot> = {
0: {generation: 5, cronch: 1}, 0: {generation: 5, cronch: 1},
1: {generation: 5, cronch: 1}, 1: {generation: 5, cronch: 1},
2: {generation: 4, cronch: 10}, 2: {generation: 4, cronch: 10},

View file

@ -1,5 +1,5 @@
import { Layer, DrawSet } from "../Applet/Render"; import { Layer, DrawSet } from "../Applet/Render";
import { Store } from "../Ecs/Data"; import { SparseStore } from "../Ecs/Data";
import { Data as EcsData } from "../Ecs/Components"; import { Data as EcsData } from "../Ecs/Components";
export enum GamePhase { export enum GamePhase {
@ -54,11 +54,11 @@ export class World {
} }
export class Data extends EcsData { export class Data extends EcsData {
boss: Store<Boss> = {}; boss: SparseStore<Boss> = {};
bullet: Store<Bullet> = {}; bullet: SparseStore<Bullet> = {};
hp: Store<Hp> = {}; hp: SparseStore<Hp> = {};
lifetime: Store<Lifetime> = {}; lifetime: SparseStore<Lifetime> = {};
message: Store<Message> = {}; message: SparseStore<Message> = {};
} }
export enum Teams { export enum Teams {