import { Select } from "../applet/Init"; import { KeyControl } from "../applet/Keyboard"; import { Loop } from "../applet/Loop"; import { DrawSet, Layer } from "../applet/Render"; import { FindCollisions } from "./Collision"; import { CollisionClass, ComponentSchema, Data, Location, PolygonComponent, RenderBounds, } from "./Components"; import { Component, copySparse, Create, EntityState, Join, Liveness, Lookup, Remove, StateForSchema, Store, } from "./Data"; import { DumbMotion } from "./Location"; import { RunRenderBounds } from "./Renderers"; class Generic extends Component { constructor(from: T) { super(from); Object.assign(this, from); } clone(): Generic & T { return new Generic(this as unknown as T) as Generic & T; } } function generic(from: T): Component & T { return new Generic(from) as Component & T; } interface Apple {} interface Banana { peeled: boolean } interface Carrot { cronch: number } interface TestSchema extends ComponentSchema { apple: Apple; banana: Banana; carrot: Carrot; } class TestData extends Data implements StateForSchema { apple: Store>; banana: Store>; carrot: Store>; constructor(from: Partial) { super(from); this.apple = copySparse(from.apple); this.banana = copySparse(from.banana); this.carrot = copySparse(from.carrot); } clone(): TestData { return new TestData(this); } } function makeTestData(): TestData { return new TestData({ entity: [ new EntityState({generation: 5, alive: Liveness.ALIVE}), new EntityState({generation: 5, alive: Liveness.DEAD}), new EntityState({generation: 5, alive: Liveness.ALIVE}), new EntityState({generation: 5, alive: Liveness.ALIVE}), new EntityState({generation: 5, alive: Liveness.INACTIVE}), new EntityState({generation: 5, alive: Liveness.ALIVE}), ], apple: [ generic({generation: 5}), generic({generation: 5}), generic({generation: -1}), generic({generation: -1}), generic({generation: 5}), generic({generation: 5}), ], banana: { 3: generic({generation: 5, peeled: false}), 4: generic({generation: 5, peeled: true}), }, carrot: { 0: generic({generation: 5, cronch: 1}), 1: generic({generation: 5, cronch: 1}), 2: generic({generation: 4, cronch: 10}), 3: generic({generation: 5, cronch: 1}), }, }); } class EcsJoinTest { constructor(pre: HTMLElement) { const data = new TestData({ }); pre.innerText = JSON.stringify({ "apples": Join(data, "apple"), "bananas": Join(data, "banana"), "carrots": Join(data, "carrot"), "apples+carrots": Join(data, "apple", "carrot"), }, null, 2); } } class EcsLookupTest { constructor(pre: HTMLElement) { const data = makeTestData(); const applesMaybeCarrots = Join(data, "apple", "id").map(([apple, id]) => ({ apple, maybeCarrot: Lookup(data, id, "carrot")[0] })); pre.innerText = JSON.stringify(applesMaybeCarrots, null, 2); } } class EcsRemoveTest { constructor(pre: HTMLElement) { const data = makeTestData(); const beforeDelete = Join(data, "apple", "carrot", "id",); Remove(data, [0, 5]); const afterDelete = Join(data, "apple", "carrot", "id"); pre.innerText = JSON.stringify({ beforeDelete, afterDelete }, null, 2); } } class EcsCreateTest { constructor(pre: HTMLElement) { const data = makeTestData(); const beforeCreate = Join(data, "apple", "banana", "carrot", "id"); const createdId = Create(data, { apple: generic({}), banana: generic({peeled: false}), carrot: generic({cronch: 11}) }); const afterCreate = Join(data, "apple", "banana", "carrot", "id"); pre.innerText = JSON.stringify({ beforeCreate, afterCreate, createdId }, null, 2); } } class LoopTest { data = new Data({}); constructor(public canvas: HTMLCanvasElement, cx: CanvasRenderingContext2D, keys: KeyControl) { const drawSet = new DrawSet(); // spinner box Create(this.data, { location: new Location({ X: 200, Y: 200, VAngle: Math.PI }), bounds: new PolygonComponent({points: [-50, 50, -60, 250, 60, 250, 50, 50]}), collisionTargetClass: new CollisionClass({ name: "block"}), renderBounds: new RenderBounds({color: "#0a0", layer: 0}), }); // triangles [0, 1, 2, 3, 4, 5].forEach(angle => Create(this.data, { location: new Location({ X: 200, Y: 200, Angle: angle, VAngle: -Math.PI/10 }), bounds: new PolygonComponent({points: [70, 0, 55, 40, 85, 40]}), collisionSourceClass: new CollisionClass({ name: "tri"}), renderBounds: new RenderBounds({ color: "#d40", layer: 0, }) })); const loop = new Loop(30, interval => { DumbMotion(this.data, interval); Join(this.data, "collisionSourceClass", "renderBounds").forEach(([collisionSourceClass, renderBounds]) => { if(collisionSourceClass.name === "tri") { renderBounds.color = "#d40"; } }); FindCollisions(this.data, 500, (className, sourceId, _targetId) => { switch(className) { case "tri>block": const [debug] = Lookup(this.data, sourceId, "renderBounds"); if(debug) debug.color = "#0ff"; break; } }); }, dt => { cx.fillStyle = "#848"; cx.fillRect(0, 0, canvas.width, canvas.height); RunRenderBounds(this.data, drawSet); drawSet.draw(cx, dt); } ); loop.start(); keys.setHandler({ press: key => { if(key == "a") loop.start(); else if(key == "b") loop.stop(); } }); } } export function BindTests(): void { Select("#EcsJoinTest").forEach(e => new EcsJoinTest(e)); Select("#EcsLookupTest").forEach(e => new EcsLookupTest(e)); Select("#EcsRemoveTest").forEach(e => new EcsRemoveTest(e)); Select("#EcsCreateTest").forEach(e => new EcsCreateTest(e)); Select("#RenderTest").forEachCanvas((c, cx, keys) => new LoopTest(c, cx, keys)); }