ecs: forward entity refs, deferred components, %binary

This commit is contained in:
Steven Hugg 2022-02-10 09:21:24 -06:00
parent 999aed9cb4
commit fc0a43b9af
9 changed files with 1576 additions and 118 deletions

View File

@ -19,6 +19,8 @@ npm i
npm run build
```
To use GitHub integration locally, download the Firebase config file, e.g. https://8bitworkshop.com/v[version]/config.js
### Start Server
Start a web server on http://localhost:8000/ while TypeScript compiles in the background:
@ -102,3 +104,4 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg?
* https://github.com/sehugg/8bitworkshop-compilers
* https://github.com/sehugg/8bit-tools
* https://github.com/sehugg/awesome-8bitgamedev

View File

@ -1,8 +1,8 @@
import { mergeLocs, Tokenizer, TokenType } from "../tokenizer";
import { SourceLocated } from "../workertypes";
import { mergeLocs, Token, Tokenizer, TokenType } from "../tokenizer";
import { SourceLocated, SourceLocation } from "../workertypes";
import { newDecoder } from "./decoder";
import { Action, ActionWithJoin, ArrayType, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System } from "./ecs";
import { Action, ActionOwner, ActionNode, ActionWithJoin, ArrayType, CodeLiteralNode, CodePlaceholderNode, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System } from "./ecs";
export enum ECSTokenType {
Ellipsis = 'ellipsis',
@ -10,11 +10,18 @@ export enum ECSTokenType {
QuotedString = 'quoted-string',
Integer = 'integer',
CodeFragment = 'code-fragment',
Placeholder = 'placeholder',
}
interface ForwardRef {
reftype: RefType | undefined
token: Token
}
export class ECSCompiler extends Tokenizer {
currentScope: EntityScope | null = null;
currentContext: ActionOwner | null = null;
debuginfo = false;
constructor(
@ -26,9 +33,10 @@ export class ECSCompiler extends Tokenizer {
{ type: ECSTokenType.Ellipsis, regex: /\.\./ },
{ type: ECSTokenType.QuotedString, regex: /".*?"/ },
{ type: ECSTokenType.CodeFragment, regex: /---.*?---/ },
{ type: ECSTokenType.Integer, regex: /[-]?0x[A-Fa-f0-9]+/ },
{ type: ECSTokenType.Integer, regex: /[-]?0[xX][A-Fa-f0-9]+/ },
{ type: ECSTokenType.Integer, regex: /[-]?\$[A-Fa-f0-9]+/ },
{ type: ECSTokenType.Integer, regex: /[-]?\d+/ },
{ type: ECSTokenType.Integer, regex: /[%][01]+/ },
{ type: ECSTokenType.Operator, regex: /[#=,:(){}\[\]\-]/ },
{ type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ },
{ type: TokenType.Ignore, regex: /\/\/.*?[\n\r]/ },
@ -54,6 +62,7 @@ export class ECSCompiler extends Tokenizer {
this.annotate(() => t); // TODO? typescript bug?
}
}
this.runDeferred();
}
getImportFile: (path: string) => string;
@ -108,6 +117,7 @@ export class ECSCompiler extends Tokenizer {
parseComponentDefinition(): ComponentType {
let name = this.expectIdent().str;
let fields = [];
this.em.deferComponent(name);
while (this.peekToken().str != 'end') {
fields.push(this.parseComponentField());
}
@ -148,7 +158,7 @@ export class ECSCompiler extends Tokenizer {
this.compileError(`I expected a data type here.`); throw new Error();
}
parseDataValue(field: DataField) : DataValue {
parseDataValue(field: DataField) : DataValue | ForwardRef {
let tok = this.peekToken();
if (tok.type == 'integer') {
return this.expectInteger();
@ -158,20 +168,10 @@ export class ECSCompiler extends Tokenizer {
return new Uint8Array(this.parseDataArray());
}
if (tok.str == '#') {
this.consumeToken();
let reftype = field.dtype == 'ref' ? field as RefType : undefined;
let e = this.parseEntityRef();
let id = e.id;
if (reftype) {
// TODO: make this a function? elo ehi etc?
if (!this.currentScope) {
this.compileError("This type can only exist inside of a scope."); throw new Error()
};
let atypes = this.em.archetypesMatching(reftype.query);
let entities = this.currentScope.entitiesMatching(atypes);
if (entities.length == 0) this.compileError(`This entitiy doesn't seem to fit the reference type.`);
id -= entities[0].id;
}
return id;
let token = this.expectIdent();
return { reftype, token };
}
this.compileError(`I expected a ${field.dtype} here.`); throw new Error();
}
@ -185,8 +185,13 @@ export class ECSCompiler extends Tokenizer {
expectInteger(): number {
let s = this.consumeToken().str;
if (s.startsWith('$')) s = '0x' + s.substring(1);
let i = parseInt(s);
let i : number;
if (s.startsWith('$'))
i = parseInt(s.substring(1), 16); // hex $...
else if (s.startsWith('%'))
i = parseInt(s.substring(1), 2); // binary %...
else
i = parseInt(s); // default base 10 or 16 (0x...)
if (isNaN(i)) this.compileError('There should be an integer here.');
return i;
}
@ -198,7 +203,7 @@ export class ECSCompiler extends Tokenizer {
let cmd;
while ((cmd = this.expectTokens(['on','locals','end']).str) != 'end') {
if (cmd == 'on') {
let action = this.annotate(() => this.parseAction());
let action = this.annotate(() => this.parseAction(system));
actions.push(action);
} else if (cmd == 'locals') {
system.tempbytes = this.expectInteger();
@ -216,13 +221,16 @@ export class ECSCompiler extends Tokenizer {
this.consumeToken();
tempbytes = this.expectInteger();
}
let text = this.parseCode();
let system : System = { name, tempbytes, actions: [] };
let context : ActionOwner = { scope: null, system };
let text = this.parseCode(context);
let select : SelectType = 'once';
let action : Action = { text, event: name, select };
return { name, tempbytes, actions: [action] };
system.actions.push(action);
return system;
}
parseAction(): Action {
parseAction(system: System): Action {
// TODO: unused events?
const event = this.expectIdent().str;
this.expectToken('do');
@ -245,7 +253,8 @@ export class ECSCompiler extends Tokenizer {
if (!query) { this.compileError(`A "${select}" query can't include a limit.`); }
else query.limit = this.expectInteger();
}
let text = this.parseCode();
let context : ActionOwner = { scope: null, system };
let text = this.parseCode(context);
let direction = undefined;
if (modifiers['asc']) direction = 'asc';
else if (modifiers['desc']) direction = 'desc';
@ -287,13 +296,20 @@ export class ECSCompiler extends Tokenizer {
return this.parseList(this.parseEventName, ",");
}
parseCode(): string {
parseCode(context: ActionOwner): string { // TODOActionNode[] {
// TODO: add $loc
let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]);
let code = tok.str.substring(3, tok.str.length-3);
/*
let lines = code.split('\n');
// TODO: add after parsing maybe?
if (this.debuginfo) this.addDebugInfo(lines, tok.$loc.line);
return lines.join('\n');
code = lines.join('\n');
*/
let acomp = new ECSActionCompiler(context);
let nodes = acomp.parseFile(code, this.path);
// TODO: return nodes
return code;
}
addDebugInfo(lines: string[], startline: number) {
@ -342,6 +358,7 @@ export class ECSCompiler extends Tokenizer {
parseEntity() : Entity {
if (!this.currentScope) { this.internalError(); throw new Error(); }
const scope = this.currentScope;
let entname = '';
if (this.peekToken().type == TokenType.Ident) {
entname = this.expectIdent().str;
@ -349,17 +366,30 @@ export class ECSCompiler extends Tokenizer {
let etype = this.parseEntityArchetype();
let entity = this.currentScope.newEntity(etype);
entity.name = entname;
let cmd;
let cmd2 : string;
// TODO: remove init?
while ((cmd = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
while ((cmd2 = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
let cmd = cmd2; // put in scope
if (cmd == 'var') cmd = 'init';
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);
});
let { component, field } = this.getEntityField(entity, name);
let symtype = this.currentScope.isConstOrInit(component, name);
if (symtype && symtype != cmd)
this.compileError(`I can't mix const and init values for a given field in a scope.`);
this.expectToken('=');
let valueOrRef = this.parseDataValue(field);
if ((valueOrRef as ForwardRef).token != null) {
this.deferred.push(() => {
let refvalue = this.resolveEntityRef(scope, valueOrRef as ForwardRef);
if (cmd == 'const') scope.setConstValue(entity, component, name, refvalue);
if (cmd == 'init') scope.setInitValue(entity, component, name, refvalue);
});
} else {
if (cmd == 'const') scope.setConstValue(entity, component, name, valueOrRef as DataValue);
if (cmd == 'init') scope.setInitValue(entity, component, name, valueOrRef as DataValue);
}
} else if (cmd == 'decode') {
let decoderid = this.expectIdent().str;
let code = this.expectTokenTypes([ECSTokenType.CodeFragment]).str;
@ -368,28 +398,23 @@ export class ECSCompiler extends Tokenizer {
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];
});
let { component, field } = this.getEntityField(entity, entry[0]);
scope.setConstValue(entity, component, field.name, entry[1]);
}
}
}
return entity;
}
setEntityProperty(e: Entity, name: string, cmd: 'init' | 'const', valuefn: (field: DataField) => DataValue) {
getEntityField(e: Entity, name: string) {
if (!this.currentScope) { this.internalError(); throw new Error(); }
let comps = this.em.componentsWithFieldName([e.etype], 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);
let component = comps[0];
let field = component.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);
return { component, field };
}
parseEntityArchetype() : EntityArchetype {
@ -406,18 +431,25 @@ export class ECSCompiler extends Tokenizer {
return cref;
}
parseEntityRef(reftype?: RefType) : Entity {
if (!this.currentScope) { this.internalError(); throw new Error(); }
this.expectToken('#');
let name = this.expectIdent().str;
let eref = this.currentScope.entities.find(e => e.name == name);
resolveEntityRef(scope: EntityScope, ref: ForwardRef) : number {
let name = ref.token.str;
let eref = scope.entities.find(e => e.name == name);
if (!eref) {
this.compileError(`I couldn't find an entity named "${name}" in this scope.`)
this.compileError(`I couldn't find an entity named "${name}" in this scope.`, ref.token.$loc)
throw new Error();
}
return eref;
let id = eref.id;
if (ref.reftype) {
// TODO: make this a function? elo ehi etc?
let atypes = this.em.archetypesMatching(ref.reftype.query);
let entities = scope.entitiesMatching(atypes);
if (entities.length == 0)
this.compileError(`This entity doesn't seem to fit the reference type.`, ref.token.$loc);
id -= entities[0].id;
}
return id;
}
parseSystemRef() : System {
let name = this.expectIdent().str;
let sys = this.em.getSystemByName(name);
@ -438,3 +470,31 @@ export class ECSCompiler extends Tokenizer {
return src.toString();
}
}
export class ECSActionCompiler extends Tokenizer {
constructor(
public readonly context: ActionOwner)
{
super();
this.setTokenRules([
{ type: ECSTokenType.Placeholder, regex: /\{\{.*?\}\}/ },
{ type: TokenType.CatchAll, regex: /[^{\n]+\n*/ },
]);
this.errorOnCatchAll = false;
}
parseFile(text: string, path: string) {
this.tokenizeFile(text, path);
let nodes = [];
while (!this.isEOF()) {
let tok = this.consumeToken();
if (tok.type == ECSTokenType.Placeholder) {
let args = tok.str.substring(2, tok.str.length-2).split(/\s+/);
nodes.push(new CodePlaceholderNode(this.context, tok.$loc, args));
} else if (tok.type == TokenType.CatchAll) {
nodes.push(new CodeLiteralNode(this.context, tok.$loc, tok.str));
}
}
return nodes;
}
}

View File

@ -1,66 +1,3 @@
/*
entity scopes contain entities, and are nested
also contain segments (code, bss, rodata)
components and systems are global
component fields are stored in arrays, range of entities, can be bit-packed
some values can be constant, are stored in rodata (or loaded immediate)
optional components? on or off
union components? either X or Y or Z...
systems receive and send events, execute code on entities
systems are generated on a per-scope basis
system queries can only contain entities from self and parent scopes
starting from the 'init' event walk the event tree
include systems that have at least 1 entity in scope (except init?)
when entering scope, entities are initialized (zero or init w/ data)
to change scope, fire event w/ scope name
- how to handle bank-switching?
helps with:
- rapid prototyping w/ reasonable defaults
- deconstructing objects into arrays
- packing/unpacking bitfields
- initializing objects
- building lookup tables
- selecting and iterating objects
- managing events
- managing memory and scope
- converting assets to native formats?
- removing unused data
it's more convenient to have loops be zero-indexed
for page cross, temp storage, etc
should references be zero-indexed to a field, or global?
should we limit # of entities passed to systems? min-max
join thru a reference? load both x and y
code fragments can be parameterized like macros
if two fragments are identical, do a JSR
(do we have to look for labels?)
should events have parameters? e.g. addscore X Y Z
how are Z80 arrays working?
https://forums.nesdev.org/viewtopic.php?f=20&t=14691
https://www.cpcwiki.eu/forum/programming/trying-not-to-use-ix/msg133416/#msg133416
how to select two between two entities with once? like scoreboard
maybe stack-based interpreter?
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? bin packing
system define order, action order, entity order, using order?
what happens when a system must be nested inside another? like display kernels
*/
import { SourceLocated, SourceLocation } from "../workertypes";
import { Bin, Packer } from "./binpack";
@ -130,6 +67,39 @@ export class ActionStats {
callcount: number = 0;
}
export interface ActionOwner {
system: System
scope: EntityScope | null
}
export class ActionNode implements SourceLocated {
constructor(
public readonly owner: ActionOwner,
public readonly $loc: SourceLocation
) { }
}
export class CodeLiteralNode extends ActionNode {
constructor(
owner: ActionOwner,
$loc: SourceLocation,
public readonly text: string
) {
super(owner, $loc);
}
}
export class CodePlaceholderNode extends ActionNode {
constructor(
owner: ActionOwner,
$loc: SourceLocation,
public readonly args: string[]
) {
super(owner, $loc);
}
}
export interface ActionBase extends SourceLocated {
select: SelectType;
event: string;
@ -532,6 +502,10 @@ class ActionCPUState {
yofs: number = 0;
}
class ActionContext {
}
class ActionEval {
em : EntityManager;
dialect : Dialect_CA65;
@ -1272,15 +1246,24 @@ export class EntityManager {
if (!parent) this.topScopes[name] = scope;
return scope;
}
deferComponent(name: string) {
this.components[name] = { name, fields: [] };
}
defineComponent(ctype: ComponentType) {
let existing = this.components[ctype.name];
if (existing) throw new ECSError(`component ${ctype.name} already defined`, existing);
if (existing && existing.fields.length > 0)
throw new ECSError(`component ${ctype.name} already defined`, existing);
for (let field of ctype.fields) {
let list = this.name2cfpairs[field.name];
if (!list) list = this.name2cfpairs[field.name] = [];
list.push({ c: ctype, f: field });
}
return this.components[ctype.name] = ctype;
if (existing) {
existing.fields = ctype.fields;
return existing;
} else {
return this.components[ctype.name] = ctype;
}
}
defineSystem(system: System) {
let existing = this.systems[system.name];

View File

@ -69,6 +69,7 @@ export class Tokenizer {
curlabel: string;
eof: Token;
errorOnCatchAll = false;
deferred: (() => void)[] = [];
constructor() {
this.errors = [];
@ -230,4 +231,9 @@ export class Tokenizer {
this.pushbackToken(sep);
return list;
}
runDeferred() {
while (this.deferred.length) {
this.deferred.shift()();
}
}
}

View File

@ -3,7 +3,7 @@ import { execFileSync, spawnSync } from "child_process";
import { readdirSync, readFileSync, writeFileSync } from "fs";
import { describe } from "mocha";
import { Bin, BoxConstraints, Packer } from "../common/ecs/binpack";
import { ECSCompiler } from "../common/ecs/compiler";
import { ECSActionCompiler, ECSCompiler } from "../common/ecs/compiler";
import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs";
const TEMPLATE1 = `
@ -344,6 +344,7 @@ end
c.exportToFile(src);
// TODO: test?
//console.log(src.toString());
return em;
} catch (e) {
console.log(e);
for (let err of c.errors) {

400
test/ecs/sprites1.ecs Normal file
View File

@ -0,0 +1,400 @@
//#resource "vcs-ca65.h"
import "vcslib.ecs"
component Bitmap
bitmapdata: array of 0..255 baseoffset 31
height: 0..255
end
component HasBitmap
bitmap: [Bitmap]
end
component Colormap
colormapdata: array of 0..255 baseoffset 31
end
component HasColormap
colormap: [Colormap]
end
component Sprite
plyrflags: 0..63
end
component HasXpos
xpos: 0..255
end
component HasYpos
ypos: 0..255
end
component SpriteSlot
sprite: [Sprite,HasBitmap,HasColormap,HasYpos]
end
component Missile
end
system Kernel2Sprite
locals 13
on preframe do with [KernelSection]
---
.define KLINES {{<lines}}
.define KPAD 32
---
on preframe do join
[SpriteSlot] with
[Sprite,HasBitmap,HasColormap,HasYpos] limit 2
---
; set player object flags
lda {{<plyrflags}}
sta NUSIZ0,y
sta REFP0,y
; calculate screen height - ypos
lda KLINES+KPAD
sec
sbc {{<ypos}}
sta {{$11}}
; calculate bitmap pointer
stx {{$12}} ; save X (Sprite index)
lda {{<bitmap}} ; deref bitmap
tax
lda {{<Bitmap:bitmapdata}},x
sec
sbc {{$11}}
sta {{$0}},y ; Y = sprite slot index
lda {{>Bitmap:bitmapdata}},x
sbc #0
sta {{$2}},y
; get bitmap height
lda {{<Bitmap:height}},x
sta {{$8}},y
; calculate colormap pointer
ldx {{$12}} ; restore X
lda {{<colormap}} ; deref colormap
tax
lda {{<Colormap:colormapdata}},x
sec
sbc {{$11}}
sta {{$4}},y
lda {{>Colormap:colormapdata}},x
sbc #0
sta {{$6}},y
; save ypos
ldx {{$12}} ; restore X
lda {{<ypos}}
sta {{$10}},y
---
on preframe do once
---
; 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 preframe do if [BGColor]
---
lda {{<bgcolor}}
sta COLUBK
---
on preframe do if [Missile,HasYpos]
---
lda KLINES
sec
sbc {{<ypos}}
sta {{$12}}
---
on kernel do with [KernelSection]
---
ldy #0
sty VDELP0
iny
sta VDELP1
---
on kernel do with [KernelSection]
---
; define macro for each line
.macro @DrawLine do_wsync
.local DoDraw1
.local DoDraw2
; draw player 0
lda {{$8}} ; height
dcp {{$10}} ; ypos
bcs DoDraw1
lda #0
.byte $2C
DoDraw1:
lda ({{$0}}),y
.if do_wsync
sta WSYNC
.endif
sta GRP0
lda ({{$4}}),y
sta COLUP0
; draw player 1
lda {{$9}} ; height
dcp {{$11}} ; ypos
bcs DoDraw2
lda #0
.byte $2C
DoDraw2:
lda ({{$2}}),y
sta GRP1
lda ({{$6}}),y
sta COLUP1
.endmacro
ldy {{<lines}}
@LVScan:
{{!scanline1}}
@DrawLine 1 ; macro: draw scanline w/ WSYNC
dey ; next scanline
{{!scanline2}}
@DrawLine 0 ; macro: draw scanline no WSYNC
dey ; next scanline
bne @LVScan ; repeat until out of lines
---
on kernel do with [KernelSection]
---
lda #0
sta GRP0
sta GRP1
sta GRP0
sta GRP1
---
on scanline1 do if [Missile,HasYpos]
---
cpy {{$12}}
php
pla
sta ENAM0
---
end
system VersatilePlayfield
locals 2
on preframe do with [VersatilePlayfield]
---
lda {{<data}}
sta {{$0}}
lda {{>data}}
sta {{$1}}
---
on scanline1 do with [VersatilePlayfield]
---
lda ({{local 0}}),y
tax
---
on scanline2 do with [VersatilePlayfield]
---
lda ({{local 0}}),y
sta $00,x
---
end
system SetXPos
on preframe do once
---
sta HMCLR
---
on preframe do join [SpriteSlot] with [HasXpos]
limit 2
---
lda {{<xpos}}
{{!SetHorizPos}}
---
on preframe do if [Missile,HasXpos]
---
lda {{<xpos}}
ldx #2
{{!SetHorizPos}}
---
on preframe do once
---
sta WSYNC
sta HMOVE
---
end
system MoveJoyX
on joyleft do with [HasXpos]
---
lda {{<xpos}}
sec
sbc #1
bcc @nomove
sta {{<xpos}}
@nomove:
---
on joyright do with [HasXpos]
---
lda {{<xpos}}
clc
adc #1
cmp #152
bcs @nomove
sta {{<xpos}}
@nomove:
---
end
system MoveJoyY
on joyup do with [HasYpos]
---
lda {{<ypos}}
sec
sbc #1
bcc @nomove
sta {{<ypos}}
@nomove:
---
on joydown do with [HasYpos]
---
lda {{<ypos}}
clc
adc #1
cmp #220
bcs @nomove
sta {{<ypos}}
@nomove:
---
end
system SpriteShuffler
locals 2
on postframe do select [SpriteSlot]
---
; load two sprite slots at left side of array
lda {{<SpriteSlot:sprite}}
sta {{$0}}
lda {{<SpriteSlot:sprite}}+1
sta {{$1}}
; move two slots to the left
ldx #0
@loop:
lda {{<SpriteSlot:sprite}}+2,x
sta {{<SpriteSlot:sprite}},x
inx
cpx #{{%ecount}}-2
bne @loop
; store two sprite slots at right side of array
lda {{$0}}
sta {{<SpriteSlot:sprite}}+{{%ecount}}-2
lda {{$1}}
sta {{<SpriteSlot:sprite}}+{{%ecount}}-1
---
end
system SpriteHider
locals 1
on postframe do select [SpriteSlot]
---
lda #{{%efullcount}}-1
sta {{$0}}
---
on postframe do
join [SpriteSlot]
with [Sprite,HasYpos]
limit 2
---
lda {{<ypos}}
cmp #192
bcc @skip
; swap this sprite slot with slot at end of array
lda {{<SpriteSlot:sprite}},y
pha
ldx {{$0}} ; clobbers X, but no longer used
lda {{<SpriteSlot:sprite}},x
sta {{<SpriteSlot:sprite}},y
pla
sta {{<SpriteSlot:sprite}},x
dec {{$0}}
@skip:
---
end
///
demo Main
using FrameLoop, Kernel2Sprite
using Joystick, MoveJoyX, MoveJoyY
using SetXPos, SetHorizPos
using SpriteShuffler, SpriteHider
entity Kernel [KernelSection, BGColor]
const lines = 192
const bgcolor = 0xa2
end
entity Bitmap1 [Bitmap]
const bitmapdata = [1, 1, 3, 7, 15, 31, 63, 127]
const height = 8
end
entity Bitmap2 [Bitmap]
const bitmapdata = [$18,$3e,$ff,$ff,$ff,$ff,$3e,$18]
const height = 8
end
entity Colormap1 [Colormap]
const colormapdata = [6, 3, 6, 9, 12, 14, 31, 63]
end
entity Sprite0 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
init xpos = 50
init ypos = 150
init bitmap = #Bitmap2
const plyrflags = 0
end
entity Sprite1 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
init xpos = 100
init ypos = 60
init bitmap = #Bitmap1
const plyrflags = 3
end
entity Sprite2 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
init xpos = 80
init ypos = 90
init bitmap = #Bitmap2
const plyrflags = 0
end
entity Sprite3 [Sprite,HasBitmap,HasColormap,HasXpos,HasYpos,Player]
init xpos = 40
init ypos = 150
init bitmap = #Bitmap1
const plyrflags = 0
end
/*
entity [Missile,HasXpos,HasYpos]
init xpos = 70
init ypos = 70
end
*/
entity Slot0 [SpriteSlot]
init sprite = #Sprite0
end
entity Slot1 [SpriteSlot]
init sprite = #Sprite1
end
entity Slot2 [SpriteSlot]
init sprite = #Sprite2
end
entity Slot3 [SpriteSlot]
init sprite = #Sprite3
end
end

515
test/ecs/sprites1.txt Normal file
View File

@ -0,0 +1,515 @@
.scope Main
.zeropage
SpriteSlot_sprite_b0:
.res 1
.res 1
.res 1
.res 1
HasYpos_ypos_b0:
.res 1
.res 1
.res 1
.res 1
HasXpos_xpos_b0:
.res 1
.res 1
.res 1
.res 1
HasColormap_colormap_b0:
.res 1
.res 1
.res 1
.res 1
HasBitmap_bitmap_b0:
.res 1
.res 1
.res 1
.res 1
TEMP:
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
.res 1
Kernel2Sprite__tmp = TEMP+0
Joystick__tmp = TEMP+0
SpriteShuffler__tmp = TEMP+1
SpriteHider__tmp = TEMP+3
.code
Bitmap_bitmapdata_e1_b0:
.byte 1
.byte 1
.byte 3
.byte 7
.byte 15
.byte 31
.byte 63
.byte 127
Bitmap_bitmapdata_b0:
.byte <(Bitmap_bitmapdata_e1_b0+31)
.byte <(Bitmap_bitmapdata_e2_b0+31)
Bitmap_bitmapdata_b8:
.byte >(Bitmap_bitmapdata_e1_b0+31)
.byte >(Bitmap_bitmapdata_e2_b0+31)
Bitmap_height_b0:
.byte 8
.byte 8
Bitmap_bitmapdata_e2_b0:
.byte 24
.byte 62
.byte 255
.byte 255
.byte 255
.byte 255
.byte 62
.byte 24
Colormap_colormapdata_e3_b0:
.byte 6
.byte 3
.byte 6
.byte 9
.byte 12
.byte 14
.byte 31
.byte 63
Colormap_colormapdata_b0:
.byte <(Colormap_colormapdata_e3_b0+31)
Colormap_colormapdata_b8:
.byte >(Colormap_colormapdata_e3_b0+31)
Sprite_plyrflags_b0:
.byte 0
.byte 3
.byte 0
.byte 0
Main__INITDATA:
.byte 0
.byte 1
.byte 2
.byte 3
.byte 150
.byte 60
.byte 90
.byte 150
.byte 50
.byte 100
.byte 80
.byte 40
.byte 0
.byte 0
.byte 0
.byte 0
.byte 1
.byte 0
.byte 1
.byte 0
__Start:
.code
;;; start action Init main_init
.include "vcs-ca65.h"
.macpack longbranch
.define PAL 0
__NMI:
__Reset:
__BRK:
CLEAN_START
ldy #20
: lda Main__INITDATA-1,y
sta SpriteSlot_sprite_b0-1,y
dey
bne :-
; initialize data segment
.code
;;; start action FrameLoop start
FrameLoop__start__2__NextFrame:
FRAME_START
.code
;;; start action Kernel2Sprite preframe
.define KLINES #192
.define KPAD 32
;;; end action Kernel2Sprite preframe
;;; start action Kernel2Sprite preframe
ldy #0
Kernel2Sprite__preframe__4____each:
ldx SpriteSlot_sprite_b0,y
; set player object flags
lda Sprite_plyrflags_b0,x
sta NUSIZ0,y
sta REFP0,y
; calculate screen height - ypos
lda KLINES+KPAD
sec
sbc HasYpos_ypos_b0,x
sta Kernel2Sprite__tmp+11
; calculate bitmap pointer
stx Kernel2Sprite__tmp+12 ; save X (Sprite index)
lda HasBitmap_bitmap_b0,x ; deref bitmap
tax
lda Bitmap_bitmapdata_b0,x
sec
sbc Kernel2Sprite__tmp+11
sta Kernel2Sprite__tmp+0,y ; Y = sprite slot index
lda Bitmap_bitmapdata_b8,x
sbc #0
sta Kernel2Sprite__tmp+2,y
; get bitmap height
lda Bitmap_height_b0,x
sta Kernel2Sprite__tmp+8,y
; calculate colormap pointer
ldx Kernel2Sprite__tmp+12 ; restore X
lda HasColormap_colormap_b0,x ; deref colormap
tax
lda Colormap_colormapdata_b0,x
sec
sbc Kernel2Sprite__tmp+11
sta Kernel2Sprite__tmp+4,y
lda Colormap_colormapdata_b8,x
sbc #0
sta Kernel2Sprite__tmp+6,y
; save ypos
ldx Kernel2Sprite__tmp+12 ; restore X
lda HasYpos_ypos_b0,x
sta Kernel2Sprite__tmp+10,y
iny
cpy #2
jne Kernel2Sprite__preframe__4____each
Kernel2Sprite__preframe__4____exit:
;;; end action Kernel2Sprite preframe
;;; start action Kernel2Sprite preframe
; L0 L1 H0 H1 -> L0 H0 L1 H1
lda Kernel2Sprite__tmp+1
ldy Kernel2Sprite__tmp+2
sty Kernel2Sprite__tmp+1
sta Kernel2Sprite__tmp+2
lda Kernel2Sprite__tmp+5
ldy Kernel2Sprite__tmp+6
sty Kernel2Sprite__tmp+5
sta Kernel2Sprite__tmp+6
;;; end action Kernel2Sprite preframe
;;; start action Kernel2Sprite preframe
lda #162
sta COLUBK
;;; end action Kernel2Sprite preframe
;;; start action Kernel2Sprite preframe
;;; end action Kernel2Sprite preframe
;;; start action SetXPos preframe
sta HMCLR
;;; end action SetXPos preframe
;;; start action SetXPos preframe
ldy #0
SetXPos__preframe__8____each:
ldx SpriteSlot_sprite_b0,y
lda HasXpos_xpos_b0,x
.code
;;; start action SetHorizPos SetHorizPos
; SetHorizPos routine
; A = X coordinate
; Y = player number (0 or 1)
sta WSYNC ; start a new line
sec ; set carry flag
nop
SetHorizPos__SetHorizPos__9__DivideLoop:
sbc #15 ; subtract 15
bcs SetHorizPos__SetHorizPos__9__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
;;; end action SetHorizPos SetHorizPos
iny
cpy #2
jne SetXPos__preframe__8____each
SetXPos__preframe__8____exit:
;;; end action SetXPos preframe
;;; start action SetXPos preframe
;;; end action SetXPos preframe
;;; start action SetXPos preframe
sta WSYNC
sta HMOVE
;;; end action SetXPos preframe
KERNEL_START
.code
;;; start action Kernel2Sprite kernel
ldy #0
sty VDELP0
iny
sta VDELP1
;;; end action Kernel2Sprite kernel
;;; start action Kernel2Sprite kernel
; define macro for each line
.macro Kernel2Sprite__kernel__12__DrawLine do_wsync
.local DoDraw1
.local DoDraw2
; draw player 0
lda Kernel2Sprite__tmp+8 ; height
dcp Kernel2Sprite__tmp+10 ; ypos
bcs DoDraw1
lda #0
.byte $2C
DoDraw1:
lda (Kernel2Sprite__tmp+0),y
.if do_wsync
sta WSYNC
.endif
sta GRP0
lda (Kernel2Sprite__tmp+4),y
sta COLUP0
; draw player 1
lda Kernel2Sprite__tmp+9 ; height
dcp Kernel2Sprite__tmp+11 ; ypos
bcs DoDraw2
lda #0
.byte $2C
DoDraw2:
lda (Kernel2Sprite__tmp+2),y
sta GRP1
lda (Kernel2Sprite__tmp+6),y
sta COLUP1
.endmacro
ldy #192
Kernel2Sprite__kernel__12__LVScan:
.code
;;; start action Kernel2Sprite scanline1
;;; end action Kernel2Sprite scanline1
Kernel2Sprite__kernel__12__DrawLine 1 ; macro: draw scanline w/ WSYNC
dey ; next scanline
.code
Kernel2Sprite__kernel__12__DrawLine 0 ; macro: draw scanline no WSYNC
dey ; next scanline
bne Kernel2Sprite__kernel__12__LVScan ; repeat until out of lines
;;; end action Kernel2Sprite kernel
;;; start action Kernel2Sprite kernel
lda #0
sta GRP0
sta GRP1
sta GRP0
sta GRP1
;;; end action Kernel2Sprite kernel
KERNEL_END
.code
;;; start action Joystick postframe
; 2 control inputs share a single byte, 4 bits each
lda SWCHA
sta Joystick__tmp+0
;;; end action Joystick postframe
;;; start action Joystick postframe
ldx #0
Joystick__postframe__15____each:
asl Joystick__tmp+0
bcs Joystick__postframe__15__SkipMoveRight
.code
;;; start action MoveJoyX joyright
lda HasXpos_xpos_b0,x
clc
adc #1
cmp #152
bcs MoveJoyX__joyright__16__nomove
sta HasXpos_xpos_b0,x
MoveJoyX__joyright__16__nomove:
;;; end action MoveJoyX joyright
Joystick__postframe__15__SkipMoveRight:
asl Joystick__tmp+0
bcs Joystick__postframe__15__SkipMoveLeft
.code
;;; start action MoveJoyX joyleft
lda HasXpos_xpos_b0,x
sec
sbc #1
bcc MoveJoyX__joyleft__17__nomove
sta HasXpos_xpos_b0,x
MoveJoyX__joyleft__17__nomove:
;;; end action MoveJoyX joyleft
Joystick__postframe__15__SkipMoveLeft:
asl Joystick__tmp+0
bcs Joystick__postframe__15__SkipMoveDown
.code
;;; start action MoveJoyY joydown
lda HasYpos_ypos_b0,x
clc
adc #1
cmp #220
bcs MoveJoyY__joydown__18__nomove
sta HasYpos_ypos_b0,x
MoveJoyY__joydown__18__nomove:
;;; end action MoveJoyY joydown
Joystick__postframe__15__SkipMoveDown:
asl Joystick__tmp+0
bcs Joystick__postframe__15__SkipMoveUp
.code
;;; start action MoveJoyY joyup
lda HasYpos_ypos_b0,x
sec
sbc #1
bcc MoveJoyY__joyup__19__nomove
sta HasYpos_ypos_b0,x
MoveJoyY__joyup__19__nomove:
;;; end action MoveJoyY joyup
Joystick__postframe__15__SkipMoveUp:
inx
cpx #4
jne Joystick__postframe__15____each
Joystick__postframe__15____exit:
;;; end action Joystick postframe
;;; start action SpriteShuffler postframe
; load two sprite slots at left side of array
lda SpriteSlot_sprite_b0
sta SpriteShuffler__tmp+0
lda SpriteSlot_sprite_b0+1
sta SpriteShuffler__tmp+1
; move two slots to the left
ldx #0
SpriteShuffler__postframe__20__loop:
lda SpriteSlot_sprite_b0+2,x
sta SpriteSlot_sprite_b0,x
inx
cpx #4-2
bne SpriteShuffler__postframe__20__loop
; store two sprite slots at right side of array
lda SpriteShuffler__tmp+0
sta SpriteSlot_sprite_b0+4-2
lda SpriteShuffler__tmp+1
sta SpriteSlot_sprite_b0+4-1
;;; end action SpriteShuffler postframe
;;; start action SpriteHider postframe
lda #4-1
sta SpriteHider__tmp+0
;;; end action SpriteHider postframe
;;; start action SpriteHider postframe
ldy #0
SpriteHider__postframe__22____each:
ldx SpriteSlot_sprite_b0,y
lda HasYpos_ypos_b0,x
cmp #192
bcc SpriteHider__postframe__22__skip
; swap this sprite slot with slot at end of array
lda SpriteSlot_sprite_b0,y
pha
ldx SpriteHider__tmp+0 ; clobbers X, but no longer used
lda SpriteSlot_sprite_b0,x
sta SpriteSlot_sprite_b0,y
pla
sta SpriteSlot_sprite_b0,x
dec SpriteHider__tmp+0
SpriteHider__postframe__22__skip:
iny
cpy #2
jne SpriteHider__postframe__22____each
SpriteHider__postframe__22____exit:
;;; end action SpriteHider postframe
FRAME_END
.code
jmp FrameLoop__start__2__NextFrame ; loop to next frame
;;; end action FrameLoop start
; start main routine
.segment "VECTORS"
Return: .word $6060
VecNMI:
VecReset: .word Main::__Reset
VecBRK: .word Main::__BRK
;;; end action Init main_init
.endscope
Main__Start = Main::__Start

240
test/ecs/vcslib.ecs Normal file
View File

@ -0,0 +1,240 @@
//#resource "vcs-ca65.h"
system Init
on main_init do once
---
.include "vcs-ca65.h"
.macpack longbranch
.define PAL 0
__NMI:
__Reset:
__BRK:
CLEAN_START
{{bss_init}} ; initialize data segment
{{!start}} ; start main routine
.segment "VECTORS"
Return: .word $6060
VecNMI:
VecReset: .word Main::__Reset
VecBRK: .word Main::__BRK
---
end
component Player
end
component KernelSection
lines: 1..255
end
component BGColor
bgcolor: 0..255
end
component PFColor
pfcolor: 0..255
end
component Playfield
pf: 0..0xffffff
end
component AsymPlayfield
pfleft: 0..0xffffff
pfright: 0..0xffffff
end
component VersatilePlayfield
data: array of 0..255 baseoffset -1
end
system FrameLoop
on start do once
---
@NextFrame:
FRAME_START
{{emit preframe}}
KERNEL_START
{{emit kernel}}
KERNEL_END
{{emit postframe}}
FRAME_END
{{emit nextframe}}
jmp @NextFrame ; loop to next frame
---
end
system ResetSwitch
on nextframe do once
---
lsr SWCHB ; test Game Reset switch
bcs @NoStart
{{!resetswitch}}
@NoStart:
---
end
system ResetConsole
on resetswitch do once
---
jmp Main::__Reset ; jump to Reset handler
---
end
system JoyButton
on postframe do foreach [Player]
---
lda {{index INPT4}} ;read button input
bmi @NotPressed
{{emit joybutton}}
@NotPressed:
---
end
system Joystick
locals 1
on postframe do once
---
; 2 control inputs share a single byte, 4 bits each
lda SWCHA
sta {{$0}}
---
on postframe do foreach [Player]
---
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 SetHorizPos
on SetHorizPos do once
---
; SetHorizPos routine
; A = X coordinate
; Y = player number (0 or 1)
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
---
end
system StaticKernel
on preframe do foreach [KernelSection] limit 1
---
{{!kernelsetup}}
---
on kernel do foreach [KernelSection]
---
sta WSYNC
{{!kernelsetup}}
{{!kerneldraw}}
{{!kerneldone}}
---
on kerneldraw do with [KernelSection]
---
ldy {{<lines}}
@loop:
sta WSYNC
{{!scanline}}
dey
bne @loop
---
on kernelsetup do if [BGColor]
---
lda {{<bgcolor}}
sta COLUBK
---
on kernelsetup do if [PFColor]
---
lda {{get pfcolor}}
sta COLUPF
---
on kernelsetup do if [Playfield]
---
lda {{get pf 0}}
sta PF0
lda {{get pf 8}}
sta PF1
lda {{get pf 16}}
sta PF2
---
end
///
demo Main
using FrameLoop, ResetSwitch, ResetConsole
using StaticKernel, JoyButton
entity [Player]
end
entity [KernelSection,BGColor]
const lines = 2
const bgcolor = $18
end
entity [KernelSection,BGColor]
const lines = 2
const bgcolor = $16
end
entity [KernelSection,BGColor]
const lines = 2
const bgcolor = $14
end
entity [KernelSection,BGColor]
const lines = 2
const bgcolor = $12
end
entity [KernelSection,BGColor,Playfield]
const lines = 10
const bgcolor = $14
const pf = 0x125244
end
entity Trees [KernelSection,BGColor,PFColor,Playfield]
const lines = 50
const bgcolor = $14
const pf = 0x112244
end
entity [KernelSection,BGColor,PFColor,Playfield]
const lines = 50
const bgcolor = $16
const pf = 0x124
end
entity [KernelSection,BGColor,Playfield]
const lines = 10
const bgcolor = $18
const pf = 0
end
system Local
locals 1
on joybutton do foreach [PFColor] limit 1 ---
inc {{$0}}
inc {{set Trees.pfcolor}}
---
end
end

250
test/ecs/vcslib.txt Normal file
View File

@ -0,0 +1,250 @@
.scope Main
.zeropage
PFColor_pfcolor_b0:
.res 1
.res 1
TEMP:
.res 1
Local__tmp = TEMP+0
.code
KernelSection_lines_b0:
.byte 2
.byte 2
.byte 2
.byte 2
.byte 10
.byte 50
.byte 50
.byte 10
BGColor_bgcolor_b0:
.byte 24
.byte 22
.byte 20
.byte 18
.byte 20
.byte 20
.byte 22
.byte 24
Playfield_pf_b0:
.byte 68
.byte 68
.byte 36
.byte 0
Playfield_pf_b8:
.byte 82
.byte 34
.byte 1
.byte 0
Playfield_pf_b16:
.byte 18
.byte 17
.byte 0
.byte 0
Main__INITDATA:
.byte 0
.byte 0
__Start:
.code
;;; start action Init main_init
.include "vcs-ca65.h"
.macpack longbranch
.define PAL 0
__NMI:
__Reset:
__BRK:
CLEAN_START
ldy #2
: lda Main__INITDATA-1,y
sta PFColor_pfcolor_b0-1,y
dey
bne :-
; initialize data segment
.code
;;; start action FrameLoop start
FrameLoop__start__2__NextFrame:
FRAME_START
.code
;;; start action StaticKernel preframe
.code
;;; start action StaticKernel kernelsetup
lda #24
sta COLUBK
;;; end action StaticKernel kernelsetup
;;; start action StaticKernel kernelsetup
cpx #0+2
jcs StaticKernel__kernelsetup__5____skipxhi
cpx #0
jcc StaticKernel__kernelsetup__5____skipxlo
lda PFColor_pfcolor_b0,x
sta COLUPF
StaticKernel__kernelsetup__5____skipxlo:
StaticKernel__kernelsetup__5____skipxhi:
;;; end action StaticKernel kernelsetup
;;; start action StaticKernel kernelsetup
cpx #0+4
jcs StaticKernel__kernelsetup__6____skipxhi
cpx #0
jcc StaticKernel__kernelsetup__6____skipxlo
lda Playfield_pf_b0,x
sta PF0
lda Playfield_pf_b8,x
sta PF1
lda Playfield_pf_b16,x
sta PF2
StaticKernel__kernelsetup__6____skipxlo:
StaticKernel__kernelsetup__6____skipxhi:
;;; end action StaticKernel kernelsetup
;;; end action StaticKernel preframe
KERNEL_START
.code
;;; start action StaticKernel kernel
ldx #0
StaticKernel__kernel__7____each:
sta WSYNC
.code
;;; start action StaticKernel kernelsetup
lda BGColor_bgcolor_b0,x
sta COLUBK
;;; end action StaticKernel kernelsetup
;;; start action StaticKernel kernelsetup
cpx #5+2
jcs StaticKernel__kernelsetup__9____skipxhi
cpx #5
jcc StaticKernel__kernelsetup__9____skipxlo
lda PFColor_pfcolor_b0-5,x
sta COLUPF
StaticKernel__kernelsetup__9____skipxlo:
StaticKernel__kernelsetup__9____skipxhi:
;;; end action StaticKernel kernelsetup
;;; start action StaticKernel kernelsetup
cpx #4
jcc StaticKernel__kernelsetup__10____skipxlo
lda Playfield_pf_b0-4,x
sta PF0
lda Playfield_pf_b8-4,x
sta PF1
lda Playfield_pf_b16-4,x
sta PF2
StaticKernel__kernelsetup__10____skipxlo:
;;; end action StaticKernel kernelsetup
.code
;;; start action StaticKernel kerneldraw
ldy KernelSection_lines_b0,x
StaticKernel__kerneldraw__11__loop:
sta WSYNC
dey
bne StaticKernel__kerneldraw__11__loop
;;; end action StaticKernel kerneldraw
inx
cpx #8
jne StaticKernel__kernel__7____each
StaticKernel__kernel__7____exit:
;;; end action StaticKernel kernel
KERNEL_END
.code
;;; start action JoyButton postframe
lda INPT4 ;read button input
bmi JoyButton__postframe__12__NotPressed
.code
;;; start action Local joybutton
inc Local__tmp+0
inc PFColor_pfcolor_b0
;;; end action Local joybutton
JoyButton__postframe__12__NotPressed:
;;; end action JoyButton postframe
FRAME_END
.code
;;; start action ResetSwitch nextframe
lsr SWCHB ; test Game Reset switch
bcs ResetSwitch__nextframe__14__NoStart
.code
;;; start action ResetConsole resetswitch
jmp Main::__Reset ; jump to Reset handler
;;; end action ResetConsole resetswitch
ResetSwitch__nextframe__14__NoStart:
;;; end action ResetSwitch nextframe
jmp FrameLoop__start__2__NextFrame ; loop to next frame
;;; end action FrameLoop start
; start main routine
.segment "VECTORS"
Return: .word $6060
VecNMI:
VecReset: .word Main::__Reset
VecBRK: .word Main::__BRK
;;; end action Init main_init
.endscope
Main__Start = Main::__Start