mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-29 14:51:17 +00:00
ecs: refactored working and index register sets
This commit is contained in:
parent
c73263d944
commit
e09f115084
@ -795,6 +795,7 @@ export class ECSCompiler extends Tokenizer {
|
|||||||
parseQueryStatement() : QueryExpr {
|
parseQueryStatement() : QueryExpr {
|
||||||
// TODO: include modifiers in error msg
|
// TODO: include modifiers in error msg
|
||||||
const select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check?
|
const select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check?
|
||||||
|
let all = this.ifToken('all') != null;
|
||||||
let query = undefined;
|
let query = undefined;
|
||||||
let join = undefined;
|
let join = undefined;
|
||||||
if (select == 'once') {
|
if (select == 'once') {
|
||||||
@ -816,7 +817,7 @@ export class ECSCompiler extends Tokenizer {
|
|||||||
if (modifiers['asc']) direction = 'asc';
|
if (modifiers['asc']) direction = 'asc';
|
||||||
else if (modifiers['desc']) direction = 'desc';
|
else if (modifiers['desc']) direction = 'desc';
|
||||||
let body = this.annotate(() => this.parseBlockStatement());
|
let body = this.annotate(() => this.parseBlockStatement());
|
||||||
return { select, query, join, direction, stmts: [body], loop: select == 'foreach' } as QueryExpr;
|
return { select, query, join, direction, all, stmts: [body], loop: select == 'foreach' } as QueryExpr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,6 +246,7 @@ export interface QueryExpr extends BlockExpr {
|
|||||||
query: Query
|
query: Query
|
||||||
direction?: 'asc' | 'desc'
|
direction?: 'asc' | 'desc'
|
||||||
join?: Query
|
join?: Query
|
||||||
|
all?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -595,25 +596,28 @@ class EntitySet {
|
|||||||
entities: Entity[];
|
entities: Entity[];
|
||||||
scope;
|
scope;
|
||||||
|
|
||||||
constructor(scope: EntityScope, query?: Query, a?: EntityArchetype[], e?: Entity[]) {
|
constructor(scope: EntityScope, query?: Query, e?: Entity[]) {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
if (query) {
|
if (query) {
|
||||||
if (query.entities) {
|
if (query.entities) {
|
||||||
this.entities = query.entities.slice(0);
|
this.entities = query.entities.slice(0);
|
||||||
this.atypes = [];
|
|
||||||
for (let e of this.entities)
|
|
||||||
if (!this.atypes.includes(e.etype))
|
|
||||||
this.atypes.push(e.etype);
|
|
||||||
} else {
|
} else {
|
||||||
this.atypes = scope.em.archetypesMatching(query);
|
this.atypes = scope.em.archetypesMatching(query);
|
||||||
this.entities = scope.entitiesMatching(this.atypes);
|
this.entities = scope.entitiesMatching(this.atypes);
|
||||||
|
}
|
||||||
|
// TODO: desc?
|
||||||
if (query.limit) {
|
if (query.limit) {
|
||||||
this.entities = this.entities.slice(0, query.limit);
|
this.entities = this.entities.slice(0, query.limit);
|
||||||
}
|
}
|
||||||
}
|
} else if (e) {
|
||||||
} else if (a && e) {
|
|
||||||
this.atypes = a;
|
|
||||||
this.entities = e;
|
this.entities = e;
|
||||||
|
} else {
|
||||||
|
throw new ECSError('invalid EntitySet constructor')
|
||||||
|
}
|
||||||
|
if (!this.atypes) {
|
||||||
|
let at = new Set<EntityArchetype>();
|
||||||
|
for (let e of this.entities) at.add(e.etype);
|
||||||
|
this.atypes = Array.from(at.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contains(c: ComponentType, f: DataField, where: SourceLocated) {
|
contains(c: ComponentType, f: DataField, where: SourceLocated) {
|
||||||
@ -622,14 +626,13 @@ class EntitySet {
|
|||||||
}
|
}
|
||||||
intersection(qr: EntitySet) {
|
intersection(qr: EntitySet) {
|
||||||
let ents = this.entities.filter(e => qr.entities.includes(e));
|
let ents = this.entities.filter(e => qr.entities.includes(e));
|
||||||
let atypes = this.atypes.filter(a1 => qr.atypes.find(a2 => a2 == a1));
|
return new EntitySet(this.scope, undefined, ents);
|
||||||
return new EntitySet(this.scope, undefined, atypes, ents);
|
|
||||||
}
|
}
|
||||||
union(qr: EntitySet) {
|
union(qr: EntitySet) {
|
||||||
// TODO: remove dups
|
// TODO: remove dups
|
||||||
let ents = this.entities.concat(qr.entities);
|
let ents = this.entities.concat(qr.entities);
|
||||||
let atypes = this.atypes.concat(qr.atypes);
|
let atypes = this.atypes.concat(qr.atypes);
|
||||||
return new EntitySet(this.scope, undefined, atypes, ents);
|
return new EntitySet(this.scope, undefined, ents);
|
||||||
}
|
}
|
||||||
isContiguous() {
|
isContiguous() {
|
||||||
if (this.entities.length == 0) return true;
|
if (this.entities.length == 0) return true;
|
||||||
@ -965,7 +968,7 @@ class ActionEval {
|
|||||||
let refs = Array.from(this.scope.iterateArchetypeFields(atypes, (c, f) => f.dtype == 'ref'));
|
let refs = Array.from(this.scope.iterateArchetypeFields(atypes, (c, f) => f.dtype == 'ref'));
|
||||||
// TODO: better error message
|
// TODO: better error message
|
||||||
if (refs.length == 0) throw new ECSError(`cannot find join fields`, action);
|
if (refs.length == 0) throw new ECSError(`cannot find join fields`, action);
|
||||||
if (refs.length > 1) throw new ECSError(`cannot join multiple fields`, action);
|
if (refs.length > 1) throw new ECSError(`cannot join multiple fields (${refs.map(r => r.f.name).join(' ')})`, action);
|
||||||
// TODO: check to make sure join works
|
// TODO: check to make sure join works
|
||||||
return refs[0]; // TODO
|
return refs[0]; // TODO
|
||||||
/* TODO
|
/* TODO
|
||||||
@ -1013,7 +1016,8 @@ class ActionEval {
|
|||||||
queryExprToCode(qexpr: QueryExpr) : string {
|
queryExprToCode(qexpr: QueryExpr) : string {
|
||||||
//console.log('query', this.action.event, qexpr.select, qexpr.query.include);
|
//console.log('query', this.action.event, qexpr.select, qexpr.query.include);
|
||||||
let q = this.startQuery(qexpr);
|
let q = this.startQuery(qexpr);
|
||||||
const allowEmpty = ['if','foreach','unroll','join'];
|
// TODO: move elsewhere? is "foreach" and "join" part of the empty set?
|
||||||
|
const allowEmpty = ['if','foreach','join'];
|
||||||
if (q.working.entities.length == 0 && allowEmpty.includes(qexpr.select)) {
|
if (q.working.entities.length == 0 && allowEmpty.includes(qexpr.select)) {
|
||||||
//console.log('empty', this.action.event);
|
//console.log('empty', this.action.event);
|
||||||
this.endQuery(q);
|
this.endQuery(q);
|
||||||
@ -1030,20 +1034,22 @@ class ActionEval {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startQuery(qexpr: QueryExpr) {
|
queryWorkingSet(qexpr: QueryExpr) {
|
||||||
let oldState = this.scope.state;
|
|
||||||
let state = this.scope.state = Object.assign(new ActionCPUState(), oldState);
|
|
||||||
const action = this.action;
|
|
||||||
const scope = this.scope;
|
const scope = this.scope;
|
||||||
const instance = this.instance;
|
const instance = this.instance;
|
||||||
let select = qexpr.select;
|
let select = qexpr.select;
|
||||||
let q = qexpr.query;
|
let q = qexpr.query;
|
||||||
let qr;
|
let qr = new EntitySet(scope, q);
|
||||||
let jr;
|
// narrow query w/ working set?
|
||||||
if (q)
|
if (!(qexpr.all || q.entities)) {
|
||||||
qr = new EntitySet(scope, q);
|
let ir = qr.intersection(scope.state.working);
|
||||||
else
|
// if intersection is empty, take the global set
|
||||||
qr = new EntitySet(scope, undefined, [], []);
|
// if doing otherwise would generate an error (execpt for "if")
|
||||||
|
// TODO: ambiguous?
|
||||||
|
if (ir.entities.length || select == 'if') {
|
||||||
|
qr = ir;
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO? error if none?
|
// TODO? error if none?
|
||||||
if (instance.params.refEntity && instance.params.refField) {
|
if (instance.params.refEntity && instance.params.refField) {
|
||||||
let rf = instance.params.refField;
|
let rf = instance.params.refField;
|
||||||
@ -1055,12 +1061,17 @@ class ActionEval {
|
|||||||
} else if (instance.params.query) {
|
} else if (instance.params.query) {
|
||||||
qr = qr.intersection(new EntitySet(scope, instance.params.query));
|
qr = qr.intersection(new EntitySet(scope, instance.params.query));
|
||||||
}
|
}
|
||||||
|
return qr;
|
||||||
|
}
|
||||||
|
updateIndexRegisters(qr: EntitySet, jr: EntitySet | null, select: SelectType) {
|
||||||
|
const action = this.action;
|
||||||
|
const scope = this.scope;
|
||||||
|
const instance = this.instance;
|
||||||
|
const state = this.scope.state;
|
||||||
// TODO: generalize to other cpus/langs
|
// TODO: generalize to other cpus/langs
|
||||||
|
if (qr.entities.length > 1) {
|
||||||
switch (select) {
|
switch (select) {
|
||||||
case 'once':
|
case 'once':
|
||||||
// TODO: how is this different from begin/end?
|
|
||||||
//state.xreg = state.yreg = null;
|
|
||||||
//state.working = new EntitySet(scope, undefined, [], []);
|
|
||||||
break;
|
break;
|
||||||
case 'foreach':
|
case 'foreach':
|
||||||
case 'unroll':
|
case 'unroll':
|
||||||
@ -1072,8 +1083,7 @@ class ActionEval {
|
|||||||
// TODO: Joins don't work in superman (arrays offset?)
|
// TODO: Joins don't work in superman (arrays offset?)
|
||||||
// ignore the join query, use the ref
|
// ignore the join query, use the ref
|
||||||
if (state.xreg || state.yreg) throw new ECSError('no free index registers for join', action);
|
if (state.xreg || state.yreg) throw new ECSError('no free index registers for join', action);
|
||||||
jr = new EntitySet(scope, qexpr.join);
|
if (jr) state.xreg = new IndexRegister(scope, jr);
|
||||||
state.xreg = new IndexRegister(scope, jr);
|
|
||||||
state.yreg = new IndexRegister(scope, qr);
|
state.yreg = new IndexRegister(scope, qr);
|
||||||
break;
|
break;
|
||||||
case 'if':
|
case 'if':
|
||||||
@ -1081,16 +1091,6 @@ class ActionEval {
|
|||||||
// TODO: what if not in X because 1 element?
|
// TODO: what if not in X because 1 element?
|
||||||
if (state.xreg && state.xreg.eset) {
|
if (state.xreg && state.xreg.eset) {
|
||||||
state.xreg = state.xreg.narrow(qr, action);
|
state.xreg = state.xreg.narrow(qr, action);
|
||||||
if (state.xreg == null || state.xreg.eset?.entities == null) {
|
|
||||||
if (select == 'if') {
|
|
||||||
qr.entities = []; // "if" failed
|
|
||||||
} else {
|
|
||||||
throw new ECSError(`no entities match query`, qexpr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: must be a better way...
|
|
||||||
qr.entities = state.xreg.eset.entities;
|
|
||||||
}
|
|
||||||
} else if (select == 'with') {
|
} else if (select == 'with') {
|
||||||
if (instance.params.refEntity && instance.params.refField) {
|
if (instance.params.refEntity && instance.params.refField) {
|
||||||
if (state.xreg)
|
if (state.xreg)
|
||||||
@ -1098,14 +1098,18 @@ class ActionEval {
|
|||||||
else
|
else
|
||||||
state.xreg = new IndexRegister(scope, qr);
|
state.xreg = new IndexRegister(scope, qr);
|
||||||
// ???
|
// ???
|
||||||
} else if (qr.entities.length != 1) {
|
|
||||||
throw new ECSError(`${instance.system.name} query outside of loop must match exactly one entity`, action); //TODO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//
|
}
|
||||||
let entities = qr.entities;
|
}
|
||||||
|
getCodeAndProps(qexpr: QueryExpr, qr: EntitySet, jr: EntitySet|null,
|
||||||
|
oldState: ActionCPUState)
|
||||||
|
{
|
||||||
|
// get properties and code
|
||||||
|
const entities = qr.entities;
|
||||||
|
const select = qexpr.select;
|
||||||
let code = '%%CODE%%';
|
let code = '%%CODE%%';
|
||||||
let props: { [name: string]: string } = {};
|
let props: { [name: string]: string } = {};
|
||||||
// TODO: detect cycles
|
// TODO: detect cycles
|
||||||
@ -1118,7 +1122,7 @@ class ActionEval {
|
|||||||
// TODO? throw new ECSError(`join query doesn't match any entities`, (action as ActionWithJoin).join); // TODO
|
// TODO? throw new ECSError(`join query doesn't match any entities`, (action as ActionWithJoin).join); // TODO
|
||||||
//console.log('join', qr, jr);
|
//console.log('join', qr, jr);
|
||||||
if (qr.entities.length) {
|
if (qr.entities.length) {
|
||||||
let joinfield = this.getJoinField(action, qr.atypes, jr.atypes);
|
let joinfield = this.getJoinField(this.action, qr.atypes, jr.atypes);
|
||||||
// TODO: what if only 1 item?
|
// TODO: what if only 1 item?
|
||||||
// TODO: should be able to access fields via Y reg
|
// TODO: should be able to access fields via Y reg
|
||||||
code = this.wrapCodeInLoop(code, qexpr, qr.entities, joinfield);
|
code = this.wrapCodeInLoop(code, qexpr, qr.entities, joinfield);
|
||||||
@ -1142,11 +1146,11 @@ class ActionEval {
|
|||||||
let eidofs = re.id - range.elo;
|
let eidofs = re.id - range.elo;
|
||||||
props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`;
|
props['%reffield'] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`;
|
||||||
} else {
|
} else {
|
||||||
code = this.wrapCodeInFilter(code, qr, oldState);
|
code = this.wrapCodeInFilter(code, qr, oldState, props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (select == 'if') {
|
if (select == 'if') {
|
||||||
code = this.wrapCodeInFilter(code, qr, oldState);
|
code = this.wrapCodeInFilter(code, qr, oldState, props);
|
||||||
}
|
}
|
||||||
if (select == 'foreach' && entities.length > 1) {
|
if (select == 'foreach' && entities.length > 1) {
|
||||||
code = this.wrapCodeInLoop(code, qexpr, qr.entities);
|
code = this.wrapCodeInLoop(code, qexpr, qr.entities);
|
||||||
@ -1161,11 +1165,32 @@ class ActionEval {
|
|||||||
}
|
}
|
||||||
props['%ecount'] = entities.length.toString();
|
props['%ecount'] = entities.length.toString();
|
||||||
props['%efullcount'] = fullEntityCount.toString();
|
props['%efullcount'] = fullEntityCount.toString();
|
||||||
// TODO
|
|
||||||
props['%xofs'] = (this.scope.state.xreg?.offset() || 0).toString();
|
|
||||||
props['%yofs'] = (this.scope.state.yreg?.offset() || 0).toString();
|
|
||||||
let working = jr ? jr.union(qr) : qr;
|
|
||||||
//console.log('working', action.event, working.entities.length, entities.length);
|
//console.log('working', action.event, working.entities.length, entities.length);
|
||||||
|
return { code, props };
|
||||||
|
}
|
||||||
|
startQuery(qexpr: QueryExpr) {
|
||||||
|
const scope = this.scope;
|
||||||
|
const action = this.action;
|
||||||
|
const select = qexpr.select;
|
||||||
|
|
||||||
|
// save old state and make clone
|
||||||
|
const oldState = this.scope.state;
|
||||||
|
this.scope.state = Object.assign(new ActionCPUState(), oldState);
|
||||||
|
|
||||||
|
// get working set for this query
|
||||||
|
const qr = this.queryWorkingSet(qexpr);
|
||||||
|
|
||||||
|
// is it a join? query that too
|
||||||
|
const jr = qexpr.join && qr.entities.length ? new EntitySet(scope, qexpr.join) : null;
|
||||||
|
|
||||||
|
// update x, y state
|
||||||
|
this.updateIndexRegisters(qr, jr, select);
|
||||||
|
|
||||||
|
const { code, props } = this.getCodeAndProps(qexpr, qr, jr, oldState);
|
||||||
|
|
||||||
|
// if join, working set is union of both parts
|
||||||
|
let working = jr ? qr.union(jr) : qr;
|
||||||
|
|
||||||
return { working, oldState, props, code };
|
return { working, oldState, props, code };
|
||||||
}
|
}
|
||||||
endQuery(q : { oldState: ActionCPUState }) {
|
endQuery(q : { oldState: ActionCPUState }) {
|
||||||
@ -1182,20 +1207,23 @@ class ActionEval {
|
|||||||
s = s.replace('{{%code}}', code);
|
s = s.replace('{{%code}}', code);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
wrapCodeInFilter(code: string, qr: EntitySet, oldState: ActionCPUState) {
|
wrapCodeInFilter(code: string, qr: EntitySet, oldState: ActionCPUState, props: any) {
|
||||||
// TODO: :-p filters too often?
|
// TODO: :-p filters too often?
|
||||||
const ents = this.scope.state.xreg?.eset?.entities;
|
const ents = qr.entities;
|
||||||
const ents2 = oldState.xreg?.eset?.entities;
|
const ents2 = oldState.xreg?.eset?.entities;
|
||||||
if (ents && ents.length && ents2) {
|
if (ents && ents.length && ents2) {
|
||||||
let lo = ents[0].id;
|
let lo = ents[0].id;
|
||||||
let hi = ents[ents.length - 1].id;
|
let hi = ents[ents.length - 1].id;
|
||||||
let lo2 = ents2[0].id;
|
let lo2 = ents2[0].id;
|
||||||
let hi2 = ents2[ents2.length - 1].id;
|
let hi2 = ents2[ents2.length - 1].id;
|
||||||
if (lo != lo2)
|
if (lo != lo2) {
|
||||||
code = this.dialect.ASM_FILTER_RANGE_LO_X.replace('{{%code}}', code);
|
code = this.dialect.ASM_FILTER_RANGE_LO_X.replace('{{%code}}', code);
|
||||||
if (hi != hi2)
|
props['%xofs'] = lo - lo2;
|
||||||
|
}
|
||||||
|
if (hi != hi2) {
|
||||||
code = this.dialect.ASM_FILTER_RANGE_HI_X.replace('{{%code}}', code);
|
code = this.dialect.ASM_FILTER_RANGE_HI_X.replace('{{%code}}', code);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
wrapCodeInRefLookup(code: string) {
|
wrapCodeInRefLookup(code: string) {
|
||||||
@ -1242,7 +1270,8 @@ export class EntityScope implements SourceLocated {
|
|||||||
) {
|
) {
|
||||||
parent?.childScopes.push(this);
|
parent?.childScopes.push(this);
|
||||||
this.state = new ActionCPUState();
|
this.state = new ActionCPUState();
|
||||||
this.state.working = new EntitySet(this, undefined, [], []);
|
// TODO: parent scope entities too?
|
||||||
|
this.state.working = new EntitySet(this, undefined, this.entities); // working set = all entities
|
||||||
}
|
}
|
||||||
newEntity(etype: EntityArchetype, name: string): Entity {
|
newEntity(etype: EntityArchetype, name: string): Entity {
|
||||||
// TODO: add parent ID? lock parent scope?
|
// TODO: add parent ID? lock parent scope?
|
||||||
|
Loading…
Reference in New Issue
Block a user