ecs: tests, refactor, event args, fixed c.f
This commit is contained in:
parent
10baf3abc6
commit
3268925638
|
@ -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 {
|
||||
left?: number;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue