mirror of
https://github.com/felixrieseberg/macintosh.js.git
synced 2025-02-06 18:30:13 +00:00
feat: Somewhat reliable shutdown
This commit is contained in:
parent
7133d6f7d8
commit
ccedc1920d
10
package.json
10
package.json
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "macintosh",
|
||||
"productName": "macintosh",
|
||||
"name": "macintosh.js",
|
||||
"productName": "macintosh.js",
|
||||
"version": "1.0.0",
|
||||
"description": "Macintosh's System 7 in an Electron app. I'm sorry.",
|
||||
"description": "Macintosh's System 8 in an Electron app. I'm sorry.",
|
||||
"main": "src/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"lint": "npx prettier --check src/{main,renderer}/*.js",
|
||||
"fix": "npx prettier --write src/{main,renderer}/*.js"
|
||||
"lint": "npx prettier --check src/{main,renderer}/*.{js,css}",
|
||||
"fix": "npx prettier --write src/{main,renderer}/*.{js,css}"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
|
@ -1,30 +1,9 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const memAllocSet = new Set();
|
||||
const 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) {
|
||||
@ -37,10 +16,10 @@ function pathGetFilename(path) {
|
||||
}
|
||||
|
||||
function addAutoloader(module) {
|
||||
var loadDatafiles = function() {
|
||||
module.autoloadFiles.forEach(function(filepath) {
|
||||
var loadDatafiles = function () {
|
||||
module.autoloadFiles.forEach(function (filepath) {
|
||||
module.FS_createPreloadedFile(
|
||||
'/',
|
||||
"/",
|
||||
pathGetFilename(filepath),
|
||||
filepath,
|
||||
true,
|
||||
@ -61,10 +40,10 @@ function addCustomAsyncInit(module) {
|
||||
if (module.asyncInit) {
|
||||
module.preRun = module.preRun || [];
|
||||
module.preRun.push(function waitForCustomAsyncInit() {
|
||||
module.addRunDependency('__moduleAsyncInit');
|
||||
module.addRunDependency("__moduleAsyncInit");
|
||||
|
||||
module.asyncInit(module, function asyncInitCallback() {
|
||||
module.removeRunDependency('__moduleAsyncInit');
|
||||
module.removeRunDependency("__moduleAsyncInit");
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -90,22 +69,26 @@ var LockStates = {
|
||||
|
||||
var Module = null;
|
||||
|
||||
self.onmessage = function(msg) {
|
||||
console.log('Worker message received', msg.data);
|
||||
self.onmessage = function (msg) {
|
||||
console.log("Worker message received", msg.data);
|
||||
|
||||
// If it's a config object, start the show
|
||||
if (msg && msg.data && msg.data.SCREEN_WIDTH) {
|
||||
console.log('Start emulator worker');
|
||||
startEmulator(Object.assign({}, msg.data, {singleThreadedEmscripten: true}));
|
||||
console.log("Start emulator worker");
|
||||
startEmulator(
|
||||
Object.assign({}, msg.data, { singleThreadedEmscripten: true })
|
||||
);
|
||||
}
|
||||
|
||||
if (msg && msg.data === 'save') {
|
||||
const diskData = Module.FS.readFile('/disk');
|
||||
const diskPath = path.join(__dirname, 'disk');
|
||||
if (msg && msg.data === "disk_save") {
|
||||
const diskData = Module.FS.readFile("/disk");
|
||||
const diskPath = path.join(__dirname, "disk");
|
||||
|
||||
fs.writeFile(diskPath, diskData, (error) => {
|
||||
console.log(`Finished writing disk`);
|
||||
|
||||
postMessage({ type: "disk_saved" });
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -140,20 +123,6 @@ function startEmulator(parentConfig) {
|
||||
);
|
||||
|
||||
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(
|
||||
@ -217,9 +186,9 @@ function startEmulator(parentConfig) {
|
||||
var AudioBufferQueue = [];
|
||||
|
||||
Module = {
|
||||
autoloadFiles: ['disk', 'rom', 'prefs'],
|
||||
autoloadFiles: ["disk", "rom", "prefs"],
|
||||
|
||||
arguments: ['--config', 'prefs'],
|
||||
arguments: ["--config", "prefs"],
|
||||
canvas: null,
|
||||
|
||||
blit: function blit(bufPtr, width, height, depth, usingPalette) {
|
||||
@ -266,7 +235,7 @@ function startEmulator(parentConfig) {
|
||||
|
||||
if (audioDataBufferView[writingChunkAddr] === LockStates.UI_THREAD_LOCK) {
|
||||
console.warn(
|
||||
'worker tried to write audio data to UI-thread-locked chunk',
|
||||
"worker tried to write audio data to UI-thread-locked chunk",
|
||||
writingChunkIndex
|
||||
);
|
||||
return 0;
|
||||
@ -279,7 +248,6 @@ function startEmulator(parentConfig) {
|
||||
) {
|
||||
nextNextChunkIndex = 0;
|
||||
}
|
||||
// console.assert(nextNextChunkIndex != writingChunkIndex, `writingChunkIndex=${nextNextChunkIndex} == nextChunkIndex=${nextNextChunkIndex}`)
|
||||
|
||||
audioDataBufferView[writingChunkAddr + 1] = nextNextChunkIndex;
|
||||
audioDataBufferView.set(newAudio, writingChunkAddr + 2);
|
||||
@ -290,7 +258,7 @@ function startEmulator(parentConfig) {
|
||||
},
|
||||
|
||||
debugPointer: function debugPointer(ptr) {
|
||||
console.log('debugPointer', ptr);
|
||||
console.log("debugPointer", ptr);
|
||||
},
|
||||
|
||||
acquireInputLock: acquireInputLock,
|
||||
@ -302,23 +270,25 @@ function startEmulator(parentConfig) {
|
||||
},
|
||||
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left) {
|
||||
monitorRunDependencies: function (left) {
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
|
||||
if (left == 0) {
|
||||
postMessage({type: 'emulator_ready'});
|
||||
postMessage({ type: "emulator_ready" });
|
||||
} else {
|
||||
postMessage({
|
||||
type: 'emulator_loading',
|
||||
type: "emulator_loading",
|
||||
completion: (this.totalDependencies - left) / this.totalDependencies,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
print: (message) => {
|
||||
console.log(message);
|
||||
|
||||
postMessage({
|
||||
type: 'TTY',
|
||||
data: message
|
||||
type: "TTY",
|
||||
data: message,
|
||||
});
|
||||
},
|
||||
|
||||
@ -332,6 +302,6 @@ function startEmulator(parentConfig) {
|
||||
addCustomAsyncInit(Module);
|
||||
|
||||
if (parentConfig.singleThreadedEmscripten) {
|
||||
importScripts('BasiliskII.js');
|
||||
importScripts("BasiliskII.js");
|
||||
}
|
||||
}
|
||||
|
@ -5017,9 +5017,13 @@ function copyTempDouble(ptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function _emscripten_get_now() { abort() }function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop, arg, noSetTiming) {
|
||||
function _emscripten_get_now() { abort() }
|
||||
|
||||
function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop, arg, noSetTiming) {
|
||||
Module['noExitRuntime'] = true;
|
||||
|
||||
Module.print('Hiii')
|
||||
|
||||
assert(!Browser.mainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.');
|
||||
|
||||
Browser.mainLoop.func = func;
|
||||
@ -5039,6 +5043,8 @@ function copyTempDouble(ptr) {
|
||||
var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop;
|
||||
|
||||
Browser.mainLoop.runner = function Browser_mainLoop_runner() {
|
||||
Module.print("runner");
|
||||
|
||||
if (ABORT) return;
|
||||
if (Browser.mainLoop.queue.length > 0) {
|
||||
var start = Date.now();
|
||||
|
@ -1,5 +1,5 @@
|
||||
const isDevMode = true;
|
||||
|
||||
module.exports = {
|
||||
isDevMode
|
||||
}
|
||||
isDevMode,
|
||||
};
|
||||
|
@ -4,7 +4,9 @@ function registerIpcHandlers() {
|
||||
ipcMain.handle("quit", () => app.quit());
|
||||
|
||||
ipcMain.handle("devtools", () => {
|
||||
BrowserWindow.getAllWindows().forEach((w) => w.webContents.toggleDevTools());
|
||||
BrowserWindow.getAllWindows().forEach((w) =>
|
||||
w.webContents.toggleDevTools()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { app, BrowserWindow, shell } = require("electron");
|
||||
const path = require("path");
|
||||
|
||||
const { isDevMode } = require('./devmode')
|
||||
const { isDevMode } = require("./devmode");
|
||||
|
||||
const windowList = {};
|
||||
let mainWindow;
|
||||
@ -26,14 +26,14 @@ function handleNewWindow(event, url, frameName, disposition, options) {
|
||||
resizable: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
navigateOnDragDrop: false
|
||||
navigateOnDragDrop: false,
|
||||
},
|
||||
});
|
||||
|
||||
let newWindow = new BrowserWindow(options)
|
||||
let newWindow = new BrowserWindow(options);
|
||||
|
||||
newWindow.webContents.on('will-navigate', (event, url) => {
|
||||
if (url.startsWith('http')) {
|
||||
newWindow.webContents.on("will-navigate", (event, url) => {
|
||||
if (url.startsWith("http")) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
@ -47,7 +47,7 @@ function handleNewWindow(event, url, frameName, disposition, options) {
|
||||
newWindow.webContents.toggleDevTools();
|
||||
}
|
||||
|
||||
newWindow.on('closed', () => {
|
||||
newWindow.on("closed", () => {
|
||||
delete windowList[url];
|
||||
});
|
||||
}
|
||||
@ -66,7 +66,7 @@ function createWindow() {
|
||||
nativeWindowOpen: true,
|
||||
navigateOnDragDrop: false,
|
||||
nodeIntegrationInWorker: true,
|
||||
sandbox: false
|
||||
sandbox: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -82,8 +82,8 @@ function createWindow() {
|
||||
if (isDevMode) {
|
||||
mainWindow.webContents.toggleDevTools();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createWindow
|
||||
}
|
||||
createWindow,
|
||||
};
|
||||
|
10
src/renderer/controls.js
vendored
10
src/renderer/controls.js
vendored
@ -1,8 +1,16 @@
|
||||
const { quit, devtools } = require("./ipc");
|
||||
const { getIsWorkerRunning, getIsWorkerSaving } = require("./worker");
|
||||
const { showCloseWarning } = require("./dialogs");
|
||||
|
||||
function registerControls() {
|
||||
document.querySelector("#close").addEventListener("click", () => {
|
||||
quit();
|
||||
if (!getIsWorkerRunning()) {
|
||||
quit();
|
||||
} else if (!getIsWorkerSaving()) {
|
||||
showCloseWarning();
|
||||
} else {
|
||||
// We're saving, and we're doing nothing. We're making the user wait.
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector("#devtools").addEventListener("click", () => {
|
||||
|
24
src/renderer/dialogs.js
Normal file
24
src/renderer/dialogs.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { quit } = require("./ipc");
|
||||
|
||||
function setupDialogs() {
|
||||
// Still empty
|
||||
}
|
||||
|
||||
function showCloseWarning() {
|
||||
const warningDialog = document.querySelector("#warning");
|
||||
|
||||
document.querySelector("#warning-quit").onclick = () => {
|
||||
quit();
|
||||
};
|
||||
|
||||
document.querySelector("#warning-cancel").onclick = () => {
|
||||
warningDialog.classList.add("hidden");
|
||||
};
|
||||
|
||||
warningDialog.classList.remove("hidden");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupDialogs,
|
||||
showCloseWarning,
|
||||
};
|
@ -1,7 +1,8 @@
|
||||
const { drawScreen } = require("./screen");
|
||||
const { openAudio } = require("./audio");
|
||||
const { tryToSendInput } = require("./input");
|
||||
const { registerWorker } = require("./worker");
|
||||
const { registerWorker, setCanvasBlank } = require("./worker");
|
||||
const { setupDialogs } = require("./dialogs");
|
||||
|
||||
function asyncLoop() {
|
||||
drawScreen();
|
||||
@ -9,8 +10,9 @@ function asyncLoop() {
|
||||
requestAnimationFrame(asyncLoop);
|
||||
}
|
||||
|
||||
function start() {
|
||||
async function start() {
|
||||
registerWorker();
|
||||
setupDialogs();
|
||||
openAudio();
|
||||
asyncLoop();
|
||||
}
|
||||
|
@ -2,19 +2,44 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<title>macintosh.js</title>
|
||||
<link rel="stylesheet" href="style/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<body class="emulator_running">
|
||||
<canvas id="canvas" width="800" height="600" oncontextmenu="event.preventDefault()"></canvas>
|
||||
<div class="controls">
|
||||
<div id="disk_saving" class="dialog absolute_center hidden">
|
||||
<p>Saving virtual machine disk.</p>
|
||||
<p>We'll quit once done.</p>
|
||||
<div class="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div class="controls garamond">
|
||||
<div class="clear">
|
||||
<a id="close" href="#">Quit</a>
|
||||
<a id="credits" href="#" onclick="window.open('credits.html')">Credits</a>
|
||||
<a id="devtools" href="#">Dev</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="warning">
|
||||
<div id="warning" class="dialog hidden absolute_center">
|
||||
<div>
|
||||
<h1><img src="images/important_note.png" /> Don't lose your data!</h1>
|
||||
<p>
|
||||
Warning: <strong>All changes to your virtual Macintosh will be lost</strong>
|
||||
unless you shut down the virtual machine.
|
||||
</p>
|
||||
<p>
|
||||
Click on the <img width="17" src="images/finder_icon.png" /> icon
|
||||
in the upper right and select "Finder". Then, in the menu bar at the
|
||||
top of the screen, click on "Special" and select "Shut Down".
|
||||
</p>
|
||||
|
||||
<button id="warning-quit">
|
||||
Quit anyway
|
||||
</button>
|
||||
|
||||
<button id="warning-cancel">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./controls.js"></script>
|
||||
<script src="./emulator.js"></script>
|
||||
|
@ -7,5 +7,5 @@ module.exports = {
|
||||
|
||||
devtools() {
|
||||
ipcRenderer.invoke("devtools");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -14,7 +14,10 @@ canvas.height = SCREEN_HEIGHT;
|
||||
const canvasCtx = canvas.getContext("2d");
|
||||
const imageData = canvasCtx.createImageData(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
let stopDrawing = false;
|
||||
|
||||
function drawScreen() {
|
||||
if (stopDrawing) return;
|
||||
const pixelsRGBA = imageData.data;
|
||||
const numPixels = SCREEN_WIDTH * SCREEN_HEIGHT;
|
||||
const expandedFromPalettedMode = videoModeBufferView[3];
|
||||
@ -41,6 +44,26 @@ function drawScreen() {
|
||||
canvasCtx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
function setCanvasBlank() {
|
||||
return new Promise((resolve) => {
|
||||
stopDrawing = true;
|
||||
const canvas = document.getElementById("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const bg = new Image();
|
||||
|
||||
// Clear
|
||||
ctx.canvas.width = ctx.canvas.width;
|
||||
|
||||
bg.onload = () => {
|
||||
const pattern = ctx.createPattern(bg, "repeat");
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
resolve();
|
||||
};
|
||||
bg.src = "images/off_bg.png";
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
screenBuffer,
|
||||
screenBufferView,
|
||||
@ -48,4 +71,5 @@ module.exports = {
|
||||
drawScreen,
|
||||
SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT,
|
||||
setCanvasBlank,
|
||||
};
|
||||
|
@ -3,13 +3,71 @@
|
||||
src: url(garamond.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Oswald;
|
||||
src: url(oswald.ttf);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Garamond, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: Oswald, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.garamond {
|
||||
font-family: Garamond, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;;
|
||||
}
|
||||
|
||||
:root {
|
||||
--canvas-width: 800px;
|
||||
--canvas-height: 600px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-image: url(../images/button_left.png), url(../images/button_right.png), url(../images/button_middle.png);
|
||||
background-position: left, right, center;
|
||||
background-repeat: no-repeat, no-repeat, repeat-x;
|
||||
background-size: contain;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 5px 9px 6px 9px;
|
||||
font-family: Oswald;
|
||||
margin-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
button:nth-of-type(1) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
button:active {
|
||||
color: #fff;
|
||||
background-image: url(../images/button_left_pressed.png), url(../images/button_right_pressed.png), url(../images/button_middle_pressed.png);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.absolute_center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 40%;
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background: #ddd;
|
||||
z-index: 100;
|
||||
padding: 0 20px;
|
||||
border-left: 1px solid #fff;
|
||||
border-top: 1px solid #fff;
|
||||
border-right: 1px solid #aaa;
|
||||
border-bottom: 1px solid #aaa;
|
||||
outline: 1px solid #000;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
}
|
@ -1,6 +1,12 @@
|
||||
@import "base.css";
|
||||
@import "spinner.css";
|
||||
|
||||
canvas {
|
||||
:root {
|
||||
--warning-height: 250px;
|
||||
--warning-width: 600px;
|
||||
}
|
||||
|
||||
.emulator_running canvas {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
@ -17,6 +23,8 @@ canvas {
|
||||
background-repeat: no-repeat, no-repeat, repeat-x;
|
||||
background-size: contain;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.controls .clear {
|
||||
@ -41,12 +49,10 @@ canvas {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.emulator_running #turned_off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#warning {
|
||||
height: 150px;
|
||||
width: 350px;
|
||||
background: #ddd;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 200px;
|
||||
left: 225px
|
||||
}
|
||||
width: var(--warning-width);
|
||||
}
|
||||
|
BIN
src/renderer/style/oswald.ttf
Normal file
BIN
src/renderer/style/oswald.ttf
Normal file
Binary file not shown.
80
src/renderer/style/spinner.css
Normal file
80
src/renderer/style/spinner.css
Normal file
@ -0,0 +1,80 @@
|
||||
.lds-spinner {
|
||||
color: official;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
transform: scale(0.5);
|
||||
margin: auto;
|
||||
}
|
||||
.lds-spinner div {
|
||||
transform-origin: 40px 40px;
|
||||
animation: lds-spinner 1.2s linear infinite;
|
||||
}
|
||||
.lds-spinner div:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 37px;
|
||||
width: 6px;
|
||||
height: 18px;
|
||||
border-radius: 20%;
|
||||
background: #000;
|
||||
}
|
||||
.lds-spinner div:nth-child(1) {
|
||||
transform: rotate(0deg);
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(2) {
|
||||
transform: rotate(30deg);
|
||||
animation-delay: -1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(3) {
|
||||
transform: rotate(60deg);
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
.lds-spinner div:nth-child(4) {
|
||||
transform: rotate(90deg);
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
.lds-spinner div:nth-child(5) {
|
||||
transform: rotate(120deg);
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
.lds-spinner div:nth-child(6) {
|
||||
transform: rotate(150deg);
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
.lds-spinner div:nth-child(7) {
|
||||
transform: rotate(180deg);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
.lds-spinner div:nth-child(8) {
|
||||
transform: rotate(210deg);
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
.lds-spinner div:nth-child(9) {
|
||||
transform: rotate(240deg);
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-spinner div:nth-child(10) {
|
||||
transform: rotate(270deg);
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
.lds-spinner div:nth-child(11) {
|
||||
transform: rotate(300deg);
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
.lds-spinner div:nth-child(12) {
|
||||
transform: rotate(330deg);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
@keyframes lds-spinner {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
const { inputBuffer, INPUT_BUFFER_SIZE } = require("./input");
|
||||
const { videoModeBuffer, VIDEO_MODE_BUFFER_SIZE } = require("./video");
|
||||
const { setCanvasBlank } = require("./screen");
|
||||
const {
|
||||
screenBuffer,
|
||||
SCREEN_BUFFER_SIZE,
|
||||
@ -12,6 +13,43 @@ const {
|
||||
audioBlockChunkSize,
|
||||
AUDIO_DATA_BUFFER_SIZE,
|
||||
} = require("./audio");
|
||||
const { quit } = require("./ipc");
|
||||
|
||||
let isWorkerRunning = false;
|
||||
let isWorkerSaving = false;
|
||||
let worker;
|
||||
|
||||
function getIsWorkerRunning() {
|
||||
return isWorkerRunning;
|
||||
}
|
||||
|
||||
function getIsWorkerSaving() {
|
||||
return isWorkerSaving;
|
||||
}
|
||||
|
||||
function saveDisk() {
|
||||
isWorkerSaving = true;
|
||||
document.querySelector("#disk_saving").classList.remove("hidden");
|
||||
worker.postMessage("disk_save");
|
||||
}
|
||||
|
||||
function handleDiskSaved() {
|
||||
isWorkerSaving = false;
|
||||
|
||||
// We're just gonna quit
|
||||
quit();
|
||||
}
|
||||
|
||||
async function handleWorkerShutdown() {
|
||||
console.log(`Handling worker shutdown`);
|
||||
|
||||
document.body.classList.remove("emulator_running");
|
||||
|
||||
// Then, update the canvas
|
||||
await setCanvasBlank();
|
||||
|
||||
saveDisk();
|
||||
}
|
||||
|
||||
function registerWorker() {
|
||||
var workerConfig = {
|
||||
@ -29,7 +67,12 @@ function registerWorker() {
|
||||
SCREEN_HEIGHT: SCREEN_HEIGHT,
|
||||
};
|
||||
|
||||
var worker = window.emulatorWorker = new Worker("../basilisk/BasiliskII-worker-boot.js");
|
||||
worker = window.emulatorWorker = new Worker(
|
||||
"../basilisk/BasiliskII-worker-boot.js"
|
||||
);
|
||||
|
||||
// We'll need this info
|
||||
isWorkerRunning = true;
|
||||
|
||||
worker.postMessage(workerConfig);
|
||||
worker.onmessage = function (e) {
|
||||
@ -51,23 +94,26 @@ function registerWorker() {
|
||||
// }
|
||||
}
|
||||
|
||||
if (e.data.type === 'TTY') {
|
||||
if (e.data.type === "TTY") {
|
||||
// If we're shutting down, Basilisk II will send
|
||||
// close_audio to TTY - our signal that we can
|
||||
// save the disk image
|
||||
if (e.data.message === 'close_audio') {
|
||||
worker.postMessage('save');
|
||||
if (e.data.data === "close_audio") {
|
||||
handleWorkerShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.data.type === "disk_saved") {
|
||||
handleDiskSaved();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function saveDisk() {
|
||||
|
||||
|
||||
worker.postMessage('save');
|
||||
}
|
||||
window.setCanvasBlank = setCanvasBlank;
|
||||
|
||||
module.exports = {
|
||||
registerWorker,
|
||||
getIsWorkerRunning,
|
||||
getIsWorkerSaving,
|
||||
setCanvasBlank,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user