mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-25 15:30:03 +00:00
ecs: with, select queries
This commit is contained in:
parent
dcb11a7080
commit
c299bcddae
@ -17,10 +17,10 @@
|
||||
var keywords1, keywords2;
|
||||
|
||||
var directives_list = [
|
||||
'end', 'component', 'system', 'entity', 'scope', 'using',
|
||||
'end', 'component', 'system', 'entity', 'scope', 'using', 'demo',
|
||||
'const', 'init', 'locals',
|
||||
'on', 'do', 'emit', 'limit',
|
||||
'once', 'foreach', 'source', 'join'
|
||||
'once', 'foreach', 'with', 'join'
|
||||
];
|
||||
var keywords_list = [
|
||||
'processor',
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import { mergeLocs, Tokenizer, TokenType } from "../tokenizer";
|
||||
import { SourceLocated } from "../workertypes";
|
||||
import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, Dialect_CA65, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SourceFileExport, System } from "./ecs";
|
||||
import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SourceFileExport, System } from "./ecs";
|
||||
|
||||
export enum ECSTokenType {
|
||||
Ellipsis = 'ellipsis',
|
||||
@ -196,7 +196,6 @@ export class ECSCompiler extends Tokenizer {
|
||||
|
||||
parseResource(): System {
|
||||
let name = this.expectIdent().str;
|
||||
let query = this.parseQuery();
|
||||
let tempbytes;
|
||||
if (this.peekToken().str == 'locals') {
|
||||
this.consumeToken();
|
||||
@ -204,7 +203,7 @@ export class ECSCompiler extends Tokenizer {
|
||||
}
|
||||
let text = this.parseCode();
|
||||
let select : SelectType = 'once';
|
||||
let action : Action = { text, event: name, query, select };
|
||||
let action : Action = { text, event: name, select };
|
||||
return { name, tempbytes, actions: [action] };
|
||||
}
|
||||
|
||||
@ -212,10 +211,17 @@ export class ECSCompiler extends Tokenizer {
|
||||
// TODO: unused events?
|
||||
let event = this.expectIdent().str;
|
||||
this.expectToken('do');
|
||||
let select = this.expectTokens(['once', 'foreach', 'source', 'join']).str as SelectType; // TODO: type check?
|
||||
let query = this.parseQuery();
|
||||
let select = this.expectTokens(
|
||||
['once', 'foreach', 'join', 'with', 'select']).str as SelectType; // TODO: type check?
|
||||
let query = undefined;
|
||||
let join = undefined;
|
||||
if (select == 'join') join = this.parseQuery();
|
||||
if (select != 'once') {
|
||||
query = this.parseQuery();
|
||||
}
|
||||
if (select == 'join') {
|
||||
this.expectToken('with');
|
||||
join = this.parseQuery();
|
||||
}
|
||||
let emits;
|
||||
let limit;
|
||||
if (this.peekToken().str == 'limit') {
|
||||
@ -231,7 +237,7 @@ export class ECSCompiler extends Tokenizer {
|
||||
}
|
||||
let text = this.parseCode();
|
||||
let action = { text, event, query, join, select, limit };
|
||||
return action;
|
||||
return action as Action;
|
||||
}
|
||||
|
||||
parseQuery() {
|
||||
|
@ -111,17 +111,31 @@ export interface System extends SourceLocated {
|
||||
tempbytes?: number;
|
||||
}
|
||||
|
||||
export interface Action extends SourceLocated {
|
||||
text: string;
|
||||
export type SelectType = 'once' | 'foreach' | 'join' | 'with' | 'select';
|
||||
|
||||
export interface ActionBase extends SourceLocated {
|
||||
select: SelectType;
|
||||
event: string;
|
||||
select: SelectType
|
||||
query: Query;
|
||||
join?: Query;
|
||||
limit?: number;
|
||||
text: string;
|
||||
emits?: string[];
|
||||
}
|
||||
|
||||
export type SelectType = 'once' | 'foreach' | 'source' | 'join';
|
||||
export interface ActionOnce extends ActionBase {
|
||||
select: 'once'
|
||||
}
|
||||
|
||||
export interface ActionWithQuery extends ActionBase {
|
||||
select: 'foreach' | 'join' | 'with' | 'select'
|
||||
query: Query
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface ActionWithJoin extends ActionWithQuery {
|
||||
select: 'join'
|
||||
join?: Query
|
||||
}
|
||||
|
||||
export type Action = ActionWithQuery | ActionWithJoin | ActionOnce;
|
||||
|
||||
export type DataValue = number | boolean | Uint8Array | Uint16Array;
|
||||
|
||||
@ -491,8 +505,10 @@ class ActionEval {
|
||||
readonly action: Action) {
|
||||
this.em = scope.em;
|
||||
this.dialect = scope.em.dialect;
|
||||
this.qr = new QueryResult(scope, action.query);
|
||||
this.oldState = scope.state;
|
||||
let q = (action as ActionWithQuery).query;
|
||||
if (q) this.qr = new QueryResult(scope, q);
|
||||
else this.qr = new QueryResult(scope, undefined, [], []);
|
||||
}
|
||||
begin() {
|
||||
let state = this.scope.state = Object.assign({}, this.scope.state);
|
||||
@ -506,19 +522,21 @@ class ActionEval {
|
||||
case 'join':
|
||||
if (state.x || state.y) throw new ECSError('no free index registers for join', this.action);
|
||||
state.y = this.qr;
|
||||
if (this.action.join) {
|
||||
this.jr = new QueryResult(this.scope, this.action.join);
|
||||
state.x = this.jr;
|
||||
}
|
||||
this.jr = new QueryResult(this.scope, (this.action as ActionWithJoin).join);
|
||||
state.x = this.jr;
|
||||
break;
|
||||
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;
|
||||
state.x = int;
|
||||
case 'with':
|
||||
if (state.x) {
|
||||
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;
|
||||
state.x = int;
|
||||
} else {
|
||||
if (this.qr.entities.length != 1)
|
||||
throw new ECSError(`query outside of loop must match exactly one entity`, this.action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -532,43 +550,46 @@ class ActionEval {
|
||||
let action = this.action;
|
||||
let sys = this.sys;
|
||||
let code = action.text;
|
||||
let label = `${sys.name}__${action.event}`; // TODO: better label that won't conflict (seq?)
|
||||
// TODO: detect cycles
|
||||
// TODO: "source"?
|
||||
// TODO: what if only 1 item?
|
||||
let label = `${sys.name}__${action.event}__${this.em.seq++}`; // TODO: better label that won't conflict (seq?)
|
||||
let props: { [name: string]: string } = {};
|
||||
if (action.select == 'foreach') {
|
||||
code = this.wrapCodeInLoop(code, action, this.qr.entities);
|
||||
}
|
||||
if (action.select == 'join' && this.jr) {
|
||||
let jentities = this.jr.entities;
|
||||
if (jentities.length == 0)
|
||||
throw new ECSError(`join query for ${label} doesn't match any entities`, action.join); // TODO
|
||||
let joinfield = this.getJoinField(action, this.qr.atypes, this.jr.atypes);
|
||||
if (action.select != 'once') {
|
||||
// TODO: detect cycles
|
||||
// TODO: "source"?
|
||||
// TODO: what if only 1 item?
|
||||
// TODO: should be able to access fields via Y reg
|
||||
code = this.wrapCodeInLoop(code, action, this.qr.entities, joinfield);
|
||||
props['%joinfield'] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0); //TODO?
|
||||
this.qr = this.jr; // TODO?
|
||||
if (action.select == 'foreach') {
|
||||
code = this.wrapCodeInLoop(code, action, this.qr.entities);
|
||||
}
|
||||
if (action.select == 'join' && this.jr) {
|
||||
let jentities = this.jr.entities;
|
||||
if (jentities.length == 0)
|
||||
throw new ECSError(`join query doesn't match any entities`, action); // TODO
|
||||
let joinfield = this.getJoinField(action, this.qr.atypes, this.jr.atypes);
|
||||
// TODO: what if only 1 item?
|
||||
// TODO: should be able to access fields via Y reg
|
||||
code = this.wrapCodeInLoop(code, action, this.qr.entities, joinfield);
|
||||
props['%joinfield'] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0); //TODO?
|
||||
this.qr = this.jr; // TODO?
|
||||
}
|
||||
let entities = this.qr.entities;
|
||||
props['%efullcount'] = entities.length.toString();
|
||||
if (action.limit) {
|
||||
entities = entities.slice(0, action.limit);
|
||||
}
|
||||
if (entities.length == 0)
|
||||
throw new ECSError(`query doesn't match any entities`, action.query); // TODO
|
||||
// filter entities from loop?
|
||||
if (action.select == 'with' && entities.length > 1) {
|
||||
// TODO: what if not needed
|
||||
code = this.wrapCodeInFilter(code);
|
||||
}
|
||||
// define properties
|
||||
props['%elo'] = entities[0].id.toString();
|
||||
props['%ehi'] = entities[entities.length - 1].id.toString();
|
||||
props['%ecount'] = entities.length.toString();
|
||||
props['%xofs'] = this.scope.state.xofs.toString();
|
||||
props['%yofs'] = this.scope.state.yofs.toString();
|
||||
this.qr.entities = entities;
|
||||
}
|
||||
if (action.select == 'source') {
|
||||
// TODO: what if not needed
|
||||
code = this.wrapCodeInFilter(code);
|
||||
}
|
||||
let entities = this.qr.entities;
|
||||
props['%efullcount'] = entities.length.toString();
|
||||
if (action.limit) {
|
||||
entities = entities.slice(0, action.limit);
|
||||
}
|
||||
if (entities.length == 0)
|
||||
throw new ECSError(`query for ${label} doesn't match any entities`, action.query); // TODO
|
||||
// define properties
|
||||
props['%elo'] = entities[0].id.toString();
|
||||
props['%ehi'] = entities[entities.length - 1].id.toString();
|
||||
props['%ecount'] = entities.length.toString();
|
||||
props['%xofs'] = this.scope.state.xofs.toString();
|
||||
props['%yofs'] = this.scope.state.yofs.toString();
|
||||
this.qr.entities = entities;
|
||||
// replace @labels
|
||||
code = code.replace(label_re, (s: string, a: string) => `${label}__${a}`);
|
||||
// replace {{...}} tags
|
||||
@ -664,16 +685,14 @@ class ActionEval {
|
||||
// TODO: don't mix const and init data
|
||||
let range = this.scope.bss.getFieldRange(component, fieldName) || this.scope.rodata.getFieldRange(component, fieldName);
|
||||
if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); // TODO
|
||||
let eidofs = range.elo - qr.entities[0].id; // TODO
|
||||
// TODO: dialect
|
||||
let ident = this.dialect.fieldsymbol(component, field, bitofs);
|
||||
if (qualified) {
|
||||
return this.dialect.absolute(ident);
|
||||
} else if (action.select == 'once') {
|
||||
if (qr.entities.length != 1)
|
||||
throw new ECSError(`can't choose multiple entities for ${fieldName} with select=once`, action);
|
||||
} else if (qr.entities.length == 1) {
|
||||
return this.dialect.absolute(ident);
|
||||
} else {
|
||||
let eidofs = range.elo - qr.entities[0].id; // TODO
|
||||
// TODO: eidofs?
|
||||
let ir;
|
||||
if (this.scope.state.x?.intersection(this.qr)) {
|
||||
@ -697,8 +716,8 @@ class ActionEval {
|
||||
getJoinField(action: Action, atypes: ArchetypeMatch[], jtypes: ArchetypeMatch[]): ComponentFieldPair {
|
||||
let refs = Array.from(this.scope.iterateArchetypeFields(atypes, (c, f) => f.dtype == 'ref'));
|
||||
// TODO: better error message
|
||||
if (refs.length == 0) throw new ECSError(`cannot find join fields`, action.query);
|
||||
if (refs.length > 1) throw new ECSError(`cannot join multiple fields`, action.query);
|
||||
if (refs.length == 0) throw new ECSError(`cannot find join fields`, action);
|
||||
if (refs.length > 1) throw new ECSError(`cannot join multiple fields`, action);
|
||||
// TODO: check to make sure join works
|
||||
return refs[0]; // TODO
|
||||
/* TODO
|
||||
@ -1043,6 +1062,8 @@ export class EntityManager {
|
||||
symbols: { [name: string]: 'init' | 'const' } = {};
|
||||
event2systems: { [event: string]: System[] } = {};
|
||||
name2cfpairs: { [cfname: string]: ComponentFieldPair[] } = {};
|
||||
mainPath: string = '';
|
||||
seq = 1;
|
||||
|
||||
constructor(public readonly dialect: Dialect_CA65) {
|
||||
}
|
||||
@ -1140,8 +1161,7 @@ export class EntityManager {
|
||||
exportToFile(file: SourceFileExport) {
|
||||
file.text(this.dialect.HEADER); // TODO
|
||||
for (let scope of Object.values(this.topScopes)) {
|
||||
// TODO: demos
|
||||
if (!scope.isDemo) {
|
||||
if (!scope.isDemo || scope.filePath == this.mainPath) {
|
||||
scope.dump(file);
|
||||
}
|
||||
}
|
||||
|
@ -210,18 +210,18 @@ function testECS() {
|
||||
tempbytes: 12,
|
||||
actions: [
|
||||
{
|
||||
text: TEMPLATE4_S1, event: 'preframe', select: 'once', query: {
|
||||
text: TEMPLATE4_S1, event: 'preframe', select: 'with', query: {
|
||||
include: [c_kernel]
|
||||
}
|
||||
},
|
||||
{
|
||||
// TODO: should include kernel for numlines
|
||||
text: TEMPLATE4_S2, event: 'preframe', select: 'once', query: {
|
||||
text: TEMPLATE4_S2, event: 'preframe', select: 'with', query: {
|
||||
include: [c_sprite, c_hasbitmap, c_hascolormap, c_ypos],
|
||||
},
|
||||
},
|
||||
{
|
||||
text: TEMPLATE4_K, event: 'kernel', select: 'once', query: {
|
||||
text: TEMPLATE4_K, event: 'kernel', select: 'with', query: {
|
||||
include: [c_kernel]
|
||||
}
|
||||
},
|
||||
@ -242,14 +242,14 @@ function testECS() {
|
||||
em.defineSystem({
|
||||
name: 'frameloop',
|
||||
actions: [
|
||||
{ text: TEMPLATE1, event: 'start', select: 'once', query: { include: [c_kernel] },
|
||||
{ text: TEMPLATE1, event: 'start', select: 'with', query: { include: [c_kernel] },
|
||||
emits: ['preframe', 'kernel', 'postframe'] }
|
||||
]
|
||||
})
|
||||
em.defineSystem({
|
||||
name: 'SetHorizPos',
|
||||
actions: [
|
||||
{ text: SETHORIZPOS, event: 'SetHorizPos', select: 'once', query: { include: [c_xpos] } },
|
||||
{ text: SETHORIZPOS, event: 'SetHorizPos', select: 'with', query: { include: [c_xpos] } },
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -11,6 +11,7 @@ export function assembleECS(step: BuildStep): BuildStepResult {
|
||||
return getWorkFileAsString(path);
|
||||
}
|
||||
gatherFiles(step, { mainFilePath: "main.ecs" });
|
||||
if (step.mainfile) em.mainPath = step.path;
|
||||
var destpath = step.prefix + '.ca65';
|
||||
if (staleFiles(step, [destpath])) {
|
||||
let code = getWorkFileAsString(step.path);
|
||||
|
Loading…
Reference in New Issue
Block a user