diff --git a/src/codemirror/ecs.js b/src/codemirror/ecs.js index 869f9eab..bf3696f9 100644 --- a/src/codemirror/ecs.js +++ b/src/codemirror/ecs.js @@ -17,8 +17,8 @@ var keywords1, keywords2; var directives_list = [ - 'end', 'component', 'system', 'entity', 'scope', 'using', 'demo', - 'const', 'init', 'locals', + 'end', 'component', 'system', 'entity', 'scope', 'using', 'demo', 'decode', + 'const', 'locals', 'var', 'on', 'do', 'emit', 'limit', 'once', 'foreach', 'with', 'join', 'if', ]; @@ -34,7 +34,7 @@ directives_list.forEach(function (s) { directives.set(s, 'def'); }); keywords_list.forEach(function (s) { directives.set(s, 'keyword'); }); - var opcodes = /^[a-z][a-z][a-z]\b/i; + var opcodes = /^\s[a-z][a-z][a-z]\s/i; var numbers = /^(0x[\da-f]+|[\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; var tags = /^\{\{.*\}\}/; var comment = /\/\/.*/; diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index b3b96948..d4e96c3a 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -1,6 +1,7 @@ import { mergeLocs, Tokenizer, TokenType } from "../tokenizer"; import { SourceLocated } from "../workertypes"; +import { newDecoder } from "./decoder"; import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SourceFileExport, System } from "./ecs"; export enum ECSTokenType { @@ -142,7 +143,7 @@ export class ECSCompiler extends Tokenizer { } return { dtype: 'array', index, elem, baseoffset } as ArrayType; } - this.internalError(); throw new Error(); + this.compileError(`I expected a data type here.`); throw new Error(); } parseDataValue(field: DataField) : DataValue { @@ -170,7 +171,7 @@ export class ECSCompiler extends Tokenizer { } return id; } - this.internalError(); throw new Error(); + this.compileError(`I expected a ${field.dtype} here.`); throw new Error(); } parseDataArray() { @@ -234,17 +235,12 @@ export class ECSCompiler extends Tokenizer { this.expectToken('with'); join = this.parseQuery(); } - let emits; - if (this.peekToken().str == 'limit') { - this.consumeToken(); + if (this.ifToken('limit')) { if (!query) { this.compileError(`A "${select}" query can't include a limit.`); } else query.limit = this.expectInteger(); } - if (this.peekToken().str == 'emit') { - this.consumeToken(); - this.expectToken('('); - emits = this.parseEventList(); - this.expectToken(')'); + if (this.ifToken('cyclecritical')) { + // TODO } let text = this.parseCode(); let action = { text, event, query, join, select }; @@ -336,33 +332,54 @@ export class ECSCompiler extends Tokenizer { parseEntity() : Entity { if (!this.currentScope) { this.internalError(); throw new Error(); } - let name = ''; + let entname = ''; if (this.peekToken().type == TokenType.Ident) { - name = this.expectIdent().str; + entname = this.expectIdent().str; } let etype = this.parseEntityArchetype(); - let e = this.currentScope.newEntity(etype); - e.name = name; + let entity = this.currentScope.newEntity(etype); + entity.name = entname; let cmd; // TODO: remove init? - while ((cmd = this.expectTokens(['const', 'init', 'var', 'end']).str) != 'end') { + while ((cmd = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') { if (cmd == 'var') cmd = 'init'; - // TODO: check data types - let name = this.expectIdent().str; - 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 > 1) this.compileError(`I found more than one field named "${name}" for this entity.`) - let field = comps[0].fields.find(f => f.name == name); - if (!field) { this.internalError(); throw new Error(); } - this.expectToken('='); - let value = this.parseDataValue(field); - let symtype = this.currentScope.isConstOrInit(comps[0], name); - if (symtype && symtype != cmd) - this.compileError(`I can't mix const and init values for a given field in a scope.`); - 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' || cmd == 'const') { + // TODO: check data types + let name = this.expectIdent().str; + this.setEntityProperty(entity, name, cmd, (field) : DataValue => { + this.expectToken('='); + return this.parseDataValue(field); + }); + } else if (cmd == 'decode') { + let decoderid = this.expectIdent().str; + let code = this.expectTokenTypes([ECSTokenType.CodeFragment]).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(); + for (let entry of Object.entries(result.properties)) { + this.setEntityProperty(entity, entry[0], 'const', (field) : DataValue => { + return entry[1]; + }); + } + } } - return e; + return entity; + } + + setEntityProperty(e: Entity, name: string, cmd: 'init' | 'const', valuefn: (field: DataField) => DataValue) { + if (!this.currentScope) { this.internalError(); throw new Error(); } + 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 > 1) this.compileError(`I found more than one field named "${name}" for this entity.`) + let field = comps[0].fields.find(f => f.name == name); + if (!field) { this.internalError(); throw new Error(); } + let value = valuefn(field); + let symtype = this.currentScope.isConstOrInit(comps[0], name); + if (symtype && symtype != cmd) + this.compileError(`I can't mix const and init values for a given field in a scope.`); + if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value); + if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value); } parseEntityArchetype() : EntityArchetype { diff --git a/src/common/ecs/decoder.ts b/src/common/ecs/decoder.ts new file mode 100644 index 00000000..8f2a8617 --- /dev/null +++ b/src/common/ecs/decoder.ts @@ -0,0 +1,114 @@ +import { threadId } from "worker_threads"; +import { DataValue, ECSError } from "./ecs"; + +export interface DecoderResult { + properties: {[name: string] : DataValue} +} + +abstract class LineDecoder { + lines : string[][]; + + constructor( + text: string + ) { + // split the text into lines and into tokens + this.lines = text.split('\n').map(s => s.trim()).filter(s => !!s).map(s => s.split(/\s+/)); + } + + decodeBits(s: string, n: number, msbfirst: boolean) { + if (s.length != n) throw new ECSError(`Expected ${n} characters`); + let b = 0; + for (let i=0; i 1) { + console.log(changed, cur, prev); + 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]; + prev[chgidx] = cur[chgidx]; + } + return { + properties: { + data + } + } + } +} + +export function newDecoder(name: string, text: string) : LineDecoder | undefined { + let cons = (DECODERS as any)[name]; + if (cons) return new cons(text); +} + +const DECODERS = { + 'vcs_sprite': VCSSpriteDecoder, + 'vcs_versatile': VCSVersatilePlayfieldDecoder, +} diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index 83ee871d..fb447351 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -51,6 +51,8 @@ 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? @@ -119,7 +121,6 @@ export interface ActionBase extends SourceLocated { select: SelectType; event: string; text: string; - emits?: string[]; } export interface ActionOnce extends ActionBase { @@ -675,7 +676,8 @@ class ActionEval { return this.scope.includeResource(args[0]); } __emit(args: string[]) { - return this.scope.generateCodeForEvent(args[0]); + let event = args[0]; + return this.scope.generateCodeForEvent(event); } __local(args: string[]) { let tempinc = parseInt(args[0]); @@ -1058,11 +1060,9 @@ 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? // TODO: share storage //if (sys.tempbytes) this.allocateTempBytes(sys.tempbytes); let tmplabel = `${sys.name}_tmp`; @@ -1071,18 +1071,8 @@ export class EntityScope implements SourceLocated { let numActions = 0; for (let action of sys.actions) { if (action.event == event) { - if (action.emits) { - for (let emit of action.emits) { - if (emitcode[emit]) { - console.log(`already emitted for ${sys.name}:${event}`); - } - //console.log('>', emit); - // TODO: cycles - emitcode[emit] = this.generateCodeForEvent(emit); - //console.log('<', emit, emitcode[emit].length); - } - } // TODO: use Tokenizer so error msgs are better + // TODO: keep event tree let codeeval = new ActionEval(this, sys, action); codeeval.tmplabel = tmplabel; codeeval.begin(); diff --git a/src/test/testecs.ts b/src/test/testecs.ts index 0af4fede..05d78187 100644 --- a/src/test/testecs.ts +++ b/src/test/testecs.ts @@ -242,8 +242,7 @@ function testECS() { em.defineSystem({ name: 'frameloop', actions: [ - { text: TEMPLATE1, event: 'start', select: 'with', query: { include: [c_kernel] }, - emits: ['preframe', 'kernel', 'postframe'] } + { text: TEMPLATE1, event: 'start', select: 'with', query: { include: [c_kernel] } } ] }) em.defineSystem({ @@ -313,7 +312,7 @@ end system SimpleKernel locals 8 -on preframe do once [Kernel] --- JUNK_AT_END +on preframe do with [Kernel] --- JUNK_AT_END lda #5 sta #6 Label: