diff --git a/src/codemirror/ecs.js b/src/codemirror/ecs.js index c418cc03..a54a7e91 100644 --- a/src/codemirror/ecs.js +++ b/src/codemirror/ecs.js @@ -17,7 +17,7 @@ var keywords1, keywords2; var directives_list = [ - 'component', 'system', 'entity', 'scope', 'end', + 'end', 'component', 'system', 'entity', 'scope', 'using', 'const', 'init', 'locals', 'on', 'do', 'emit', 'limit', 'once', 'foreach', 'source', 'join' diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index e456a332..7d530730 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -255,7 +255,10 @@ export class ECSCompiler extends Tokenizer { let scope = this.em.newScope(name, this.currentScope || undefined); this.currentScope = scope; let cmd; - while ((cmd = this.expectTokens(['entity', 'comment', 'end']).str) != 'end') { + while ((cmd = this.expectTokens(['using', 'entity', 'comment', 'end']).str) != 'end') { + if (cmd == 'using') { + this.parseScopeUsing(); + } if (cmd == 'entity') { this.annotate(() => this.parseEntity()); } @@ -267,6 +270,13 @@ export class ECSCompiler extends Tokenizer { return scope; } + parseScopeUsing() { + let syslist = this.parseList(this.parseSystemRef, ','); + for (let sys of syslist) { + this.currentScope?.systems.push(sys); + } + } + parseEntity() : Entity { if (!this.currentScope) { this.internalError(); throw new Error(); } let name = ''; @@ -319,12 +329,15 @@ export class ECSCompiler extends Tokenizer { return eref; } + parseSystemRef() : System { + let name = this.expectIdent().str; + let sys = this.em.getSystemByName(name); + if (!sys) this.compileError(`I couldn't find a system named "${name}".`, this.lasttoken.$loc); + return sys; + } + exportToFile(src: SourceFileExport) { - for (let scope of Object.values(this.em.scopes)) { - scope.analyzeEntities(); - scope.generateCode(); - scope.dump(src); - } + this.em.exportToFile(src); } export() { diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index db1aa0c7..f90d16f8 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -230,6 +230,7 @@ export class Dialect_CA65 { ` readonly HEADER = ` .include "vcs-ca65.h" +.code ` readonly FOOTER = ` .segment "VECTORS" @@ -237,7 +238,7 @@ VecNMI: .word Start VecReset: .word Start VecBRK: .word Start ` - readonly TEMPLATE_INIT = ` + readonly TEMPLATE_INIT_MAIN = ` Start: CLEAN_START ` @@ -502,6 +503,7 @@ class ActionEval { case 'source': if (!state.x) throw new ECSError('expected index register', this.action); let int = state.x.intersection(this.qr); + // TODO: what if we filter 0 entities? if (int.entities.length == 0) throw new ECSError('queries do not intersect', this.action); let indofs = int.entities[0].id - state.x.entities[0].id; state.xofs += indofs; @@ -705,6 +707,7 @@ class ActionEval { export class EntityScope implements SourceLocated { $loc: SourceLocation; childScopes: EntityScope[] = []; + systems: System[] = []; entities: Entity[] = []; bss = new DataSegment(); rodata = new DataSegment(); @@ -909,6 +912,7 @@ export class EntityScope implements SourceLocated { code = code.replace('{{%dest}}', segment.getOriginSymbol()); return code; } + // TODO: check type/range of value setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) { let c = this.em.singleComponentWithFieldName([{ etype: e.etype, cmatch: [component] }], fieldName, e); e.consts[mksymbol(component, fieldName)] = value; @@ -935,6 +939,7 @@ export class EntityScope implements SourceLocated { let s = this.dialect.code(); //s += `\n; event ${event}\n`; let emitcode: { [event: string]: string } = {}; + systems = systems.filter(s => this.systems.includes(s)); for (let sys of systems) { // TODO: does this work if multiple actions? // TODO: should 'emits' be on action? @@ -984,7 +989,10 @@ export class EntityScope implements SourceLocated { } generateCode() { this.tempOffset = this.maxTempBytes = 0; - this.code.addCodeFragment(this.dialect.TEMPLATE_INIT); + // TODO: main scope? + if (this.name.toLowerCase() == 'main') { + this.code.addCodeFragment(this.dialect.TEMPLATE_INIT_MAIN); + } let initcode = this.allocateInitData(this.bss); this.code.addCodeFragment(initcode); let start = this.generateCodeForEvent('start'); @@ -995,7 +1003,6 @@ export class EntityScope implements SourceLocated { } } dump(file: SourceFileExport) { - file.text(this.dialect.HEADER); // TODO file.segment(`${this.name}_DATA`, 'bss'); if (this.maxTempBytes) this.bss.allocateBytes('TEMP', this.maxTempBytes); this.bss.dump(file); @@ -1003,7 +1010,6 @@ export class EntityScope implements SourceLocated { this.rodata.dump(file); //file.segment(`${this.name}_CODE`, 'code'); this.code.dump(file); - file.text(this.dialect.FOOTER); // TODO } } @@ -1013,8 +1019,8 @@ export class EntityManager { systems: { [name: string]: System } = {}; scopes: { [name: string]: EntityScope } = {}; symbols: { [name: string]: 'init' | 'const' } = {}; - event2systems: { [name: string]: System[] } = {}; - name2cfpairs: { [name: string]: ComponentFieldPair[] } = {}; + event2systems: { [event: string]: System[] } = {}; + name2cfpairs: { [cfname: string]: ComponentFieldPair[] } = {}; constructor(public readonly dialect: Dialect_CA65) { } @@ -1089,6 +1095,9 @@ export class EntityManager { getComponentByName(name: string): ComponentType { return this.components[name]; } + getSystemByName(name: string): System { + return this.systems[name]; + } singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: SourceLocated) { let components = this.componentsWithFieldName(atypes, fieldName); // TODO: use name2cfpairs? @@ -1106,4 +1115,13 @@ export class EntityManager { systems: this.systems }) } + exportToFile(file: SourceFileExport) { + file.text(this.dialect.HEADER); // TODO + for (let scope of Object.values(this.scopes)) { + scope.analyzeEntities(); + scope.generateCode(); + scope.dump(file); + } + file.text(this.dialect.FOOTER); // TODO + } } diff --git a/src/common/tokenizer.ts b/src/common/tokenizer.ts index d62aae82..4fad3494 100644 --- a/src/common/tokenizer.ts +++ b/src/common/tokenizer.ts @@ -110,7 +110,8 @@ export class Tokenizer { let s: string = m[i + 1]; if (s != null) { found = true; - let loc = { path: this.path, line: this.lineno, start: m.index, end: m.index + s.length }; + let col = m.index - (this.lineindex[this.lineno-1] || -1) - 1; + let loc = { path: this.path, line: this.lineno, start: col, end: col + s.length }; let rule = rules[i]; // add token to list switch (rule.type) {