codemirror ecs mode

This commit is contained in:
Steven Hugg 2022-01-29 18:21:38 -06:00
parent ce21eb29e5
commit 69cd28aa0e
8 changed files with 135 additions and 392 deletions

View File

@ -1,279 +0,0 @@
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}},y
sec
sbc {{$11}}
sta {{$0}},x
lda {{>Bitmap.bitmapdata}},y
sbc #0
sta {{$2}},x
ldy {{<colormap}}
lda {{<Colormap.colormapdata}},y
sec
sbc {{$11}}
sta {{$4}},x
lda {{>Colormap.colormapdata}},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 = #Bitmap2
end
end

View File

@ -540,6 +540,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<script src="src/codemirror/basic.js"></script>
<script src="src/codemirror/wiz.js"></script>
<script src="src/codemirror/vasm.js"></script>
<script src="src/codemirror/ecs.js"></script>
<link rel="stylesheet" href="css/codemirror.css">
<script src="codemirror/addon/edit/matchbrackets.js"></script>
<script src="codemirror/addon/search/search.js"></script>

103
src/codemirror/ecs.js Normal file
View File

@ -0,0 +1,103 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function (mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function (CodeMirror) {
"use strict";
// 6502 DASM syntax
CodeMirror.defineMode('ecs', function (_config, parserConfig) {
var keywords1, keywords2;
var directives_list = [
'component', 'system', 'entity', 'scope', 'end',
'const', 'init', 'locals',
'on', 'do', 'emit',
'once', 'foreach', 'source',
];
var keywords_list = [
'processor',
'byte', 'word', 'long',
'include', 'seg', 'dc', 'ds', 'dv', 'hex', 'err', 'org', 'rorg', 'echo', 'rend',
'align', 'subroutine', 'equ', 'eqm', 'set', 'mac', 'endm', 'mexit', 'ifconst',
'ifnconst', 'if', 'else', 'endif', 'eif', 'repeat', 'repend'
];
var directives = new Map();
directives_list.forEach(function (s) { directives.set(s, 'def'); });
keywords_list.forEach(function (s) { directives.set(s, 'keyword'); });
var opcodes = /^[a-z][a-z][a-z]\b/i;
var numbers = /^(0x[\da-f]+|[\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i;
var tags = /^\{\{.*\}\}/;
var mlcomment = /^---.+?---\b/i;
return {
startState: function () {
return {
context: 0
};
},
token: function (stream, state) {
if (stream.eatSpace())
return null;
if (stream.match(tags)) {
return 'meta';
}
var w;
if (stream.eatWhile(/\w/)) {
w = stream.current();
var cur = w.toLowerCase();
var style = directives.get(cur);
if (style)
return style;
if (opcodes.test(w)) {
return 'keyword';
} else if (numbers.test(w)) {
return 'number';
} else if (w == 'comment') {
stream.match(mlcomment);
return 'comment';
}
} else if (stream.eat(';')) {
stream.skipToEnd();
return 'comment';
} else if (stream.eat('"')) {
while (w = stream.next()) {
if (w == '"')
break;
if (w == '\\')
stream.next();
}
return 'string';
} else if (stream.eat('\'')) {
if (stream.match(/\\?.'/))
return 'number';
} else if (stream.eat('$') || stream.eat('#')) {
if (stream.eatWhile(/[^;]/i))
return 'number';
} else if (stream.eat('%')) {
if (stream.eatWhile(/[01]/))
return 'number';
} else {
stream.next();
}
return null;
}
};
});
CodeMirror.defineMIME("text/x-ecs", "ecs");
});

View File

@ -152,7 +152,7 @@ export class ECSCompiler extends Tokenizer {
parseAction(): Action {
let event = this.expectIdent().str;
this.expectToken('do');
let select = this.expectTokens(['once', 'each', 'source']).str as SelectType; // TODO: type check?
let select = this.expectTokens(['once', 'foreach', 'source']).str as SelectType; // TODO: type check?
let query = this.parseQuery();
let emits;
if (this.peekToken().str == 'emit') {
@ -183,6 +183,7 @@ export class ECSCompiler extends Tokenizer {
}
parseCode(): string {
// TODO: add $loc
let tok = this.expectTokenTypes([TokenType.CodeFragment]);
let code = tok.str;
let lines = code.split('\n');

View File

@ -97,7 +97,7 @@ export interface Action {
emits?: string[];
}
export type SelectType = 'once' | 'each' | 'source';
export type SelectType = 'once' | 'foreach' | 'source';
export type DataValue = number | boolean | Uint8Array | Uint16Array;
@ -154,11 +154,11 @@ interface ComponentFieldPair {
export class Dialect_CA65 {
readonly ASM_ITERATE_EACH = `
ldx #0
{{@__each}}:
@__each:
{{code}}
inx
cpx #{{ecount}}
bne {{@__each}}
bne @__each
`;
readonly INIT_FROM_ARRAY = `
ldy #{{nbytes}}
@ -254,13 +254,15 @@ class Segment {
this.codefrags.push(code);
}
allocateBytes(name: string, bytes: number) {
if (this.symbols[name]) return this.symbols[name]; // TODO: check size
let ofs = this.size;
this.symbols[name] = ofs;
if (!this.ofs2sym.has(ofs))
this.ofs2sym.set(ofs, []);
this.ofs2sym.get(ofs)?.push(name);
this.size += bytes;
let ofs = this.symbols[name];
if (ofs == null) {
ofs = this.size;
this.symbols[name] = ofs;
if (!this.ofs2sym.has(ofs))
this.ofs2sym.set(ofs, []);
this.ofs2sym.get(ofs)?.push(name);
this.size += bytes;
}
return ofs;
}
// TODO: optimize shared data
@ -388,7 +390,7 @@ export class EntityScope {
// variable size? make it a pointer
if (bits == 0) bits = 16; // TODO?
let rangelen = (f.ehi - f.elo + 1);
let bytesperelem = Math.ceil(bits / 8) * rangelen;
let bytesperelem = Math.ceil(bits / 8);
// TODO: packing bits
// TODO: split arrays
f.access = [];
@ -419,7 +421,7 @@ export class EntityScope {
let hiofs = segment.allocateBytes(ptrhisym, entcount);
segment.initdata[loofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 0 };
segment.initdata[hiofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 8 };
// TODO: } else if (v instanceof Uint16Array) {
// TODO: } else if (v instanceof Uint16Array) {
} else if (typeof v === 'number') {
// more than 1 entity, add an array
// TODO: what if > 8 bits?
@ -428,6 +430,7 @@ export class EntityScope {
let datasym = this.dialect.fieldsymbol(c, f, 0);
let base = segment.allocateBytes(datasym, entcount);
segment.initdata[base + e.id - fieldrange.elo] = v;
//console.error(cfname, datasym, base, e.id, fieldrange.elo, entcount, v);
}
} else {
throw new ECSError(`unhandled constant ${e.id}:${cfname}`);
@ -524,17 +527,20 @@ export class EntityScope {
}
replaceCode(code: string, sys: System, action: Action): string {
const re = /\{\{(.+?)\}\}/g;
let label = sys.name + '_' + action.event;
let label = `${sys.name}__${action.event}`;
let atypes = this.em.archetypesMatching(action.query);
let entities = this.entitiesMatching(atypes);
// TODO: detect cycles
// TODO: "source"?
// TODO: what if only 1 item?
if (action.select == 'each') {
if (action.select == 'foreach') {
code = this.wrapCodeInLoop(code, sys, action, entities);
//console.log(sys.name, action.event, ents);
//frag = this.iterateCode(frag);
}
// replace @labels
code = code.replace(/@(\w+)\b/g, (s: string, a: string) => `${label}__${a}`);
// replace {{...}} tags
return code.replace(re, (entire, group: string) => {
let cmd = group.charAt(0);
let rest = group.substring(1);

View File

@ -114,7 +114,8 @@ const TOOL_TO_SOURCE_STYLE = {
'silice': 'verilog',
'wiz': 'text/x-wiz',
'vasmarm': 'vasm',
'armips': 'vasm'
'armips': 'vasm',
'ecs': 'ecs',
}
const TOOL_TO_HELPURL = {

View File

@ -36,6 +36,7 @@ const MODEDEFS = {
markdown: { lineWrap: true },
fastbasic: { noGutters: true },
basic: { noLineNumbers: true, noGutters: true }, // TODO: not used?
ecs: { theme: 'mbo', isAsm: false },
}
export var textMapFunctions = {

View File

@ -3,7 +3,7 @@ import { ECSCompiler } from "../common/ecs/compiler";
import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs";
const TEMPLATE1 = `
{{@NextFrame}}:
@NextFrame:
FRAME_START
{{!preframe}}
KERNEL_START
@ -15,74 +15,12 @@ const TEMPLATE1 = `
bcs @NoStart
jmp Start
@NoStart:
jmp {{@NextFrame}}
jmp @NextFrame
`;
// TODO: two sticks?
const TEMPLATE2_a = `
lda SWCHA
sta {{$0}}
`
const TEMPLATE2_b = `
asl {{$0}}
bcs {{@SkipMoveRight}}
{{!joyright}}
{{@SkipMoveRight}}:
asl {{$0}}
bcs {{@SkipMoveLeft}}
{{!joyleft}}
{{@SkipMoveLeft}}:
asl {{$0}}
bcs {{@SkipMoveDown}}
{{!joydown}}
{{@SkipMoveDown}}:
asl {{$0}}
bcs {{@SkipMoveUp}}
{{!joyup}}
{{@SkipMoveUp}}:
`;
const TEMPLATE3_L = `
lda {{<xpos}}
sec
sbc #1
bcc {{@nomove}}
sta {{<xpos}}
{{@nomove}}:
`;
const TEMPLATE3_R = `
lda {{<xpos}}
clc
adc #1
cmp #150
bcs {{@nomove}}
sta {{<xpos}}
{{@nomove}}:
`;
const TEMPLATE3_U = `
lda {{<ypos}}
sec
sbc #1
bcc {{@nomove}}
sta {{<ypos}}
{{@nomove}}:
`;
const TEMPLATE3_D = `
lda {{<ypos}}
clc
adc #1
cmp #150
bcs {{@nomove}}
sta {{<ypos}}
{{@nomove}}:
`;
const TEMPLATE4_S1 = `
.macro {{@KernelSetup}} ent,ofs
.macro @KernelSetup ent,ofs
lda #192 ; TODO: numlines
sec
sbc ypos_ypos_b0+ent
@ -113,8 +51,8 @@ const TEMPLATE4_S1 = `
.endmacro
`
const TEMPLATE4_S2 = `
{{@KernelSetup}} 0,0
{{@KernelSetup}} 1,6
@KernelSetup 0,0
@KernelSetup 1,6
`
// https://atariage.com/forums/topic/75982-skipdraw-and-graphics/?tab=comments#comment-928232
@ -261,12 +199,6 @@ function testECS() {
{ name: 'ypos', dtype: 'int', lo: 0, hi: 255 }
]
})
let c_xyvel = em.defineComponent({
name: 'xyvel', fields: [
{ name: 'xvel', dtype: 'int', lo: -8, hi: 7 },
{ name: 'yvel', dtype: 'int', lo: -8, hi: 7 }
]
})
// init -> [start] -> frameloop
// frameloop -> [preframe] [kernel] [postframe]
@ -299,7 +231,7 @@ function testECS() {
name: 'set_xpos',
actions: [
{
text: SET_XPOS, event: 'preframe', select: 'each', query: {
text: SET_XPOS, event: 'preframe', select: 'foreach', query: {
include: ['sprite', 'xpos']
},
},
@ -314,29 +246,6 @@ function testECS() {
emits: ['preframe', 'kernel', 'postframe'] }
]
})
em.defineSystem({
name: 'joyread',
tempbytes: 1,
actions: [
{ text: TEMPLATE2_a, event: 'postframe', select: 'once', query: { include: ['player'] } },
{ text: TEMPLATE2_b, event: 'postframe', select: 'each', query: { include: ['player'] },
emits: ['joyup', 'joydown', 'joyleft', 'joyright', 'joybutton'] }
]
});
em.defineSystem({
name: 'move_x',
actions: [
{ text: TEMPLATE3_L, event: 'joyleft', select: 'source', query: { include: ['player', 'xpos'] }, },
{ text: TEMPLATE3_R, event: 'joyright', select: 'source', query: { include: ['player', 'xpos'] }, },
]
});
em.defineSystem({
name: 'move_y',
actions: [
{ text: TEMPLATE3_U, event: 'joyup', select: 'source', query: { include: ['player', 'ypos'] } },
{ text: TEMPLATE3_D, event: 'joydown', select: 'source', query: { include: ['player', 'ypos'] } },
]
});
em.defineSystem({
name: 'SetHorizPos',
actions: [