mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-06-03 19:29:32 +00:00
ecs: tests, refactor, event args, fixed c.f
This commit is contained in:
parent
10baf3abc6
commit
3268925638
146
src/common/ecs/README.md
Normal file
146
src/common/ecs/README.md
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
|
||||||
|
# NOTES
|
||||||
|
|
||||||
|
entity scopes contain entities, and are nested
|
||||||
|
also contain segments (code, bss, rodata)
|
||||||
|
components and systems are global
|
||||||
|
component fields are stored in arrays, range of entities, can be bit-packed
|
||||||
|
some values can be constant, are stored in rodata (or loaded immediate)
|
||||||
|
optional components? on or off
|
||||||
|
union components? either X or Y or Z...
|
||||||
|
|
||||||
|
systems receive and send events, execute code on entities
|
||||||
|
systems are generated on a per-scope basis
|
||||||
|
system queries can only contain entities from self and parent scopes
|
||||||
|
starting from the 'init' event walk the event tree
|
||||||
|
include systems that have at least 1 entity in scope (except init?)
|
||||||
|
|
||||||
|
when entering scope, entities are initialized (zero or init w/ data)
|
||||||
|
to change scope, fire event w/ scope name
|
||||||
|
- how to handle bank-switching?
|
||||||
|
|
||||||
|
helps with:
|
||||||
|
- rapid prototyping w/ reasonable defaults
|
||||||
|
- deconstructing objects into arrays
|
||||||
|
- packing/unpacking bitfields
|
||||||
|
- initializing objects
|
||||||
|
- building lookup tables
|
||||||
|
- selecting and iterating objects
|
||||||
|
- managing events
|
||||||
|
- managing memory and scope
|
||||||
|
- converting assets to native formats?
|
||||||
|
- removing unused data
|
||||||
|
|
||||||
|
it's more convenient to have loops be zero-indexed
|
||||||
|
for page cross, temp storage, etc
|
||||||
|
should references be zero-indexed to a field, or global?
|
||||||
|
should we limit # of entities passed to systems? min-max
|
||||||
|
join thru a reference? load both x and y
|
||||||
|
|
||||||
|
code fragments can be parameterized like macros
|
||||||
|
if two fragments are identical, do a JSR
|
||||||
|
(do we have to look for labels?)
|
||||||
|
should events have parameters? e.g. addscore X Y Z
|
||||||
|
how are Z80 arrays working?
|
||||||
|
https://forums.nesdev.org/viewtopic.php?f=20&t=14691
|
||||||
|
https://www.cpcwiki.eu/forum/programming/trying-not-to-use-ix/msg133416/#msg133416
|
||||||
|
|
||||||
|
how to select two between two entities with once? like scoreboard
|
||||||
|
maybe stack-based interpreter?
|
||||||
|
|
||||||
|
can you query specific entities? merge with existing queries?
|
||||||
|
bigints?
|
||||||
|
source/if query?
|
||||||
|
|
||||||
|
only worry about intersection when non-contiguous ranges?
|
||||||
|
|
||||||
|
crazy idea -- full expansion, then relooper
|
||||||
|
|
||||||
|
how to avoid cycle crossing for critical code and data? bin packing
|
||||||
|
|
||||||
|
system define order, action order, entity order, using order?
|
||||||
|
what happens when a system must be nested inside another? like display kernels
|
||||||
|
|
||||||
|
constants? (NTSC vs PAL)
|
||||||
|
|
||||||
|
set operations:
|
||||||
|
|
||||||
|
E = select entities from scope
|
||||||
|
A intersect B
|
||||||
|
A join B
|
||||||
|
loop over E limit N asc/desc
|
||||||
|
select Nth from E
|
||||||
|
run if A intersects B (if)
|
||||||
|
|
||||||
|
|
||||||
|
virtual machine
|
||||||
|
- entityset stack
|
||||||
|
- register states
|
||||||
|
|
||||||
|
|
||||||
|
entity Foo[Bar] { }
|
||||||
|
|
||||||
|
system FrameLoop
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class StaticKernel
|
||||||
|
locals 12
|
||||||
|
func init ---
|
||||||
|
lda {{$0}}
|
||||||
|
sta {{$1}}
|
||||||
|
---
|
||||||
|
func display ---
|
||||||
|
lda {{$0}}
|
||||||
|
sta {{$1}}
|
||||||
|
---
|
||||||
|
end
|
||||||
|
|
||||||
|
Game {
|
||||||
|
superman: [Sprite, ...] {
|
||||||
|
var xpos=12
|
||||||
|
}
|
||||||
|
FrameLoop {
|
||||||
|
a: StaticKernel(lines=30,bgcolor=$30)
|
||||||
|
b: Kernel48(...)
|
||||||
|
c: StaticKernel(...)
|
||||||
|
|
||||||
|
on preframe {
|
||||||
|
}
|
||||||
|
on display {
|
||||||
|
}
|
||||||
|
on postframe {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
systems and scope same thing?
|
||||||
|
nested systems?
|
||||||
|
systems allocated in blocks
|
||||||
|
entities allocated in arrays, take up 1 or more blocks
|
||||||
|
mid-level abstraction for scopes/macros/(banks?)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Init
|
||||||
|
with FrameLoop do
|
||||||
|
with StaticKernel do
|
||||||
|
end
|
||||||
|
ResetSwitch:
|
||||||
|
on reset do ResetConsole
|
||||||
|
end
|
||||||
|
StaticKernel:
|
||||||
|
end
|
||||||
|
JoyButton:
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
scopes are banks!
|
||||||
|
banks need to duplicate code and/or rodata
|
||||||
|
- don't split critical code across banks
|
||||||
|
need bank trampoline macro
|
||||||
|
|
||||||
|
events might need args
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
var debug = true;
|
var debug = false;
|
||||||
|
|
||||||
export interface BoxConstraints {
|
export interface BoxConstraints {
|
||||||
left?: number;
|
left?: number;
|
||||||
|
|
|
@ -129,7 +129,7 @@ export class ECSCompiler extends Tokenizer {
|
||||||
let name = this.expectIdent();
|
let name = this.expectIdent();
|
||||||
this.expectToken(':', 'I expected either a ":" or "end" here.'); // TODO
|
this.expectToken(':', 'I expected either a ":" or "end" here.'); // TODO
|
||||||
let type = this.parseDataType();
|
let type = this.parseDataType();
|
||||||
return { name: name.str, ...type };
|
return { name: name.str, $loc: name.$loc, ...type };
|
||||||
}
|
}
|
||||||
|
|
||||||
parseDataType(): DataType {
|
parseDataType(): DataType {
|
||||||
|
@ -369,7 +369,7 @@ export class ECSCompiler extends Tokenizer {
|
||||||
// TODO: remove init?
|
// TODO: remove init?
|
||||||
while ((cmd2 = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
|
while ((cmd2 = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
|
||||||
let cmd = cmd2; // put in scope
|
let cmd = cmd2; // put in scope
|
||||||
if (cmd == 'var') cmd = 'init';
|
if (cmd == 'var') cmd = 'init'; // TODO: remove?
|
||||||
if (cmd == 'init' || cmd == 'const') {
|
if (cmd == 'init' || cmd == 'const') {
|
||||||
// TODO: check data types
|
// TODO: check data types
|
||||||
let name = this.expectIdent().str;
|
let name = this.expectIdent().str;
|
||||||
|
@ -391,14 +391,20 @@ export class ECSCompiler extends Tokenizer {
|
||||||
}
|
}
|
||||||
} else if (cmd == 'decode') {
|
} else if (cmd == 'decode') {
|
||||||
let decoderid = this.expectIdent().str;
|
let decoderid = this.expectIdent().str;
|
||||||
let code = this.expectTokenTypes([ECSTokenType.CodeFragment]).str;
|
let codetok = this.expectTokenTypes([ECSTokenType.CodeFragment]);
|
||||||
|
let code = codetok.str;
|
||||||
code = code.substring(3, code.length-3);
|
code = code.substring(3, code.length-3);
|
||||||
let decoder = newDecoder(decoderid, code);
|
let decoder = newDecoder(decoderid, code);
|
||||||
if (!decoder) { this.compileError(`I can't find a "${decoderid}" decoder.`); throw new Error() }
|
if (!decoder) { this.compileError(`I can't find a "${decoderid}" decoder.`); throw new Error() }
|
||||||
let result = decoder.parse();
|
let result;
|
||||||
|
try {
|
||||||
|
result = decoder.parse();
|
||||||
|
} catch (e) {
|
||||||
|
throw new ECSError(e.message, decoder.getErrorLocation(codetok.$loc));
|
||||||
|
}
|
||||||
for (let entry of Object.entries(result.properties)) {
|
for (let entry of Object.entries(result.properties)) {
|
||||||
let { c, f } = this.getEntityField(entity, entry[0]);
|
let { c, f } = this.getEntityField(entity, entry[0]);
|
||||||
scope.setConstValue(entity, c, f.name, entry[1]);
|
scope.setConstValue(entity, c, f.name, entry[1] as DataValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { threadId } from "worker_threads";
|
|
||||||
|
import { SourceLocation } from "../workertypes";
|
||||||
import { DataValue, ECSError } from "./ecs";
|
import { DataValue, ECSError } from "./ecs";
|
||||||
|
|
||||||
export interface DecoderResult {
|
export interface DecoderResult {
|
||||||
|
@ -6,7 +7,8 @@ export interface DecoderResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class LineDecoder {
|
abstract class LineDecoder {
|
||||||
lines : string[][];
|
curline: number = 0; // for debugging, zero-indexed
|
||||||
|
lines : string[][]; // array of token arrays
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: string
|
text: string
|
||||||
|
@ -41,6 +43,12 @@ abstract class LineDecoder {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getErrorLocation($loc: SourceLocation): SourceLocation {
|
||||||
|
// TODO: blank lines mess this up
|
||||||
|
$loc.line += this.curline + 1;
|
||||||
|
return $loc;
|
||||||
|
}
|
||||||
|
|
||||||
abstract parse() : DecoderResult;
|
abstract parse() : DecoderResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +58,8 @@ export class VCSSpriteDecoder extends LineDecoder {
|
||||||
let bitmapdata = new Uint8Array(height);
|
let bitmapdata = new Uint8Array(height);
|
||||||
let colormapdata = new Uint8Array(height);
|
let colormapdata = new Uint8Array(height);
|
||||||
for (let i=0; i<height; i++) {
|
for (let i=0; i<height; i++) {
|
||||||
let toks = this.lines[height - 1 - i];
|
this.curline = height - 1 - i;
|
||||||
|
let toks = this.lines[this.curline];
|
||||||
this.assertTokens(toks, 2);
|
this.assertTokens(toks, 2);
|
||||||
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
|
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
|
||||||
colormapdata[i] = this.hex(toks[1]);
|
colormapdata[i] = this.hex(toks[1]);
|
||||||
|
@ -68,7 +77,8 @@ export class VCSPlayfieldDecoder extends LineDecoder {
|
||||||
let height = this.lines.length;
|
let height = this.lines.length;
|
||||||
let pf = new Uint32Array(height);
|
let pf = new Uint32Array(height);
|
||||||
for (let i=0; i<height; i++) {
|
for (let i=0; i<height; i++) {
|
||||||
let toks = this.lines[height - 1 - i];
|
this.curline = height - 1 - i;
|
||||||
|
let toks = this.lines[this.curline];
|
||||||
this.assertTokens(toks, 1);
|
this.assertTokens(toks, 1);
|
||||||
let pf0 = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
|
let pf0 = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
|
||||||
let pf1 = this.decodeBits(toks[0].substring(4,12), 8, true);
|
let pf1 = this.decodeBits(toks[0].substring(4,12), 8, true);
|
||||||
|
@ -87,14 +97,21 @@ export class VCSPlayfieldDecoder extends LineDecoder {
|
||||||
export class VCSVersatilePlayfieldDecoder extends LineDecoder {
|
export class VCSVersatilePlayfieldDecoder extends LineDecoder {
|
||||||
parse() {
|
parse() {
|
||||||
let height = this.lines.length;
|
let height = this.lines.length;
|
||||||
let data = new Uint8Array(192) //height * 2); TODO
|
let data = new Uint8Array(height * 2);
|
||||||
data.fill(0x3f);
|
data.fill(0x3f);
|
||||||
// pf0 pf1 pf2 colupf colubk ctrlpf trash
|
// pf0 pf1 pf2 colupf colubk ctrlpf trash
|
||||||
const regs = [0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x3f];
|
const regs = [0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x3f];
|
||||||
let prev = [0,0,0,0,0,0,0];
|
let prev = [0,0,0,0,0,0,0];
|
||||||
let cur = [0,0,0,0,0,0,0];
|
let cur = [0,0,0,0,0,0,0];
|
||||||
for (let i=0; i<height; i++) {
|
for (let i=0; i<height; i++) {
|
||||||
let toks = this.lines[i];
|
let dataofs = height*2 - i*2;
|
||||||
|
this.curline = i;
|
||||||
|
let toks = this.lines[this.curline];
|
||||||
|
if (toks.length == 2) {
|
||||||
|
data[dataofs - 1] = this.hex(toks[0]);
|
||||||
|
data[dataofs - 2] = this.hex(toks[1]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.assertTokens(toks, 4);
|
this.assertTokens(toks, 4);
|
||||||
cur[0] = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
|
cur[0] = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
|
||||||
cur[1] = this.decodeBits(toks[0].substring(4,12), 8, true);
|
cur[1] = this.decodeBits(toks[0].substring(4,12), 8, true);
|
||||||
|
@ -112,8 +129,8 @@ export class VCSVersatilePlayfieldDecoder extends LineDecoder {
|
||||||
throw new ECSError(`More than one register change in line ${i+1}: [${changed}]`);
|
throw new ECSError(`More than one register change in line ${i+1}: [${changed}]`);
|
||||||
}
|
}
|
||||||
let chgidx = changed.length ? changed[0] : regs.length-1;
|
let chgidx = changed.length ? changed[0] : regs.length-1;
|
||||||
data[height*2 - i*2 - 1] = regs[chgidx];
|
data[dataofs - 1] = regs[chgidx];
|
||||||
data[height*2 - i*2 - 2] = cur[chgidx];
|
data[dataofs - 2] = cur[chgidx];
|
||||||
prev[chgidx] = cur[chgidx];
|
prev[chgidx] = cur[chgidx];
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -134,7 +151,8 @@ export class VCSBitmap48Decoder extends LineDecoder {
|
||||||
let bitmap4 = new Uint8Array(height);
|
let bitmap4 = new Uint8Array(height);
|
||||||
let bitmap5 = new Uint8Array(height);
|
let bitmap5 = new Uint8Array(height);
|
||||||
for (let i=0; i<height; i++) {
|
for (let i=0; i<height; i++) {
|
||||||
let toks = this.lines[height - 1 - i];
|
this.curline = height - 1 - i;
|
||||||
|
let toks = this.lines[this.curline];
|
||||||
this.assertTokens(toks, 1);
|
this.assertTokens(toks, 1);
|
||||||
bitmap0[i] = this.decodeBits(toks[0].slice(0,8), 8, true);
|
bitmap0[i] = this.decodeBits(toks[0].slice(0,8), 8, true);
|
||||||
bitmap1[i] = this.decodeBits(toks[0].slice(8,16), 8, true);
|
bitmap1[i] = this.decodeBits(toks[0].slice(8,16), 8, true);
|
||||||
|
|
|
@ -23,6 +23,7 @@ export interface Entity extends SourceLocated {
|
||||||
name?: string;
|
name?: string;
|
||||||
etype: EntityArchetype;
|
etype: EntityArchetype;
|
||||||
consts: { [component_field: string]: DataValue };
|
consts: { [component_field: string]: DataValue };
|
||||||
|
// TODO: need scope scoping?
|
||||||
inits: { [scope_component_field: string]: DataValue };
|
inits: { [scope_component_field: string]: DataValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ export type Action = ActionWithQuery | ActionWithJoin | ActionOnce;
|
||||||
|
|
||||||
export type DataValue = number | boolean | Uint8Array | Uint16Array | Uint32Array;
|
export type DataValue = number | boolean | Uint8Array | Uint16Array | Uint32Array;
|
||||||
|
|
||||||
export type DataField = { name: string } & DataType;
|
export type DataField = { name: string; $loc: SourceLocation } & DataType;
|
||||||
|
|
||||||
export type DataType = IntType | ArrayType | RefType;
|
export type DataType = IntType | ArrayType | RefType;
|
||||||
|
|
||||||
|
@ -418,6 +419,8 @@ class DataSegment {
|
||||||
return this.fieldranges[mksymbol(component, fieldName)];
|
return this.fieldranges[mksymbol(component, fieldName)];
|
||||||
}
|
}
|
||||||
getByteOffset(range: FieldArray, access: FieldAccess, entityID: number) {
|
getByteOffset(range: FieldArray, access: FieldAccess, entityID: number) {
|
||||||
|
if (entityID < range.elo) throw new ECSError(`entity ID ${entityID} too low for ${access.symbol}`);
|
||||||
|
if (entityID > range.ehi) throw new ECSError(`entity ID ${entityID} too high for ${access.symbol}`);
|
||||||
let ofs = this.symbols[access.symbol];
|
let ofs = this.symbols[access.symbol];
|
||||||
if (ofs !== undefined) {
|
if (ofs !== undefined) {
|
||||||
return ofs + entityID - range.elo;
|
return ofs + entityID - range.elo;
|
||||||
|
@ -425,21 +428,6 @@ class DataSegment {
|
||||||
// TODO: show entity name?
|
// TODO: show entity name?
|
||||||
throw new ECSError(`cannot find field access for ${access.symbol}`);
|
throw new ECSError(`cannot find field access for ${access.symbol}`);
|
||||||
}
|
}
|
||||||
getSegmentByteOffset(component: ComponentType, fieldName: string, entityID: number, bitofs: number, width: number) {
|
|
||||||
let range = this.getFieldRange(component, fieldName);
|
|
||||||
if (range && range.access) {
|
|
||||||
for (let a of range.access) {
|
|
||||||
if (a.bit == bitofs && a.width == width) {
|
|
||||||
let ofs = this.symbols[a.symbol];
|
|
||||||
if (ofs !== undefined) {
|
|
||||||
return ofs + entityID - range.elo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: show entity name?
|
|
||||||
throw new ECSError(`cannot find field offset for ${component.name}:${fieldName} entity #${entityID} bits ${bitofs} ${width}`)
|
|
||||||
}
|
|
||||||
getOriginSymbol() {
|
getOriginSymbol() {
|
||||||
let a = this.ofs2sym.get(0);
|
let a = this.ofs2sym.get(0);
|
||||||
if (!a) throw new ECSError('getOriginSymbol(): no symbol at offset 0'); // TODO
|
if (!a) throw new ECSError('getOriginSymbol(): no symbol at offset 0'); // TODO
|
||||||
|
@ -516,13 +504,59 @@ class EntitySet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IndexRegister {
|
||||||
|
lo: number | null;
|
||||||
|
hi: number | null;
|
||||||
|
offset = 0;
|
||||||
|
elo: number;
|
||||||
|
ehi: number;
|
||||||
|
eset: EntitySet | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly scope: EntityScope,
|
||||||
|
eset?: EntitySet
|
||||||
|
) {
|
||||||
|
this.elo = 0;
|
||||||
|
this.ehi = scope.entities.length - 1;
|
||||||
|
this.lo = null;
|
||||||
|
this.hi = null;
|
||||||
|
if (eset) { this.narrowInPlace(eset); }
|
||||||
|
}
|
||||||
|
entityCount() {
|
||||||
|
return this.ehi - this.elo + 1;
|
||||||
|
}
|
||||||
|
narrow(eset: EntitySet): IndexRegister {
|
||||||
|
let i = new IndexRegister(this.scope);
|
||||||
|
i.narrowInPlace(eset);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
narrowInPlace(eset: EntitySet) {
|
||||||
|
if (this.scope != eset.scope) throw new ECSError(`scope mismatch`);
|
||||||
|
if (!eset.isContiguous()) throw new ECSError(`entities are not contiguous`);
|
||||||
|
let newelo = eset.entities[0].id;
|
||||||
|
let newehi = eset.entities[eset.entities.length - 1].id;
|
||||||
|
if (this.elo > newelo) throw new ECSError(`cannot narrow to new range, elo`);
|
||||||
|
if (this.ehi < newehi) throw new ECSError(`cannot narrow to new range, ehi`);
|
||||||
|
if (this.lo == null || this.hi == null) {
|
||||||
|
this.lo = 0;
|
||||||
|
this.hi = newehi - newelo + 1;
|
||||||
|
} else {
|
||||||
|
this.offset += newelo - this.elo;
|
||||||
|
}
|
||||||
|
this.elo = newelo;
|
||||||
|
this.ehi = newehi;
|
||||||
|
this.eset = eset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: generalize
|
// todo: generalize
|
||||||
class ActionCPUState {
|
class ActionCPUState {
|
||||||
loops: EntitySet[] = [];
|
|
||||||
x: EntitySet | null = null;
|
x: EntitySet | null = null;
|
||||||
y: EntitySet | null = null;
|
y: EntitySet | null = null;
|
||||||
xofs: number = 0;
|
xofs: number = 0;
|
||||||
yofs: number = 0;
|
yofs: number = 0;
|
||||||
|
xreg: IndexRegister | null = null;
|
||||||
|
yreg: IndexRegister | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionEval {
|
class ActionEval {
|
||||||
|
@ -537,7 +571,8 @@ class ActionEval {
|
||||||
constructor(
|
constructor(
|
||||||
readonly scope: EntityScope,
|
readonly scope: EntityScope,
|
||||||
readonly instance: SystemInstance,
|
readonly instance: SystemInstance,
|
||||||
readonly action: Action)
|
readonly action: Action,
|
||||||
|
readonly eventargs: string[])
|
||||||
{
|
{
|
||||||
this.em = scope.em;
|
this.em = scope.em;
|
||||||
this.dialect = scope.em.dialect;
|
this.dialect = scope.em.dialect;
|
||||||
|
@ -570,14 +605,12 @@ class ActionEval {
|
||||||
if (state.x && state.y) throw new ECSError('no more index registers', this.action);
|
if (state.x && state.y) throw new ECSError('no more index registers', this.action);
|
||||||
if (state.x) state.y = this.qr;
|
if (state.x) state.y = this.qr;
|
||||||
else state.x = this.qr;
|
else state.x = this.qr;
|
||||||
state.loops = state.loops.concat([this.qr]);
|
|
||||||
break;
|
break;
|
||||||
case 'join':
|
case 'join':
|
||||||
if (state.x || state.y) throw new ECSError('no free index registers for join', this.action);
|
if (state.x || state.y) throw new ECSError('no free index registers for join', this.action);
|
||||||
this.jr = new EntitySet(this.scope, (this.action as ActionWithJoin).join);
|
this.jr = new EntitySet(this.scope, (this.action as ActionWithJoin).join);
|
||||||
state.y = this.qr;
|
state.y = this.qr;
|
||||||
state.x = this.jr;
|
state.x = this.jr;
|
||||||
state.loops = state.loops.concat([this.qr]);
|
|
||||||
break;
|
break;
|
||||||
case 'if':
|
case 'if':
|
||||||
case 'with':
|
case 'with':
|
||||||
|
@ -639,8 +672,6 @@ class ActionEval {
|
||||||
// select subset of entities
|
// select subset of entities
|
||||||
let fullEntityCount = this.qr.entities.length; //entities.length.toString();
|
let fullEntityCount = this.qr.entities.length; //entities.length.toString();
|
||||||
let entities = this.entities;
|
let entities = this.entities;
|
||||||
let loops = this.scope.state.loops;
|
|
||||||
let loopents = loops[loops.length-1]?.entities;
|
|
||||||
// TODO: let loopreduce = !loopents || entities.length < loopents.length;
|
// TODO: let loopreduce = !loopents || entities.length < loopents.length;
|
||||||
//console.log(action.event, entities.length, loopents.length);
|
//console.log(action.event, entities.length, loopents.length);
|
||||||
// filter entities from loop?
|
// filter entities from loop?
|
||||||
|
@ -652,7 +683,7 @@ class ActionEval {
|
||||||
let rf = this.instance.params.refField;
|
let rf = this.instance.params.refField;
|
||||||
code = this.wrapCodeInRefLookup(code);
|
code = this.wrapCodeInRefLookup(code);
|
||||||
// TODO: only fetches 1st entity in list, need offset
|
// TODO: only fetches 1st entity in list, need offset
|
||||||
let range = this.scope.bss.getFieldRange(rf.c, rf.f.name) || this.scope.rodata.getFieldRange(rf.c, rf.f.name);
|
let range = this.scope.getFieldRange(rf.c, rf.f.name);
|
||||||
let eidofs = re.id - range.elo;
|
let eidofs = re.id - range.elo;
|
||||||
props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`;
|
props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -680,13 +711,15 @@ class ActionEval {
|
||||||
let toks = group.split(/\s+/);
|
let toks = group.split(/\s+/);
|
||||||
if (toks.length == 0) throw new ECSError(`empty command`, action);
|
if (toks.length == 0) throw new ECSError(`empty command`, action);
|
||||||
let cmd = group.charAt(0);
|
let cmd = group.charAt(0);
|
||||||
let rest = group.substring(1).trim();
|
let arg0 = toks[0].substring(1).trim();
|
||||||
|
let args = [arg0].concat(toks.slice(1));
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case '!': return this.__emit([rest]);
|
case '!': return this.__emit(args);
|
||||||
case '$': return this.__local([rest]);
|
case '$': return this.__local(args);
|
||||||
case '^': return this.__use([rest]);
|
case '^': return this.__use(args);
|
||||||
case '<': return this.__get([rest, '0']);
|
case '#': return this.__arg(args);
|
||||||
case '>': return this.__get([rest, '8']);
|
case '<': return this.__get([arg0, '0']);
|
||||||
|
case '>': return this.__get([arg0, '8']);
|
||||||
default:
|
default:
|
||||||
let value = props[toks[0]];
|
let value = props[toks[0]];
|
||||||
if (value) return value;
|
if (value) return value;
|
||||||
|
@ -740,7 +773,8 @@ class ActionEval {
|
||||||
}
|
}
|
||||||
__emit(args: string[]) {
|
__emit(args: string[]) {
|
||||||
let event = args[0];
|
let event = args[0];
|
||||||
return this.scope.generateCodeForEvent(event);
|
let eventargs = args.slice(1);
|
||||||
|
return this.scope.generateCodeForEvent(event, eventargs);
|
||||||
}
|
}
|
||||||
__local(args: string[]) {
|
__local(args: string[]) {
|
||||||
let tempinc = parseInt(args[0]);
|
let tempinc = parseInt(args[0]);
|
||||||
|
@ -751,6 +785,10 @@ class ActionEval {
|
||||||
this.scope.updateTempLiveness(this.instance);
|
this.scope.updateTempLiveness(this.instance);
|
||||||
return `${this.tmplabel}+${tempinc}`;
|
return `${this.tmplabel}+${tempinc}`;
|
||||||
}
|
}
|
||||||
|
__arg(args: string[]) {
|
||||||
|
let index = parseInt(args[0] || '0');
|
||||||
|
return this.eventargs[index] || '';
|
||||||
|
}
|
||||||
__bss_init(args: string[]) {
|
__bss_init(args: string[]) {
|
||||||
return this.scope.allocateInitData(this.scope.bss);
|
return this.scope.allocateInitData(this.scope.bss);
|
||||||
}
|
}
|
||||||
|
@ -791,15 +829,17 @@ class ActionEval {
|
||||||
|
|
||||||
var component: ComponentType;
|
var component: ComponentType;
|
||||||
var baseLookup = false;
|
var baseLookup = false;
|
||||||
|
var entityLookup = false;
|
||||||
let entities: Entity[];
|
let entities: Entity[];
|
||||||
// is qualified field?
|
// is qualified field?
|
||||||
if (fieldName.indexOf('.') > 0) {
|
if (fieldName.indexOf('.') > 0) {
|
||||||
let [entname, fname] = fieldName.split('.');
|
let [entname, fname] = fieldName.split('.');
|
||||||
let ent = this.scope.getEntityByName(entname);
|
let ent = this.scope.getEntityByName(entname);
|
||||||
if (ent == null) throw new ECSError(`no entity named "${entname}" in this scope`, action);
|
if (ent == null) throw new ECSError(`no entity named "${entname}" in this scope`, action);
|
||||||
component = this.em.singleComponentWithFieldName(this.qr.atypes, fname, action);
|
component = this.em.singleComponentWithFieldName([ent.etype], fname, action);
|
||||||
fieldName = fname;
|
fieldName = fname;
|
||||||
entities = [ent];
|
entities = [ent];
|
||||||
|
entityLookup = true;
|
||||||
} else if (fieldName.indexOf(':') > 0) {
|
} else if (fieldName.indexOf(':') > 0) {
|
||||||
let [cname, fname] = fieldName.split(':');
|
let [cname, fname] = fieldName.split(':');
|
||||||
component = this.em.getComponentByName(cname);
|
component = this.em.getComponentByName(cname);
|
||||||
|
@ -838,10 +878,13 @@ class ActionEval {
|
||||||
}
|
}
|
||||||
// TODO: offset > 0?
|
// TODO: offset > 0?
|
||||||
// TODO: don't mix const and init data
|
// TODO: don't mix const and init data
|
||||||
let range = this.scope.bss.getFieldRange(component, fieldName) || this.scope.rodata.getFieldRange(component, fieldName);
|
let range = this.scope.getFieldRange(component, field.name);
|
||||||
if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); // TODO
|
if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); // TODO
|
||||||
// TODO: dialect
|
// TODO: dialect
|
||||||
|
// TODO: doesnt work for entity.field
|
||||||
let eidofs = qr.entities.length && qr.entities[0].id - range.elo; // TODO: negative?
|
let eidofs = qr.entities.length && qr.entities[0].id - range.elo; // TODO: negative?
|
||||||
|
if (entityLookup)
|
||||||
|
eidofs = entities[0].id - range.elo;
|
||||||
// TODO: array field baseoffset?
|
// TODO: array field baseoffset?
|
||||||
if (baseLookup) {
|
if (baseLookup) {
|
||||||
return this.dialect.absolute(ident);
|
return this.dialect.absolute(ident);
|
||||||
|
@ -982,8 +1025,11 @@ export class EntityScope implements SourceLocated {
|
||||||
for (var o = iter.next(); o.value; o = iter.next()) {
|
for (var o = iter.next(); o.value; o = iter.next()) {
|
||||||
let { i, e, c, f, v } = o.value;
|
let { i, e, c, f, v } = o.value;
|
||||||
// constants and array pointers go into rodata
|
// constants and array pointers go into rodata
|
||||||
let segment = v === undefined && f.dtype != 'array' ? this.bss : this.rodata;
|
|
||||||
let cfname = mksymbol(c, f.name);
|
let cfname = mksymbol(c, f.name);
|
||||||
|
let ftype = this.fieldtypes[cfname];
|
||||||
|
let segment = ftype == 'const' ? this.rodata : this.bss;
|
||||||
|
if (v === undefined && ftype == 'const')
|
||||||
|
throw new ECSError(`no value for const field ${cfname}`, e);
|
||||||
// determine range of indices for entities
|
// determine range of indices for entities
|
||||||
let array = segment.fieldranges[cfname];
|
let array = segment.fieldranges[cfname];
|
||||||
if (!array) {
|
if (!array) {
|
||||||
|
@ -991,7 +1037,7 @@ export class EntityScope implements SourceLocated {
|
||||||
} else {
|
} else {
|
||||||
array.ehi = i;
|
array.ehi = i;
|
||||||
}
|
}
|
||||||
//console.log(i,array,cfname);
|
//console.log(i,e.name,array,cfname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: cull unused entity fields
|
// TODO: cull unused entity fields
|
||||||
|
@ -1032,18 +1078,20 @@ export class EntityScope implements SourceLocated {
|
||||||
let entcount = range ? range.ehi - range.elo + 1 : 0;
|
let entcount = range ? range.ehi - range.elo + 1 : 0;
|
||||||
if (v == null && f.dtype == 'int') v = 0;
|
if (v == null && f.dtype == 'int') v = 0;
|
||||||
if (v == null && f.dtype == 'ref') v = 0;
|
if (v == null && f.dtype == 'ref') v = 0;
|
||||||
if (v == null && f.dtype == 'array') throw new ECSError(`no default value for array ${cfname}`)
|
if (v == null && f.dtype == 'array')
|
||||||
|
throw new ECSError(`no default value for array ${cfname}`, e);
|
||||||
//console.log(c.name, f.name, '#'+e.id, '=', v);
|
//console.log(c.name, f.name, '#'+e.id, '=', v);
|
||||||
// this is a constant
|
// this is a constant
|
||||||
// is it a byte array?
|
// is it a byte array?
|
||||||
//TODO? if (ArrayBuffer.isView(v) && f.dtype == 'array') {
|
//TODO? if (ArrayBuffer.isView(v) && f.dtype == 'array') {
|
||||||
if (v instanceof Uint8Array && f.dtype == 'array') {
|
if (v instanceof Uint8Array && f.dtype == 'array') {
|
||||||
let datasym = this.dialect.datasymbol(c, f, e.id, 0);
|
|
||||||
segment.allocateInitData(datasym, v);
|
|
||||||
let ptrlosym = this.dialect.fieldsymbol(c, f, 0);
|
let ptrlosym = this.dialect.fieldsymbol(c, f, 0);
|
||||||
let ptrhisym = this.dialect.fieldsymbol(c, f, 8);
|
let ptrhisym = this.dialect.fieldsymbol(c, f, 8);
|
||||||
let loofs = segment.allocateBytes(ptrlosym, entcount);
|
let loofs = segment.allocateBytes(ptrlosym, entcount);
|
||||||
let hiofs = segment.allocateBytes(ptrhisym, entcount);
|
let hiofs = segment.allocateBytes(ptrhisym, entcount);
|
||||||
|
let datasym = this.dialect.datasymbol(c, f, e.id, 0);
|
||||||
|
// TODO: share shared data
|
||||||
|
segment.allocateInitData(datasym, v);
|
||||||
if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`;
|
if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`;
|
||||||
segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 };
|
segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 };
|
||||||
segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 };
|
segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 };
|
||||||
|
@ -1056,8 +1104,8 @@ export class EntityScope implements SourceLocated {
|
||||||
segment.allocateBytes(a.symbol, entcount);
|
segment.allocateBytes(a.symbol, entcount);
|
||||||
let ofs = segment.getByteOffset(range, a, e.id);
|
let ofs = segment.getByteOffset(range, a, e.id);
|
||||||
// TODO: this happens if you forget a const field on an object?
|
// TODO: this happens if you forget a const field on an object?
|
||||||
if (e.id < range.elo) throw new ECSError(c.name + ' ' + f.name);
|
if (e.id < range.elo) throw new ECSError('entity out of range ' + c.name + ' ' + f.name, e);
|
||||||
if (typeof segment.initdata[ofs] !== 'undefined') throw new ECSError(ofs+"");
|
if (segment.initdata[ofs] !== undefined) throw new ECSError('initdata already set ' + ofs), e;
|
||||||
segment.initdata[ofs] = (v >> a.bit) & 0xff;
|
segment.initdata[ofs] = (v >> a.bit) & 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1122,21 +1170,31 @@ export class EntityScope implements SourceLocated {
|
||||||
code = code.replace('{{%dest}}', segment.getOriginSymbol());
|
code = code.replace('{{%dest}}', segment.getOriginSymbol());
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
getFieldRange(c: ComponentType, fn: string) {
|
||||||
|
return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn);
|
||||||
|
}
|
||||||
// TODO: check type/range of value
|
// TODO: check type/range of value
|
||||||
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
||||||
let c = this.em.singleComponentWithFieldName([e.etype], fieldName, e);
|
this.setConstInitValue(e, component, fieldName, value, 'const');
|
||||||
e.consts[mksymbol(component, fieldName)] = value;
|
|
||||||
this.fieldtypes[mksymbol(component, fieldName)] = 'const';
|
|
||||||
}
|
}
|
||||||
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
||||||
let c = this.em.singleComponentWithFieldName([e.etype], fieldName, e);
|
this.setConstInitValue(e, component, fieldName, value, 'init');
|
||||||
e.inits[mkscopesymbol(this, component, fieldName)] = value;
|
}
|
||||||
this.fieldtypes[mksymbol(component, fieldName)] = 'init';
|
setConstInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue,
|
||||||
|
type: 'const'|'init') {
|
||||||
|
this.em.singleComponentWithFieldName([e.etype], fieldName, e);
|
||||||
|
let cfname = mksymbol(component, fieldName);
|
||||||
|
let ecfname = mkscopesymbol(this, component, fieldName);
|
||||||
|
if (e.consts[cfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a constant`, e);
|
||||||
|
if (e.inits[ecfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a`, e);
|
||||||
|
if (type == 'const') e.consts[cfname] = value;
|
||||||
|
if (type == 'init') e.inits[ecfname] = value;
|
||||||
|
this.fieldtypes[cfname] = type;
|
||||||
}
|
}
|
||||||
isConstOrInit(component: ComponentType, fieldName: string) : 'const' | 'init' {
|
isConstOrInit(component: ComponentType, fieldName: string) : 'const' | 'init' {
|
||||||
return this.fieldtypes[mksymbol(component, fieldName)];
|
return this.fieldtypes[mksymbol(component, fieldName)];
|
||||||
}
|
}
|
||||||
generateCodeForEvent(event: string): string {
|
generateCodeForEvent(event: string, args?: string[]): string {
|
||||||
// find systems that respond to event
|
// find systems that respond to event
|
||||||
// and have entities in this scope
|
// and have entities in this scope
|
||||||
let systems = this.em.event2systems[event];
|
let systems = this.em.event2systems[event];
|
||||||
|
@ -1150,14 +1208,16 @@ export class EntityScope implements SourceLocated {
|
||||||
// generate code
|
// generate code
|
||||||
let s = this.dialect.code();
|
let s = this.dialect.code();
|
||||||
//s += `\n; event ${event}\n`;
|
//s += `\n; event ${event}\n`;
|
||||||
|
let eventCount = 0;
|
||||||
let instances = this.instances.filter(inst => systems.includes(inst.system));
|
let instances = this.instances.filter(inst => systems.includes(inst.system));
|
||||||
for (let inst of instances) {
|
for (let inst of instances) {
|
||||||
let sys = inst.system;
|
let sys = inst.system;
|
||||||
for (let action of sys.actions) {
|
for (let action of sys.actions) {
|
||||||
if (action.event == event) {
|
if (action.event == event) {
|
||||||
|
eventCount++;
|
||||||
// TODO: use Tokenizer so error msgs are better
|
// TODO: use Tokenizer so error msgs are better
|
||||||
// TODO: keep event tree
|
// TODO: keep event tree
|
||||||
let codeeval = new ActionEval(this, inst, action);
|
let codeeval = new ActionEval(this, inst, action, args || []);
|
||||||
codeeval.begin();
|
codeeval.begin();
|
||||||
s += this.dialect.comment(`start action ${sys.name} ${inst.id} ${event}`); // TODO
|
s += this.dialect.comment(`start action ${sys.name} ${inst.id} ${event}`); // TODO
|
||||||
s += codeeval.codeToString();
|
s += codeeval.codeToString();
|
||||||
|
@ -1167,6 +1227,9 @@ export class EntityScope implements SourceLocated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (eventCount == 0) {
|
||||||
|
console.log(`warning: event ${event} not handled`);
|
||||||
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
getSystemStats(inst: SystemInstance) : SystemStats {
|
getSystemStats(inst: SystemInstance) : SystemStats {
|
||||||
|
@ -1210,7 +1273,7 @@ export class EntityScope implements SourceLocated {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!pack.pack()) console.log('cannot pack temporary local vars'); // TODO
|
if (!pack.pack()) console.log('cannot pack temporary local vars'); // TODO
|
||||||
console.log('tempvars', pack);
|
//console.log('tempvars', pack);
|
||||||
if (bssbin.extents.right > 0) {
|
if (bssbin.extents.right > 0) {
|
||||||
this.bss.allocateBytes('TEMP', bssbin.extents.right);
|
this.bss.allocateBytes('TEMP', bssbin.extents.right);
|
||||||
for (let b of pack.boxes) {
|
for (let b of pack.boxes) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
import { readdirSync, readFileSync, writeFileSync } from "fs";
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { describe } from "mocha";
|
import { describe } from "mocha";
|
||||||
import { Bin, BoxConstraints, Packer } from "../common/ecs/binpack";
|
import { Bin, BoxConstraints, Packer } from "../common/ecs/binpack";
|
||||||
import { ECSCompiler } from "../common/ecs/compiler";
|
import { ECSCompiler } from "../common/ecs/compiler";
|
||||||
|
@ -19,11 +19,9 @@ component Kernel
|
||||||
lines: 0..255
|
lines: 0..255
|
||||||
bgcolor: 0..255
|
bgcolor: 0..255
|
||||||
end
|
end
|
||||||
|
|
||||||
component Bitmap
|
component Bitmap
|
||||||
data: array of 0..255
|
data: array of 0..255
|
||||||
end
|
end
|
||||||
|
|
||||||
component HasBitmap
|
component HasBitmap
|
||||||
bitmap: [Bitmap]
|
bitmap: [Bitmap]
|
||||||
end
|
end
|
||||||
|
@ -42,16 +40,19 @@ comment ---
|
||||||
---
|
---
|
||||||
|
|
||||||
scope Root
|
scope Root
|
||||||
|
|
||||||
entity kernel [Kernel]
|
entity kernel [Kernel]
|
||||||
const lines = 0xc0
|
const lines = 0xc0
|
||||||
const lines = $c0
|
//const lines = $c0
|
||||||
|
end
|
||||||
|
entity nobmp [Bitmap]
|
||||||
|
const data = [4]
|
||||||
|
end
|
||||||
|
entity bmp [Bitmap]
|
||||||
|
const data = [1,2,3]
|
||||||
end
|
end
|
||||||
|
|
||||||
entity player1 [HasBitmap]
|
entity player1 [HasBitmap]
|
||||||
init bitmap = 1
|
init bitmap = #bmp
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
`, 'foo.txt');
|
`, 'foo.txt');
|
||||||
|
@ -59,7 +60,7 @@ end
|
||||||
let src = new SourceFileExport();
|
let src = new SourceFileExport();
|
||||||
c.exportToFile(src);
|
c.exportToFile(src);
|
||||||
// TODO: test?
|
// TODO: test?
|
||||||
//console.log(src.toString());
|
console.log(src.toString());
|
||||||
return em;
|
return em;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
@ -97,10 +98,11 @@ describe('Compiler', function() {
|
||||||
}
|
}
|
||||||
let code = readFileSync(ecspath, 'utf-8');
|
let code = readFileSync(ecspath, 'utf-8');
|
||||||
compiler.parseFile(code, ecspath);
|
compiler.parseFile(code, ecspath);
|
||||||
|
// TODO: errors
|
||||||
let out = new SourceFileExport();
|
let out = new SourceFileExport();
|
||||||
em.exportToFile(out);
|
em.exportToFile(out);
|
||||||
let outtxt = out.toString();
|
let outtxt = out.toString();
|
||||||
let goodtxt = readFileSync(goodpath, 'utf-8');
|
let goodtxt = existsSync(goodpath) ? readFileSync(goodpath, 'utf-8') : '';
|
||||||
if (outtxt.trim() != goodtxt.trim()) {
|
if (outtxt.trim() != goodtxt.trim()) {
|
||||||
let asmpath = '/tmp/' + asmfn;
|
let asmpath = '/tmp/' + asmfn;
|
||||||
writeFileSync(asmpath, outtxt, 'utf-8');
|
writeFileSync(asmpath, outtxt, 'utf-8');
|
||||||
|
@ -115,8 +117,8 @@ function testPack(bins: Bin[], boxes: BoxConstraints[]) {
|
||||||
for (let bin of bins) packer.bins.push(bin);
|
for (let bin of bins) packer.bins.push(bin);
|
||||||
for (let bc of boxes) packer.boxes.push(bc);
|
for (let bc of boxes) packer.boxes.push(bc);
|
||||||
if (!packer.pack()) throw new Error('cannot pack')
|
if (!packer.pack()) throw new Error('cannot pack')
|
||||||
console.log(packer.boxes);
|
//console.log(packer.boxes);
|
||||||
console.log(packer.bins[0].free)
|
//console.log(packer.bins[0].free)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Box Packer', function() {
|
describe('Box Packer', function() {
|
||||||
|
|
28
test/ecs/narrow1.ecs
Normal file
28
test/ecs/narrow1.ecs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
component Xpos
|
||||||
|
x: 0..255
|
||||||
|
end
|
||||||
|
component Player
|
||||||
|
end
|
||||||
|
component Enemy
|
||||||
|
end
|
||||||
|
|
||||||
|
scope Main
|
||||||
|
entity foo [Xpos]
|
||||||
|
end
|
||||||
|
entity p [Xpos,Player]
|
||||||
|
init x = 50
|
||||||
|
end
|
||||||
|
entity e1 [Xpos,Enemy]
|
||||||
|
init x = 100
|
||||||
|
end
|
||||||
|
entity e2 [Xpos,Enemy]
|
||||||
|
init x = 150
|
||||||
|
end
|
||||||
|
system move
|
||||||
|
on start do foreach [Enemy]
|
||||||
|
---
|
||||||
|
lda {{<x}}
|
||||||
|
---
|
||||||
|
end
|
||||||
|
end
|
27
test/ecs/narrow1.txt
Normal file
27
test/ecs/narrow1.txt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.scope Main
|
||||||
|
.zeropage
|
||||||
|
Xpos_x_b0:
|
||||||
|
.res 1
|
||||||
|
.res 1
|
||||||
|
.res 1
|
||||||
|
.res 1
|
||||||
|
.code
|
||||||
|
__Start:
|
||||||
|
.code
|
||||||
|
|
||||||
|
;;; start action move 1 start
|
||||||
|
|
||||||
|
ldx #0
|
||||||
|
move__start__1____each:
|
||||||
|
|
||||||
|
lda Xpos_x_b0+2,x
|
||||||
|
|
||||||
|
inx
|
||||||
|
cpx #2
|
||||||
|
jne move__start__1____each
|
||||||
|
move__start__1____exit:
|
||||||
|
|
||||||
|
;;; end action move 1 start
|
||||||
|
|
||||||
|
.endscope
|
||||||
|
Main__Start = Main::__Start
|
42
test/ecs/noconstvalues.ecs
Normal file
42
test/ecs/noconstvalues.ecs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
component RoomGraphics
|
||||||
|
graphics: array 0..8 of 0..255
|
||||||
|
end
|
||||||
|
|
||||||
|
component Room
|
||||||
|
fgcolor: 0..255
|
||||||
|
bgcolor: 0..255
|
||||||
|
north: [Room]
|
||||||
|
east: [Room]
|
||||||
|
south: [Room]
|
||||||
|
west: [Room]
|
||||||
|
end
|
||||||
|
|
||||||
|
component Location
|
||||||
|
room: [Room]
|
||||||
|
end
|
||||||
|
|
||||||
|
scope Main
|
||||||
|
/*
|
||||||
|
entity NullRoom [Room]
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
entity InsideDailyPlanet [Room]
|
||||||
|
const fgcolor = $0c
|
||||||
|
const bgcolor = $12
|
||||||
|
const north = #InsideDailyPlanet
|
||||||
|
const south = #InsideDailyPlanet
|
||||||
|
const east = #OutsideDailyPlanet
|
||||||
|
const west = #InsideDailyPlanet
|
||||||
|
end
|
||||||
|
|
||||||
|
entity OutsideDailyPlanet [Room]
|
||||||
|
const fgcolor = $0c
|
||||||
|
const bgcolor = $12
|
||||||
|
const north = #InsideDailyPlanet
|
||||||
|
const south = #InsideDailyPlanet
|
||||||
|
const east = #InsideDailyPlanet
|
||||||
|
const west = #OutsideDailyPlanet
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
25
test/ecs/noconstvalues.txt
Normal file
25
test/ecs/noconstvalues.txt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
.scope Main
|
||||||
|
.zeropage
|
||||||
|
.code
|
||||||
|
Room_fgcolor_b0:
|
||||||
|
.byte 12
|
||||||
|
.byte 12
|
||||||
|
Room_bgcolor_b0:
|
||||||
|
.byte 18
|
||||||
|
.byte 18
|
||||||
|
Room_north_b0:
|
||||||
|
.byte 0
|
||||||
|
.byte 0
|
||||||
|
Room_east_b0:
|
||||||
|
.byte 1
|
||||||
|
.byte 0
|
||||||
|
Room_south_b0:
|
||||||
|
.byte 0
|
||||||
|
.byte 0
|
||||||
|
Room_west_b0:
|
||||||
|
.byte 0
|
||||||
|
.byte 1
|
||||||
|
__Start:
|
||||||
|
|
||||||
|
.endscope
|
||||||
|
Main__Start = Main::__Start
|
|
@ -48,6 +48,12 @@ KernelSection_lines_b0:
|
||||||
.byte 192
|
.byte 192
|
||||||
BGColor_bgcolor_b0:
|
BGColor_bgcolor_b0:
|
||||||
.byte 162
|
.byte 162
|
||||||
|
Bitmap_bitmapdata_b0:
|
||||||
|
.byte <(Bitmap_bitmapdata_e1_b0+31)
|
||||||
|
.byte <(Bitmap_bitmapdata_e2_b0+31)
|
||||||
|
Bitmap_bitmapdata_b8:
|
||||||
|
.byte >(Bitmap_bitmapdata_e1_b0+31)
|
||||||
|
.byte >(Bitmap_bitmapdata_e2_b0+31)
|
||||||
Bitmap_bitmapdata_e1_b0:
|
Bitmap_bitmapdata_e1_b0:
|
||||||
.byte 1
|
.byte 1
|
||||||
.byte 1
|
.byte 1
|
||||||
|
@ -57,12 +63,6 @@ Bitmap_bitmapdata_e1_b0:
|
||||||
.byte 31
|
.byte 31
|
||||||
.byte 63
|
.byte 63
|
||||||
.byte 127
|
.byte 127
|
||||||
Bitmap_bitmapdata_b0:
|
|
||||||
.byte <(Bitmap_bitmapdata_e1_b0+31)
|
|
||||||
.byte <(Bitmap_bitmapdata_e2_b0+31)
|
|
||||||
Bitmap_bitmapdata_b8:
|
|
||||||
.byte >(Bitmap_bitmapdata_e1_b0+31)
|
|
||||||
.byte >(Bitmap_bitmapdata_e2_b0+31)
|
|
||||||
Bitmap_height_b0:
|
Bitmap_height_b0:
|
||||||
.byte 8
|
.byte 8
|
||||||
.byte 8
|
.byte 8
|
||||||
|
@ -75,6 +75,10 @@ Bitmap_bitmapdata_e2_b0:
|
||||||
.byte 255
|
.byte 255
|
||||||
.byte 62
|
.byte 62
|
||||||
.byte 24
|
.byte 24
|
||||||
|
Colormap_colormapdata_b0:
|
||||||
|
.byte <(Colormap_colormapdata_e3_b0+31)
|
||||||
|
Colormap_colormapdata_b8:
|
||||||
|
.byte >(Colormap_colormapdata_e3_b0+31)
|
||||||
Colormap_colormapdata_e3_b0:
|
Colormap_colormapdata_e3_b0:
|
||||||
.byte 6
|
.byte 6
|
||||||
.byte 3
|
.byte 3
|
||||||
|
@ -84,10 +88,6 @@ Colormap_colormapdata_e3_b0:
|
||||||
.byte 14
|
.byte 14
|
||||||
.byte 31
|
.byte 31
|
||||||
.byte 63
|
.byte 63
|
||||||
Colormap_colormapdata_b0:
|
|
||||||
.byte <(Colormap_colormapdata_e3_b0+31)
|
|
||||||
Colormap_colormapdata_b8:
|
|
||||||
.byte >(Colormap_colormapdata_e3_b0+31)
|
|
||||||
Sprite_plyrflags_b0:
|
Sprite_plyrflags_b0:
|
||||||
.byte 0
|
.byte 0
|
||||||
.byte 3
|
.byte 3
|
||||||
|
|
Loading…
Reference in New Issue
Block a user