diff --git a/README.md b/README.md index 86389797..ef77a928 100644 --- a/README.md +++ b/README.md @@ -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 + diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index 5089dac8..c600224f 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -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; + } +} diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index ca1a4ee5..bb53cd83 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -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]; diff --git a/src/common/tokenizer.ts b/src/common/tokenizer.ts index 1b7c3fc9..502cee15 100644 --- a/src/common/tokenizer.ts +++ b/src/common/tokenizer.ts @@ -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()(); + } + } } diff --git a/src/test/testecs.ts b/src/test/testecs.ts index 35649752..a8dbc712 100644 --- a/src/test/testecs.ts +++ b/src/test/testecs.ts @@ -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) { diff --git a/test/ecs/sprites1.ecs b/test/ecs/sprites1.ecs new file mode 100644 index 00000000..382bd3cf --- /dev/null +++ b/test/ecs/sprites1.ecs @@ -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 {{Bitmap:bitmapdata}},x + sbc #0 + sta {{$2}},y +; get bitmap height + lda {{Colormap:colormapdata}},x + sbc #0 + sta {{$6}},y +; save ypos + ldx {{$12}} ; restore X + lda {{ 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 {{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 {{(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 \ No newline at end of file diff --git a/test/ecs/vcslib.ecs b/test/ecs/vcslib.ecs new file mode 100644 index 00000000..ac74cf00 --- /dev/null +++ b/test/ecs/vcslib.ecs @@ -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 {{