mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
starting on js scripting language; worker msgs can run async functions (but we don't need to ... yet)
This commit is contained in:
parent
bd00d98b77
commit
a8b2b7c043
25
css/ui.css
25
css/ui.css
@ -768,3 +768,28 @@ div.asset_toolbar {
|
||||
.waverow.editable:hover {
|
||||
background-color: #336633;
|
||||
}
|
||||
|
||||
.scripting-cell {
|
||||
background: #444;
|
||||
color: #99cc99;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.scripting-cell canvas {
|
||||
height: 15vw;
|
||||
border: 2px solid #222;
|
||||
outline-color: #ccc;
|
||||
background: #000;
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
pointer-events:auto;
|
||||
}
|
||||
.scripting-cell canvas:focus {
|
||||
outline:none;
|
||||
border-color:#888;
|
||||
}
|
||||
.scripting-cell div {
|
||||
display: inline;
|
||||
}
|
||||
|
164
package-lock.json
generated
164
package-lock.json
generated
@ -13,13 +13,16 @@
|
||||
"@wasmer/wasmfs": "^0.12.0",
|
||||
"binaryen": "^101.0.0",
|
||||
"clipboard": "^2.0.6",
|
||||
"error-stack-parser": "^2.0.6",
|
||||
"fast-png": "^5.0.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"jquery": "^3.6.0",
|
||||
"jszip": "^3.7.0",
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"split.js": "^1.6.2"
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootbox": "^5.1.3",
|
||||
@ -40,7 +43,6 @@
|
||||
"lzg": "^1.0.x",
|
||||
"mocha": "^7.2.0",
|
||||
"mocha-simple-html-reporter": "^2.0.0",
|
||||
"pngjs": "^3.4.0",
|
||||
"typedoc": "^0.21.0",
|
||||
"typescript": "^4.3.4",
|
||||
"typescript-formatter": "^7.2.2"
|
||||
@ -820,6 +822,11 @@
|
||||
"integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.2.tgz",
|
||||
"integrity": "sha512-8UJl2MjkqqS6ncpLZqRZ5LmGiFBkbYxocD4e4jmBqGvfRG1RS23gKsBQbdtV9O9GvRyjFTiRHRByjSlKCLlmZw=="
|
||||
},
|
||||
"node_modules/@types/plist": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz",
|
||||
@ -3180,6 +3187,14 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/error-stack-parser": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
|
||||
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
|
||||
"dependencies": {
|
||||
"stackframe": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.18.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz",
|
||||
@ -3419,6 +3434,21 @@
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/fast-png": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
|
||||
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
|
||||
"dependencies": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"iobuffer": "^5.0.2",
|
||||
"pako": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-png/node_modules/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz",
|
||||
@ -4503,6 +4533,11 @@
|
||||
"integrity": "sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.0.3.tgz",
|
||||
"integrity": "sha512-0SNk4hbHVXx9oE27vTJY+oiI0txkhBdQV12RvILd/7XuIhBZ0TkImq5EnhFYCcRcDff8jpFhZ9C2Sg+NIo3ZMQ=="
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
@ -5890,6 +5925,14 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"dependencies": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@ -7699,15 +7742,6 @@
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@ -8614,6 +8648,11 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||
},
|
||||
"node_modules/spawn-wrap": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
|
||||
@ -8720,6 +8759,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackframe": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
|
||||
"integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA=="
|
||||
},
|
||||
"node_modules/stat-mode": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
|
||||
@ -10061,6 +10105,29 @@
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yufka": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.0.1.tgz",
|
||||
"integrity": "sha512-VdqiJocmYfhx/yPiLFmF6ZyxKE2bzaHbocO0Y27sC5ytSQ09CLhTWP8GJAZMhGq2UGvhCM6wYXuDxNP5PUusHg==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.0.1",
|
||||
"magic-string": "^0.25.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yufka/node_modules/acorn": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz",
|
||||
"integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -10703,6 +10770,11 @@
|
||||
"integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/pako": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.2.tgz",
|
||||
"integrity": "sha512-8UJl2MjkqqS6ncpLZqRZ5LmGiFBkbYxocD4e4jmBqGvfRG1RS23gKsBQbdtV9O9GvRyjFTiRHRByjSlKCLlmZw=="
|
||||
},
|
||||
"@types/plist": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz",
|
||||
@ -12588,6 +12660,14 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"error-stack-parser": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
|
||||
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
|
||||
"requires": {
|
||||
"stackframe": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.18.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz",
|
||||
@ -12759,6 +12839,23 @@
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"devOptional": true
|
||||
},
|
||||
"fast-png": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
|
||||
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
|
||||
"requires": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"iobuffer": "^5.0.2",
|
||||
"pako": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz",
|
||||
@ -13618,6 +13715,11 @@
|
||||
"integrity": "sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==",
|
||||
"optional": true
|
||||
},
|
||||
"iobuffer": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.0.3.tgz",
|
||||
"integrity": "sha512-0SNk4hbHVXx9oE27vTJY+oiI0txkhBdQV12RvILd/7XuIhBZ0TkImq5EnhFYCcRcDff8jpFhZ9C2Sg+NIo3ZMQ=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
@ -14719,6 +14821,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@ -16171,12 +16281,6 @@
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@ -16879,6 +16983,11 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||
},
|
||||
"spawn-wrap": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
|
||||
@ -16973,6 +17082,11 @@
|
||||
"tweetnacl": "~0.14.0"
|
||||
}
|
||||
},
|
||||
"stackframe": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
|
||||
"integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA=="
|
||||
},
|
||||
"stat-mode": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
|
||||
@ -18055,6 +18169,22 @@
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"yufka": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.0.1.tgz",
|
||||
"integrity": "sha512-VdqiJocmYfhx/yPiLFmF6ZyxKE2bzaHbocO0Y27sC5ytSQ09CLhTWP8GJAZMhGq2UGvhCM6wYXuDxNP5PUusHg==",
|
||||
"requires": {
|
||||
"acorn": "^8.0.1",
|
||||
"magic-string": "^0.25.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz",
|
||||
"integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,16 @@
|
||||
"@wasmer/wasmfs": "^0.12.0",
|
||||
"binaryen": "^101.0.0",
|
||||
"clipboard": "^2.0.6",
|
||||
"error-stack-parser": "^2.0.6",
|
||||
"fast-png": "^5.0.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"jquery": "^3.6.0",
|
||||
"jszip": "^3.7.0",
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"split.js": "^1.6.2"
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootbox": "^5.1.3",
|
||||
@ -42,7 +45,6 @@
|
||||
"lzg": "^1.0.x",
|
||||
"mocha": "^7.2.0",
|
||||
"mocha-simple-html-reporter": "^2.0.0",
|
||||
"pngjs": "^3.4.0",
|
||||
"typedoc": "^0.21.0",
|
||||
"typescript": "^4.3.4",
|
||||
"typescript-formatter": "^7.2.2"
|
||||
|
@ -31,8 +31,8 @@ export function setNoiseSeed(x : number) {
|
||||
|
||||
type KeyboardCallback = (which:number, charCode:number, flags:KeyFlags) => void;
|
||||
|
||||
function __createCanvas(mainElement:HTMLElement, width:number, height:number) : HTMLCanvasElement {
|
||||
var canvas = document.createElement('canvas');
|
||||
function __createCanvas(doc:HTMLDocument, mainElement:HTMLElement, width:number, height:number) : HTMLCanvasElement {
|
||||
var canvas = doc.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.classList.add("emuvideo");
|
||||
@ -104,9 +104,9 @@ export class RasterVideo {
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
create(doc?: HTMLDocument) {
|
||||
var canvas;
|
||||
this.canvas = canvas = __createCanvas(this.mainElement, this.width, this.height);
|
||||
this.canvas = canvas = __createCanvas(doc || document, this.mainElement, this.width, this.height);
|
||||
this.vcanvas = $(canvas);
|
||||
if (this.options && this.options.rotate) {
|
||||
this.setRotate(this.options.rotate);
|
||||
|
159
src/common/script/bitmap.ts
Normal file
159
src/common/script/bitmap.ts
Normal file
@ -0,0 +1,159 @@
|
||||
|
||||
import * as fastpng from 'fast-png';
|
||||
import { convertWordsToImages, PixelEditorImageFormat } from '../../ide/pixeleditor';
|
||||
import { arrayCompare } from '../util';
|
||||
import * as io from './io'
|
||||
|
||||
export abstract class AbstractBitmap {
|
||||
constructor(
|
||||
public readonly width: number,
|
||||
public readonly height: number,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export class RGBABitmap extends AbstractBitmap {
|
||||
public readonly rgba: Uint32Array
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
super(width, height);
|
||||
this.rgba = new Uint32Array(this.width * this.height);
|
||||
}
|
||||
setPixel(x: number, y: number, rgba: number): void {
|
||||
this.rgba[y * this.width + x] = rgba;
|
||||
}
|
||||
getPixel(x: number, y: number): number {
|
||||
return this.rgba[y * this.width + x];
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MappedBitmap extends AbstractBitmap {
|
||||
public readonly pixels: Uint8Array
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
public readonly bitsPerPixel: number,
|
||||
pixels?: Uint8Array
|
||||
) {
|
||||
super(width, height);
|
||||
this.pixels = pixels || new Uint8Array(this.width * this.height);
|
||||
}
|
||||
|
||||
setPixel(x: number, y: number, index: number): void {
|
||||
this.pixels[y * this.width + x] = index;
|
||||
}
|
||||
getPixel(x: number, y: number): number {
|
||||
return this.pixels[y * this.width + x];
|
||||
}
|
||||
abstract getRGBAForIndex(index: number): number;
|
||||
}
|
||||
|
||||
export class Palette {
|
||||
colors: Uint32Array;
|
||||
|
||||
constructor(numColors: number) {
|
||||
this.colors = new Uint32Array(numColors);
|
||||
}
|
||||
}
|
||||
|
||||
export class IndexedBitmap extends MappedBitmap {
|
||||
public palette: Palette;
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
bitsPerPixel: number,
|
||||
pixels?: Uint8Array
|
||||
) {
|
||||
super(width, height, bitsPerPixel, pixels);
|
||||
this.palette = getDefaultPalette(bitsPerPixel);
|
||||
}
|
||||
|
||||
getRGBAForIndex(index: number): number {
|
||||
return this.palette.colors[index];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO?
|
||||
function getDefaultPalette(bpp: number) {
|
||||
var pal = new Palette(1 << bpp);
|
||||
for (var i=0; i<pal.colors.length; i++) {
|
||||
pal.colors[i] = 0xff000000 | (i * 7919);
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
export function rgbaToUint32(rgba: number[]): number {
|
||||
let v = 0;
|
||||
v |= rgba[0] << 0;
|
||||
v |= rgba[1] << 8;
|
||||
v |= rgba[2] << 16;
|
||||
v |= rgba[3] << 24;
|
||||
return v;
|
||||
}
|
||||
|
||||
export function rgba(width: number, height: number) {
|
||||
return new RGBABitmap(width, height);
|
||||
}
|
||||
|
||||
export function indexed(width: number, height: number, bpp: number) {
|
||||
return new IndexedBitmap(width, height, bpp);
|
||||
}
|
||||
|
||||
export type BitmapType = RGBABitmap | IndexedBitmap;
|
||||
|
||||
export namespace png {
|
||||
export function load(url: string): BitmapType {
|
||||
return decode(io.loadbin(url));
|
||||
}
|
||||
export function decode(data: Uint8Array): BitmapType {
|
||||
let png = fastpng.decode(data);
|
||||
return convertToBitmap(png);
|
||||
}
|
||||
function convertToBitmap(png: fastpng.IDecodedPNG): BitmapType {
|
||||
if (png.palette && png.depth <= 8) {
|
||||
return convertIndexedToBitmap(png);
|
||||
} else {
|
||||
return convertRGBAToBitmap(png);
|
||||
}
|
||||
}
|
||||
function convertIndexedToBitmap(png: fastpng.IDecodedPNG): IndexedBitmap {
|
||||
var palarr = <any>png.palette as [number, number, number, number][];
|
||||
var palette = new Palette(palarr.length);
|
||||
for (let i = 0; i < palarr.length; i++) {
|
||||
// TODO: alpha?
|
||||
palette.colors[i] = rgbaToUint32(palarr[i]) | 0xff000000;
|
||||
}
|
||||
let bitmap = new IndexedBitmap(png.width, png.height, png.depth);
|
||||
for (let i = 0; i < bitmap.pixels.length; i++) {
|
||||
bitmap.pixels[i] = png.data[i];
|
||||
}
|
||||
bitmap.palette = palette;
|
||||
return bitmap;
|
||||
}
|
||||
function convertRGBAToBitmap(png: fastpng.IDecodedPNG): RGBABitmap {
|
||||
let bitmap = new RGBABitmap(png.width, png.height);
|
||||
let rgba = [0, 0, 0, 0];
|
||||
for (let i = 0; i < bitmap.rgba.length; i++) {
|
||||
for (let j = 0; j < 4; j++)
|
||||
rgba[j] = png.data[i * 4 + j];
|
||||
bitmap.rgba[i] = rgbaToUint32(rgba);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace from {
|
||||
// TODO: check arguments
|
||||
export function bytes(arr: Uint8Array, fmt: PixelEditorImageFormat) {
|
||||
var pixels = convertWordsToImages(arr, fmt);
|
||||
// TODO: guess if missing w/h/count?
|
||||
// TODO: reverse mapping
|
||||
// TODO: maybe better composable functions
|
||||
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, fmt.bpp|1, data));
|
||||
}
|
||||
}
|
125
src/common/script/env.ts
Normal file
125
src/common/script/env.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { WorkerError } from "../workertypes";
|
||||
import ErrorStackParser = require("error-stack-parser");
|
||||
import yufka from 'yufka';
|
||||
import * as bitmap from "./bitmap";
|
||||
import * as io from "./io";
|
||||
import * as output from "./output";
|
||||
import { escapeHTML } from "../util";
|
||||
|
||||
export interface Cell {
|
||||
id: string;
|
||||
object?: any;
|
||||
}
|
||||
|
||||
const IMPORTS = {
|
||||
'bitmap': bitmap,
|
||||
'io': io,
|
||||
'output': output
|
||||
}
|
||||
|
||||
const LINE_NUMBER_OFFSET = 3;
|
||||
|
||||
const GLOBAL_BADLIST = [
|
||||
'eval'
|
||||
]
|
||||
|
||||
const GLOBAL_GOODLIST = [
|
||||
'eval', // 'eval' can't be defined or assigned to in strict mode code
|
||||
'Math', 'JSON',
|
||||
'parseFloat', 'parseInt', 'isFinite', 'isNaN',
|
||||
'String', 'Symbol', 'Number', 'Object', 'Boolean', 'NaN', 'Infinity', 'Date', 'BigInt',
|
||||
'Set', 'Map', 'RegExp', 'Array', 'ArrayBuffer', 'DataView',
|
||||
'Float32Array', 'Float64Array',
|
||||
'Int8Array', 'Int16Array', 'Int32Array',
|
||||
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Uint8ClampedArray',
|
||||
]
|
||||
|
||||
export class Environment {
|
||||
preamble: string;
|
||||
postamble: string;
|
||||
obj: {};
|
||||
|
||||
constructor(
|
||||
public readonly globalenv: any,
|
||||
public readonly path: string
|
||||
) {
|
||||
var badlst = Object.getOwnPropertyNames(this.globalenv).filter(name => GLOBAL_GOODLIST.indexOf(name) < 0);
|
||||
this.preamble = `'use strict';var ${badlst.join(',')};`;
|
||||
for (var impname in IMPORTS) {
|
||||
this.preamble += `var ${impname}=$$.${impname};`
|
||||
}
|
||||
this.preamble += '{\n';
|
||||
this.postamble = '\n}';
|
||||
}
|
||||
preprocess(code: string): string {
|
||||
var declvars = {};
|
||||
const result = yufka(code, (node, { update, source, parent }) => {
|
||||
switch (node.type) {
|
||||
case 'Identifier':
|
||||
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
|
||||
update(`__FORBIDDEN__KEYWORD__${source()}__`) // TODO? how to preserve line number?
|
||||
}
|
||||
break;
|
||||
case 'AssignmentExpression':
|
||||
/*
|
||||
// x = expr --> var x = expr
|
||||
if (parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program') { // TODO
|
||||
let left = node['left'];
|
||||
if (left && left.type === 'Identifier' && declvars[left.name] == null) {
|
||||
update(`var ${source()}`)
|
||||
declvars[left.name] = true;
|
||||
}
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
})
|
||||
return result.toString();
|
||||
}
|
||||
async run(code: string): Promise<void> {
|
||||
code = this.preprocess(code);
|
||||
this.obj = {};
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
||||
const fn = new AsyncFunction('$$', this.preamble + code + this.postamble).bind(this.obj, IMPORTS);
|
||||
await fn.call(this);
|
||||
this.checkResult();
|
||||
}
|
||||
checkResult() {
|
||||
for (var [key, value] of Object.entries(this.obj)) {
|
||||
if (value instanceof Promise) {
|
||||
throw new Error(`'${key}' is unresolved. Use 'await' before expression.`) // TODO?
|
||||
}
|
||||
}
|
||||
}
|
||||
render(): Cell[] {
|
||||
var cells = [];
|
||||
for (var [key, value] of Object.entries(this.obj)) {
|
||||
if (typeof value === 'function') {
|
||||
// TODO: find other values, functions embedded in objects?
|
||||
} else {
|
||||
var cell: Cell = { id: key, object: value };
|
||||
cells.push(cell);
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
extractErrors(e: Error): WorkerError[] {
|
||||
if (e['loc'] != null) {
|
||||
return [{
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: e['loc'].line,
|
||||
start: e['loc'].column,
|
||||
}]
|
||||
}
|
||||
// TODO: Cannot parse given Error object
|
||||
var frames = ErrorStackParser.parse(e);
|
||||
var frame = frames.find(f => f.functionName === 'anonymous');
|
||||
return [{
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: frame ? frame.lineNumber - LINE_NUMBER_OFFSET : 0,
|
||||
start: frame ? frame.columnNumber : 0,
|
||||
}];
|
||||
}
|
||||
}
|
69
src/common/script/io.ts
Normal file
69
src/common/script/io.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { ProjectFilesystem } from "../../ide/project";
|
||||
import { FileData } from "../workertypes";
|
||||
|
||||
// TODO
|
||||
|
||||
var $$fs: ProjectFilesystem;
|
||||
var $$cache: { [path: string]: FileData } = {};
|
||||
|
||||
export class IOWaitError extends Error {
|
||||
}
|
||||
|
||||
export function $$setupFS(fs: ProjectFilesystem) {
|
||||
$$fs = fs;
|
||||
}
|
||||
|
||||
function getFS(): ProjectFilesystem {
|
||||
if ($$fs == null) throw new Error(`Internal Error: The 'io' module has not been set up properly.`)
|
||||
return $$fs;
|
||||
}
|
||||
|
||||
export function canonicalurl(url: string) : string {
|
||||
// get raw resource URL for github
|
||||
if (url.startsWith('https://github.com/')) {
|
||||
let toks = url.split('/');
|
||||
if (toks[5] === 'blob') {
|
||||
return `https://raw.githubusercontent.com/${toks[3]}/${toks[4]}/${toks.slice(6).join('/')}`
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function load(url: string, type?: 'binary' | 'text'): FileData {
|
||||
url = canonicalurl(url);
|
||||
// TODO: only works in web worker
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = type === 'text' ? 'text' : 'arraybuffer';
|
||||
xhr.open("GET", url, false); // synchronous request
|
||||
xhr.send(null);
|
||||
if (xhr.response != null && xhr.status == 200) {
|
||||
if (type !== 'text') {
|
||||
return new Uint8Array(xhr.response);
|
||||
} else {
|
||||
return xhr.response;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`The resource at "${url}" responded with status code of ${xhr.status}.`)
|
||||
}
|
||||
}
|
||||
|
||||
export function loadbin(url: string): Uint8Array {
|
||||
var data = load(url, 'binary');
|
||||
if (data instanceof Uint8Array)
|
||||
return data;
|
||||
else
|
||||
throw new Error(`The resource at "${url}" is not a binary file.`);
|
||||
}
|
||||
|
||||
export function xload(path: string): FileData {
|
||||
var data = $$cache[path];
|
||||
if (data == null) {
|
||||
getFS().getFileData(path).then((value) => {
|
||||
$$cache[path] = value;
|
||||
})
|
||||
throw new IOWaitError(path);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
165
src/common/script/node.ts
Normal file
165
src/common/script/node.ts
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
var lastTimestamp = 0;
|
||||
|
||||
function newTimestamp() {
|
||||
return ++lastTimestamp;
|
||||
}
|
||||
|
||||
export type DependencySet = {[id:string] : ComputeNode | any}
|
||||
|
||||
export abstract class ComputeNode {
|
||||
private src_ts: number = newTimestamp();
|
||||
private result_ts: number = 0;
|
||||
private depends: DependencySet = {};
|
||||
private busy: Promise<void> = null;
|
||||
|
||||
modified() {
|
||||
this.src_ts = newTimestamp();
|
||||
}
|
||||
|
||||
isStale(ts: number) {
|
||||
return this.result_ts < ts;
|
||||
}
|
||||
|
||||
setDependencies(depends: DependencySet) {
|
||||
this.depends = depends;
|
||||
// compute latest timestamp of all dependencies
|
||||
var ts = 0;
|
||||
for (let [key, dep] of Object.entries(this.depends)) {
|
||||
if (dep instanceof ComputeNode && dep.result_ts) {
|
||||
ts = Math.max(ts, dep.result_ts);
|
||||
} else {
|
||||
ts = newTimestamp();
|
||||
}
|
||||
}
|
||||
this.src_ts = ts;
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return this.depends;
|
||||
}
|
||||
|
||||
async update() : Promise<void> {
|
||||
let maxts = 0;
|
||||
let dependsComputes = []
|
||||
for (let [key, dep] of Object.entries(this.depends)) {
|
||||
if (dep instanceof ComputeNode && dep.isStale(this.src_ts)) {
|
||||
dependsComputes.push(dep.compute());
|
||||
}
|
||||
}
|
||||
if (dependsComputes.length) {
|
||||
await Promise.all(dependsComputes);
|
||||
this.recompute(maxts);
|
||||
}
|
||||
}
|
||||
|
||||
async recompute(ts: number) : Promise<void> {
|
||||
// are we currently waiting for a computation to finish?
|
||||
if (this.busy == null || ts > this.result_ts) {
|
||||
// wait for previous operation to finish (no-op if null)
|
||||
await this.busy;
|
||||
this.result_ts = ts;
|
||||
this.busy = this.compute();
|
||||
}
|
||||
await this.busy;
|
||||
this.busy = null;
|
||||
}
|
||||
|
||||
abstract compute(): Promise<void>;
|
||||
}
|
||||
|
||||
class ValueNode<T> extends ComputeNode {
|
||||
private value : T;
|
||||
|
||||
constructor(value : T) {
|
||||
super();
|
||||
this.set(value);
|
||||
}
|
||||
|
||||
get() : T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
set(newValue : T) {
|
||||
this.value = newValue;
|
||||
this.modified();
|
||||
}
|
||||
|
||||
async compute() { }
|
||||
}
|
||||
|
||||
class ArrayNode<T> extends ValueNode<T> {
|
||||
}
|
||||
|
||||
class IntegerNode extends ValueNode<number> {
|
||||
}
|
||||
|
||||
abstract class BitmapNode extends ComputeNode {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
class RGBABitmapNode extends BitmapNode {
|
||||
rgba: ArrayNode<Uint32Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class IndexedBitmapNode extends BitmapNode {
|
||||
indices: ArrayNode<Uint8Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class PaletteNode {
|
||||
colors: ArrayNode<Uint32Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class PaletteMapNode extends ComputeNode {
|
||||
palette: PaletteNode;
|
||||
indices: ArrayNode<Uint8Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
function valueOf<T>(node : ValueNode<T>) : T {
|
||||
return node.get();
|
||||
}
|
||||
|
||||
class TestNode extends ComputeNode {
|
||||
value : string;
|
||||
|
||||
async compute() {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
this.value = Object.values(this.getDependencies()).map(valueOf).join('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
async function test() {
|
||||
var val1 = new ValueNode<number>(1234);
|
||||
var arr1 = new ValueNode<number[]>([1,2,3]);
|
||||
var join = new TestNode();
|
||||
join.setDependencies({a:val1, b:arr1});
|
||||
await join.update();
|
||||
console.log(join);
|
||||
val1.set(9999);
|
||||
join.update();
|
||||
val1.set(9989)
|
||||
await join.update();
|
||||
console.log(join);
|
||||
}
|
||||
|
||||
test();
|
85
src/common/script/output.ts
Normal file
85
src/common/script/output.ts
Normal file
@ -0,0 +1,85 @@
|
||||
|
||||
enum DataType {
|
||||
unknown,
|
||||
u8,
|
||||
s8,
|
||||
u16,
|
||||
s16,
|
||||
u32,
|
||||
s32,
|
||||
f32,
|
||||
f64,
|
||||
};
|
||||
|
||||
function getArrayDataType(value: any) : DataType {
|
||||
if (value instanceof Uint8Array) {
|
||||
return DataType.u8;
|
||||
} else if (value instanceof Int8Array) {
|
||||
return DataType.s8;
|
||||
} else if (value instanceof Uint16Array) {
|
||||
return DataType.u16;
|
||||
} else if (value instanceof Int16Array) {
|
||||
return DataType.s16;
|
||||
} else if (value instanceof Uint32Array) {
|
||||
return DataType.u32;
|
||||
} else if (value instanceof Int32Array) {
|
||||
return DataType.s32;
|
||||
} else if (value instanceof Float32Array) {
|
||||
return DataType.f32;
|
||||
} else if (value instanceof Float64Array) {
|
||||
return DataType.f64;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class OutputFile {
|
||||
constructor(
|
||||
public readonly path : string,
|
||||
public readonly decls : {}
|
||||
) {
|
||||
}
|
||||
abstract declToText(label: string, value: any) : string;
|
||||
toString() : string {
|
||||
return Object.entries(this.decls).map(entry => this.declToText(entry[0],entry[1])).join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
export class COutputFile extends OutputFile {
|
||||
toString() : string {
|
||||
return `#include <stdint.h>\n\n${super.toString()}\n`;
|
||||
}
|
||||
dataTypeToString(dtype: DataType) {
|
||||
switch (dtype) {
|
||||
case DataType.u8: return 'uint8_t';
|
||||
case DataType.s8: return 'int8_t';
|
||||
case DataType.u16: return 'uint16_t';
|
||||
case DataType.s16: return 'int16_t';
|
||||
case DataType.u32: return 'uint32_t';
|
||||
case DataType.s32: return 'int32_t';
|
||||
case DataType.f32: return 'float';
|
||||
case DataType.f64: return 'double';
|
||||
default:
|
||||
throw new Error('Cannot convert data type'); // TODO
|
||||
}
|
||||
}
|
||||
valueToString(value, atype: DataType) : string {
|
||||
// TODO: round, check value
|
||||
return value+"";
|
||||
}
|
||||
declToText(label: string, value: any) : string {
|
||||
if (Array.isArray(value) || value['BYTES_PER_ELEMENT']) {
|
||||
let atype = getArrayDataType(value);
|
||||
if (atype != null) {
|
||||
let dtypestr = this.dataTypeToString(atype);
|
||||
let dtext = value.map(elem => this.valueToString(elem, atype)).join(',');
|
||||
let len = value.length;
|
||||
return `${dtypestr} ${label}[${len}] = { ${dtext} };`;
|
||||
}
|
||||
}
|
||||
throw new Error(`Cannot convert array "${label}"`); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: header file, auto-detect tool?
|
||||
export function file(path: string, decls: {}) {
|
||||
return new COutputFile(path, decls);
|
||||
}
|
13
src/common/script/test.ts
Normal file
13
src/common/script/test.ts
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
import 'fs';
|
||||
import * as bitmap from './bitmap'
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
var data = fs.readFileSync('images/book_a2600.png');
|
||||
//var data = fs.readFileSync('images/print-head.png');
|
||||
console.log(data);
|
||||
|
||||
var png = bitmap.png.decode(data);
|
||||
console.log(png)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { UintArray } from "../ide/pixeleditor";
|
||||
|
||||
export function lpad(s:string, n:number):string {
|
||||
s += ''; // convert to string
|
||||
@ -55,7 +56,7 @@ export function toradix(v:number, nd:number, radix:number) {
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayCompare(a:any[], b:any[]):boolean {
|
||||
export function arrayCompare(a:any[]|UintArray, b:any[]|UintArray):boolean {
|
||||
if (a == null && b == null) return true;
|
||||
if (a == null) return false;
|
||||
if (b == null) return false;
|
||||
@ -618,3 +619,7 @@ export function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: X
|
||||
return top;
|
||||
}
|
||||
|
||||
export function escapeHTML(s: string): string {
|
||||
return s.replace(/[&]/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>');
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ function reindexMask(x:number, inds:number[]) : [number, number] {
|
||||
return [i >> 3, i & 7];
|
||||
}
|
||||
|
||||
function convertWordsToImages(words:UintArray, fmt:PixelEditorImageFormat) : Uint8Array[] {
|
||||
export function convertWordsToImages(words:UintArray, fmt:PixelEditorImageFormat) : Uint8Array[] {
|
||||
var width = fmt.w;
|
||||
var height = fmt.h;
|
||||
var count = fmt.count || 1;
|
||||
@ -190,7 +190,7 @@ function convertWordsToImages(words:UintArray, fmt:PixelEditorImageFormat) : Uin
|
||||
return images;
|
||||
}
|
||||
|
||||
function convertImagesToWords(images:Uint8Array[], fmt:PixelEditorImageFormat) : number[] {
|
||||
export function convertImagesToWords(images:Uint8Array[], fmt:PixelEditorImageFormat) : number[] {
|
||||
if (fmt.destfmt) fmt = fmt.destfmt;
|
||||
var width = fmt.w;
|
||||
var height = fmt.h;
|
||||
@ -236,7 +236,7 @@ function convertImagesToWords(images:Uint8Array[], fmt:PixelEditorImageFormat) :
|
||||
}
|
||||
|
||||
// TODO
|
||||
function convertPaletteBytes(arr:UintArray,r0,r1,g0,g1,b0,b1) : number[] {
|
||||
export function convertPaletteBytes(arr:UintArray,r0,r1,g0,g1,b0,b1) : number[] {
|
||||
var result = [];
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
var d = arr[i];
|
||||
@ -266,7 +266,7 @@ export function getPaletteLength(palfmt: PixelEditorPaletteFormat) : number {
|
||||
}
|
||||
}
|
||||
|
||||
function convertPaletteFormat(palbytes:UintArray, palfmt: PixelEditorPaletteFormat) : number[] {
|
||||
export function convertPaletteFormat(palbytes:UintArray, palfmt: PixelEditorPaletteFormat) : number[] {
|
||||
var pal = palfmt.pal;
|
||||
var newpalette;
|
||||
if (typeof pal === 'number') {
|
||||
@ -290,7 +290,7 @@ function convertPaletteFormat(palbytes:UintArray, palfmt: PixelEditorPaletteForm
|
||||
}
|
||||
|
||||
// TODO: illegal colors?
|
||||
var PREDEF_PALETTES = {
|
||||
const PREDEF_PALETTES = {
|
||||
'nes':[
|
||||
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
|
||||
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
|
||||
|
@ -221,16 +221,20 @@ function getCurrentPresetTitle() : string {
|
||||
return current_preset.title || current_preset.name || current_project.mainPath || "ROM";
|
||||
}
|
||||
|
||||
async function initProject() {
|
||||
async function newFilesystem() {
|
||||
var basefs : ProjectFilesystem = new WebPresetsFileSystem(platform_id);
|
||||
if (isElectron) {
|
||||
console.log('using electron with local filesystem', alternateLocalFilesystem);
|
||||
var filesystem = new OverlayFilesystem(basefs, alternateLocalFilesystem);
|
||||
return new OverlayFilesystem(basefs, alternateLocalFilesystem);
|
||||
} else if (qs.localfs != null) {
|
||||
var filesystem = new OverlayFilesystem(basefs, await getLocalFilesystem(qs.localfs));
|
||||
return new OverlayFilesystem(basefs, await getLocalFilesystem(qs.localfs));
|
||||
} else {
|
||||
var filesystem = new OverlayFilesystem(basefs, new LocalForageFilesystem(store));
|
||||
return new OverlayFilesystem(basefs, new LocalForageFilesystem(store));
|
||||
}
|
||||
}
|
||||
|
||||
async function initProject() {
|
||||
var filesystem = await newFilesystem();
|
||||
current_project = new CodeProject(newWorker(), platform_id, platform, filesystem);
|
||||
projectWindows = new ProjectWindows($("#workspace")[0] as HTMLElement, current_project);
|
||||
current_project.callbackBuildResult = (result:WorkerResult) => {
|
||||
|
@ -17,6 +17,7 @@ export function importPlatform(name: string) : Promise<any> {
|
||||
case "msx": return import("../platform/msx");
|
||||
case "mw8080bw": return import("../platform/mw8080bw");
|
||||
case "nes": return import("../platform/nes");
|
||||
case "script": return import("../platform/script");
|
||||
case "sms": return import("../platform/sms");
|
||||
case "sound_konami": return import("../platform/sound_konami");
|
||||
case "sound_williams": return import("../platform/sound_williams");
|
||||
|
164
src/platform/script.ts
Normal file
164
src/platform/script.ts
Normal file
@ -0,0 +1,164 @@
|
||||
|
||||
import { PLATFORMS, RasterVideo } from "../common/emu";
|
||||
import { Platform } from "../common/baseplatform";
|
||||
import { Cell } from "../common/script/env";
|
||||
import { escapeHTML } from "../common/util";
|
||||
import { BitmapType, IndexedBitmap, RGBABitmap } from "../common/script/bitmap";
|
||||
|
||||
abstract class TileEditor<T> {
|
||||
video: RasterVideo;
|
||||
|
||||
constructor(
|
||||
public readonly tileWidth: number,
|
||||
public readonly tileHeight: number,
|
||||
public readonly numColumns: number,
|
||||
public readonly numRows: number,
|
||||
) {
|
||||
}
|
||||
getPixelWidth() { return this.tileWidth * this.numColumns }
|
||||
getPixelHeight() { return this.tileHeight * this.numRows }
|
||||
attach(div: HTMLElement) {
|
||||
this.video = new RasterVideo(div, this.getPixelWidth(), this.getPixelHeight());
|
||||
this.video.create();
|
||||
}
|
||||
detach() {
|
||||
this.video = null;
|
||||
}
|
||||
update() {
|
||||
if (this.video) this.video.updateFrame();
|
||||
}
|
||||
abstract getTile(x: number, y: number): T;
|
||||
abstract renderTile(x: number, y: number): void;
|
||||
}
|
||||
|
||||
class RGBABitmapEditor extends TileEditor<number> {
|
||||
constructor(
|
||||
public readonly bitmap: RGBABitmap
|
||||
) {
|
||||
super(1, 1, bitmap.width, bitmap.height);
|
||||
}
|
||||
getTile(x: number, y: number) {
|
||||
return this.bitmap.getPixel(x, y);
|
||||
}
|
||||
renderTile(x: number, y: number): void {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
function bitmap2image(doc: HTMLDocument, div: HTMLElement, bitmap: BitmapType): HTMLCanvasElement {
|
||||
var video = new RasterVideo(div, bitmap.width, bitmap.height);
|
||||
video.create(doc);
|
||||
video.canvas.className = 'pixelated';
|
||||
let vdata = video.getFrameData();
|
||||
if (bitmap['palette'] != null) {
|
||||
let bmp = bitmap as IndexedBitmap;
|
||||
let pal = bmp.palette.colors;
|
||||
for (var i = 0; i < bmp.pixels.length; i++) {
|
||||
vdata[i] = pal[bmp.pixels[i]];
|
||||
}
|
||||
} else {
|
||||
let bmp = bitmap as RGBABitmap;
|
||||
vdata.set(bmp.rgba);
|
||||
}
|
||||
video.updateFrame();
|
||||
return video.canvas;
|
||||
}
|
||||
|
||||
class Notebook {
|
||||
constructor(
|
||||
public readonly maindoc: HTMLDocument,
|
||||
public readonly maindiv: HTMLElement
|
||||
) {
|
||||
maindiv.classList.add('vertical-scroll');
|
||||
//maindiv.classList.add('container')
|
||||
}
|
||||
updateCells(cells: Cell[]) {
|
||||
let body = this.maindiv;
|
||||
body.innerHTML = '';
|
||||
//var body = $(this.iframe).contents().find('body');
|
||||
//body.empty();
|
||||
for (let cell of cells) {
|
||||
if (cell.object != null) {
|
||||
let div = this.objectToDiv(cell.object);
|
||||
div.id = cell.id;
|
||||
div.classList.add('scripting-cell')
|
||||
//div.classList.add('row')
|
||||
body.append(div);
|
||||
}
|
||||
}
|
||||
}
|
||||
objectToDiv(object: any) {
|
||||
let div = document.createElement('div');
|
||||
//div.classList.add('col-auto')
|
||||
//grid-template-columns: repeat(auto-fit, minmax(50px, 1fr));
|
||||
// TODO: tile editor
|
||||
if (Array.isArray(object) || object.BYTES_PER_ELEMENT) {
|
||||
object.forEach((obj) => {
|
||||
div.appendChild(this.objectToDiv(obj));
|
||||
});
|
||||
// TODO
|
||||
} else if (object['bitsPerPixel'] && object['pixels'] && object['palette']) {
|
||||
bitmap2image(this.maindoc, div, object as IndexedBitmap);
|
||||
} else if (object['rgba']) {
|
||||
bitmap2image(this.maindoc, div, object as RGBABitmap);
|
||||
} else if (object != null) {
|
||||
div.innerHTML = escapeHTML(JSON.stringify(object));
|
||||
}
|
||||
return div;
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptingPlatform implements Platform {
|
||||
mainElement: HTMLElement;
|
||||
iframe: HTMLIFrameElement;
|
||||
notebook: Notebook;
|
||||
|
||||
constructor(mainElement: HTMLElement) {
|
||||
this.mainElement = mainElement;
|
||||
/*
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
|
||||
this.iframe = $(`<iframe sandbox="allow-same-origin" width="100%" height="100%"/>`).appendTo(mainElement)[0] as HTMLIFrameElement;
|
||||
mainElement.classList.add("vertical-scroll"); //100% height
|
||||
mainElement.style.overflowY = 'auto';
|
||||
this.iframe.onload = (e) => {
|
||||
let head = this.iframe.contentDocument.head;
|
||||
head.appendChild($(`<link rel="stylesheet" href="css/script.css">`)[0]);
|
||||
};
|
||||
*/
|
||||
this.notebook = new Notebook(document, mainElement);
|
||||
}
|
||||
start() {
|
||||
}
|
||||
reset() {
|
||||
}
|
||||
pause() {
|
||||
}
|
||||
resume() {
|
||||
}
|
||||
loadROM(title, cells: Cell[]) {
|
||||
this.notebook.updateCells(cells);
|
||||
}
|
||||
isRunning() {
|
||||
return false;
|
||||
}
|
||||
isDebugging(): boolean {
|
||||
return false;
|
||||
}
|
||||
getToolForFilename(fn: string): string {
|
||||
return "js";
|
||||
}
|
||||
getDefaultExtension(): string {
|
||||
return ".js";
|
||||
}
|
||||
getPresets() {
|
||||
return [
|
||||
];
|
||||
}
|
||||
/*
|
||||
showHelp() {
|
||||
window.open("https://github.com/showdownjs/showdown/wiki/Showdown's-Markdown-syntax", "_help");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
PLATFORMS['script'] = ScriptingPlatform;
|
29
src/worker/tools/script.ts
Normal file
29
src/worker/tools/script.ts
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
import { Environment } from "../../common/script/env";
|
||||
import { BuildStep, BuildStepResult, emglobal, store } from "../workermain";
|
||||
|
||||
// cache environments
|
||||
var environments : {[path:string] : Environment} = {};
|
||||
|
||||
function getEnv(path: string) : Environment {
|
||||
var env = environments[path];
|
||||
if (!env) {
|
||||
env = environments[path] = new Environment(emglobal, path);
|
||||
// TODO: load environment from store?
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
export async function runJavascript(step: BuildStep) : Promise<BuildStepResult> {
|
||||
var env = getEnv(step.path);
|
||||
var code = store.getFileAsString(step.path);
|
||||
try {
|
||||
await env.run(code);
|
||||
} catch (e) {
|
||||
return {errors: env.extractErrors(e)};
|
||||
}
|
||||
var output = env.render();
|
||||
return {
|
||||
output
|
||||
};
|
||||
}
|
@ -394,7 +394,7 @@ export interface BuildStep extends WorkerBuildStep {
|
||||
|
||||
///
|
||||
|
||||
class FileWorkingStore {
|
||||
export class FileWorkingStore {
|
||||
workfs : {[path:string]:FileEntry} = {};
|
||||
workerseq : number = 0;
|
||||
|
||||
@ -452,7 +452,7 @@ class Builder {
|
||||
wasChanged(entry:FileEntry) : boolean {
|
||||
return entry.ts > this.startseq;
|
||||
}
|
||||
executeBuildSteps() {
|
||||
async executeBuildSteps() : Promise<WorkerResult> {
|
||||
this.startseq = store.currentVersion();
|
||||
var linkstep : BuildStep = null;
|
||||
while (this.steps.length) {
|
||||
@ -462,7 +462,7 @@ class Builder {
|
||||
if (!toolfn) throw Error("no tool named " + step.tool);
|
||||
step.params = PLATFORM_PARAMS[getBasePlatform(platform)];
|
||||
try {
|
||||
step.result = toolfn(step);
|
||||
step.result = await toolfn(step);
|
||||
} catch (e) {
|
||||
console.log("EXCEPTION", e, e.stack);
|
||||
return {errors:[{line:0, msg:e+""}]}; // TODO: catch errors already generated?
|
||||
@ -509,7 +509,7 @@ class Builder {
|
||||
}
|
||||
}
|
||||
}
|
||||
handleMessage(data: WorkerMessage) : WorkerResult {
|
||||
async handleMessage(data: WorkerMessage) : Promise<WorkerResult> {
|
||||
this.steps = [];
|
||||
// file updates
|
||||
if (data.updates) {
|
||||
@ -528,7 +528,7 @@ class Builder {
|
||||
}
|
||||
// execute build steps
|
||||
if (this.steps.length) {
|
||||
var result = this.executeBuildSteps();
|
||||
var result = await this.executeBuildSteps();
|
||||
return result ? result : {unchanged:true};
|
||||
}
|
||||
// TODO: cache results
|
||||
@ -1029,6 +1029,7 @@ import * as m6502 from './tools/m6502'
|
||||
import * as z80 from './tools/z80'
|
||||
import * as x86 from './tools/x86'
|
||||
import * as arm from './tools/arm'
|
||||
import * as script from './tools/script'
|
||||
|
||||
var TOOLS = {
|
||||
'dasm': dasm.assembleDASM,
|
||||
@ -1064,6 +1065,7 @@ var TOOLS = {
|
||||
'wiz': misc.compileWiz,
|
||||
'armips': arm.assembleARMIPS,
|
||||
'vasmarm': arm.assembleVASMARM,
|
||||
'js': script.runJavascript,
|
||||
}
|
||||
|
||||
var TOOL_PRELOADFS = {
|
||||
@ -1092,7 +1094,9 @@ var TOOL_PRELOADFS = {
|
||||
'wiz': 'wiz',
|
||||
}
|
||||
|
||||
function handleMessage(data : WorkerMessage) : WorkerResult {
|
||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
async function handleMessage(data : WorkerMessage) : Promise<WorkerResult> {
|
||||
// preload file system
|
||||
if (data.preload) {
|
||||
var fs = TOOL_PRELOADFS[data.preload];
|
||||
@ -1113,12 +1117,15 @@ function handleMessage(data : WorkerMessage) : WorkerResult {
|
||||
}
|
||||
|
||||
if (ENVIRONMENT_IS_WORKER) {
|
||||
onmessage = function(e) {
|
||||
var result = handleMessage(e.data);
|
||||
var lastpromise = null;
|
||||
onmessage = async function(e) {
|
||||
await lastpromise; // wait for previous message to complete
|
||||
lastpromise = handleMessage(e.data);
|
||||
var result = await lastpromise;
|
||||
lastpromise = null;
|
||||
if (result) {
|
||||
postMessage(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//}();
|
||||
|
Loading…
Reference in New Issue
Block a user