ecs: tests, refactor, event args, fixed c.f

This commit is contained in:
Steven Hugg 2022-02-15 11:25:00 -06:00
parent 10baf3abc6
commit 3268925638
11 changed files with 442 additions and 85 deletions

146
src/common/ecs/README.md Normal file
View 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

View File

@ -1,5 +1,5 @@
var debug = true;
var debug = false;
export interface BoxConstraints {
left?: number;

View File

@ -129,7 +129,7 @@ export class ECSCompiler extends Tokenizer {
let name = this.expectIdent();
this.expectToken(':', 'I expected either a ":" or "end" here.'); // TODO
let type = this.parseDataType();
return { name: name.str, ...type };
return { name: name.str, $loc: name.$loc, ...type };
}
parseDataType(): DataType {
@ -369,7 +369,7 @@ export class ECSCompiler extends Tokenizer {
// TODO: remove init?
while ((cmd2 = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
let cmd = cmd2; // put in scope
if (cmd == 'var') cmd = 'init';
if (cmd == 'var') cmd = 'init'; // TODO: remove?
if (cmd == 'init' || cmd == 'const') {
// TODO: check data types
let name = this.expectIdent().str;
@ -391,14 +391,20 @@ export class ECSCompiler extends Tokenizer {
}
} else if (cmd == 'decode') {
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);
let decoder = newDecoder(decoderid, code);
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)) {
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);
}
}
}

View File

@ -1,4 +1,5 @@
import { threadId } from "worker_threads";
import { SourceLocation } from "../workertypes";
import { DataValue, ECSError } from "./ecs";
export interface DecoderResult {
@ -6,7 +7,8 @@ export interface DecoderResult {
}
abstract class LineDecoder {
lines : string[][];
curline: number = 0; // for debugging, zero-indexed
lines : string[][]; // array of token arrays
constructor(
text: string
@ -41,6 +43,12 @@ abstract class LineDecoder {
return v;
}
getErrorLocation($loc: SourceLocation): SourceLocation {
// TODO: blank lines mess this up
$loc.line += this.curline + 1;
return $loc;
}
abstract parse() : DecoderResult;
}
@ -50,7 +58,8 @@ export class VCSSpriteDecoder extends LineDecoder {
let bitmapdata = new Uint8Array(height);
let colormapdata = new Uint8Array(height);
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);
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
colormapdata[i] = this.hex(toks[1]);
@ -68,7 +77,8 @@ export class VCSPlayfieldDecoder extends LineDecoder {
let height = this.lines.length;
let pf = new Uint32Array(height);
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);
let pf0 = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
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 {
parse() {
let height = this.lines.length;
let data = new Uint8Array(192) //height * 2); TODO
let data = new Uint8Array(height * 2);
data.fill(0x3f);
// pf0 pf1 pf2 colupf colubk ctrlpf trash
const regs = [0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x3f];
let prev = [0,0,0,0,0,0,0];
let cur = [0,0,0,0,0,0,0];
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);
cur[0] = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
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}]`);
}
let chgidx = changed.length ? changed[0] : regs.length-1;
data[height*2 - i*2 - 1] = regs[chgidx];
data[height*2 - i*2 - 2] = cur[chgidx];
data[dataofs - 1] = regs[chgidx];
data[dataofs - 2] = cur[chgidx];
prev[chgidx] = cur[chgidx];
}
return {
@ -134,7 +151,8 @@ export class VCSBitmap48Decoder extends LineDecoder {
let bitmap4 = new Uint8Array(height);
let bitmap5 = new Uint8Array(height);
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);
bitmap0[i] = this.decodeBits(toks[0].slice(0,8), 8, true);
bitmap1[i] = this.decodeBits(toks[0].slice(8,16), 8, true);

View File

@ -23,6 +23,7 @@ export interface Entity extends SourceLocated {
name?: string;
etype: EntityArchetype;
consts: { [component_field: string]: DataValue };
// TODO: need scope scoping?
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 DataField = { name: string } & DataType;
export type DataField = { name: string; $loc: SourceLocation } & DataType;
export type DataType = IntType | ArrayType | RefType;
@ -418,6 +419,8 @@ class DataSegment {
return this.fieldranges[mksymbol(component, fieldName)];
}
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];
if (ofs !== undefined) {
return ofs + entityID - range.elo;
@ -425,21 +428,6 @@ class DataSegment {
// TODO: show entity name?
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() {
let a = this.ofs2sym.get(0);
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
class ActionCPUState {
loops: EntitySet[] = [];
x: EntitySet | null = null;
y: EntitySet | null = null;
xofs: number = 0;
yofs: number = 0;
xreg: IndexRegister | null = null;
yreg: IndexRegister | null = null;
}
class ActionEval {
@ -537,7 +571,8 @@ class ActionEval {
constructor(
readonly scope: EntityScope,
readonly instance: SystemInstance,
readonly action: Action)
readonly action: Action,
readonly eventargs: string[])
{
this.em = scope.em;
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 = this.qr;
else state.x = this.qr;
state.loops = state.loops.concat([this.qr]);
break;
case 'join':
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);
state.y = this.qr;
state.x = this.jr;
state.loops = state.loops.concat([this.qr]);
break;
case 'if':
case 'with':
@ -639,8 +672,6 @@ class ActionEval {
// select subset of entities
let fullEntityCount = this.qr.entities.length; //entities.length.toString();
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;
//console.log(action.event, entities.length, loopents.length);
// filter entities from loop?
@ -652,7 +683,7 @@ class ActionEval {
let rf = this.instance.params.refField;
code = this.wrapCodeInRefLookup(code);
// 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;
props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`;
} else {
@ -680,13 +711,15 @@ class ActionEval {
let toks = group.split(/\s+/);
if (toks.length == 0) throw new ECSError(`empty command`, action);
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) {
case '!': return this.__emit([rest]);
case '$': return this.__local([rest]);
case '^': return this.__use([rest]);
case '<': return this.__get([rest, '0']);
case '>': return this.__get([rest, '8']);
case '!': return this.__emit(args);
case '$': return this.__local(args);
case '^': return this.__use(args);
case '#': return this.__arg(args);
case '<': return this.__get([arg0, '0']);
case '>': return this.__get([arg0, '8']);
default:
let value = props[toks[0]];
if (value) return value;
@ -740,7 +773,8 @@ class ActionEval {
}
__emit(args: string[]) {
let event = args[0];
return this.scope.generateCodeForEvent(event);
let eventargs = args.slice(1);
return this.scope.generateCodeForEvent(event, eventargs);
}
__local(args: string[]) {
let tempinc = parseInt(args[0]);
@ -751,6 +785,10 @@ class ActionEval {
this.scope.updateTempLiveness(this.instance);
return `${this.tmplabel}+${tempinc}`;
}
__arg(args: string[]) {
let index = parseInt(args[0] || '0');
return this.eventargs[index] || '';
}
__bss_init(args: string[]) {
return this.scope.allocateInitData(this.scope.bss);
}
@ -791,15 +829,17 @@ class ActionEval {
var component: ComponentType;
var baseLookup = false;
var entityLookup = false;
let entities: Entity[];
// is qualified field?
if (fieldName.indexOf('.') > 0) {
let [entname, fname] = fieldName.split('.');
let ent = this.scope.getEntityByName(entname);
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;
entities = [ent];
entityLookup = true;
} else if (fieldName.indexOf(':') > 0) {
let [cname, fname] = fieldName.split(':');
component = this.em.getComponentByName(cname);
@ -838,10 +878,13 @@ class ActionEval {
}
// TODO: offset > 0?
// 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
// TODO: dialect
// TODO: doesnt work for entity.field
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?
if (baseLookup) {
return this.dialect.absolute(ident);
@ -982,8 +1025,11 @@ export class EntityScope implements SourceLocated {
for (var o = iter.next(); o.value; o = iter.next()) {
let { i, e, c, f, v } = o.value;
// 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 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
let array = segment.fieldranges[cfname];
if (!array) {
@ -991,7 +1037,7 @@ export class EntityScope implements SourceLocated {
} else {
array.ehi = i;
}
//console.log(i,array,cfname);
//console.log(i,e.name,array,cfname);
}
}
// TODO: cull unused entity fields
@ -1032,18 +1078,20 @@ export class EntityScope implements SourceLocated {
let entcount = range ? range.ehi - range.elo + 1 : 0;
if (v == null && f.dtype == 'int') 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);
// this is a constant
// is it a byte array?
//TODO? if (ArrayBuffer.isView(v) && 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 ptrhisym = this.dialect.fieldsymbol(c, f, 8);
let loofs = segment.allocateBytes(ptrlosym, 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})`;
segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 };
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);
let ofs = segment.getByteOffset(range, a, e.id);
// 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 (typeof segment.initdata[ofs] !== 'undefined') throw new ECSError(ofs+"");
if (e.id < range.elo) throw new ECSError('entity out of range ' + c.name + ' ' + f.name, e);
if (segment.initdata[ofs] !== undefined) throw new ECSError('initdata already set ' + ofs), e;
segment.initdata[ofs] = (v >> a.bit) & 0xff;
}
}
@ -1122,21 +1170,31 @@ export class EntityScope implements SourceLocated {
code = code.replace('{{%dest}}', segment.getOriginSymbol());
return code;
}
getFieldRange(c: ComponentType, fn: string) {
return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn);
}
// TODO: check type/range of value
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
let c = this.em.singleComponentWithFieldName([e.etype], fieldName, e);
e.consts[mksymbol(component, fieldName)] = value;
this.fieldtypes[mksymbol(component, fieldName)] = 'const';
this.setConstInitValue(e, component, fieldName, value, 'const');
}
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
let c = this.em.singleComponentWithFieldName([e.etype], fieldName, e);
e.inits[mkscopesymbol(this, component, fieldName)] = value;
this.fieldtypes[mksymbol(component, fieldName)] = 'init';
this.setConstInitValue(e, component, fieldName, value, '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' {
return this.fieldtypes[mksymbol(component, fieldName)];
}
generateCodeForEvent(event: string): string {
generateCodeForEvent(event: string, args?: string[]): string {
// find systems that respond to event
// and have entities in this scope
let systems = this.em.event2systems[event];
@ -1150,14 +1208,16 @@ export class EntityScope implements SourceLocated {
// generate code
let s = this.dialect.code();
//s += `\n; event ${event}\n`;
let eventCount = 0;
let instances = this.instances.filter(inst => systems.includes(inst.system));
for (let inst of instances) {
let sys = inst.system;
for (let action of sys.actions) {
if (action.event == event) {
eventCount++;
// TODO: use Tokenizer so error msgs are better
// TODO: keep event tree
let codeeval = new ActionEval(this, inst, action);
let codeeval = new ActionEval(this, inst, action, args || []);
codeeval.begin();
s += this.dialect.comment(`start action ${sys.name} ${inst.id} ${event}`); // TODO
s += codeeval.codeToString();
@ -1167,6 +1227,9 @@ export class EntityScope implements SourceLocated {
}
}
}
if (eventCount == 0) {
console.log(`warning: event ${event} not handled`);
}
return s;
}
getSystemStats(inst: SystemInstance) : SystemStats {
@ -1210,7 +1273,7 @@ export class EntityScope implements SourceLocated {
}
}
if (!pack.pack()) console.log('cannot pack temporary local vars'); // TODO
console.log('tempvars', pack);
//console.log('tempvars', pack);
if (bssbin.extents.right > 0) {
this.bss.allocateBytes('TEMP', bssbin.extents.right);
for (let b of pack.boxes) {

View File

@ -1,6 +1,6 @@
import { spawnSync } from "child_process";
import { readdirSync, readFileSync, writeFileSync } from "fs";
import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
import { describe } from "mocha";
import { Bin, BoxConstraints, Packer } from "../common/ecs/binpack";
import { ECSCompiler } from "../common/ecs/compiler";
@ -19,11 +19,9 @@ component Kernel
lines: 0..255
bgcolor: 0..255
end
component Bitmap
data: array of 0..255
end
component HasBitmap
bitmap: [Bitmap]
end
@ -42,16 +40,19 @@ comment ---
---
scope Root
entity kernel [Kernel]
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
entity player1 [HasBitmap]
init bitmap = 1
init bitmap = #bmp
end
end
`, 'foo.txt');
@ -59,7 +60,7 @@ end
let src = new SourceFileExport();
c.exportToFile(src);
// TODO: test?
//console.log(src.toString());
console.log(src.toString());
return em;
} catch (e) {
console.log(e);
@ -97,10 +98,11 @@ describe('Compiler', function() {
}
let code = readFileSync(ecspath, 'utf-8');
compiler.parseFile(code, ecspath);
// TODO: errors
let out = new SourceFileExport();
em.exportToFile(out);
let outtxt = out.toString();
let goodtxt = readFileSync(goodpath, 'utf-8');
let goodtxt = existsSync(goodpath) ? readFileSync(goodpath, 'utf-8') : '';
if (outtxt.trim() != goodtxt.trim()) {
let asmpath = '/tmp/' + asmfn;
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 bc of boxes) packer.boxes.push(bc);
if (!packer.pack()) throw new Error('cannot pack')
console.log(packer.boxes);
console.log(packer.bins[0].free)
//console.log(packer.boxes);
//console.log(packer.bins[0].free)
}
describe('Box Packer', function() {

28
test/ecs/narrow1.ecs Normal file
View 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
View 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

View 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

View 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

View File

@ -48,6 +48,12 @@ KernelSection_lines_b0:
.byte 192
BGColor_bgcolor_b0:
.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:
.byte 1
.byte 1
@ -57,12 +63,6 @@ Bitmap_bitmapdata_e1_b0:
.byte 31
.byte 63
.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:
.byte 8
.byte 8
@ -75,6 +75,10 @@ Bitmap_bitmapdata_e2_b0:
.byte 255
.byte 62
.byte 24
Colormap_colormapdata_b0:
.byte <(Colormap_colormapdata_e3_b0+31)
Colormap_colormapdata_b8:
.byte >(Colormap_colormapdata_e3_b0+31)
Colormap_colormapdata_e3_b0:
.byte 6
.byte 3
@ -84,10 +88,6 @@ Colormap_colormapdata_e3_b0:
.byte 14
.byte 31
.byte 63
Colormap_colormapdata_b0:
.byte <(Colormap_colormapdata_e3_b0+31)
Colormap_colormapdata_b8:
.byte >(Colormap_colormapdata_e3_b0+31)
Sprite_plyrflags_b0:
.byte 0
.byte 3