1
0
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:
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 { export interface BoxConstraints {
left?: number; left?: number;

View File

@ -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);
} }
} }
} }

View File

@ -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);

View File

@ -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) {

View File

@ -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
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 .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