1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2025-08-14 11:25:43 +00:00

ecs: added join, limit, fixed some things, took out ca65 listing for now

This commit is contained in:
Steven Hugg
2022-01-30 09:01:55 -06:00
parent 69cd28aa0e
commit 249d4735eb
6 changed files with 149 additions and 71 deletions

View File

@@ -19,8 +19,8 @@
var directives_list = [ var directives_list = [
'component', 'system', 'entity', 'scope', 'end', 'component', 'system', 'entity', 'scope', 'end',
'const', 'init', 'locals', 'const', 'init', 'locals',
'on', 'do', 'emit', 'on', 'do', 'emit', 'limit',
'once', 'foreach', 'source', 'once', 'foreach', 'source', 'join'
]; ];
var keywords_list = [ var keywords_list = [
'processor', 'processor',

View File

@@ -152,9 +152,15 @@ export class ECSCompiler extends Tokenizer {
parseAction(): Action { parseAction(): Action {
let event = this.expectIdent().str; let event = this.expectIdent().str;
this.expectToken('do'); this.expectToken('do');
let select = this.expectTokens(['once', 'foreach', 'source']).str as SelectType; // TODO: type check? let select = this.expectTokens(['once', 'foreach', 'source', 'join']).str as SelectType; // TODO: type check?
let query = this.parseQuery(); let query = this.parseQuery();
let join = select == 'join' && this.parseQuery();
let emits; let emits;
let limit;
if (this.peekToken().str == 'limit') {
this.consumeToken();
limit = this.expectInteger();
}
if (this.peekToken().str == 'emit') { if (this.peekToken().str == 'emit') {
this.consumeToken(); this.consumeToken();
this.expectToken('('); this.expectToken('(');
@@ -162,7 +168,8 @@ export class ECSCompiler extends Tokenizer {
this.expectToken(')'); this.expectToken(')');
} }
let text = this.parseCode(); let text = this.parseCode();
return { text, event, query, select }; let action = { text, event, query, join, select, limit };
return action;
} }
parseQuery() { parseQuery() {
@@ -222,12 +229,12 @@ export class ECSCompiler extends Tokenizer {
// TODO: check data types // TODO: check data types
if (cmd == 'const' || cmd == 'init') { if (cmd == 'const' || cmd == 'init') {
let name = this.expectIdent().str; let name = this.expectIdent().str;
this.expectToken('=');
let comps = this.em.componentsWithFieldName([{etype: e.etype, cmatch:e.etype.components}], name); let comps = this.em.componentsWithFieldName([{etype: e.etype, cmatch:e.etype.components}], name);
if (comps.length == 0) this.compileError(`I couldn't find a field named "${name}" for this entity.`) if (comps.length == 0) this.compileError(`I couldn't find a field named "${name}" for this entity.`)
if (comps.length > 1) this.compileError(`I found more than one field named "${name}" for this entity.`) if (comps.length > 1) this.compileError(`I found more than one field named "${name}" for this entity.`)
let field = comps[0].fields.find(f => f.name == name); let field = comps[0].fields.find(f => f.name == name);
if (!field) this.internalError(); if (!field) this.internalError();
this.expectToken('=');
let value = this.parseDataValue(field); let value = this.parseDataValue(field);
if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value); if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value);
if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value); if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value);

View File

@@ -33,6 +33,7 @@
// for page cross, temp storage, etc // for page cross, temp storage, etc
// should references be zero-indexed to a field, or global? // should references be zero-indexed to a field, or global?
// should we limit # of entities passed to systems? min-max // 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";
@@ -92,12 +93,14 @@ export interface System {
export interface Action { export interface Action {
text: string; text: string;
event: string; event: string;
query: Query;
select: SelectType select: SelectType
query: Query;
join?: Query;
limit?: number;
emits?: string[]; emits?: string[];
} }
export type SelectType = 'once' | 'foreach' | 'source'; export type SelectType = 'once' | 'foreach' | 'source' | 'join';
export type DataValue = number | boolean | Uint8Array | Uint16Array; export type DataValue = number | boolean | Uint8Array | Uint16Array;
@@ -160,6 +163,17 @@ export class Dialect_CA65 {
cpx #{{ecount}} cpx #{{ecount}}
bne @__each bne @__each
`; `;
readonly ASM_ITERATE_JOIN = `
ldy #0
@__each:
ldx {{joinfield}},y
{{code}}
iny
cpy #{{ecount}}
bne @__each
`;
readonly INIT_FROM_ARRAY = ` readonly INIT_FROM_ARRAY = `
ldy #{{nbytes}} ldy #{{nbytes}}
: lda {{src}}-1,y : lda {{src}}-1,y
@@ -345,18 +359,19 @@ export class EntityScope {
} }
newEntity(etype: EntityArchetype): Entity { newEntity(etype: EntityArchetype): Entity {
// TODO: add parent ID? lock parent scope? // TODO: add parent ID? lock parent scope?
// TODO: name identical check?
let id = this.entities.length; let id = this.entities.length;
etype = this.em.addArchetype(etype);
let entity: Entity = { id, etype, consts: {}, inits: {} }; let entity: Entity = { id, etype, consts: {}, inits: {} };
this.em.archtypes.add(etype);
for (let c of etype.components) { for (let c of etype.components) {
this.componentsInScope.add(c.name); this.componentsInScope.add(c.name);
} }
this.entities.push(entity); this.entities.push(entity);
return entity; return entity;
} }
*iterateFields() { *iterateEntityFields(entities: Entity[]) {
for (let i = 0; i < this.entities.length; i++) { for (let i = 0; i < entities.length; i++) {
let e = this.entities[i]; let e = entities[i];
for (let c of e.etype.components) { for (let c of e.etype.components) {
for (let f of c.fields) { for (let f of c.fields) {
yield { i, e, c, f, v: e.consts[mksymbol(c, f.name)] }; yield { i, e, c, f, v: e.consts[mksymbol(c, f.name)] };
@@ -364,8 +379,77 @@ export class EntityScope {
} }
} }
} }
*iterateArchetypeFields(arch: ArchetypeMatch[], filter?: (c:ComponentType,f:DataField) => boolean) {
for (let i = 0; i < arch.length; i++) {
let a = arch[i];
for (let c of a.etype.components) {
for (let f of c.fields) {
if (!filter || filter(c,f))
yield { i, c, f };
}
}
}
}
entitiesMatching(atypes: ArchetypeMatch[]) {
let result : Entity[] = [];
for (let e of this.entities) {
for (let a of atypes) {
// TODO: what about subclasses?
// TODO: very scary identity ocmpare
if (e.etype === a.etype) {
result.push(e);
break;
}
}
}
return result;
}
getSystems(events: string[]) {
let result : System[] = [];
for (let sys of Object.values(this.em.systems)) {
if (this.systemListensTo(sys, events)) {
result.push(sys);
}
}
return result;
}
systemListensTo(sys: System, events: string[]) {
for (let action of sys.actions) {
if (action.event != null && events.includes(action.event)) {
let archs = this.em.archetypesMatching(action.query);
for (let arch of archs) {
for (let ctype of arch.cmatch) {
if (this.hasComponent(ctype)) {
return true;
}
}
}
}
}
}
hasComponent(ctype: ComponentType) {
return this.componentsInScope.has(ctype.name);
}
getJoinField(atypes: ArchetypeMatch[], jtypes: ArchetypeMatch[]) : ComponentFieldPair {
let refs = Array.from(this.iterateArchetypeFields(atypes, (c,f) => f.dtype == 'ref'));
if (refs.length == 0) throw new ECSError(`cannot find join fields`);
if (refs.length > 1) throw new ECSError(`cannot join multiple fields`);
// TODO: check to make sure join works
return refs[0]; // TODO
/* TODO
let match = refs.map(ref => this.em.archetypesMatching((ref.f as RefType).query));
for (let ref of refs) {
let m = this.em.archetypesMatching((ref.f as RefType).query);
for (let a of m) {
if (jtypes.includes(a.etype)) {
console.log(a,m);
}
}
}
*/
}
buildSegments() { buildSegments() {
let iter = this.iterateFields(); 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; let segment = v === undefined ? this.bss : this.rodata;
@@ -404,7 +488,7 @@ export class EntityScope {
} }
} }
allocateROData(segment: Segment) { allocateROData(segment: Segment) {
let iter = this.iterateFields(); 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 cfname = mksymbol(c, f.name); let cfname = mksymbol(c, f.name);
@@ -441,7 +525,7 @@ export class EntityScope {
} }
allocateInitData(segment: Segment) { allocateInitData(segment: Segment) {
let initbytes = new Uint8Array(segment.size); let initbytes = new Uint8Array(segment.size);
let iter = this.iterateFields(); 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 scfname = mkscopesymbol(this, c, f.name); let scfname = mkscopesymbol(this, c, f.name);
@@ -526,7 +610,7 @@ export class EntityScope {
if (n < 0) this.tempOffset = this.tempSize; if (n < 0) this.tempOffset = this.tempSize;
} }
replaceCode(code: string, sys: System, action: Action): string { replaceCode(code: string, sys: System, action: Action): string {
const re = /\{\{(.+?)\}\}/g; const tag_re = /\{\{(.+?)\}\}/g;
let label = `${sys.name}__${action.event}`; let label = `${sys.name}__${action.event}`;
let atypes = this.em.archetypesMatching(action.query); let atypes = this.em.archetypesMatching(action.query);
let entities = this.entitiesMatching(atypes); let entities = this.entitiesMatching(atypes);
@@ -534,14 +618,22 @@ export class EntityScope {
// TODO: "source"? // TODO: "source"?
// TODO: what if only 1 item? // TODO: what if only 1 item?
if (action.select == 'foreach') { if (action.select == 'foreach') {
code = this.wrapCodeInLoop(code, sys, action, entities); code = this.wrapCodeInLoop(code, action, entities);
//console.log(sys.name, action.event, ents);
//frag = this.iterateCode(frag);
} }
if (action.select == 'join' && action.join) {
let jtypes = this.em.archetypesMatching(action.join);
let jentities = this.entitiesMatching(jtypes);
let joinfield = this.getJoinField(atypes, jtypes);
// TODO: what if only 1 item?
code = this.wrapCodeInLoop(code, action, entities, joinfield);
atypes = jtypes;
entities = jentities;
}
if (entities.length == 0) throw new ECSError(`action ${label} doesn't match any entities`);
// replace @labels // replace @labels
code = code.replace(/@(\w+)\b/g, (s: string, a: string) => `${label}__${a}`); code = code.replace(/@(\w+)\b/g, (s: string, a: string) => `${label}__${a}`);
// replace {{...}} tags // replace {{...}} tags
return code.replace(re, (entire, group: string) => { return code.replace(tag_re, (entire, group: string) => {
let cmd = group.charAt(0); let cmd = group.charAt(0);
let rest = group.substring(1); let rest = group.substring(1);
switch (cmd) { switch (cmd) {
@@ -569,14 +661,22 @@ export class EntityScope {
this.subroutines.add(symbol); this.subroutines.add(symbol);
return symbol; return symbol;
} }
wrapCodeInLoop(code: string, sys: System, action: Action, ents: Entity[]): string { wrapCodeInLoop(code: string, action: Action, ents: Entity[], joinfield?: ComponentFieldPair): string {
// TODO: check ents // TODO: check ents
// TODO: check segment bounds // TODO: check segment bounds
// TODO: what if 0 or 1 entitites?
let s = this.dialect.ASM_ITERATE_EACH; let s = this.dialect.ASM_ITERATE_EACH;
s = s.replace('{{elo}}', ents[0].id.toString()); if (joinfield) s = this.dialect.ASM_ITERATE_JOIN;
s = s.replace('{{ehi}}', ents[ents.length - 1].id.toString()); if (action.limit) {
s = s.replace('{{ecount}}', ents.length.toString()); ents = ents.slice(0, action.limit);
}
s = s.replace('{{elo}}', () => ents[0].id.toString());
s = s.replace('{{ehi}}', () => ents[ents.length - 1].id.toString());
s = s.replace('{{ecount}}', () => ents.length.toString());
s = s.replace('{{code}}', code); s = s.replace('{{code}}', code);
if (joinfield) {
s = s.replace('{{joinfield}}', () => this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0));
}
return s; return s;
} }
generateCodeForField(sys: System, action: Action, generateCodeForField(sys: System, action: Action,
@@ -586,8 +686,8 @@ export class EntityScope {
var component : ComponentType; var component : ComponentType;
var qualified = false; var qualified = false;
// is qualified field? // is qualified field?
if (fieldName.indexOf('.') > 0) { if (fieldName.indexOf(':') > 0) {
let [cname,fname] = fieldName.split('.'); let [cname,fname] = fieldName.split(':');
component = this.em.getComponentByName(cname); component = this.em.getComponentByName(cname);
fieldName = fname; fieldName = fname;
qualified = true; qualified = true;
@@ -638,45 +738,6 @@ export class EntityScope {
return this.dialect.indexed_x(ident); return this.dialect.indexed_x(ident);
} }
} }
entitiesMatching(atypes: ArchetypeMatch[]) {
let result : Entity[] = [];
for (let e of this.entities) {
for (let a of atypes) {
// TODO: what about subclasses?
if (e.etype == a.etype) {
result.push(e);
break;
}
}
}
return result;
}
getSystems(events: string[]) {
let result : System[] = [];
for (let sys of Object.values(this.em.systems)) {
if (this.systemListensTo(sys, events)) {
result.push(sys);
}
}
return result;
}
systemListensTo(sys: System, events: string[]) {
for (let action of sys.actions) {
if (action.event != null && events.includes(action.event)) {
let archs = this.em.archetypesMatching(action.query);
for (let arch of archs) {
for (let ctype of arch.cmatch) {
if (this.hasComponent(ctype)) {
return true;
}
}
}
}
}
}
hasComponent(ctype: ComponentType) {
return this.componentsInScope.has(ctype.name);
}
analyzeEntities() { analyzeEntities() {
this.buildSegments(); this.buildSegments();
this.allocateSegment(this.bss, false); this.allocateSegment(this.bss, false);
@@ -708,7 +769,7 @@ export class EntityScope {
} }
export class EntityManager { export class EntityManager {
archtypes = new Set<EntityArchetype>(); archetypes: { [key: string]: EntityArchetype } = {};
components: { [name: string]: ComponentType } = {}; components: { [name: string]: ComponentType } = {};
systems: { [name: string]: System } = {}; systems: { [name: string]: System } = {};
scopes: { [name: string]: EntityScope } = {}; scopes: { [name: string]: EntityScope } = {};
@@ -730,6 +791,13 @@ export class EntityManager {
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`);
return this.systems[system.name] = system; return this.systems[system.name] = system;
} }
addArchetype(atype: EntityArchetype) : EntityArchetype {
let key = atype.components.map(c => c.name).join(',');
if (this.archetypes[key])
return this.archetypes[key];
else
return this.archetypes[key] = atype;
}
componentsMatching(q: Query, etype: EntityArchetype) { componentsMatching(q: Query, etype: EntityArchetype) {
let list = []; let list = [];
for (let c of etype.components) { for (let c of etype.components) {
@@ -746,12 +814,12 @@ export class EntityManager {
} }
archetypesMatching(q: Query) { archetypesMatching(q: Query) {
let result: ArchetypeMatch[] = []; let result: ArchetypeMatch[] = [];
this.archtypes.forEach(etype => { for (let etype of Object.values(this.archetypes)) {
let cmatch = this.componentsMatching(q, etype); let cmatch = this.componentsMatching(q, etype);
if (cmatch.length > 0) { if (cmatch.length > 0) {
result.push({ etype, cmatch }); result.push({ etype, cmatch });
} }
}); }
return result; return result;
} }
componentsWithFieldName(atypes: ArchetypeMatch[], fieldName: string) { componentsWithFieldName(atypes: ArchetypeMatch[], fieldName: string) {

View File

@@ -36,7 +36,7 @@ const MODEDEFS = {
markdown: { lineWrap: true }, markdown: { lineWrap: true },
fastbasic: { noGutters: true }, fastbasic: { noGutters: true },
basic: { noLineNumbers: true, noGutters: true }, // TODO: not used? basic: { noLineNumbers: true, noGutters: true }, // TODO: not used?
ecs: { theme: 'mbo', isAsm: false }, ecs: { theme: 'mbo', isAsm: true },
} }
export var textMapFunctions = { export var textMapFunctions = {

View File

@@ -216,7 +216,7 @@ export function linkLD65(step: BuildStep): BuildStepResult {
if (fn.endsWith('.lst')) { if (fn.endsWith('.lst')) {
var lstout = FS.readFile(fn, { encoding: 'utf8' }); var lstout = FS.readFile(fn, { encoding: 'utf8' });
lstout = lstout.split('\n\n')[1] || lstout; // remove header lstout = lstout.split('\n\n')[1] || lstout; // remove header
var asmlines = parseCA65Listing(lstout, symbolmap, params, false); var asmlines = []; // TODO: parseCA65Listing(lstout, symbolmap, params, false);
var srclines = parseCA65Listing(lstout, symbolmap, params, true); var srclines = parseCA65Listing(lstout, symbolmap, params, true);
putWorkFile(fn, lstout); putWorkFile(fn, lstout);
// TODO: you have to get rid of all source lines to get asm listing // TODO: you have to get rid of all source lines to get asm listing

View File

@@ -18,13 +18,16 @@ export function assembleECS(step: BuildStep): BuildStepResult {
throw e; throw e;
} }
} }
//var listings: CodeListingMap = {}; let outtext = compiler.export().toString();
putWorkFile(destpath, compiler.export().toString()); putWorkFile(destpath, outtext);
var listings: CodeListingMap = {};
listings[destpath] = {lines:[], text:outtext} // TODO
} }
return { return {
nexttool: "ca65", nexttool: "ca65",
path: destpath, path: destpath,
args: [destpath], args: [destpath],
files: [destpath, 'vcs-ca65.h'], //TODO files: [destpath, 'vcs-ca65.h'], //TODO
listings
}; };
} }