base2020/src/ecs/test.ts

237 lines
7.1 KiB
TypeScript

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,
FixedPoint,
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<T> extends Component<T> {
constructor(from: T) {
super(from);
Object.assign(this, from);
}
clone(): Generic<T> & T {
return new Generic<T>(this as unknown as T) as Generic<T> & T;
}
}
function generic<T>(from: T): Component<T> & T {
return new Generic<T>(from) as Component<T> & 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<TestSchema> {
apple: Store<Generic<Apple>>;
banana: Store<Generic<Banana>>;
carrot: Store<Generic<Carrot>>;
constructor(from: Partial<TestData>) {
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 as FixedPoint,
Y: 200 as FixedPoint,
VAngle: Math.PI
}),
bounds: new PolygonComponent({points: [-50, 50, -60, 250, 60, 250, 50, 50] as FixedPoint[]}),
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 as FixedPoint,
Y: 200 as FixedPoint,
Angle: angle,
VAngle: -Math.PI/10
}),
bounds: new PolygonComponent({points: [70, 0, 55, 40, 85, 40] as FixedPoint[]}),
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));
}