mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-25 15:30:03 +00:00
ecs: decoders
This commit is contained in:
parent
3a77e31e47
commit
0904e83bf6
@ -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 = /\/\/.*/;
|
||||
|
@ -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 {
|
||||
|
114
src/common/ecs/decoder.ts
Normal file
114
src/common/ecs/decoder.ts
Normal file
@ -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<n; i++) {
|
||||
let bit;
|
||||
if (s.charAt(i) == 'x') bit = 1;
|
||||
else if (s.charAt(i) == '.') bit = 0;
|
||||
else throw new ECSError('need x or .');
|
||||
if (bit) {
|
||||
if (msbfirst) b |= 1 << (n-1-i);
|
||||
else b |= 1 << i;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
assertTokens(toks: string[], count: number) {
|
||||
if (toks.length != count) throw new ECSError(`Expected ${count} tokens on line.`);
|
||||
}
|
||||
|
||||
hex(s: string) {
|
||||
let v = parseInt(s, 16);
|
||||
if (isNaN(v)) throw new ECSError(`Invalid hex value: ${s}`)
|
||||
return v;
|
||||
}
|
||||
|
||||
abstract parse() : DecoderResult;
|
||||
}
|
||||
|
||||
export class VCSSpriteDecoder extends LineDecoder {
|
||||
parse() {
|
||||
let height = this.lines.length;
|
||||
let bitmapdata = new Uint8Array(height);
|
||||
let colormapdata = new Uint8Array(height);
|
||||
for (let i=0; i<height; i++) {
|
||||
let toks = this.lines[height - 1 - i];
|
||||
this.assertTokens(toks, 2);
|
||||
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
|
||||
colormapdata[i] = this.hex(toks[1]);
|
||||
}
|
||||
return {
|
||||
properties: {
|
||||
bitmapdata, colormapdata, height: height-1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VCSVersatilePlayfieldDecoder extends LineDecoder {
|
||||
parse() {
|
||||
let height = this.lines.length;
|
||||
let data = new Uint8Array(192) //height * 2); TODO
|
||||
data.fill(0x3f);
|
||||
// pf0 pf1 pf2 colupf colubk ctrlpf trash
|
||||
const regs = [0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x3f];
|
||||
let prev = [0,0,0,0,0,0,0];
|
||||
let cur = [0,0,0,0,0,0,0];
|
||||
for (let i=0; i<height; i++) {
|
||||
let toks = this.lines[i];
|
||||
this.assertTokens(toks, 4);
|
||||
cur[0] = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
|
||||
cur[1] = this.decodeBits(toks[0].substring(4,12), 8, true);
|
||||
cur[2] = this.decodeBits(toks[0].substring(12,20), 8, false);
|
||||
if (toks[1] != '..') cur[3] = this.hex(toks[1]);
|
||||
if (toks[2] != '..') cur[4] = this.hex(toks[2]);
|
||||
if (toks[3] != '..') cur[5] = this.hex(toks[3]);
|
||||
let changed = [];
|
||||
for (let j=0; j<cur.length; j++) {
|
||||
if (cur[j] != prev[j])
|
||||
changed.push(j);
|
||||
}
|
||||
if (changed.length > 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,
|
||||
}
|
@ -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();
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user