1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2025-02-18 00:30:43 +00:00

ecs: trying to do arrays, scores

This commit is contained in:
Steven Hugg 2022-01-31 13:28:55 -06:00
parent ec73e41f8c
commit ce1c444900
2 changed files with 106 additions and 57 deletions

View File

@ -97,13 +97,13 @@ export class ECSCompiler extends Tokenizer {
return { dtype: 'ref', query: this.parseQuery() } as RefType; return { dtype: 'ref', query: this.parseQuery() } as RefType;
} }
if (this.peekToken().str == 'array') { if (this.peekToken().str == 'array') {
let range : IntType; let index : IntType;
this.expectToken('array'); this.expectToken('array');
if (this.peekToken().type == ECSTokenType.Integer) { if (this.peekToken().type == ECSTokenType.Integer) {
range = this.parseDataType() as IntType; index = this.parseDataType() as IntType;
} }
this.expectToken('of'); this.expectToken('of');
return { dtype: 'array', range, elem: this.parseDataType() } as ArrayType; return { dtype: 'array', index, elem: this.parseDataType() } as ArrayType;
} }
this.compileError(`Unknown data type`); // TODO this.compileError(`Unknown data type`); // TODO
} }

View File

@ -1,39 +1,44 @@
/*
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
*/
// 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
import { SourceLocated, SourceLocation } from "../workertypes"; import { SourceLocated, SourceLocation } from "../workertypes";
@ -230,9 +235,9 @@ export class SourceFileExport {
} }
segment(seg: string, segtype: 'rodata' | 'bss') { segment(seg: string, segtype: 'rodata' | 'bss') {
if (segtype == 'bss') { if (segtype == 'bss') {
this.lines.push(`.segment "ZEROPAGE"`); // TODO this.lines.push(`.zeropage`); // TODO
} else { } else {
this.lines.push(`.segment "CODE"`); // TODO this.lines.push(`.code`); // TODO
} }
} }
label(sym: string) { label(sym: string) {
@ -333,11 +338,19 @@ function getFieldBits(f: IntType) {
return Math.ceil(Math.log2(n)); return Math.ceil(Math.log2(n));
} }
function getFieldLength(f: DataType) {
if (f.dtype == 'int') {
return f.hi - f.lo + 1;
} else {
return 1; //TODO?
}
}
function getPackedFieldSize(f: DataType, constValue?: DataValue): number { function getPackedFieldSize(f: DataType, constValue?: DataValue): number {
if (f.dtype == 'int') { if (f.dtype == 'int') {
return getFieldBits(f); return getFieldBits(f);
} if (f.dtype == 'array' && f.index) { } if (f.dtype == 'array' && f.index) {
return getPackedFieldSize(f.index) * getPackedFieldSize(f.elem); return 0; // TODO? getFieldLength(f.index) * getPackedFieldSize(f.elem);
} if (f.dtype == 'array' && constValue != null && Array.isArray(constValue)) { } if (f.dtype == 'array' && constValue != null && Array.isArray(constValue)) {
return constValue.length * getPackedFieldSize(f.elem); return constValue.length * getPackedFieldSize(f.elem);
} if (f.dtype == 'ref') { } if (f.dtype == 'ref') {
@ -437,11 +450,15 @@ export class EntityScope implements SourceLocated {
*/ */
} }
buildSegments() { buildSegments() {
// build FieldArray for each component/field pair
// they will be different for bss/rodata segments
let iter = this.iterateEntityFields(this.entities); let iter = this.iterateEntityFields(this.entities);
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;
let segment = v === undefined ? this.bss : this.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);
// determine range of indices for entities
let array = segment.fieldranges[cfname]; let array = segment.fieldranges[cfname];
if (!array) { if (!array) {
array = segment.fieldranges[cfname] = { component: c, field: f, elo: i, ehi: i }; array = segment.fieldranges[cfname] = { component: c, field: f, elo: i, ehi: i };
@ -452,27 +469,28 @@ export class EntityScope implements SourceLocated {
} }
} }
allocateSegment(segment: Segment, readonly: boolean) { allocateSegment(segment: Segment, readonly: boolean) {
let fields = Object.values(segment.fieldranges); let fields : FieldArray[] = Object.values(segment.fieldranges);
// TODO: fields.sort((a, b) => (a.ehi - a.elo + 1) * getPackedFieldSize(a.field)); // TODO: fields.sort((a, b) => (a.ehi - a.elo + 1) * getPackedFieldSize(a.field));
let f; let f : FieldArray | undefined;
while (f = fields.pop()) { while (f = fields.pop()) {
let name = mksymbol(f.component, f.field.name); let rangelen = (f.ehi - f.elo + 1);
let alloc = !readonly;
// TODO: doesn't work for packed arrays too well // TODO: doesn't work for packed arrays too well
let bits = getPackedFieldSize(f.field); let bits = getPackedFieldSize(f.field);
// variable size? make it a pointer // variable size? make it a pointer
if (bits == 0) bits = 16; // TODO? if (bits == 0) bits = 16; // TODO?
let rangelen = (f.ehi - f.elo + 1);
let bytesperelem = Math.ceil(bits / 8); let bytesperelem = Math.ceil(bits / 8);
// TODO: packing bits // TODO: packing bits
// TODO: split arrays // TODO: split arrays
f.access = []; let access = [];
for (let i = 0; i < bits; i += 8) { for (let i = 0; i < bits; i += 8) {
let symbol = this.dialect.fieldsymbol(f.component, f.field, i); let symbol = this.dialect.fieldsymbol(f.component, f.field, i);
f.access.push({ symbol, bit: 0, width: 8 }); // TODO access.push({ symbol, bit: 0, width: 8 }); // TODO
if (!readonly) { if (alloc) {
segment.allocateBytes(symbol, rangelen * bytesperelem); // TODO segment.allocateBytes(symbol, rangelen * bytesperelem); // TODO
} }
} }
f.access = access;
} }
} }
allocateROData(segment: Segment) { allocateROData(segment: Segment) {
@ -481,14 +499,28 @@ export class EntityScope implements SourceLocated {
let { i, e, c, f, v } = o.value; let { i, e, c, f, v } = o.value;
let cfname = mksymbol(c, f.name); let cfname = mksymbol(c, f.name);
let fieldrange = segment.fieldranges[cfname]; let fieldrange = segment.fieldranges[cfname];
if (v !== undefined) { let entcount = fieldrange ? fieldrange.ehi - fieldrange.elo + 1 : 0;
let entcount = fieldrange.ehi - fieldrange.elo + 1; if (v === undefined) {
// this is not a constant
// is it an array?
if (f.dtype == 'array' && f.index) {
let datasym = this.dialect.datasymbol(c, f, e.id);
let offset = this.bss.allocateBytes(datasym, getFieldLength(f.index));
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);
segment.initdata[loofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 0 };
segment.initdata[hiofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 8 };
}
} else {
// this is a constant
// is it a byte array? // is it a byte array?
if (v instanceof Uint8Array) { if (v instanceof Uint8Array) {
let datasym = this.dialect.datasymbol(c, f, e.id); let datasym = this.dialect.datasymbol(c, f, e.id);
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);
segment.allocateInitData(datasym, v);
let loofs = segment.allocateBytes(ptrlosym, entcount); let loofs = segment.allocateBytes(ptrlosym, entcount);
let hiofs = segment.allocateBytes(ptrhisym, entcount); let hiofs = segment.allocateBytes(ptrhisym, entcount);
segment.initdata[loofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 0 }; segment.initdata[loofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 0 };
@ -523,6 +555,11 @@ export class EntityScope implements SourceLocated {
let offset = segment.getSegmentByteOffset(c, f.name, 0, e.id); let offset = segment.getSegmentByteOffset(c, f.name, 0, e.id);
if (offset !== undefined && typeof initvalue === 'number') { if (offset !== undefined && typeof initvalue === 'number') {
initbytes[offset] = initvalue; // TODO: > 8 bits? initbytes[offset] = initvalue; // TODO: > 8 bits?
} else if (initvalue instanceof Uint8Array) {
// TODO???
let datasym = this.dialect.datasymbol(c, f, e.id);
let ofs = this.bss.symbols[datasym];
initbytes.set(initvalue, ofs);
} else { } else {
// TODO: init arrays? // TODO: init arrays?
throw new ECSError(`cannot initialize ${scfname}: ${offset} ${initvalue}`); // TODO?? throw new ECSError(`cannot initialize ${scfname}: ${offset} ${initvalue}`); // TODO??
@ -557,7 +594,7 @@ export class EntityScope implements SourceLocated {
generateCodeForEvent(event: string): string { generateCodeForEvent(event: 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.event2system[event]; let systems = this.em.event2systems[event];
if (!systems || systems.length == 0) { if (!systems || systems.length == 0) {
// TODO: error or warning? // TODO: error or warning?
console.log(`warning: no system responds to "${event}"`); return ''; console.log(`warning: no system responds to "${event}"`); return '';
@ -583,6 +620,7 @@ export class EntityScope implements SourceLocated {
//console.log('<', emit, emitcode[emit].length); //console.log('<', emit, emitcode[emit].length);
} }
} }
// TODO: use Tokenizer so error msgs are better
let code = this.replaceCode(action.text, sys, action); let code = this.replaceCode(action.text, sys, action);
s += this.dialect.comment(`<action ${sys.name}:${event}>`); s += this.dialect.comment(`<action ${sys.name}:${event}>`);
s += code; s += code;
@ -650,7 +688,11 @@ export class EntityScope implements SourceLocated {
case '@': // auto label case '@': // auto label
return `${label}_${rest}`; return `${label}_${rest}`;
case '$': // temp byte (TODO: check to make sure not overflowing) case '$': // temp byte (TODO: check to make sure not overflowing)
return `TEMP+${this.tempOffset}+${rest}`; let tempinc = parseInt(rest);
if (isNaN(tempinc)) throw new ECSError(`bad temporary offset`, action);
if (!sys.tempbytes) throw new ECSError(`this system has no locals`, action);
if (tempinc < 0 || tempinc >= sys.tempbytes) throw new ECSError(`this system only has ${sys.tempbytes} locals`, action);
return `TEMP+${this.tempOffset}+${tempinc}`;
case '=': case '=':
// TODO? // TODO?
case '<': // low byte case '<': // low byte
@ -775,7 +817,8 @@ export class EntityManager {
systems: { [name: string]: System } = {}; systems: { [name: string]: System } = {};
scopes: { [name: string]: EntityScope } = {}; scopes: { [name: string]: EntityScope } = {};
symbols: { [name: string] : 'init' | 'const' } = {}; symbols: { [name: string] : 'init' | 'const' } = {};
event2system: { [name: string]: System[] } = {}; event2systems: { [name: string]: System[] } = {};
name2cfpairs: { [name: string]: ComponentFieldPair[]} = {};
constructor(public readonly dialect: Dialect_CA65) { constructor(public readonly dialect: Dialect_CA65) {
} }
@ -787,14 +830,19 @@ export class EntityManager {
} }
defineComponent(ctype: ComponentType) { defineComponent(ctype: ComponentType) {
if (this.components[ctype.name]) throw new ECSError(`component ${ctype.name} already defined`); if (this.components[ctype.name]) throw new ECSError(`component ${ctype.name} already defined`);
for (let field of ctype.fields) {
let list = this.name2cfpairs[field.name];
if (!list) list = this.name2cfpairs[field.name] = [];
list.push({c: ctype, f: field});
}
return this.components[ctype.name] = ctype; return this.components[ctype.name] = ctype;
} }
defineSystem(system: System) { defineSystem(system: System) {
if (this.systems[system.name]) throw new ECSError(`system ${system.name} already defined`); if (this.systems[system.name]) throw new ECSError(`system ${system.name} already defined`);
for (let a of system.actions) { for (let a of system.actions) {
let event = a.event; let event = a.event;
let list = this.event2system[event]; let list = this.event2systems[event];
if (list == null) list = this.event2system[event] = []; if (list == null) list = this.event2systems[event] = [];
if (!list.includes(system)) list.push(system); if (!list.includes(system)) list.push(system);
} }
return this.systems[system.name] = system; return this.systems[system.name] = system;
@ -848,6 +896,7 @@ export class EntityManager {
} }
singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: string) { singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: string) {
let components = this.componentsWithFieldName(atypes, fieldName); let components = this.componentsWithFieldName(atypes, fieldName);
// TODO: use name2cfpairs?
if (components.length == 0) { if (components.length == 0) {
throw new ECSError(`cannot find component with field "${fieldName}" in ${where}`); throw new ECSError(`cannot find component with field "${fieldName}" in ${where}`);
} }