1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-08 08:33:32 +00:00

ecs: actions emit?

This commit is contained in:
Steven Hugg 2022-01-28 21:13:33 -06:00
parent d2caee8cc3
commit 310ce87bcc
7 changed files with 490 additions and 59 deletions

90
ecsroot/kernel.txt Normal file
View File

@ -0,0 +1,90 @@
# Kernel
# A Kernel draws a set of scanlines to the screen.
component Kernel
lines: 0..255 "Height of region in scanlines"
bgcolor: 0..255 "Background color"
end
system SimpleKernel
tempbytes 8
on preframe do once [Kernel] ---
lda #192 ; TODO: numlines
sec
sbc ypos_ypos_b0+ent
sta {{$5}}+ofs
ldy hasbitmap_bitmap_b0+ent
lda bitmap_bitmapdata_b0,y
sec
sbc {{$5}}+ofs
sta {{$0}}+ofs
lda bitmap_bitmapdata_b8,y
sbc #0
sta {{$1}}+ofs
ldy hascolormap_colormap_b0+ent
lda colormap_colormapdata_b0,y
sec
sbc {{$5}}+ofs
sta {{$2}}+ofs
lda colormap_colormapdata_b8,y
sbc #0
sta {{$3}}+ofs
lda sprite_height_b0+ent
sta {{$4}}+ofs
lda ypos_ypos_b0+ent
sta {{$5}}+ofs
---
on preframe do once [Sprite,HasBitmap,HasColormap,HasYpos] --
{{@KernelSetup}} 0,0
{{@KernelSetup}} 1,6
--
on kernel do once [Kernel]:
lda %{<bgcolor}
sta COLUBK
ldy %{<lines}
@LVScan:
lda %{$4} ; height
dcp %{$5}
bcs @DoDraw1
lda #0
.byte $2C
@DoDraw1:
lda (%{$0}),y
sta WSYNC
sta GRP0
lda (%{$2}),y
sta COLUP0
lda %{$10} ; height
dcp %{$11}
bcs @DoDraw2
lda #0
.byte $2C
@DoDraw2:
lda (%{$6}),y
sta GRP1
lda (%{$8}),y
sta COLUP1
dey ; decrement
bne @LVScan ; repeat until 192 lines
--
end
scope Root
entity kernel [SimpleKernel]
const lines = 100
end
entity player1 [Sprite,HasBitmap,HasColormap,HasYpos]
const plyrindex = 0
init height = 8
init xpos = 100
init ypos = 100
end
end scope

279
ecsroot/vcs/kernel.ecs Normal file
View File

@ -0,0 +1,279 @@
component Kernel
lines: 0..255
bgcolor: 0..255
end
component Bitmap
bitmapdata: array of 0..255
end
component HasBitmap
bitmap: [Bitmap]
end
component Colormap
colormapdata: array of 0..255
end
component HasColormap
colormap: [Colormap]
end
component Sprite
height: 0..255
plyrindex: 0..1
end
component Player end
component PlayerFlags
plyrflags: 0..63
end
component HasXpos
xpos: 0..255
end
component HasYpos
ypos: 0..255
end
component HasXYVel
xyvel: 0..255
end
system FrameLoop
on start do once [Kernel] emit (preframe, kernel, postframe)
---
{{@NextFrame}}:
FRAME_START
{{!preframe}}
KERNEL_START
{{!kernel}}
KERNEL_END
{{!postframe}}
FRAME_END
lsr SWCHB ; test Game Reset switch
bcs {{@NoStart}}
jmp Start
{{@NoStart}}:
jmp {{@NextFrame}}
---
end
system SimpleKernel
locals 12
on preframe do each [Sprite,HasBitmap,HasColormap,HasYpos]
---
lda #192 ; TODO: numlines
sec
sbc {{<ypos}}
sta {{$11}}
ldy {{<bitmap}} ; deref
lda Bitmap_bitmapdata_b0,y
sec
sbc {{$11}}
sta {{$0}},x
lda Bitmap_bitmapdata_b8,y
sbc #0
sta {{$2}},x
ldy {{<colormap}}
lda Colormap_colormapdata_b0,y
sec
sbc {{$11}}
sta {{$4}},x
lda Colormap_colormapdata_b8,y
sbc #0
sta {{$6}},x
lda {{<height}}
sta {{$8}},x
lda {{<ypos}}
sta {{$10}},x
---
on preframe do once [Sprite,HasBitmap,HasColormap,HasYpos]
---
; L0 L1 H0 H1 -> L0 H0 L1 H1
lda {{$1}}
ldy {{$2}}
sty {{$1}}
sta {{$2}}
lda {{$5}}
ldy {{$6}}
sty {{$5}}
sta {{$6}}
---
on kernel do once [Kernel]
---
lda {{<bgcolor}}
sta COLUBK
ldy {{<lines}}
@LVScan:
lda {{$8}} ; height
dcp {{$10}} ; ypos
bcs @DoDraw1
lda #0
.byte $2C
@DoDraw1:
lda ({{$0}}),y
sta WSYNC
sta GRP0
lda ({{$4}}),y
sta COLUP0
lda {{$9}} ; height
dcp {{$11}} ; ypos
bcs @DoDraw2
lda #0
.byte $2C
@DoDraw2:
lda ({{$2}}),y
sta GRP1
lda ({{$6}}),y
sta COLUP1
dey ; decrement
bne @LVScan ; repeat until 192 lines
---
end
system SetXPos
on preframe do each [Sprite,HasXpos] emit (SetHorizPos)
---
lda {{<xpos}}
ldy {{<plyrindex}}
sta HMCLR
jsr {{^SetHorizPos}}
---
end
system SetHorizPos
on SetHorizPos do once [HasXpos]
---
; SetHorizPos routine
; A = X coordinate
; Y = player number (0 or 1)
SetHorizPos:
sta WSYNC ; start a new line
sec ; set carry flag
nop
@DivideLoop:
sbc #15 ; subtract 15
bcs @DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,y ; fix coarse position
sta HMP0,y ; set fine offset
sta WSYNC
sta HMOVE
rts ; return to caller
---
end
system JoyRead
locals 1
on postframe do once [Player]
---
lda SWCHA
sta {{$0}}
---
on postframe do each [Player] emit (joyup, joydown, joyleft, joyright, joybutton)
---
asl {{$0}}
bcs {{@SkipMoveRight}}
{{!joyright}}
{{@SkipMoveRight}}:
asl {{$0}}
bcs {{@SkipMoveLeft}}
{{!joyleft}}
{{@SkipMoveLeft}}:
asl {{$0}}
bcs {{@SkipMoveDown}}
{{!joydown}}
{{@SkipMoveDown}}:
asl {{$0}}
bcs {{@SkipMoveUp}}
{{!joyup}}
{{@SkipMoveUp}}:
---
end
system MoveJoyX
on joyleft do source [HasXpos]
---
lda {{<xpos}}
sec
sbc #1
bcc {{@nomove}}
sta {{<xpos}}
{{@nomove}}:
---
on joyright do source [HasXpos]
---
lda {{<xpos}}
clc
adc #1
cmp #150
bcs {{@nomove}}
sta {{<xpos}}
{{@nomove}}:
---
end
system MoveJoyY
on joyup do source [HasYpos]
---
lda {{<ypos}}
sec
sbc #1
bcc {{@nomove}}
sta {{<ypos}}
{{@nomove}}:
---
on joydown do source [HasYpos]
---
lda {{<ypos}}
clc
adc #1
cmp #150
bcs {{@nomove}}
sta {{<ypos}}
{{@nomove}}:
---
end
scope Main
entity Kernel [Kernel]
const lines = 192
const bgcolor = 0xa2
end
entity Bitmap1 [Bitmap]
const bitmapdata = [1, 1, 3, 7, 15, 31, 63, 127]
end
entity Bitmap2 [Bitmap]
const bitmapdata = [$18,$3e,$ff,$ff,$ff,$ff,$3e,$18]
end
entity Colormap1 [Colormap]
const colormapdata = [6, 3, 6, 9, 12, 14, 31, 63]
end
entity Player0 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
const plyrindex = 0
init height = 8
init xpos = 50
init ypos = 50
end
entity Player1 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
const plyrindex = 1
init height = 8
init xpos = 100
init ypos = 60
init bitmap = 1
end
end

View File

@ -18,11 +18,13 @@ export class ECSCompiler extends Tokenizer {
super();
//this.includeEOL = true;
this.setTokenRules([
{ type: TokenType.Ident, regex: /[$A-Za-z_][A-Za-z0-9_-]*/ },
{ type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ },
{ type: TokenType.CodeFragment, regex: /---/ },
{ type: ECSTokenType.Ellipsis, regex: /\.\./ },
{ type: ECSTokenType.Operator, regex: /[=,:(){}\[\]]/ },
{ type: ECSTokenType.Operator, regex: /[#=,:(){}\[\]]/ },
{ type: ECSTokenType.QuotedString, regex: /".*?"/ },
{ type: ECSTokenType.Integer, regex: /[-]?0x[A-Fa-f0-9]+/ },
{ type: ECSTokenType.Integer, regex: /[-]?\$[A-Fa-f0-9]+/ },
{ type: ECSTokenType.Integer, regex: /[-]?\d+/ },
{ type: TokenType.Ignore, regex: /\s+/ },
]);
@ -91,14 +93,33 @@ export class ECSCompiler extends Tokenizer {
}
parseDataValue() : DataValue {
if (this.peekToken().type == 'integer') {
let tok = this.peekToken();
if (tok.type == 'integer') {
return this.expectInteger();
}
if (tok.str == '[') {
// TODO: 16-bit?
return new Uint8Array(this.parseDataArray());
}
if (tok.str == '#') {
let e = this.parseEntityRef();
// TODO: entity ref types by query?
return e.id;
}
this.compileError(`Unknown data value`); // TODO
}
parseDataArray() {
this.expectToken('[');
let arr = this.parseList(this.expectInteger, ',');
this.expectToken(']');
return arr;
}
expectInteger(): number {
let i = parseInt(this.consumeToken().str);
let s = this.consumeToken().str;
if (s.startsWith('$')) s = '0x' + s.substring(1);
let i = parseInt(s);
if (isNaN(i)) this.compileError('There should be an integer here.');
return i;
}
@ -123,12 +144,19 @@ export class ECSCompiler extends Tokenizer {
parseAction(): Action {
let event = this.expectIdent().str;
this.expectToken('do');
let select = this.expectTokens(['once', 'each']).str as SelectType; // TODO: type check?
let select = this.expectTokens(['once', 'each', 'source']).str as SelectType; // TODO: type check?
let query = this.parseQuery();
let emits;
if (this.peekToken().str == 'emit') {
this.consumeToken();
this.expectToken('(');
emits = this.parseEventList();
this.expectToken(')');
}
let text = this.parseCode();
return { text, event, query, select };
}
parseQuery() {
let q: Query = { include: [] };
this.expectToken('[');
@ -142,6 +170,10 @@ export class ECSCompiler extends Tokenizer {
return this.expectIdent().str;
}
parseEventList() {
return this.parseList(this.parseEvent, ",");
}
parseCode(): string {
return this.expectTokenTypes([TokenType.CodeFragment]).str;
}
@ -173,14 +205,15 @@ export class ECSCompiler extends Tokenizer {
let cmd;
while ((cmd = this.consumeToken().str) != 'end') {
// TODO: check data types
if (cmd == 'const') {
if (cmd == 'const' || cmd == 'init') {
let name = this.expectIdent().str;
this.expectToken('=');
e.consts[name] = this.parseDataValue();
} else if (cmd == 'init') {
let name = this.expectIdent().str;
this.expectToken('=');
e.inits[name] = this.parseDataValue();
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 value = this.parseDataValue();
if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value);
if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value);
} else {
this.compileError(`Unexpected scope keyword: ${cmd}`);
}
@ -202,6 +235,14 @@ export class ECSCompiler extends Tokenizer {
return cref;
}
parseEntityRef() : Entity {
this.expectToken('#');
let name = this.expectIdent().str;
let eref = this.currentScope.entities.find(e => e.name == name);
if (!eref) this.compileError(`I couldn't find an entity named "${name}" in this scope.`)
return eref;
}
exportToFile(src: SourceFileExport) {
for (let scope of Object.values(this.em.scopes)) {
scope.analyzeEntities();

View File

@ -29,8 +29,6 @@
// - converting assets to native formats?
// - removing unused data
import { Token } from "../tokenizer";
function mksymbol(c: ComponentType, fieldName: string) {
return c.name + '_' + fieldName;
}
@ -73,7 +71,6 @@ export interface System {
name: string;
actions: Action[];
tempbytes?: number;
emits?: string[];
}
export interface Action {
@ -81,6 +78,7 @@ export interface Action {
event: string;
query: Query;
select: SelectType
emits?: string[];
}
export type SelectType = 'once' | 'each' | 'source';
@ -132,6 +130,11 @@ interface ArchetypeMatch {
cmatch: ComponentType[];
}
interface ComponentFieldPair {
c: ComponentType;
f: DataField;
}
export class Dialect_CA65 {
readonly ASM_ITERATE_EACH = `
ldx #0
@ -297,6 +300,7 @@ export class EntityScope {
code = new Segment();
componentsInScope = new Set();
tempOffset = 0;
tempSize = 0;
maxTempBytes = 0;
subroutines = new Set<string>();
@ -415,7 +419,8 @@ export class EntityScope {
if (offset !== undefined && typeof initvalue === 'number') {
initbytes[offset] = initvalue; // TODO: > 8 bits?
} else {
throw new Error(`cannot access ${scfname}`);
// TODO: init arrays?
throw new Error(`cannot initialize ${scfname}: ${offset} ${initvalue}`); // TODO??
}
}
}
@ -431,11 +436,11 @@ export class EntityScope {
return code;
}
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
// TODO: check to make sure component exists
let c = this.em.singleComponentWithFieldName([{etype: e.etype, cmatch:[component]}], fieldName, "setConstValue");
e.consts[mksymbol(component, fieldName)] = value;
}
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) {
// TODO: check to make sure component exists
let c = this.em.singleComponentWithFieldName([{etype: e.etype, cmatch:[component]}], fieldName, "setConstValue");
e.inits[mkscopesymbol(this, component, fieldName)] = value;
}
generateCodeForEvent(event: string): string {
@ -450,21 +455,21 @@ export class EntityScope {
let emitcode: { [event: string]: string } = {};
for (let sys of systems) {
// TODO: does this work if multiple actions?
// TODO: should 'emits' be on action?
if (sys.tempbytes) this.allocateTempBytes(sys.tempbytes);
if (sys.emits) {
for (let emit of sys.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);
}
}
if (sys.tempbytes) this.allocateTempBytes(-sys.tempbytes);
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);
}
}
let code = this.replaceCode(action.text, sys, action);
s += this.dialect.comment(`<action ${sys.name}:${event}>`);
s += code;
@ -472,12 +477,15 @@ export class EntityScope {
// TODO: check that this happens once?
}
}
if (sys.tempbytes) this.allocateTempBytes(-sys.tempbytes);
}
return s;
}
allocateTempBytes(n: number) {
this.tempOffset += n;
this.maxTempBytes = Math.max(this.tempOffset, this.maxTempBytes);
if (n > 0) this.tempOffset = this.tempSize;
this.tempSize += n;
this.maxTempBytes = Math.max(this.tempSize, this.maxTempBytes);
if (n < 0) this.tempOffset = this.tempSize;
}
replaceCode(code: string, sys: System, action: Action): string {
const re = /\{\{(.+?)\}\}/g;
@ -512,9 +520,7 @@ export class EntityScope {
case '^': // reference
return this.includeSubroutine(rest);
default:
//throw new Error(`unrecognized command ${cmd} in ${entire}`);
console.log(`unrecognized command ${cmd} in ${entire}`);
return entire;
throw new Error(`unrecognized command ${cmd} in ${entire}`);
}
});
}
@ -536,10 +542,7 @@ export class EntityScope {
atypes: ArchetypeMatch[], entities: Entity[],
fieldName: string, bitofs: number): string {
// find archetypes
let component = this.em.componentWithFieldName(atypes, fieldName);
if (!component) {
throw new Error(`cannot find component with field "${fieldName}" in ${sys.name}:${action.event}`);
}
let component = this.em.singleComponentWithFieldName(atypes, fieldName, `${sys.name}:${action.event}`);
// see if all entities have the same constant value
let constValues = new Set<DataValue>();
for (let e of entities) {
@ -583,7 +586,7 @@ export class EntityScope {
return result;
}
getSystems(events: string[]) {
let result = [];
let result : System[] = [];
for (let sys of Object.values(this.em.systems)) {
if (this.systemListensTo(sys, events)) {
result.push(sys);
@ -684,20 +687,32 @@ export class EntityManager {
});
return result;
}
componentWithFieldName(atypes: ArchetypeMatch[], fieldName: string) {
componentsWithFieldName(atypes: ArchetypeMatch[], fieldName: string) {
// TODO???
let comps = new Set<ComponentType>();
for (let at of atypes) {
for (let c of at.cmatch) {
for (let f of c.fields) {
if (f.name == fieldName)
return c;
comps.add(c);
}
}
}
return Array.from(comps);
}
getComponentByName(name: string): ComponentType {
return this.components[name];
}
singleComponentWithFieldName(atypes: ArchetypeMatch[], fieldName: string, where: string) {
let components = this.componentsWithFieldName(atypes, fieldName);
if (components.length == 0) {
throw new Error(`cannot find component with field "${fieldName}" in ${where}`);
}
if (components.length > 1) {
throw new Error(`ambiguous field name "${fieldName}" in ${where}`);
}
return components[0];
}
toJSON() {
return JSON.stringify({
components: this.components,

View File

@ -14,16 +14,18 @@ class ECSMain {
let text = readFileSync(path, 'utf-8');
try {
this.compiler.parseFile(text, path);
} catch (e) {
console.log(e);
for (let err of this.compiler.errors) {
console.log(`${err.path}:${err.line}:${err.start}: ${err.msg}`);
if (this.compiler.errors.length == 0) {
let file = new SourceFileExport();
this.compiler.exportToFile(file);
console.log(file.toString());
}
} catch (e) {
console.error(e);
}
for (let err of this.compiler.errors) {
console.error(`${err.path}:${err.line}:${err.start}: ${err.msg}`);
}
}
let file = new SourceFileExport();
this.compiler.exportToFile(file);
console.log(file.toString());
}
}

View File

@ -181,7 +181,7 @@ export class Tokenizer {
let tok = this.consumeToken();
let tokstr = tok.str;
if (strlist.indexOf(tokstr) < 0) {
this.compileError(msg || `There should be a ${strlist.map((s) => `"${s}"`).join(' or ')} here, not "${tokstr}.`);
this.compileError(msg || `There should be a ${strlist.map((s) => `"${s}"`).join(' or ')} here, not "${tokstr}".`);
}
return tok;
}

View File

@ -23,6 +23,7 @@ const TEMPLATE2_a = `
lda SWCHA
sta {{$0}}
`
const TEMPLATE2_b = `
asl {{$0}}
bcs {{@SkipMoveRight}}
@ -308,18 +309,18 @@ function testECS() {
// https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/ecs_systems.html
em.defineSystem({
name: 'frameloop',
emits: ['preframe', 'kernel', 'postframe'],
actions: [
{ text: TEMPLATE1, event: 'start', select: 'once', query: { include: ['kernel'] } }
{ text: TEMPLATE1, event: 'start', select: 'once', query: { include: ['kernel'] },
emits: ['preframe', 'kernel', 'postframe'] }
]
})
em.defineSystem({
name: 'joyread',
tempbytes: 1,
emits: ['joyup', 'joydown', 'joyleft', 'joyright', 'joybutton'],
actions: [
{ text: TEMPLATE2_a, event: 'postframe', select: 'once', query: { include: ['player'] } },
{ text: TEMPLATE2_b, event: 'postframe', select: 'each', query: { include: ['player'] } }
{ text: TEMPLATE2_b, event: 'postframe', select: 'each', query: { include: ['player'] },
emits: ['joyup', 'joydown', 'joyleft', 'joyright', 'joybutton'] }
]
});
em.defineSystem({
@ -406,17 +407,19 @@ Label:
---
end
comment ---
---
scope Root
entity kernel [Kernel]
const lines = 100
const lines = 0xc0
const lines = $c0
end
entity player1 [HasBitmap]
const plyrindex = 0
init height = 8
init xpos = 100
init ypos = 100
init bitmap = 1
end
end
@ -433,6 +436,7 @@ end
console.log(err);
}
console.log(c.tokens);
throw e;
}
}