mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-26 10:49:17 +00:00
ecs: refactor, QueryResult, filter
This commit is contained in:
parent
2dea6b1fcf
commit
d9b8b8b7d3
@ -21,12 +21,12 @@ export class ECSCompiler extends Tokenizer {
|
|||||||
//this.includeEOL = true;
|
//this.includeEOL = true;
|
||||||
this.setTokenRules([
|
this.setTokenRules([
|
||||||
{ type: ECSTokenType.Ellipsis, regex: /\.\./ },
|
{ type: ECSTokenType.Ellipsis, regex: /\.\./ },
|
||||||
{ type: ECSTokenType.Operator, regex: /[#=,:(){}\[\]]/ },
|
|
||||||
{ type: ECSTokenType.QuotedString, regex: /".*?"/ },
|
{ type: ECSTokenType.QuotedString, regex: /".*?"/ },
|
||||||
{ type: ECSTokenType.CodeFragment, regex: /---.*?---/ },
|
{ type: ECSTokenType.CodeFragment, regex: /---.*?---/ },
|
||||||
{ type: ECSTokenType.Integer, regex: /[-]?0x[A-Fa-f0-9]+/ },
|
{ type: ECSTokenType.Integer, regex: /[-]?0x[A-Fa-f0-9]+/ },
|
||||||
{ type: ECSTokenType.Integer, regex: /[-]?\$[A-Fa-f0-9]+/ },
|
{ type: ECSTokenType.Integer, regex: /[-]?\$[A-Fa-f0-9]+/ },
|
||||||
{ type: ECSTokenType.Integer, regex: /[-]?\d+/ },
|
{ type: ECSTokenType.Integer, regex: /[-]?\d+/ },
|
||||||
|
{ type: ECSTokenType.Operator, regex: /[#=,:(){}\[\]\-]/ },
|
||||||
{ type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ },
|
{ type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ },
|
||||||
{ type: TokenType.Ignore, regex: /\/\/.*?[\n\r]/ },
|
{ type: TokenType.Ignore, regex: /\/\/.*?[\n\r]/ },
|
||||||
{ type: TokenType.Ignore, regex: /\/\*.*?\*\// },
|
{ type: TokenType.Ignore, regex: /\/\*.*?\*\// },
|
||||||
@ -218,10 +218,16 @@ export class ECSCompiler extends Tokenizer {
|
|||||||
parseQuery() {
|
parseQuery() {
|
||||||
let q: Query = { include: [] };
|
let q: Query = { include: [] };
|
||||||
let start = this.expectToken('[');
|
let start = this.expectToken('[');
|
||||||
q.include = this.parseList(this.parseComponentRef, ',').map(c => c.name);
|
q.include = this.parseList(this.parseComponentRef, ',');
|
||||||
|
this.expectToken(']');
|
||||||
|
if (this.peekToken().str == '-') {
|
||||||
|
this.consumeToken();
|
||||||
|
this.expectToken('[');
|
||||||
|
q.exclude = this.parseList(this.parseComponentRef, ',');
|
||||||
|
this.expectToken(']');
|
||||||
|
}
|
||||||
// TODO: other params
|
// TODO: other params
|
||||||
let end = this.expectToken(']');
|
q.$loc = mergeLocs(start.$loc, this.lasttoken.$loc);
|
||||||
q.$loc = mergeLocs(start.$loc, end.$loc);
|
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
entity scopes contain entities, and are nested
|
entity scopes contain entities, and are nested
|
||||||
also contain segments (code, bss, rodata)
|
also contain segments (code, bss, rodata)
|
||||||
components and systems are global
|
components and systems are global
|
||||||
@ -46,6 +47,12 @@ https://www.cpcwiki.eu/forum/programming/trying-not-to-use-ix/msg133416/#msg1334
|
|||||||
how to select two between two entities with once? like scoreboard
|
how to select two between two entities with once? like scoreboard
|
||||||
maybe stack-based interpreter?
|
maybe stack-based interpreter?
|
||||||
|
|
||||||
|
can you query specific entities? merge with existing queries?
|
||||||
|
bigints?
|
||||||
|
source/if query?
|
||||||
|
|
||||||
|
crazy idea -- full expansion, then relooper
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -92,10 +99,10 @@ export interface ComponentType extends SourceLocated {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Query extends SourceLocated {
|
export interface Query extends SourceLocated {
|
||||||
include: string[]; // TODO: make ComponentType
|
include: ComponentType[]; // TODO: make ComponentType
|
||||||
listen?: string[];
|
listen?: ComponentType[];
|
||||||
exclude?: string[];
|
exclude?: ComponentType[];
|
||||||
updates?: string[];
|
updates?: ComponentType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface System extends SourceLocated {
|
export interface System extends SourceLocated {
|
||||||
@ -191,6 +198,29 @@ export class Dialect_CA65 {
|
|||||||
@__exit:
|
@__exit:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// TODO: lo/hi side of range?
|
||||||
|
readonly ASM_FILTER_RANGE_X = `
|
||||||
|
cpx #{{%xofs}}
|
||||||
|
bcc @__skip
|
||||||
|
cpx #{{%xofs}}+{{%ecount}}
|
||||||
|
bcs @__skip
|
||||||
|
{{%code}}
|
||||||
|
@__skip:
|
||||||
|
`
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
readonly ASM_MAP_RANGES = `
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
lda {{%mapping}},x
|
||||||
|
bmi @__mapskip
|
||||||
|
tax
|
||||||
|
{{%code}}
|
||||||
|
@__mapskip:
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
`;
|
||||||
|
|
||||||
readonly INIT_FROM_ARRAY = `
|
readonly INIT_FROM_ARRAY = `
|
||||||
ldy #{{%nbytes}}
|
ldy #{{%nbytes}}
|
||||||
: lda {{%src}}-1,y
|
: lda {{%src}}-1,y
|
||||||
@ -218,8 +248,16 @@ Start:
|
|||||||
absolute(ident: string) {
|
absolute(ident: string) {
|
||||||
return ident;
|
return ident;
|
||||||
}
|
}
|
||||||
indexed_x(ident: string) {
|
addOffset(ident: string, offset: number) {
|
||||||
return ident + ',x';
|
if (offset > 0) return `${ident}+${offset}`;
|
||||||
|
if (offset < 0) return `${ident}-${-offset}`;
|
||||||
|
return ident;
|
||||||
|
}
|
||||||
|
indexed_x(ident: string, offset: number) {
|
||||||
|
return this.addOffset(ident, offset) + ',x';
|
||||||
|
}
|
||||||
|
indexed_y(ident: string, offset: number) {
|
||||||
|
return this.addOffset(ident, offset) + ',y';
|
||||||
}
|
}
|
||||||
fieldsymbol(component: ComponentType, field: DataField, bitofs: number) {
|
fieldsymbol(component: ComponentType, field: DataField, bitofs: number) {
|
||||||
return `${component.name}_${field.name}_b${bitofs}`;
|
return `${component.name}_${field.name}_b${bitofs}`;
|
||||||
@ -387,19 +425,92 @@ function getPackedFieldSize(f: DataType, constValue?: DataValue): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QueryResult {
|
||||||
|
atypes: ArchetypeMatch[];
|
||||||
|
entities: Entity[];
|
||||||
|
scope;
|
||||||
|
|
||||||
|
constructor(scope: EntityScope, query?: Query, a?: ArchetypeMatch[], e?: Entity[]) {
|
||||||
|
this.scope = scope;
|
||||||
|
if (query) {
|
||||||
|
this.atypes = scope.em.archetypesMatching(query);
|
||||||
|
this.entities = scope.entitiesMatching(this.atypes);
|
||||||
|
} else if (a && e) {
|
||||||
|
this.atypes = a;
|
||||||
|
this.entities = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contains(c: ComponentType, f: DataField, where: SourceLocated) {
|
||||||
|
// TODO: action for error msg
|
||||||
|
return this.scope.em.singleComponentWithFieldName(this.atypes, f.name, where);
|
||||||
|
}
|
||||||
|
intersection(qr: QueryResult) {
|
||||||
|
let ents = this.entities.filter(e => qr.entities.includes(e));
|
||||||
|
let atypes = this.atypes.filter(a1 => qr.atypes.find(a2 => a2.etype == a1.etype));
|
||||||
|
return new QueryResult(this.scope, undefined, atypes, ents);
|
||||||
|
}
|
||||||
|
isContiguous() {
|
||||||
|
if (this.entities.length == 0) return true;
|
||||||
|
let id = this.entities[0].id;
|
||||||
|
for (let i=1; i<this.entities.length; i++) {
|
||||||
|
if (this.entities[i].id != ++id) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActionState {
|
||||||
|
x: QueryResult | null = null;
|
||||||
|
y: QueryResult | null = null;
|
||||||
|
xofs: number = 0;
|
||||||
|
yofs: number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
class ActionEval {
|
class ActionEval {
|
||||||
em;
|
em;
|
||||||
dialect;
|
dialect;
|
||||||
atypes;
|
qr : QueryResult;
|
||||||
entities;
|
jr : QueryResult | undefined;
|
||||||
|
oldState;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly scope: EntityScope,
|
readonly scope: EntityScope,
|
||||||
readonly sys: System,
|
readonly sys: System,
|
||||||
readonly action: Action) {
|
readonly action: Action) {
|
||||||
this.em = scope.em;
|
this.em = scope.em;
|
||||||
this.dialect = scope.em.dialect;
|
this.dialect = scope.em.dialect;
|
||||||
this.atypes = this.em.archetypesMatching(action.query);
|
this.qr = new QueryResult(scope, action.query);
|
||||||
this.entities = this.scope.entitiesMatching(this.atypes);
|
this.oldState = scope.state;
|
||||||
|
}
|
||||||
|
begin() {
|
||||||
|
let state = this.scope.state = Object.assign({}, this.scope.state);
|
||||||
|
// TODO: generalize to other cpus/langs
|
||||||
|
switch (this.action.select) {
|
||||||
|
case 'foreach':
|
||||||
|
if (state.x && state.y) throw new ECSError('no more index registers', this.action);
|
||||||
|
if (state.x) state.y = this.qr;
|
||||||
|
else state.x = this.qr;
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'source':
|
||||||
|
if (!state.x) throw new ECSError('expected index register', this.action);
|
||||||
|
let int = state.x.intersection(this.qr);
|
||||||
|
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;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end() {
|
||||||
|
this.scope.state = this.oldState;
|
||||||
}
|
}
|
||||||
codeToString(): string {
|
codeToString(): string {
|
||||||
const tag_re = /\{\{(.+?)\}\}/g;
|
const tag_re = /\{\{(.+?)\}\}/g;
|
||||||
@ -414,31 +525,37 @@ class ActionEval {
|
|||||||
// TODO: what if only 1 item?
|
// TODO: what if only 1 item?
|
||||||
let props: { [name: string]: string } = {};
|
let props: { [name: string]: string } = {};
|
||||||
if (action.select == 'foreach') {
|
if (action.select == 'foreach') {
|
||||||
code = this.wrapCodeInLoop(code, action, this.entities);
|
code = this.wrapCodeInLoop(code, action, this.qr.entities);
|
||||||
}
|
}
|
||||||
if (action.select == 'join' && action.join) {
|
if (action.select == 'join' && this.jr) {
|
||||||
let jtypes = this.em.archetypesMatching(action.join);
|
let jentities = this.jr.entities;
|
||||||
let jentities = this.scope.entitiesMatching(jtypes);
|
|
||||||
if (jentities.length == 0)
|
if (jentities.length == 0)
|
||||||
throw new ECSError(`join query for ${label} doesn't match any entities`, action.join); // TODO
|
throw new ECSError(`join query for ${label} doesn't match any entities`, action.join); // TODO
|
||||||
let joinfield = this.getJoinField(action, this.atypes, jtypes);
|
let joinfield = this.getJoinField(action, this.qr.atypes, this.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, action, this.entities, joinfield);
|
code = this.wrapCodeInLoop(code, action, this.qr.entities, joinfield);
|
||||||
this.atypes = jtypes;
|
props['%joinfield'] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0); //TODO?
|
||||||
this.entities = jentities;
|
this.qr = this.jr; // TODO?
|
||||||
props['%joinfield'] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0);
|
|
||||||
}
|
}
|
||||||
props['%efullcount'] = this.entities.length.toString();
|
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) {
|
if (action.limit) {
|
||||||
this.entities = this.entities.slice(0, action.limit);
|
entities = entities.slice(0, action.limit);
|
||||||
}
|
}
|
||||||
if (this.entities.length == 0)
|
if (entities.length == 0)
|
||||||
throw new ECSError(`query for ${label} doesn't match any entities`, action.query); // TODO
|
throw new ECSError(`query for ${label} doesn't match any entities`, action.query); // TODO
|
||||||
// define properties
|
// define properties
|
||||||
props['%elo'] = this.entities[0].id.toString();
|
props['%elo'] = entities[0].id.toString();
|
||||||
props['%ehi'] = this.entities[this.entities.length - 1].id.toString();
|
props['%ehi'] = entities[entities.length - 1].id.toString();
|
||||||
props['%ecount'] = this.entities.length.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
|
// replace @labels
|
||||||
code = code.replace(label_re, (s: string, a: string) => `${label}__${a}`);
|
code = code.replace(label_re, (s: string, a: string) => `${label}__${a}`);
|
||||||
// replace {{...}} tags
|
// replace {{...}} tags
|
||||||
@ -466,7 +583,7 @@ class ActionEval {
|
|||||||
__byte(args: string[]) {
|
__byte(args: string[]) {
|
||||||
let fieldName = args[0];
|
let fieldName = args[0];
|
||||||
let bitofs = parseInt(args[1] || '0');
|
let bitofs = parseInt(args[1] || '0');
|
||||||
return this.generateCodeForField(this.sys, this.action, this.atypes, this.entities, fieldName, bitofs);
|
return this.generateCodeForField(this.sys, this.action, this.qr, fieldName, bitofs);
|
||||||
}
|
}
|
||||||
__use(args: string[]) {
|
__use(args: string[]) {
|
||||||
return this.scope.includeResource(args[0]);
|
return this.scope.includeResource(args[0]);
|
||||||
@ -490,8 +607,13 @@ class ActionEval {
|
|||||||
s = s.replace('{{%code}}', code);
|
s = s.replace('{{%code}}', code);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
generateCodeForField(sys: System, action: Action,
|
wrapCodeInFilter(code: string) {
|
||||||
atypes: ArchetypeMatch[], entities: Entity[],
|
// TODO: what if not needed?
|
||||||
|
let s = this.dialect.ASM_FILTER_RANGE_X;
|
||||||
|
s = s.replace('{{%code}}', code);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
generateCodeForField(sys: System, action: Action, qr: QueryResult,
|
||||||
fieldName: string, bitofs: number): string {
|
fieldName: string, bitofs: number): string {
|
||||||
|
|
||||||
var component: ComponentType;
|
var component: ComponentType;
|
||||||
@ -504,14 +626,15 @@ class ActionEval {
|
|||||||
qualified = true;
|
qualified = true;
|
||||||
if (component == null) throw new ECSError(`no component named "${cname}"`)
|
if (component == null) throw new ECSError(`no component named "${cname}"`)
|
||||||
} else {
|
} else {
|
||||||
component = this.em.singleComponentWithFieldName(atypes, fieldName, `${sys.name}:${action.event}`);
|
component = this.em.singleComponentWithFieldName(qr.atypes, fieldName, action);
|
||||||
}
|
}
|
||||||
// find archetypes
|
// find archetypes
|
||||||
let field = component.fields.find(f => f.name == fieldName);
|
let field = component.fields.find(f => f.name == fieldName);
|
||||||
if (field == null) throw new ECSError(`no field named "${fieldName}" in component`)
|
if (field == null) throw new ECSError(`no field named "${fieldName}" in component`)
|
||||||
// see if all entities have the same constant value
|
// see if all entities have the same constant value
|
||||||
|
// TODO: should be done somewhere else?
|
||||||
let constValues = new Set<DataValue>();
|
let constValues = new Set<DataValue>();
|
||||||
for (let e of entities) {
|
for (let e of qr.entities) {
|
||||||
let constVal = e.consts[mksymbol(component, fieldName)];
|
let constVal = e.consts[mksymbol(component, fieldName)];
|
||||||
constValues.add(constVal); // constVal === undefined is allowed
|
constValues.add(constVal); // constVal === undefined is allowed
|
||||||
}
|
}
|
||||||
@ -528,23 +651,34 @@ class ActionEval {
|
|||||||
// TODO: don't mix const and init data
|
// TODO: don't mix const and init data
|
||||||
let range = this.scope.bss.getFieldRange(component, fieldName) || this.scope.rodata.getFieldRange(component, fieldName);
|
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
|
if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); // TODO
|
||||||
let eidofs = range.elo - entities[0].id;
|
let eidofs = range.elo - qr.entities[0].id; // TODO
|
||||||
// TODO: dialect
|
// TODO: dialect
|
||||||
let ident = this.dialect.fieldsymbol(component, field, bitofs);
|
let ident = this.dialect.fieldsymbol(component, field, bitofs);
|
||||||
if (qualified) {
|
if (qualified) {
|
||||||
return this.dialect.absolute(ident);
|
return this.dialect.absolute(ident);
|
||||||
} else if (action.select == 'once') {
|
} else if (action.select == 'once') {
|
||||||
if (entities.length != 1)
|
if (qr.entities.length != 1)
|
||||||
throw new ECSError(`can't choose multiple entities for ${fieldName} with select=once`, action);
|
throw new ECSError(`can't choose multiple entities for ${fieldName} with select=once`, action);
|
||||||
return this.dialect.absolute(ident);
|
return this.dialect.absolute(ident);
|
||||||
} else {
|
} else {
|
||||||
// TODO: right direction?
|
// TODO: eidofs?
|
||||||
if (eidofs > 0) {
|
let ir;
|
||||||
ident += '+' + eidofs;
|
if (this.scope.state.x?.intersection(this.qr)) {
|
||||||
} else if (eidofs < 0) {
|
ir = this.scope.state.x;
|
||||||
ident += '' + eidofs;
|
eidofs -= this.scope.state.xofs;
|
||||||
}
|
}
|
||||||
return this.dialect.indexed_x(ident);
|
else if (this.scope.state.y?.intersection(this.qr)) {
|
||||||
|
ir = this.scope.state.y;
|
||||||
|
eidofs -= this.scope.state.yofs;
|
||||||
|
}
|
||||||
|
if (!ir) throw new ECSError(`no intersection for index register`, action);
|
||||||
|
if (ir.entities.length == 0) throw new ECSError(`no common entities for index register`, action);
|
||||||
|
if (!ir.isContiguous()) throw new ECSError(`entities in query are not contiguous`, action);
|
||||||
|
if (ir == this.scope.state.x)
|
||||||
|
return this.dialect.indexed_x(ident, eidofs);
|
||||||
|
if (ir == this.scope.state.y)
|
||||||
|
return this.dialect.indexed_y(ident, eidofs);
|
||||||
|
throw new ECSError(`cannot find "${component.name}:${field.name}" in state`, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getJoinField(action: Action, atypes: ArchetypeMatch[], jtypes: ArchetypeMatch[]): ComponentFieldPair {
|
getJoinField(action: Action, atypes: ArchetypeMatch[], jtypes: ArchetypeMatch[]): ComponentFieldPair {
|
||||||
@ -580,6 +714,7 @@ export class EntityScope implements SourceLocated {
|
|||||||
tempSize = 0;
|
tempSize = 0;
|
||||||
maxTempBytes = 0;
|
maxTempBytes = 0;
|
||||||
resources = new Set<string>();
|
resources = new Set<string>();
|
||||||
|
state = new ActionState();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly em: EntityManager,
|
public readonly em: EntityManager,
|
||||||
@ -719,12 +854,12 @@ export class EntityScope implements SourceLocated {
|
|||||||
// TODO: } else if (v instanceof Uint16Array) {
|
// TODO: } else if (v instanceof Uint16Array) {
|
||||||
} else if (typeof v === 'number') {
|
} else if (typeof v === 'number') {
|
||||||
// more than 1 entity, add an array
|
// more than 1 entity, add an array
|
||||||
if (range.ehi > range.elo) {
|
if (entcount > 1) {
|
||||||
if (!range.access) throw new ECSError(`no access for field ${cfname}`)
|
if (!range.access) throw new ECSError(`no access for field ${cfname}`)
|
||||||
let base = segment.allocateBytes(range.access[0].symbol, range.ehi - range.elo + 1);
|
|
||||||
for (let a of range.access) {
|
for (let a of range.access) {
|
||||||
|
segment.allocateBytes(a.symbol, entcount);
|
||||||
let ofs = segment.getByteOffset(range, a, e.id);
|
let ofs = segment.getByteOffset(range, a, e.id);
|
||||||
segment.initdata[ofs] = v;
|
segment.initdata[ofs] = (v >> a.bit) & 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: what if mix of var, const, and init values?
|
// TODO: what if mix of var, const, and init values?
|
||||||
@ -775,14 +910,14 @@ export class EntityScope implements SourceLocated {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
||||||
let c = this.em.singleComponentWithFieldName([{ etype: e.etype, cmatch: [component] }], fieldName, "setConstValue");
|
let c = this.em.singleComponentWithFieldName([{ etype: e.etype, cmatch: [component] }], fieldName, e);
|
||||||
e.consts[mksymbol(component, fieldName)] = value;
|
e.consts[mksymbol(component, fieldName)] = value;
|
||||||
if (this.em.symbols[mksymbol(component, fieldName)] == 'init')
|
if (this.em.symbols[mksymbol(component, fieldName)] == 'init')
|
||||||
throw new ECSError(`Can't mix const and init values for a component field`, e);
|
throw new ECSError(`Can't mix const and init values for a component field`, e);
|
||||||
this.em.symbols[mksymbol(component, fieldName)] = 'const';
|
this.em.symbols[mksymbol(component, fieldName)] = 'const';
|
||||||
}
|
}
|
||||||
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
|
||||||
let c = this.em.singleComponentWithFieldName([{ etype: e.etype, cmatch: [component] }], fieldName, "setInitValue");
|
let c = this.em.singleComponentWithFieldName([{ etype: e.etype, cmatch: [component] }], fieldName, e);
|
||||||
e.inits[mkscopesymbol(this, component, fieldName)] = value;
|
e.inits[mkscopesymbol(this, component, fieldName)] = value;
|
||||||
if (this.em.symbols[mksymbol(component, fieldName)] == 'const')
|
if (this.em.symbols[mksymbol(component, fieldName)] == 'const')
|
||||||
throw new ECSError(`Can't mix const and init values for a component field`, e);
|
throw new ECSError(`Can't mix const and init values for a component field`, e);
|
||||||
@ -819,10 +954,12 @@ export class EntityScope implements SourceLocated {
|
|||||||
}
|
}
|
||||||
// TODO: use Tokenizer so error msgs are better
|
// TODO: use Tokenizer so error msgs are better
|
||||||
let codeeval = new ActionEval(this, sys, action);
|
let codeeval = new ActionEval(this, sys, action);
|
||||||
|
codeeval.begin();
|
||||||
s += this.dialect.comment(`<action ${sys.name}:${event}>`); // TODO
|
s += this.dialect.comment(`<action ${sys.name}:${event}>`); // TODO
|
||||||
s += codeeval.codeToString();
|
s += codeeval.codeToString();
|
||||||
s += this.dialect.comment(`</action ${sys.name}:${event}>`);
|
s += this.dialect.comment(`</action ${sys.name}:${event}>`);
|
||||||
// TODO: check that this happens once?
|
// TODO: check that this happens once?
|
||||||
|
codeeval.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sys.tempbytes) this.allocateTempBytes(-sys.tempbytes);
|
if (sys.tempbytes) this.allocateTempBytes(-sys.tempbytes);
|
||||||
@ -916,12 +1053,11 @@ export class EntityManager {
|
|||||||
componentsMatching(q: Query, etype: EntityArchetype) {
|
componentsMatching(q: Query, etype: EntityArchetype) {
|
||||||
let list = [];
|
let list = [];
|
||||||
for (let c of etype.components) {
|
for (let c of etype.components) {
|
||||||
let cname = c.name;
|
if (q.exclude?.includes(c)) {
|
||||||
if (q.exclude?.includes(cname)) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// TODO: 0 includes == all entities?
|
// TODO: 0 includes == all entities?
|
||||||
if (q.include.length == 0 || q.include.includes(cname)) {
|
if (q.include.length == 0 || q.include.includes(c)) {
|
||||||
list.push(c);
|
list.push(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -953,14 +1089,14 @@ export class EntityManager {
|
|||||||
getComponentByName(name: string): ComponentType {
|
getComponentByName(name: string): ComponentType {
|
||||||
return this.components[name];
|
return this.components[name];
|
||||||
}
|
}
|
||||||
singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: string) {
|
singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: SourceLocated) {
|
||||||
let components = this.componentsWithFieldName(atypes, fieldName);
|
let components = this.componentsWithFieldName(atypes, fieldName);
|
||||||
// TODO: use name2cfpairs?
|
// TODO: use name2cfpairs?
|
||||||
if (components.length == 0) {
|
if (components.length == 0) {
|
||||||
throw new ECSError(`cannot find component with field "${fieldName}" in ${where}`);
|
throw new ECSError(`cannot find component with field "${fieldName}"`, where);
|
||||||
}
|
}
|
||||||
if (components.length > 1) {
|
if (components.length > 1) {
|
||||||
throw new ECSError(`ambiguous field name "${fieldName}" in ${where}`);
|
throw new ECSError(`ambiguous field name "${fieldName}"`, where);
|
||||||
}
|
}
|
||||||
return components[0];
|
return components[0];
|
||||||
}
|
}
|
||||||
|
@ -169,16 +169,6 @@ function testECS() {
|
|||||||
//TODO: optional?
|
//TODO: optional?
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
let c_hasbitmap = em.defineComponent({
|
|
||||||
name: 'hasbitmap', fields: [
|
|
||||||
{ name: 'bitmap', dtype: 'ref', query: { include: ['bitmap'] } },
|
|
||||||
]
|
|
||||||
})
|
|
||||||
let c_hascolormap = em.defineComponent({
|
|
||||||
name: 'hascolormap', fields: [
|
|
||||||
{ name: 'colormap', dtype: 'ref', query: { include: ['colormap'] } },
|
|
||||||
]
|
|
||||||
})
|
|
||||||
let c_bitmap = em.defineComponent({
|
let c_bitmap = em.defineComponent({
|
||||||
name: 'bitmap', fields: [
|
name: 'bitmap', fields: [
|
||||||
{ name: 'bitmapdata', dtype: 'array', elem: { dtype: 'int', lo: 0, hi: 255 } }
|
{ name: 'bitmapdata', dtype: 'array', elem: { dtype: 'int', lo: 0, hi: 255 } }
|
||||||
@ -189,6 +179,16 @@ function testECS() {
|
|||||||
{ name: 'colormapdata', dtype: 'array', elem: { dtype: 'int', lo: 0, hi: 255 } }
|
{ name: 'colormapdata', dtype: 'array', elem: { dtype: 'int', lo: 0, hi: 255 } }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
let c_hasbitmap = em.defineComponent({
|
||||||
|
name: 'hasbitmap', fields: [
|
||||||
|
{ name: 'bitmap', dtype: 'ref', query: { include: [c_bitmap] } },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
let c_hascolormap = em.defineComponent({
|
||||||
|
name: 'hascolormap', fields: [
|
||||||
|
{ name: 'colormap', dtype: 'ref', query: { include: [c_colormap] } },
|
||||||
|
]
|
||||||
|
})
|
||||||
let c_xpos = em.defineComponent({
|
let c_xpos = em.defineComponent({
|
||||||
name: 'xpos', fields: [
|
name: 'xpos', fields: [
|
||||||
{ name: 'xpos', dtype: 'int', lo: 0, hi: 255 }
|
{ name: 'xpos', dtype: 'int', lo: 0, hi: 255 }
|
||||||
@ -211,18 +211,18 @@ function testECS() {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
text: TEMPLATE4_S1, event: 'preframe', select: 'once', query: {
|
text: TEMPLATE4_S1, event: 'preframe', select: 'once', query: {
|
||||||
include: ['kernel']
|
include: [c_kernel]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// TODO: should include kernel for numlines
|
// TODO: should include kernel for numlines
|
||||||
text: TEMPLATE4_S2, event: 'preframe', select: 'once', query: {
|
text: TEMPLATE4_S2, event: 'preframe', select: 'once', query: {
|
||||||
include: ['sprite', 'hasbitmap', 'hascolormap', 'ypos'],
|
include: [c_sprite, c_hasbitmap, c_hascolormap, c_ypos],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: TEMPLATE4_K, event: 'kernel', select: 'once', query: {
|
text: TEMPLATE4_K, event: 'kernel', select: 'once', query: {
|
||||||
include: ['kernel']
|
include: [c_kernel]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -232,7 +232,7 @@ function testECS() {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
text: SET_XPOS, event: 'preframe', select: 'foreach', query: {
|
text: SET_XPOS, event: 'preframe', select: 'foreach', query: {
|
||||||
include: ['sprite', 'xpos']
|
include: [c_sprite, c_xpos]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//{ text:SETHORIZPOS },
|
//{ text:SETHORIZPOS },
|
||||||
@ -242,14 +242,14 @@ function testECS() {
|
|||||||
em.defineSystem({
|
em.defineSystem({
|
||||||
name: 'frameloop',
|
name: 'frameloop',
|
||||||
actions: [
|
actions: [
|
||||||
{ text: TEMPLATE1, event: 'start', select: 'once', query: { include: ['kernel'] },
|
{ text: TEMPLATE1, event: 'start', select: 'once', query: { include: [c_kernel] },
|
||||||
emits: ['preframe', 'kernel', 'postframe'] }
|
emits: ['preframe', 'kernel', 'postframe'] }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
em.defineSystem({
|
em.defineSystem({
|
||||||
name: 'SetHorizPos',
|
name: 'SetHorizPos',
|
||||||
actions: [
|
actions: [
|
||||||
{ text: SETHORIZPOS, event: 'SetHorizPos', select: 'once', query: { include: ['xpos'] } },
|
{ text: SETHORIZPOS, event: 'SetHorizPos', select: 'once', query: { include: [c_xpos] } },
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user