1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-11-25 18:33:11 +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;
}
if (this.peekToken().str == 'array') {
let range : IntType;
let index : IntType;
this.expectToken('array');
if (this.peekToken().type == ECSTokenType.Integer) {
range = this.parseDataType() as IntType;
index = this.parseDataType() as IntType;
}
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
}

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";
@ -230,9 +235,9 @@ export class SourceFileExport {
}
segment(seg: string, segtype: 'rodata' | 'bss') {
if (segtype == 'bss') {
this.lines.push(`.segment "ZEROPAGE"`); // TODO
this.lines.push(`.zeropage`); // TODO
} else {
this.lines.push(`.segment "CODE"`); // TODO
this.lines.push(`.code`); // TODO
}
}
label(sym: string) {
@ -333,11 +338,19 @@ function getFieldBits(f: IntType) {
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 {
if (f.dtype == 'int') {
return getFieldBits(f);
} 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)) {
return constValue.length * getPackedFieldSize(f.elem);
} if (f.dtype == 'ref') {
@ -437,11 +450,15 @@ export class EntityScope implements SourceLocated {
*/
}
buildSegments() {
// build FieldArray for each component/field pair
// they will be different for bss/rodata segments
let iter = this.iterateEntityFields(this.entities);
for (var o = iter.next(); o.value; o = iter.next()) {
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);
// determine range of indices for entities
let array = segment.fieldranges[cfname];
if (!array) {
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) {
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));
let f;
let f : FieldArray | undefined;
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
let bits = getPackedFieldSize(f.field);
// variable size? make it a pointer
if (bits == 0) bits = 16; // TODO?
let rangelen = (f.ehi - f.elo + 1);
let bytesperelem = Math.ceil(bits / 8);
// TODO: packing bits
// TODO: split arrays
f.access = [];
let access = [];
for (let i = 0; i < bits; i += 8) {
let symbol = this.dialect.fieldsymbol(f.component, f.field, i);
f.access.push({ symbol, bit: 0, width: 8 }); // TODO
if (!readonly) {
access.push({ symbol, bit: 0, width: 8 }); // TODO
if (alloc) {
segment.allocateBytes(symbol, rangelen * bytesperelem); // TODO
}
}
f.access = access;
}
}
allocateROData(segment: Segment) {
@ -481,14 +499,28 @@ export class EntityScope implements SourceLocated {
let { i, e, c, f, v } = o.value;
let cfname = mksymbol(c, f.name);
let fieldrange = segment.fieldranges[cfname];
if (v !== undefined) {
let entcount = fieldrange.ehi - fieldrange.elo + 1;
let entcount = fieldrange ? fieldrange.ehi - fieldrange.elo + 1 : 0;
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?
if (v instanceof Uint8Array) {
let datasym = this.dialect.datasymbol(c, f, e.id);
segment.allocateInitData(datasym, v);
let ptrlosym = this.dialect.fieldsymbol(c, f, 0);
let ptrhisym = this.dialect.fieldsymbol(c, f, 8);
segment.allocateInitData(datasym, v);
let loofs = segment.allocateBytes(ptrlosym, entcount);
let hiofs = segment.allocateBytes(ptrhisym, entcount);
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);
if (offset !== undefined && typeof initvalue === 'number') {
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 {
// TODO: init arrays?
throw new ECSError(`cannot initialize ${scfname}: ${offset} ${initvalue}`); // TODO??
@ -557,7 +594,7 @@ export class EntityScope implements SourceLocated {
generateCodeForEvent(event: string): string {
// find systems that respond to event
// and have entities in this scope
let systems = this.em.event2system[event];
let systems = this.em.event2systems[event];
if (!systems || systems.length == 0) {
// TODO: error or warning?
console.log(`warning: no system responds to "${event}"`); return '';
@ -583,6 +620,7 @@ export class EntityScope implements SourceLocated {
//console.log('<', emit, emitcode[emit].length);
}
}
// TODO: use Tokenizer so error msgs are better
let code = this.replaceCode(action.text, sys, action);
s += this.dialect.comment(`<action ${sys.name}:${event}>`);
s += code;
@ -650,7 +688,11 @@ export class EntityScope implements SourceLocated {
case '@': // auto label
return `${label}_${rest}`;
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 '=':
// TODO?
case '<': // low byte
@ -775,7 +817,8 @@ export class EntityManager {
systems: { [name: string]: System } = {};
scopes: { [name: string]: EntityScope } = {};
symbols: { [name: string] : 'init' | 'const' } = {};
event2system: { [name: string]: System[] } = {};
event2systems: { [name: string]: System[] } = {};
name2cfpairs: { [name: string]: ComponentFieldPair[]} = {};
constructor(public readonly dialect: Dialect_CA65) {
}
@ -787,14 +830,19 @@ export class EntityManager {
}
defineComponent(ctype: ComponentType) {
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;
}
defineSystem(system: System) {
if (this.systems[system.name]) throw new ECSError(`system ${system.name} already defined`);
for (let a of system.actions) {
let event = a.event;
let list = this.event2system[event];
if (list == null) list = this.event2system[event] = [];
let list = this.event2systems[event];
if (list == null) list = this.event2systems[event] = [];
if (!list.includes(system)) list.push(system);
}
return this.systems[system.name] = system;
@ -848,6 +896,7 @@ export class EntityManager {
}
singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: string) {
let components = this.componentsWithFieldName(atypes, fieldName);
// TODO: use name2cfpairs?
if (components.length == 0) {
throw new ECSError(`cannot find component with field "${fieldName}" in ${where}`);
}