mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-29 14:51:17 +00:00
ecs: sys, action stats
This commit is contained in:
parent
dda51c3467
commit
01056c66a8
170
src/common/ecs/binpack.ts
Normal file
170
src/common/ecs/binpack.ts
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
|
||||||
|
var debug = true;
|
||||||
|
|
||||||
|
export interface BoxConstraints {
|
||||||
|
left?: number;
|
||||||
|
top?: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
box?: PlacedBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BoxPlacement {
|
||||||
|
TopLeft=0, TopRight=1, BottomLeft=2, BottomRight=3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Box {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlacedBox extends Box {
|
||||||
|
bin: Bin;
|
||||||
|
parent: Box;
|
||||||
|
place: BoxPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function boxesIntersect(a: Box, b: Box) : boolean {
|
||||||
|
return !(b.left >= a.right || b.right <= a.left || b.top >= a.bottom || b.bottom <= a.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBoxPlacements(b: PlacedBox) {
|
||||||
|
let posns : BoxPlacement[];
|
||||||
|
let snugw = b.right - b.left == b.parent.right - b.parent.left;
|
||||||
|
let snugh = b.bottom - b.top == b.parent.bottom - b.parent.top;
|
||||||
|
if (snugw && snugh) {
|
||||||
|
posns = [BoxPlacement.TopLeft];
|
||||||
|
} else if (snugw && !snugh) {
|
||||||
|
posns = [BoxPlacement.TopLeft, BoxPlacement.BottomLeft];
|
||||||
|
} else if (!snugw && snugh) {
|
||||||
|
posns = [BoxPlacement.TopLeft, BoxPlacement.TopRight];
|
||||||
|
} else {
|
||||||
|
posns = [BoxPlacement.TopLeft, BoxPlacement.TopRight,
|
||||||
|
BoxPlacement.BottomLeft, BoxPlacement.BottomRight];
|
||||||
|
}
|
||||||
|
return posns;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Bin {
|
||||||
|
boxes: Box[] = [];
|
||||||
|
free: Box[] = [];
|
||||||
|
|
||||||
|
constructor(public readonly binbounds: Box) {
|
||||||
|
this.free.push(binbounds);
|
||||||
|
}
|
||||||
|
getBoxes(bounds: Box, limit: number) : Box[] {
|
||||||
|
let result = [];
|
||||||
|
for (let box of this.boxes) {
|
||||||
|
//console.log(bounds, box, boxesIntersect(bounds, box))
|
||||||
|
if (boxesIntersect(bounds, box)) {
|
||||||
|
result.push(box);
|
||||||
|
if (result.length >= limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
fits(b: Box) {
|
||||||
|
if (!boxesIntersect(this.binbounds, b)) return false;
|
||||||
|
if (this.getBoxes(b, 1).length > 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bestFit(b: Box) : Box | null {
|
||||||
|
let bestscore = 0;
|
||||||
|
let best = null;
|
||||||
|
for (let f of this.free) {
|
||||||
|
let dx = (f.right - f.left) - (b.right - b.left);
|
||||||
|
let dy = (f.bottom - f.top) - (b.bottom - b.top);
|
||||||
|
if (dx >= 0 && dy >= 0) {
|
||||||
|
let score = 1 / (1 + dx + dy);
|
||||||
|
if (score > bestscore) {
|
||||||
|
best = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
add(b: PlacedBox) {
|
||||||
|
if (debug) console.log('added',b.left,b.top,b.right,b.bottom);
|
||||||
|
if (!this.fits(b)) {
|
||||||
|
//console.log('collided with', this.getBoxes(b, 1));
|
||||||
|
throw new Error(`bad fit ${b.left} ${b.top} ${b.right} ${b.bottom}`)
|
||||||
|
}
|
||||||
|
// add box to list
|
||||||
|
this.boxes.push(b);
|
||||||
|
// delete bin
|
||||||
|
let i = this.free.indexOf(b.parent);
|
||||||
|
if (i < 0) throw new Error('cannot find parent');
|
||||||
|
if (debug) console.log('removed',b.parent.left,b.parent.top,b.parent.right,b.parent.bottom);
|
||||||
|
this.free.splice(i, 1);
|
||||||
|
// split into new bins
|
||||||
|
switch (b.place) {
|
||||||
|
case BoxPlacement.TopLeft:
|
||||||
|
this.addFree( { top: b.top, left: b.right, bottom: b.bottom, right: b.parent.right } );
|
||||||
|
this.addFree( { top: b.bottom, left: b.parent.left, bottom: b.parent.bottom, right: b.parent.right } );
|
||||||
|
break;
|
||||||
|
case BoxPlacement.TopRight:
|
||||||
|
this.addFree( { top: b.top, left: b.parent.left, bottom: b.bottom, right: b.left } );
|
||||||
|
this.addFree( { top: b.bottom, left: b.parent.left, bottom: b.parent.bottom, right: b.parent.right } );
|
||||||
|
break;
|
||||||
|
case BoxPlacement.BottomLeft:
|
||||||
|
this.addFree( { top: b.parent.top, left: b.parent.left, bottom: b.top, right: b.parent.right } );
|
||||||
|
this.addFree( { top: b.top, left: b.right, bottom: b.parent.bottom, right: b.parent.right } );
|
||||||
|
break;
|
||||||
|
case BoxPlacement.BottomRight:
|
||||||
|
this.addFree( { top: b.parent.top, left: b.parent.left, bottom: b.top, right: b.parent.right } );
|
||||||
|
this.addFree( { top: b.top, left: b.parent.left, bottom: b.parent.bottom, right: b.left } );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFree(b: Box) {
|
||||||
|
if (b.bottom > b.top && b.right > b.left) {
|
||||||
|
if (debug) console.log('free',b.left,b.top,b.right,b.bottom);
|
||||||
|
this.free.push(b);
|
||||||
|
}
|
||||||
|
// TODO: merge free boxes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Packer {
|
||||||
|
bins : Bin[] = [];
|
||||||
|
boxes : BoxConstraints[] = [];
|
||||||
|
|
||||||
|
pack() : boolean {
|
||||||
|
for (let bc of this.boxes) {
|
||||||
|
let box = this.bestPlacement(bc);
|
||||||
|
if (!box) return false;
|
||||||
|
box.bin.add(box);
|
||||||
|
bc.box = box;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bestPlacement(b: BoxConstraints) : PlacedBox | null {
|
||||||
|
let left = b.left != null ? b.left : 0;
|
||||||
|
let top = b.top != null ? b.top : 0;
|
||||||
|
let right = left + b.width;
|
||||||
|
let bottom = top + b.height;
|
||||||
|
for (let bin of this.bins) {
|
||||||
|
let place : BoxPlacement = BoxPlacement.TopLeft; //TODO
|
||||||
|
let box = { left, top, right, bottom };
|
||||||
|
let parent = bin.bestFit(box);
|
||||||
|
if (parent) {
|
||||||
|
box.left = parent.left;
|
||||||
|
box.top = parent.top;
|
||||||
|
box.right = parent.left + b.width;
|
||||||
|
box.bottom = parent.top + b.height;
|
||||||
|
/*
|
||||||
|
if (place == BoxPlacement.BottomLeft || place == BoxPlacement.BottomRight) {
|
||||||
|
box.top = parent.bottom - (box.bottom - box.top);
|
||||||
|
}
|
||||||
|
if (place == BoxPlacement.TopRight || place == BoxPlacement.BottomRight) {
|
||||||
|
box.left = parent.right - (box.right - box.left);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return { parent, place, bin, ...box };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -107,6 +107,11 @@ export interface Query extends SourceLocated {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SystemStats {
|
||||||
|
tempstartseq: number | undefined;
|
||||||
|
tempendseq: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface System extends SourceLocated {
|
export interface System extends SourceLocated {
|
||||||
name: string;
|
name: string;
|
||||||
actions: Action[];
|
actions: Action[];
|
||||||
@ -117,6 +122,10 @@ export const SELECT_TYPE = ['once', 'foreach', 'join', 'with', 'if', 'select'] a
|
|||||||
|
|
||||||
export type SelectType = typeof SELECT_TYPE[number];
|
export type SelectType = typeof SELECT_TYPE[number];
|
||||||
|
|
||||||
|
export class ActionStats {
|
||||||
|
callcount: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ActionBase extends SourceLocated {
|
export interface ActionBase extends SourceLocated {
|
||||||
select: SelectType;
|
select: SelectType;
|
||||||
event: string;
|
event: string;
|
||||||
@ -687,6 +696,7 @@ class ActionEval {
|
|||||||
if (isNaN(tempinc)) throw new ECSError(`bad temporary offset`, this.action);
|
if (isNaN(tempinc)) throw new ECSError(`bad temporary offset`, this.action);
|
||||||
if (!this.sys.tempbytes) throw new ECSError(`this system has no locals`, this.action);
|
if (!this.sys.tempbytes) throw new ECSError(`this system has no locals`, this.action);
|
||||||
if (tempinc < 0 || tempinc >= this.sys.tempbytes) throw new ECSError(`this system only has ${this.sys.tempbytes} locals`, this.action);
|
if (tempinc < 0 || tempinc >= this.sys.tempbytes) throw new ECSError(`this system only has ${this.sys.tempbytes} locals`, this.action);
|
||||||
|
this.scope.updateTempLiveness(this.sys);
|
||||||
return `${this.tmplabel}+${tempinc}`;
|
return `${this.tmplabel}+${tempinc}`;
|
||||||
//return `TEMP+${this.scope.tempOffset}+${tempinc}`;
|
//return `TEMP+${this.scope.tempOffset}+${tempinc}`;
|
||||||
}
|
}
|
||||||
@ -828,10 +838,13 @@ export class EntityScope implements SourceLocated {
|
|||||||
systems: System[] = [];
|
systems: System[] = [];
|
||||||
entities: Entity[] = [];
|
entities: Entity[] = [];
|
||||||
fieldtypes: { [name: string]: 'init' | 'const' } = {};
|
fieldtypes: { [name: string]: 'init' | 'const' } = {};
|
||||||
|
sysstats = new Map<System, SystemStats>();
|
||||||
|
actionstats = new Map<Action, ActionStats>();
|
||||||
bss = new UninitDataSegment();
|
bss = new UninitDataSegment();
|
||||||
rodata = new ConstDataSegment();
|
rodata = new ConstDataSegment();
|
||||||
code = new CodeSegment();
|
code = new CodeSegment();
|
||||||
componentsInScope = new Set();
|
componentsInScope = new Set();
|
||||||
|
eventSeq = 0;
|
||||||
tempOffset = 0;
|
tempOffset = 0;
|
||||||
tempSize = 0;
|
tempSize = 0;
|
||||||
maxTempBytes = 0;
|
maxTempBytes = 0;
|
||||||
@ -861,6 +874,7 @@ export class EntityScope implements SourceLocated {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
addUsingSystem(system: System) {
|
addUsingSystem(system: System) {
|
||||||
|
if (!system) throw new Error();
|
||||||
this.systems.push(system);
|
this.systems.push(system);
|
||||||
}
|
}
|
||||||
getEntityByName(name: string) {
|
getEntityByName(name: string) {
|
||||||
@ -1063,9 +1077,12 @@ export class EntityScope implements SourceLocated {
|
|||||||
let systems = this.em.event2systems[event];
|
let systems = this.em.event2systems[event];
|
||||||
if (!systems || systems.length == 0) {
|
if (!systems || systems.length == 0) {
|
||||||
// TODO: error or warning?
|
// TODO: error or warning?
|
||||||
console.log(`warning: no system responds to "${event}"`); return '';
|
|
||||||
//throw new ECSError(`warning: no system responds to "${event}"`);
|
//throw new ECSError(`warning: no system responds to "${event}"`);
|
||||||
|
console.log(`warning: no system responds to "${event}"`);
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
this.eventSeq++;
|
||||||
|
// generate code
|
||||||
let s = this.dialect.code();
|
let s = this.dialect.code();
|
||||||
//s += `\n; event ${event}\n`;
|
//s += `\n; event ${event}\n`;
|
||||||
systems = systems.filter(s => this.systems.includes(s));
|
systems = systems.filter(s => this.systems.includes(s));
|
||||||
@ -1089,6 +1106,7 @@ export class EntityScope implements SourceLocated {
|
|||||||
s += this.dialect.comment(`end action ${sys.name} ${event}`);
|
s += this.dialect.comment(`end action ${sys.name} ${event}`);
|
||||||
// TODO: check that this happens once?
|
// TODO: check that this happens once?
|
||||||
codeeval.end();
|
codeeval.end();
|
||||||
|
this.getActionStats(action).callcount++;
|
||||||
numActions++;
|
numActions++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1102,6 +1120,32 @@ export class EntityScope implements SourceLocated {
|
|||||||
this.maxTempBytes = Math.max(this.tempSize, this.maxTempBytes);
|
this.maxTempBytes = Math.max(this.tempSize, this.maxTempBytes);
|
||||||
if (n < 0) this.tempOffset = this.tempSize;
|
if (n < 0) this.tempOffset = this.tempSize;
|
||||||
}
|
}
|
||||||
|
getSystemStats(sys: System) : SystemStats {
|
||||||
|
let stats = this.sysstats.get(sys);
|
||||||
|
if (!stats) {
|
||||||
|
stats = new SystemStats();
|
||||||
|
this.sysstats.set(sys, stats);
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
getActionStats(action: Action) : ActionStats {
|
||||||
|
let stats = this.actionstats.get(action);
|
||||||
|
if (!stats) {
|
||||||
|
stats = new ActionStats();
|
||||||
|
this.actionstats.set(action, stats);
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
updateTempLiveness(sys: System) {
|
||||||
|
let stats = this.getSystemStats(sys);
|
||||||
|
let n = this.eventSeq;
|
||||||
|
if (stats.tempstartseq && stats.tempendseq) {
|
||||||
|
stats.tempstartseq = Math.min(stats.tempstartseq, n);
|
||||||
|
stats.tempendseq = Math.max(stats.tempendseq, n);
|
||||||
|
} else {
|
||||||
|
stats.tempstartseq = stats.tempendseq = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
includeResource(symbol: string): string {
|
includeResource(symbol: string): string {
|
||||||
this.resources.add(symbol);
|
this.resources.add(symbol);
|
||||||
return symbol;
|
return symbol;
|
||||||
@ -1116,8 +1160,9 @@ export class EntityScope implements SourceLocated {
|
|||||||
let isMainScope = this.parent == null;
|
let isMainScope = this.parent == null;
|
||||||
this.tempOffset = this.maxTempBytes = 0;
|
this.tempOffset = this.maxTempBytes = 0;
|
||||||
let start;
|
let start;
|
||||||
if (isMainScope) {
|
let initsys = this.em.getSystemByName('Init');
|
||||||
this.addUsingSystem(this.em.getSystemByName('Init')); //TODO: what if none?
|
if (isMainScope && initsys) {
|
||||||
|
this.addUsingSystem(initsys); //TODO: what if none?
|
||||||
start = this.generateCodeForEvent('main_init');
|
start = this.generateCodeForEvent('main_init');
|
||||||
} else {
|
} else {
|
||||||
start = this.generateCodeForEvent('start');
|
start = this.generateCodeForEvent('start');
|
||||||
@ -1127,6 +1172,15 @@ export class EntityScope implements SourceLocated {
|
|||||||
let code = this.generateCodeForEvent(sub);
|
let code = this.generateCodeForEvent(sub);
|
||||||
this.code.addCodeFragment(code);
|
this.code.addCodeFragment(code);
|
||||||
}
|
}
|
||||||
|
//this.showStats();
|
||||||
|
}
|
||||||
|
showStats() {
|
||||||
|
for (let sys of this.systems) {
|
||||||
|
console.log(sys.name, this.getSystemStats(sys));
|
||||||
|
}
|
||||||
|
for (let action of Array.from(this.actionstats.keys())) {
|
||||||
|
console.log(action.event, this.getActionStats(action));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dump(file: SourceFileExport) {
|
dump(file: SourceFileExport) {
|
||||||
this.analyzeEntities();
|
this.analyzeEntities();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { execFileSync } from "child_process";
|
import { execFileSync, spawnSync } from "child_process";
|
||||||
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { describe } from "mocha";
|
import { describe } from "mocha";
|
||||||
|
import { Bin, Packer } from "../common/ecs/binpack";
|
||||||
import { ECSCompiler } from "../common/ecs/compiler";
|
import { ECSCompiler } from "../common/ecs/compiler";
|
||||||
import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs";
|
import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs";
|
||||||
|
|
||||||
@ -389,8 +390,24 @@ describe('Compiler', function() {
|
|||||||
let goodtxt = readFileSync(destpath, 'utf-8');
|
let goodtxt = readFileSync(destpath, 'utf-8');
|
||||||
if (outtxt.trim() != goodtxt.trim()) {
|
if (outtxt.trim() != goodtxt.trim()) {
|
||||||
writeFileSync('/tmp/' + goodfn, outtxt, 'utf-8');
|
writeFileSync('/tmp/' + goodfn, outtxt, 'utf-8');
|
||||||
execFileSync('/usr/bin/diff', [srcpath, destpath]);
|
console.log(spawnSync('/usr/bin/diff', [srcpath, destpath], {encoding:'utf-8'}).stdout);
|
||||||
throw new Error(ecsfn + ' did not match test file');
|
throw new Error(ecsfn + ' did not match test file');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Box Packer', function() {
|
||||||
|
it('Should pack boxes', function() {
|
||||||
|
let packer = new Packer();
|
||||||
|
let bin1 = new Bin({ left:0, top:0, right:10, bottom:10 });
|
||||||
|
packer.bins.push(bin1);
|
||||||
|
packer.boxes.push({ width: 5, height: 5 });
|
||||||
|
packer.boxes.push({ width: 5, height: 5 });
|
||||||
|
packer.boxes.push({ width: 5, height: 5 });
|
||||||
|
packer.boxes.push({ width: 5, height: 5 });
|
||||||
|
if (!packer.pack()) throw new Error('cannot pack')
|
||||||
|
console.log(packer.boxes);
|
||||||
|
console.log(packer.bins[0].free)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user