mirror of
https://github.com/felixrieseberg/macintosh.js.git
synced 2025-02-06 03:30:17 +00:00
🚀 First basic version
This commit is contained in:
commit
4950c7b153
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
.DS_Store
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
# Electron-Forge
|
||||
out/
|
58
package.json
Normal file
58
package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "macintosh",
|
||||
"productName": "macintosh",
|
||||
"version": "1.0.0",
|
||||
"description": "Macintosh's System 7 in an Electron app. I'm sorry.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"lint": "echo \"No linting configured\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"name": "Felix Rieseberg",
|
||||
"email": "felix@felixrieseberg.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"forge": {
|
||||
"packagerConfig": {},
|
||||
"makers": [
|
||||
{
|
||||
"name": "@electron-forge/maker-squirrel",
|
||||
"config": {
|
||||
"name": "macintosh"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-zip",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-deb",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-rpm",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-squirrel-startup": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "6.0.0-beta.52",
|
||||
"@electron-forge/maker-deb": "6.0.0-beta.52",
|
||||
"@electron-forge/maker-rpm": "6.0.0-beta.52",
|
||||
"@electron-forge/maker-squirrel": "6.0.0-beta.52",
|
||||
"@electron-forge/maker-zip": "6.0.0-beta.52",
|
||||
"electron": "9.1.0"
|
||||
}
|
||||
}
|
311
src/basilisk/BasiliskII-worker-boot.js
Normal file
311
src/basilisk/BasiliskII-worker-boot.js
Normal file
@ -0,0 +1,311 @@
|
||||
var memAllocSet = new Set();
|
||||
var memAllocSetPersistent = new Set();
|
||||
|
||||
function memAllocAdd(addr) {
|
||||
if (memAllocSet.has(addr)) {
|
||||
console.error(`unfreed memory alloc'd at ${addr}`);
|
||||
}
|
||||
memAllocSet.add(addr);
|
||||
console.warn('malloc', addr);
|
||||
memAllocSetPersistent.add(addr);
|
||||
}
|
||||
|
||||
function memAllocRemove(addr) {
|
||||
if (!memAllocSet.has(addr)) {
|
||||
console.error(
|
||||
`unalloc'd memory free'd at ${addr} (everallocd=${memAllocSetPersistent.has(
|
||||
addr
|
||||
)})`
|
||||
);
|
||||
}
|
||||
console.warn('free', addr);
|
||||
memAllocSet.delete(addr);
|
||||
}
|
||||
|
||||
var pathGetFilenameRegex = /\/([^\/]+)$/;
|
||||
|
||||
function pathGetFilename(path) {
|
||||
var matches = path.match(pathGetFilenameRegex);
|
||||
if (matches && matches.length) {
|
||||
return matches[1];
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
function addAutoloader(module) {
|
||||
var loadDatafiles = function() {
|
||||
module.autoloadFiles.forEach(function(filepath) {
|
||||
module.FS_createPreloadedFile(
|
||||
'/',
|
||||
pathGetFilename(filepath),
|
||||
filepath,
|
||||
true,
|
||||
true
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (module.autoloadFiles) {
|
||||
module.preRun = module.preRun || [];
|
||||
module.preRun.unshift(loadDatafiles);
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
function addCustomAsyncInit(module) {
|
||||
if (module.asyncInit) {
|
||||
module.preRun = module.preRun || [];
|
||||
module.preRun.push(function waitForCustomAsyncInit() {
|
||||
module.addRunDependency('__moduleAsyncInit');
|
||||
|
||||
module.asyncInit(module, function asyncInitCallback() {
|
||||
module.removeRunDependency('__moduleAsyncInit');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var InputBufferAddresses = {
|
||||
globalLockAddr: 0,
|
||||
mouseMoveFlagAddr: 1,
|
||||
mouseMoveXDeltaAddr: 2,
|
||||
mouseMoveYDeltaAddr: 3,
|
||||
mouseButtonStateAddr: 4,
|
||||
keyEventFlagAddr: 5,
|
||||
keyCodeAddr: 6,
|
||||
keyStateAddr: 7,
|
||||
};
|
||||
|
||||
var LockStates = {
|
||||
READY_FOR_UI_THREAD: 0,
|
||||
UI_THREAD_LOCK: 1,
|
||||
READY_FOR_EMUL_THREAD: 2,
|
||||
EMUL_THREAD_LOCK: 3,
|
||||
};
|
||||
|
||||
var Module = null;
|
||||
|
||||
self.onmessage = function(msg) {
|
||||
console.log('init worker');
|
||||
startEmulator(Object.assign({}, msg.data, {singleThreadedEmscripten: true}));
|
||||
};
|
||||
|
||||
function startEmulator(parentConfig) {
|
||||
var screenBufferView = new Uint8Array(
|
||||
parentConfig.screenBuffer,
|
||||
0,
|
||||
parentConfig.screenBufferSize
|
||||
);
|
||||
|
||||
var videoModeBufferView = new Int32Array(
|
||||
parentConfig.videoModeBuffer,
|
||||
0,
|
||||
parentConfig.videoModeBufferSize
|
||||
);
|
||||
|
||||
var inputBufferView = new Int32Array(
|
||||
parentConfig.inputBuffer,
|
||||
0,
|
||||
parentConfig.inputBufferSize
|
||||
);
|
||||
|
||||
var nextAudioChunkIndex = 0;
|
||||
var audioDataBufferView = new Uint8Array(
|
||||
parentConfig.audioDataBuffer,
|
||||
0,
|
||||
parentConfig.audioDataBufferSize
|
||||
);
|
||||
|
||||
function waitForTwoStateLock(bufferView, lockIndex) {
|
||||
// Atomics.wait(
|
||||
// bufferView,
|
||||
// lockIndex,
|
||||
// LockStates.UI_THREAD_LOCK
|
||||
// );
|
||||
|
||||
// while (!tryToAcquireCyclicalLock(bufferView, lockIndex)) {
|
||||
// // spin
|
||||
// }
|
||||
// if (!tryToAcquireCyclicalLock(bufferView, lockIndex)) {
|
||||
// throw new Error('failed to acquire lock for index', lockIndex);
|
||||
// }
|
||||
//
|
||||
//
|
||||
if (Atomics.load(bufferView, lockIndex) === LockStates.UI_THREAD_LOCK) {
|
||||
while (
|
||||
Atomics.compareExchange(
|
||||
bufferView,
|
||||
lockIndex,
|
||||
LockStates.UI_THREAD_LOCK,
|
||||
LockStates.EMUL_THREAD_LOCK
|
||||
) !== LockStates.UI_THREAD_LOCK
|
||||
) {
|
||||
// spin
|
||||
// TODO use wait and wake
|
||||
}
|
||||
} else {
|
||||
// already unlocked
|
||||
}
|
||||
}
|
||||
|
||||
function releaseTwoStateLock(bufferView, lockIndex) {
|
||||
Atomics.store(bufferView, lockIndex, LockStates.UI_THREAD_LOCK); // unlock
|
||||
}
|
||||
|
||||
function tryToAcquireCyclicalLock(bufferView, lockIndex) {
|
||||
var res = Atomics.compareExchange(
|
||||
bufferView,
|
||||
lockIndex,
|
||||
LockStates.READY_FOR_EMUL_THREAD,
|
||||
LockStates.EMUL_THREAD_LOCK
|
||||
);
|
||||
if (res === LockStates.READY_FOR_EMUL_THREAD) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function releaseCyclicalLock(bufferView, lockIndex) {
|
||||
Atomics.store(bufferView, lockIndex, LockStates.READY_FOR_UI_THREAD); // unlock
|
||||
}
|
||||
|
||||
function acquireInputLock() {
|
||||
return tryToAcquireCyclicalLock(
|
||||
inputBufferView,
|
||||
InputBufferAddresses.globalLockAddr
|
||||
);
|
||||
}
|
||||
|
||||
function releaseInputLock() {
|
||||
// reset
|
||||
inputBufferView[InputBufferAddresses.mouseMoveFlagAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.mouseMoveXDeltaAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.mouseMoveYDeltaAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.mouseButtonStateAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.keyEventFlagAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.keyCodeAddr] = 0;
|
||||
inputBufferView[InputBufferAddresses.keyStateAddr] = 0;
|
||||
|
||||
releaseCyclicalLock(inputBufferView, InputBufferAddresses.globalLockAddr);
|
||||
}
|
||||
|
||||
var AudioConfig = null;
|
||||
|
||||
var AudioBufferQueue = [];
|
||||
|
||||
Module = {
|
||||
autoloadFiles: ['system7.img', 'DCImage.img', 'disk1.img', 'Quadra-650.rom', 'prefs'],
|
||||
|
||||
arguments: ['--config', 'prefs'],
|
||||
canvas: null,
|
||||
|
||||
blit: function blit(bufPtr, width, height, depth, usingPalette) {
|
||||
// console.time('await worker video lock');
|
||||
// waitForTwoStateLock(videoModeBufferView, 9);
|
||||
// console.timeEnd('await worker video lock');
|
||||
|
||||
videoModeBufferView[0] = width;
|
||||
videoModeBufferView[1] = height;
|
||||
videoModeBufferView[2] = depth;
|
||||
videoModeBufferView[3] = usingPalette;
|
||||
var length = width * height * (depth === 32 ? 4 : 1); // 32bpp or 8bpp
|
||||
for (var i = 0; i < length; i++) {
|
||||
screenBufferView[i] = Module.HEAPU8[bufPtr + i];
|
||||
}
|
||||
// releaseTwoStateLock(videoModeBufferView, 9);
|
||||
},
|
||||
|
||||
openAudio: function openAudio(
|
||||
sampleRate,
|
||||
sampleSize,
|
||||
channels,
|
||||
framesPerBuffer
|
||||
) {
|
||||
AudioConfig = {
|
||||
sampleRate: sampleRate,
|
||||
sampleSize: sampleSize,
|
||||
channels: channels,
|
||||
framesPerBuffer: framesPerBuffer,
|
||||
};
|
||||
console.log(AudioConfig);
|
||||
},
|
||||
|
||||
enqueueAudio: function enqueueAudio(bufPtr, nbytes, type) {
|
||||
var newAudio = Module.HEAPU8.slice(bufPtr, bufPtr + nbytes);
|
||||
// console.assert(
|
||||
// nbytes == parentConfig.audioBlockBufferSize,
|
||||
// `emulator wrote ${nbytes}, expected ${parentConfig.audioBlockBufferSize}`
|
||||
// );
|
||||
|
||||
var writingChunkIndex = nextAudioChunkIndex;
|
||||
var writingChunkAddr =
|
||||
writingChunkIndex * parentConfig.audioBlockChunkSize;
|
||||
|
||||
if (audioDataBufferView[writingChunkAddr] === LockStates.UI_THREAD_LOCK) {
|
||||
console.warn(
|
||||
'worker tried to write audio data to UI-thread-locked chunk',
|
||||
writingChunkIndex
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var nextNextChunkIndex = writingChunkIndex + 1;
|
||||
if (
|
||||
nextNextChunkIndex * parentConfig.audioBlockChunkSize >
|
||||
audioDataBufferView.length - 1
|
||||
) {
|
||||
nextNextChunkIndex = 0;
|
||||
}
|
||||
// console.assert(nextNextChunkIndex != writingChunkIndex, `writingChunkIndex=${nextNextChunkIndex} == nextChunkIndex=${nextNextChunkIndex}`)
|
||||
|
||||
audioDataBufferView[writingChunkAddr + 1] = nextNextChunkIndex;
|
||||
audioDataBufferView.set(newAudio, writingChunkAddr + 2);
|
||||
audioDataBufferView[writingChunkAddr] = LockStates.UI_THREAD_LOCK;
|
||||
|
||||
nextAudioChunkIndex = nextNextChunkIndex;
|
||||
return nbytes;
|
||||
},
|
||||
|
||||
debugPointer: function debugPointer(ptr) {
|
||||
console.log('debugPointer', ptr);
|
||||
},
|
||||
|
||||
acquireInputLock: acquireInputLock,
|
||||
|
||||
InputBufferAddresses: InputBufferAddresses,
|
||||
|
||||
getInputValue: function getInputValue(addr) {
|
||||
return inputBufferView[addr];
|
||||
},
|
||||
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left) {
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
|
||||
if (left == 0) {
|
||||
postMessage({type: 'emulator_ready'});
|
||||
} else {
|
||||
postMessage({
|
||||
type: 'emulator_loading',
|
||||
completion: (this.totalDependencies - left) / this.totalDependencies,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
print: console.log.bind(console),
|
||||
|
||||
printErr: console.warn.bind(console),
|
||||
|
||||
releaseInputLock: releaseInputLock,
|
||||
};
|
||||
|
||||
// inject extra behaviours
|
||||
addAutoloader(Module);
|
||||
addCustomAsyncInit(Module);
|
||||
|
||||
if (parentConfig.singleThreadedEmscripten) {
|
||||
importScripts('BasiliskII.js');
|
||||
}
|
||||
}
|
104011
src/basilisk/BasiliskII.js
Normal file
104011
src/basilisk/BasiliskII.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/basilisk/BasiliskII.js.mem
Normal file
BIN
src/basilisk/BasiliskII.js.mem
Normal file
Binary file not shown.
34
src/basilisk/prefs
Normal file
34
src/basilisk/prefs
Normal file
@ -0,0 +1,34 @@
|
||||
disk system7.img
|
||||
disk DCImage.img
|
||||
extfs /
|
||||
screen win/800/600
|
||||
seriala
|
||||
serialb
|
||||
udptunnel false
|
||||
udpport 6066
|
||||
rom Quadra-650.rom
|
||||
bootdrive 0
|
||||
bootdriver 0
|
||||
ramsize 268435456
|
||||
frameskip 1
|
||||
modelid 14
|
||||
cpu 4
|
||||
fpu true
|
||||
nocdrom true
|
||||
nosound false
|
||||
noclipconversion false
|
||||
nogui true
|
||||
jit false
|
||||
jitfpu false
|
||||
jitdebug false
|
||||
jitcachesize 8192
|
||||
jitlazyflush true
|
||||
jitinline true
|
||||
keyboardtype 5
|
||||
keycodes false
|
||||
mousewheelmode 1
|
||||
mousewheellines 3
|
||||
dsp /dev/dsp
|
||||
mixer /dev/mixer
|
||||
ignoresegv false
|
||||
idlewait false
|
58
src/index.js
Normal file
58
src/index.js
Normal file
@ -0,0 +1,58 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
const { registerIpcHandlers } = require('./ipc');
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
|
||||
app.quit();
|
||||
}
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 730,
|
||||
useContentSize: true,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
});
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile(path.join(__dirname, 'renderer/index.html'));
|
||||
|
||||
mainWindow.setMenu(null);
|
||||
|
||||
mainWindow.webContents.toggleDevTools();
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', () => {
|
||||
registerIpcHandlers();
|
||||
createWindow();
|
||||
});
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
9
src/ipc.js
Normal file
9
src/ipc.js
Normal file
@ -0,0 +1,9 @@
|
||||
const { ipcMain, app } = require('electron');
|
||||
|
||||
function registerIpcHandlers() {
|
||||
ipcMain.handle('quit', () => app.quit())
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerIpcHandlers
|
||||
}
|
46
src/renderer/atomics.js
Normal file
46
src/renderer/atomics.js
Normal file
@ -0,0 +1,46 @@
|
||||
const LockStates = {
|
||||
READY_FOR_UI_THREAD: 0,
|
||||
UI_THREAD_LOCK: 1,
|
||||
READY_FOR_EMUL_THREAD: 2,
|
||||
EMUL_THREAD_LOCK: 3,
|
||||
};
|
||||
|
||||
function acquireTwoStateLock(bufferView, lockIndex) {
|
||||
const res = Atomics.compareExchange(
|
||||
bufferView,
|
||||
lockIndex,
|
||||
LockStates.EMUL_THREAD_LOCK,
|
||||
LockStates.UI_THREAD_LOCK
|
||||
);
|
||||
|
||||
return res === LockStates.EMUL_THREAD_LOCK;
|
||||
}
|
||||
|
||||
function releaseTwoStateLock(bufferView, lockIndex) {
|
||||
Atomics.store(bufferView, lockIndex, LockStates.EMUL_THREAD_LOCK); // unlock
|
||||
Atomics.notify(bufferView, lockIndex);
|
||||
}
|
||||
|
||||
function acquireLock(bufferView, lockIndex) {
|
||||
const res = Atomics.compareExchange(
|
||||
bufferView,
|
||||
lockIndex,
|
||||
LockStates.READY_FOR_UI_THREAD,
|
||||
LockStates.UI_THREAD_LOCK
|
||||
);
|
||||
|
||||
return res === LockStates.READY_FOR_UI_THREAD;
|
||||
}
|
||||
|
||||
function releaseLock(bufferView, lockIndex) {
|
||||
Atomics.store(bufferView, lockIndex, LockStates.READY_FOR_EMUL_THREAD); // unlock
|
||||
Atomics.notify(bufferView, lockIndex);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
acquireTwoStateLock,
|
||||
releaseTwoStateLock,
|
||||
acquireLock,
|
||||
releaseLock,
|
||||
LockStates
|
||||
}
|
238
src/renderer/audio.js
Normal file
238
src/renderer/audio.js
Normal file
@ -0,0 +1,238 @@
|
||||
const { LockStates } = require('./atomics');
|
||||
|
||||
var audio = {
|
||||
channels: 1,
|
||||
bytesPerSample: 2,
|
||||
samples: 4096,
|
||||
// freq: 11025,
|
||||
freq: 22050,
|
||||
// freq: 44100,
|
||||
format: 0x8010,
|
||||
paused: false,
|
||||
timer: null,
|
||||
silence: 0,
|
||||
maxBuffersInSharedMemory: 5,
|
||||
nextPlayTime: 0,
|
||||
};
|
||||
|
||||
var audioTotalSamples = audio.samples * audio.channels;
|
||||
audio.bytesPerSample =
|
||||
audio.format == 0x0008 /*AUDIO_U8*/ || audio.format == 0x8008 /*AUDIO_S8*/
|
||||
? 1
|
||||
: 2;
|
||||
audio.bufferSize = audioTotalSamples * audio.bytesPerSample;
|
||||
audio.bufferDurationSecs =
|
||||
audio.bufferSize / audio.bytesPerSample / audio.channels / audio.freq; // Duration of a single queued buffer in seconds.
|
||||
audio.bufferingDelay = 50 / 1000; // Audio samples are played with a constant delay of this many seconds to account for browser and jitter.
|
||||
|
||||
// To account for jittering in frametimes, always have multiple audio buffers queued up for the audio output device.
|
||||
// This helps that we won't starve that easily if a frame takes long to complete.
|
||||
audio.numSimultaneouslyQueuedBuffers = 5;
|
||||
audio.nextChunkIndex = 0;
|
||||
|
||||
var AUDIO_CONFIG_BUFFER_SIZE = 10;
|
||||
var audioConfigBuffer = new SharedArrayBuffer(AUDIO_CONFIG_BUFFER_SIZE);
|
||||
var audioConfigBufferView = new Uint8Array(audioConfigBuffer);
|
||||
|
||||
var audioBlockChunkSize = audio.bufferSize + 2;
|
||||
var AUDIO_DATA_BUFFER_SIZE =
|
||||
audioBlockChunkSize * audio.maxBuffersInSharedMemory;
|
||||
var audioDataBuffer = new SharedArrayBuffer(AUDIO_DATA_BUFFER_SIZE);
|
||||
var audioDataBufferView = new Uint8Array(audioDataBuffer);
|
||||
|
||||
var audioContext = new AudioContext();
|
||||
// document.querySelector('#enableAudio').addEventListener('click', function() {
|
||||
// audioContext.resume().then(() => {
|
||||
// document.querySelector('#enableAudio').remove();
|
||||
// });
|
||||
// });
|
||||
|
||||
var gainNode = audioContext.createGain();
|
||||
|
||||
gainNode.gain.value = 1;
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
function openAudio() {
|
||||
audio.pushAudio = function pushAudio(
|
||||
blockBuffer, // u8 typed array
|
||||
sizeBytes // probably (frames per block=4096) * (bytes per sample=2) * (n channels=1)
|
||||
) {
|
||||
if (audio.paused) return;
|
||||
|
||||
var sizeSamples = sizeBytes / audio.bytesPerSample; // How many samples fit in the callback buffer?
|
||||
var sizeSamplesPerChannel = sizeSamples / audio.channels; // How many samples per a single channel fit in the cb buffer?
|
||||
if (sizeSamplesPerChannel != audio.samples) {
|
||||
throw 'Received mismatching audio buffer size!';
|
||||
}
|
||||
// Allocate new sound buffer to be played.
|
||||
var source = audioContext.createBufferSource();
|
||||
var soundBuffer = audioContext.createBuffer(
|
||||
audio.channels,
|
||||
sizeSamplesPerChannel,
|
||||
audio.freq
|
||||
);
|
||||
// source.connect(audioContext.destination);
|
||||
source.connect(gainNode);
|
||||
|
||||
audio.fillWebAudioBufferFromChunk(
|
||||
blockBuffer,
|
||||
sizeSamplesPerChannel,
|
||||
soundBuffer
|
||||
);
|
||||
// Workaround https://bugzilla.mozilla.org/show_bug.cgi?id=883675 by setting the buffer only after filling. The order is important here!
|
||||
source.buffer = soundBuffer;
|
||||
|
||||
// Schedule the generated sample buffer to be played out at the correct time right after the previously scheduled
|
||||
// sample buffer has finished.
|
||||
var curtime = audioContext.currentTime;
|
||||
|
||||
// assertion
|
||||
if (curtime > audio.nextPlayTime && audio.nextPlayTime != 0) {
|
||||
console.log(
|
||||
'warning: Audio callback had starved sending audio by ' +
|
||||
(curtime - audio.nextPlayTime) +
|
||||
' seconds.'
|
||||
);
|
||||
}
|
||||
|
||||
// Don't ever start buffer playbacks earlier from current time than a given constant 'audio.bufferingDelay', since a browser
|
||||
// may not be able to mix that audio clip in immediately, and there may be subsequent jitter that might cause the stream to starve.
|
||||
var playtime = Math.max(curtime + audio.bufferingDelay, audio.nextPlayTime);
|
||||
source.start(playtime);
|
||||
// console.log(`queuing audio for ${playtime}`)
|
||||
|
||||
audio.nextPlayTime = playtime + audio.bufferDurationSecs;
|
||||
};
|
||||
|
||||
var getBlockBufferLastWarningTime = 0;
|
||||
var getBlockBufferWarningCount = 0;
|
||||
audio.getBlockBuffer = function getBlockBuffer() {
|
||||
// audio chunk layout
|
||||
// 0: lock state
|
||||
// 1: pointer to next chunk
|
||||
// 2->buffersize+2: audio buffer
|
||||
var curChunkIndex = audio.nextChunkIndex;
|
||||
var curChunkAddr = curChunkIndex * audioBlockChunkSize;
|
||||
|
||||
if (audioDataBufferView[curChunkAddr] !== LockStates.UI_THREAD_LOCK) {
|
||||
getBlockBufferWarningCount++;
|
||||
if (
|
||||
audio.gotFirstBlock &&
|
||||
Date.now() - getBlockBufferLastWarningTime > 5000
|
||||
) {
|
||||
console.warn(
|
||||
`UI thread tried to read audio data from worker-locked chunk ${getBlockBufferWarningCount} times`
|
||||
);
|
||||
console.log('curChunkIndex', curChunkIndex);
|
||||
// debugger
|
||||
getBlockBufferLastWarningTime = Date.now();
|
||||
getBlockBufferWarningCount = 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
audio.gotFirstBlock = true;
|
||||
|
||||
// debugger
|
||||
|
||||
var blockBuffer = audioDataBufferView.slice(
|
||||
curChunkAddr + 2,
|
||||
curChunkAddr + 2 + audio.bufferSize
|
||||
);
|
||||
audio.nextChunkIndex = audioDataBufferView[curChunkAddr + 1];
|
||||
// console.assert(audio.nextChunkIndex != curChunkIndex, `curChunkIndex=${curChunkIndex} == nextChunkIndex=${audio.nextChunkIndex}`)
|
||||
audioDataBufferView[curChunkAddr] = LockStates.EMUL_THREAD_LOCK;
|
||||
// debugger
|
||||
// console.log(`got buffer=${curChunkIndex}, next=${audio.nextChunkIndex}`)
|
||||
return blockBuffer;
|
||||
};
|
||||
|
||||
audio.fillWebAudioBufferFromChunk = function fillWebAudioBufferFromChunk(
|
||||
blockBuffer, // u8 typed array
|
||||
blockSize, // probably 4096
|
||||
dstAudioBuffer
|
||||
) {
|
||||
for (var c = 0; c < audio.channels; ++c) {
|
||||
var channelData = dstAudioBuffer.getChannelData(c);
|
||||
if (channelData.length != blockSize) {
|
||||
throw 'Web Audio output buffer length mismatch! Destination size: ' +
|
||||
channelData.length +
|
||||
' samples vs expected ' +
|
||||
blockSize +
|
||||
' samples!';
|
||||
}
|
||||
var blockBufferI16 = new Int16Array(blockBuffer.buffer);
|
||||
|
||||
for (var j = 0; j < blockSize; ++j) {
|
||||
channelData[j] = blockBufferI16[j] / 0x8000; // convert i16 to f32 in range -1 to +1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Pulls and queues new audio data if appropriate. This function gets "over-called" in both requestAnimationFrames and
|
||||
// setTimeouts to ensure that we get the finest granularity possible and as many chances from the browser to fill
|
||||
// new audio data. This is because setTimeouts alone have very poor granularity for audio streaming purposes, but also
|
||||
// the application might not be using emscripten_set_main_loop to drive the main loop, so we cannot rely on that alone.
|
||||
audio.queueNewAudioData = function queueNewAudioData() {
|
||||
var i = 0;
|
||||
for (; i < audio.numSimultaneouslyQueuedBuffers; ++i) {
|
||||
// Only queue new data if we don't have enough audio data already in queue. Otherwise skip this time slot
|
||||
// and wait to queue more in the next time the callback is run.
|
||||
var secsUntilNextPlayStart =
|
||||
audio.nextPlayTime - audioContext.currentTime;
|
||||
if (
|
||||
secsUntilNextPlayStart >=
|
||||
audio.bufferingDelay +
|
||||
audio.bufferDurationSecs * audio.numSimultaneouslyQueuedBuffers
|
||||
)
|
||||
return;
|
||||
|
||||
var blockBuffer = audio.getBlockBuffer();
|
||||
if (!blockBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// And queue it to be played after the currently playing audio stream.
|
||||
audio.pushAudio(blockBuffer, audio.bufferSize);
|
||||
}
|
||||
// console.log(`queued ${i} buffers of audio`);
|
||||
};
|
||||
|
||||
// Create a callback function that will be routinely called to ask more audio data from the user application.
|
||||
audio.caller = function audioCaller() {
|
||||
--audio.numAudioTimersPending;
|
||||
|
||||
audio.queueNewAudioData();
|
||||
|
||||
// Queue this callback function to be called again later to pull more audio data.
|
||||
var secsUntilNextPlayStart = audio.nextPlayTime - audioContext.currentTime;
|
||||
|
||||
// Queue the next audio frame push to be performed half-way when the previously queued buffer has finished playing.
|
||||
var preemptBufferFeedSecs = audio.bufferDurationSecs / 2.0;
|
||||
|
||||
if (audio.numAudioTimersPending < audio.numSimultaneouslyQueuedBuffers) {
|
||||
++audio.numAudioTimersPending;
|
||||
audio.timer = setTimeout(
|
||||
audio.caller,
|
||||
Math.max(0.0, 1000.0 * (secsUntilNextPlayStart - preemptBufferFeedSecs))
|
||||
);
|
||||
|
||||
// If we are risking starving, immediately queue an extra buffer.
|
||||
if (audio.numAudioTimersPending < audio.numSimultaneouslyQueuedBuffers) {
|
||||
++audio.numAudioTimersPending;
|
||||
setTimeout(audio.caller, 1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
audio.numAudioTimersPending = 1;
|
||||
audio.timer = setTimeout(audio.caller, 1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
audio,
|
||||
audioDataBuffer,
|
||||
audioContext,
|
||||
audioBlockChunkSize,
|
||||
AUDIO_DATA_BUFFER_SIZE,
|
||||
openAudio
|
||||
}
|
9
src/renderer/controls.js
vendored
Normal file
9
src/renderer/controls.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
const { quit } = require("./ipc");
|
||||
|
||||
function registerControls() {
|
||||
document.querySelector('#close').addEventListener('click', () => {
|
||||
quit();
|
||||
});
|
||||
}
|
||||
|
||||
registerControls();
|
19
src/renderer/emulator.js
Normal file
19
src/renderer/emulator.js
Normal file
@ -0,0 +1,19 @@
|
||||
const { drawScreen } = require('./screen');
|
||||
const { openAudio } = require('./audio');
|
||||
const { tryToSendInput } = require('./input');
|
||||
const { registerWorker } = require('./worker');
|
||||
|
||||
registerWorker();
|
||||
|
||||
function asyncLoop() {
|
||||
drawScreen();
|
||||
tryToSendInput();
|
||||
requestAnimationFrame(asyncLoop);
|
||||
}
|
||||
|
||||
function start() {
|
||||
openAudio();
|
||||
asyncLoop();
|
||||
}
|
||||
|
||||
start();
|
BIN
src/renderer/garamond.ttf
Normal file
BIN
src/renderer/garamond.ttf
Normal file
Binary file not shown.
BIN
src/renderer/images/tile_bg.png
Normal file
BIN
src/renderer/images/tile_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 863 B |
BIN
src/renderer/images/tile_bg_end.png
Normal file
BIN
src/renderer/images/tile_bg_end.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
src/renderer/images/tile_bg_end_right.png
Normal file
BIN
src/renderer/images/tile_bg_end_right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
src/renderer/images/tile_clear_bg.png
Normal file
BIN
src/renderer/images/tile_clear_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 420 B |
51
src/renderer/index.css
Normal file
51
src/renderer/index.css
Normal file
@ -0,0 +1,51 @@
|
||||
@font-face {
|
||||
font-family: Garamond;
|
||||
src: url(garamond.ttf);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Garamond, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
background-color: #000;
|
||||
width: 800px;
|
||||
-webkit-app-region: drag;
|
||||
height: 24px;
|
||||
color: #020202;
|
||||
background-image: url(images/tile_bg_end.png), url(images/tile_bg_end_right.png), url(images/tile_bg.png);
|
||||
background-position: left, right, center;
|
||||
background-repeat: no-repeat, no-repeat, repeat-x;
|
||||
background-size: contain;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.controls .clear {
|
||||
display: inline-block;
|
||||
height: calc(100% - 3px);
|
||||
background-image: url(images/tile_clear_bg.png);
|
||||
background-repeat: repeat-x;
|
||||
background-size: contain;
|
||||
padding-top: 3px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.controls a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
-webkit-app-region: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.controls a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
18
src/renderer/index.html
Normal file
18
src/renderer/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="800" height="600" oncontextmenu="event.preventDefault()"></canvas>
|
||||
<div class="controls">
|
||||
<div class="clear">
|
||||
<a id="close" href="#">Quit</a> - <a id="credits" href="#">Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./controls.js"></script>
|
||||
<script src="./emulator.js"></script>
|
||||
</body>
|
||||
</html>
|
107
src/renderer/input.js
Normal file
107
src/renderer/input.js
Normal file
@ -0,0 +1,107 @@
|
||||
const { acquireLock, releaseLock } = require('./atomics');
|
||||
|
||||
const INPUT_BUFFER_SIZE = 100;
|
||||
const inputBuffer = new SharedArrayBuffer(INPUT_BUFFER_SIZE * 4);
|
||||
const inputBufferView = new Int32Array(inputBuffer);
|
||||
|
||||
let inputQueue = [];
|
||||
|
||||
const InputBufferAddresses = {
|
||||
globalLockAddr: 0,
|
||||
mouseMoveFlagAddr: 1,
|
||||
mouseMoveXDeltaAddr: 2,
|
||||
mouseMoveYDeltaAddr: 3,
|
||||
mouseButtonStateAddr: 4,
|
||||
keyEventFlagAddr: 5,
|
||||
keyCodeAddr: 6,
|
||||
keyStateAddr: 7,
|
||||
};
|
||||
|
||||
function releaseInputLock() {
|
||||
releaseLock(inputBufferView, InputBufferAddresses.globalLockAddr);
|
||||
}
|
||||
|
||||
function tryToSendInput() {
|
||||
if (!acquireLock(inputBufferView, InputBufferAddresses.globalLockAddr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hasMouseMove = false;
|
||||
var mouseMoveX = 0;
|
||||
var mouseMoveY = 0;
|
||||
var mouseButtonState = -1;
|
||||
var hasKeyEvent = false;
|
||||
var keyCode = -1;
|
||||
var keyState = -1;
|
||||
|
||||
// currently only one key event can be sent per sync
|
||||
// TODO: better key handling code
|
||||
var remainingKeyEvents = [];
|
||||
|
||||
for (var i = 0; i < inputQueue.length; i++) {
|
||||
var inputEvent = inputQueue[i];
|
||||
switch (inputEvent.type) {
|
||||
case 'mousemove':
|
||||
hasMouseMove = true;
|
||||
mouseMoveX += inputEvent.dx;
|
||||
mouseMoveY += inputEvent.dy;
|
||||
break;
|
||||
case 'mousedown':
|
||||
case 'mouseup':
|
||||
mouseButtonState = inputEvent.type === 'mousedown' ? 1 : 0;
|
||||
break;
|
||||
case 'keydown':
|
||||
case 'keyup':
|
||||
if (hasKeyEvent) {
|
||||
remainingKeyEvents.push(inputEvent);
|
||||
break;
|
||||
}
|
||||
hasKeyEvent = true;
|
||||
keyState = inputEvent.type === 'keydown' ? 1 : 0;
|
||||
keyCode = inputEvent.keyCode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasMouseMove) {
|
||||
inputBufferView[InputBufferAddresses.mouseMoveFlagAddr] = 1;
|
||||
inputBufferView[InputBufferAddresses.mouseMoveXDeltaAddr] = mouseMoveX;
|
||||
inputBufferView[InputBufferAddresses.mouseMoveYDeltaAddr] = mouseMoveY;
|
||||
}
|
||||
inputBufferView[InputBufferAddresses.mouseButtonStateAddr] = mouseButtonState;
|
||||
if (hasKeyEvent) {
|
||||
inputBufferView[InputBufferAddresses.keyEventFlagAddr] = 1;
|
||||
inputBufferView[InputBufferAddresses.keyCodeAddr] = keyCode;
|
||||
inputBufferView[InputBufferAddresses.keyStateAddr] = keyState;
|
||||
}
|
||||
releaseInputLock();
|
||||
inputQueue = remainingKeyEvents;
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousemove', function (event) {
|
||||
inputQueue.push({ type: 'mousemove', dx: event.offsetX, dy: event.offsetY });
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousedown', function (event) {
|
||||
inputQueue.push({ type: 'mousedown' });
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', function (event) {
|
||||
inputQueue.push({ type: 'mouseup' });
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', function (event) {
|
||||
inputQueue.push({ type: 'keydown', keyCode: event.keyCode });
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', function (event) {
|
||||
inputQueue.push({ type: 'keyup', keyCode: event.keyCode });
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
INPUT_BUFFER_SIZE,
|
||||
inputBuffer,
|
||||
inputBufferView,
|
||||
InputBufferAddresses,
|
||||
inputQueue,
|
||||
tryToSendInput
|
||||
}
|
7
src/renderer/ipc.js
Normal file
7
src/renderer/ipc.js
Normal file
@ -0,0 +1,7 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
module.exports = {
|
||||
quit() {
|
||||
ipcRenderer.invoke('quit');
|
||||
}
|
||||
}
|
52
src/renderer/screen.js
Normal file
52
src/renderer/screen.js
Normal file
@ -0,0 +1,52 @@
|
||||
const { videoModeBufferView } = require('./video');
|
||||
const { audioContext } = require('./audio');
|
||||
|
||||
const SCREEN_WIDTH = 800;
|
||||
const SCREEN_HEIGHT = 600;
|
||||
const SCREEN_BUFFER_SIZE = SCREEN_WIDTH * SCREEN_HEIGHT * 4; // 32bpp;
|
||||
|
||||
const screenBuffer = new SharedArrayBuffer(SCREEN_BUFFER_SIZE);
|
||||
const screenBufferView = new Uint8Array(screenBuffer);
|
||||
|
||||
canvas.width = SCREEN_WIDTH;
|
||||
canvas.height = SCREEN_HEIGHT;
|
||||
|
||||
const canvasCtx = canvas.getContext('2d');
|
||||
const imageData = canvasCtx.createImageData(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
function drawScreen() {
|
||||
const pixelsRGBA = imageData.data;
|
||||
const numPixels = SCREEN_WIDTH * SCREEN_HEIGHT;
|
||||
const expandedFromPalettedMode = videoModeBufferView[3];
|
||||
const start = audioContext.currentTime;
|
||||
|
||||
if (expandedFromPalettedMode) {
|
||||
for (var i = 0; i < numPixels; i++) {
|
||||
// palette
|
||||
pixelsRGBA[i * 4 + 0] = screenBufferView[i * 4 + 0];
|
||||
pixelsRGBA[i * 4 + 1] = screenBufferView[i * 4 + 1];
|
||||
pixelsRGBA[i * 4 + 2] = screenBufferView[i * 4 + 2];
|
||||
pixelsRGBA[i * 4 + 3] = 255; // full opacity
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < numPixels; i++) {
|
||||
// ARGB
|
||||
pixelsRGBA[i * 4 + 0] = screenBufferView[i * 4 + 1];
|
||||
pixelsRGBA[i * 4 + 1] = screenBufferView[i * 4 + 2];
|
||||
pixelsRGBA[i * 4 + 2] = screenBufferView[i * 4 + 3];
|
||||
pixelsRGBA[i * 4 + 3] = 255; // full opacity
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
canvasCtx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
screenBuffer,
|
||||
screenBufferView,
|
||||
SCREEN_BUFFER_SIZE,
|
||||
drawScreen,
|
||||
SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT
|
||||
}
|
13
src/renderer/video.js
Normal file
13
src/renderer/video.js
Normal file
@ -0,0 +1,13 @@
|
||||
const { releaseTwoStateLock } = require('./atomics')
|
||||
|
||||
const VIDEO_MODE_BUFFER_SIZE = 10;
|
||||
const videoModeBuffer = new SharedArrayBuffer(VIDEO_MODE_BUFFER_SIZE * 4);
|
||||
const videoModeBufferView = new Int32Array(videoModeBuffer);
|
||||
|
||||
releaseTwoStateLock(videoModeBufferView, 9);
|
||||
|
||||
module.exports = {
|
||||
VIDEO_MODE_BUFFER_SIZE,
|
||||
videoModeBuffer,
|
||||
videoModeBufferView
|
||||
}
|
50
src/renderer/worker.js
Normal file
50
src/renderer/worker.js
Normal file
@ -0,0 +1,50 @@
|
||||
const { inputBuffer, INPUT_BUFFER_SIZE } = require('./input')
|
||||
const { videoModeBuffer, VIDEO_MODE_BUFFER_SIZE } = require('./video')
|
||||
const { screenBuffer, SCREEN_BUFFER_SIZE, SCREEN_WIDTH, SCREEN_HEIGHT } = require('./screen')
|
||||
const { audio, audioDataBuffer, audioBlockChunkSize, AUDIO_DATA_BUFFER_SIZE } = require('./audio')
|
||||
|
||||
function registerWorker() {
|
||||
var workerConfig = {
|
||||
inputBuffer: inputBuffer,
|
||||
inputBufferSize: INPUT_BUFFER_SIZE,
|
||||
screenBuffer: screenBuffer,
|
||||
screenBufferSize: SCREEN_BUFFER_SIZE,
|
||||
videoModeBuffer: videoModeBuffer,
|
||||
videoModeBufferSize: VIDEO_MODE_BUFFER_SIZE,
|
||||
audioDataBuffer: audioDataBuffer,
|
||||
audioDataBufferSize: AUDIO_DATA_BUFFER_SIZE,
|
||||
audioBlockBufferSize: audio.bufferSize,
|
||||
audioBlockChunkSize: audioBlockChunkSize,
|
||||
SCREEN_WIDTH: SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT: SCREEN_HEIGHT,
|
||||
};
|
||||
|
||||
var worker = new Worker('../basilisk/BasiliskII-worker-boot.js');
|
||||
|
||||
worker.postMessage(workerConfig);
|
||||
worker.onmessage = function(e) {
|
||||
if (
|
||||
e.data.type === 'emulator_ready' ||
|
||||
e.data.type === 'emulator_loading'
|
||||
) {
|
||||
// document.body.className =
|
||||
// e.data.type === 'emulator_ready' ? '' : 'loading';
|
||||
|
||||
// const progressElement = document.getElementById('progress');
|
||||
|
||||
// if (progressElement && e.data.type === 'emulator_loading') {
|
||||
// progressElement.value = Math.max(10, e.data.completion * 100);
|
||||
// progressElement.max = 100;
|
||||
// progressElement.hidden = false;
|
||||
// } else {
|
||||
// progressElement.value = null;
|
||||
// progressElement.max = null;
|
||||
// progressElement.hidden = true;
|
||||
// }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerWorker
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user