Thursday, February 24, 2022

Running retro-b5500 Without a Browser

When we were first considering designing what became the retro-b5500 emulator back in late 2012 and early 2013, Nigel Williams more or less insisted that it be implemented in JavaScript to be run in a standard web browser. So we did, and that turned out to have been one of the best decisions we could have made.

We recognized from the beginning that there were other JavaScript environments that we might want to support one day, so I carefully separated the core components of the emulator (processor, memory, Central Control, I/O channels) from the user interface and peripheral device implementations, as the latter would be heavily dependent upon the specific environment in which the emulator ran. One of the alternate environments I have always had in mind was Node.js. To make that work, though, would require redesigning and reimplementing all of the user and peripheral device interfaces, which would be a lot of work.

Jim Fehlinger recently wrote to me about a way he has found to host the emulator outside of a browser that takes an entirely different approach and allows you to run the emulator essentially as an ordinary desktop application. Jim has long been a friend of and participant in the retro-b5500 project, particularly in doing yeoman work transcribing several large items of B5500 source code from listings.

I thought Jim's approach was so interesting and useful, and so easy to do, that I asked him to write up his experience as the following guest post for this blog. Jim has subsequently reported that he has used this method to run retro-b5500 on a Raspberry Pi, although you will probably need a Pi 4 to get decent performance.


I'm electrifyin', and I ain't even tryin'...
"You Gotta Get a Gimmick", Gypsy

I am a retired computer programmer, well past the age even of those employees of a certain maturity from a well-known computer firm that an executive recently referred to as "dinobabies." The systems I've worked on have been client-server applications predating the Web and its staggering proliferation of technologies that have sprung up since the mid-90s.

But I have been fascinated by emulators since I first discovered their existence in 2004, starting with SimH and Hercules, and I've been playing with Paul Kimpel's and Nigel Williams' retro-b5500 since I first found out about it in 2014. retro-b5500 is an early example of a trend that seems to have become more common since it first appeared -- the idea of writing the emulator using Web technologies (JavaScript/HTML/CSS) and having it run in a web browser, making it both cross-platform by default and easy to host on a web site so that a user can play with it without having to install anything on the local machine.

This seems like a very attractive idea, though there have been potential drawbacks from the beginning, having to do with lurking incompatibilities among the popular Web browsers (Firefox, Chrome, Safari, etc.) In addition, continual browser updates, and tightening up of security constraints, have always made it possible that the next browser update might break compatibility with a Web-based application like retro-b5500. In particular, local storage (in the form of IndexedDB or similar) has always been something that makes the browser "nervous" and is liable to break. Similarly with pop-up windows. Instead of having a no-fuss experience, a user might have to worry about browser security settings, plug-in incompatibilities, and similar woes.

So I was watching a video on the "Computerphile" YouTube channel the other day, in which David Domminney Fowler sings the praises of a couple of fairly recent technologies that he says have revitalized his interest in building applications in a way he's not experienced since first encountering BASIC on an Acorn microcomputer as a child.

Namely, Node.js and Electron, together with the usual JavaScript/HTML/CSS combo.

JavaScript is usually thought of as a programming language for things intended to be run in a browser, but in 2009 a wunderkind named Ryan Dahl came up with a way to encapsulate the Chrome V8 interpreter and Just-in-Time compiler for JavaScript into a browser-independent wrapper, so that JavaScript programs can be run outside of a browser just like any other desktop application, with the same more-or-less unrestricted access to the host computer's file system as other desktop applications. Therefore, people would be able to write both client-side ("front-end") and server-side ("back-end") programs in the same language. Thus was born Node.js.

That was fine as far as it went, but by itself such a JavaScript program would have no way to interact with a DOM, render HTML in a window, or do any of the other things a browser's runtime environment typically provides.

So then, about four years later, the folks at GitHub decided to marry the Chromium HTML rendering engine with Node.js's existing runtime library, to enable a sort of "upside-down" way to build JavaScript applications -- "upside-down" with respect to the usual inside-the-browser approach. You have Node.js providing JavaScript "directly" on top of the operating system (albeit with V8 sandwiched invisibly between), hosting GUI windows each rendered by their own sort of "browserlet" created by the Chromium engine, and using the same runtime API calls an application would use to manipulate windows inside a browser. The biggest criticism of this approach seems to be that it's a resource hog.

Watching that Computerphile video made me wonder...

And so I found a simple tutorial, written with the Mac in mind, but it worked on my Windows 10 machine, too. Following the tutorial, I downloaded and installed:

  • npm (v8.5.0) -- a command-line package manager for Node.js-related stuff
  • node (v16.14.0) -- Node.js itself, packaged with npm in the Windows installer file node-v16.14.0-x64.msi
  • electron (v17.0.0) -- which can be installed via "npm install -g electron" in a command-line window.

I made a package.json file by running "npm init" in a command-line window and answering the npm-prompted questions. Then in a text editor I made a main.js file and specified an HTML entry point for my application, thus:

const {app, BrowserWindow} = require('electron');
const url = require('url');
const path = require('path');

let win;

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600});
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'webUI/B5500Console.html'),
      protocol: 'file:',
      slashes: true
   }));
}

app.on('ready', createWindow);

This code was copied directly from the introductory tutorial with only one modification, replacing

pathname: path.join(__dirname, 'index.html'),

with

pathname: path.join(__dirname, 'webUI/B5500Console.html'),

Then I started it up in a command-line window with:

electron ./main.js

... and I could hardly believe my eyes. It just worked! No modifications to the retro-b5500 code were required at all. I was expecting that at least the parts related to persistent storage might have to be changed, but no, the IndexedDB storage creation worked without complaint. Both the configuration database and the emulated disk went into my C:\Users\My Name\AppData\Roaming\Electron\IndexedDB\file__0.indexeddb.leveldb\ directory.

The Electron-based retro-b5500 has no problems such as you might encounter these days with a standard browser -- windows open up smoothly without delay, except for the initial console window, which takes a few seconds longer to appear than you might be used to. There's no problem creating the local storage, and there's no question of it disappearing unexpectedly when you clear your browser history.

There are also ways to package up Electron applications for distribution, so that the target machines don't have to have the Node and Electron development environments. In that case, Electron itself is all you need. You can either recompile Electron itself if you want certain customizations, or (as I did) use a pre-compiled version corresponding to your target platform, which in my case is electron-v17.1.0-win32-x64.zip. There are also packages for Mac (Darwin) and Linux, for both x86-64 (and 32) and ARM64 available at https://github.com/electron/electron/releases/.

You unzip the Electron package into a directory. In the \resources subdirectory, delete default_app.asar. Create a \resources\app subdirectory and copy all the usual retro-b5500 files into it. The easiest way to get those is to go to the GitHub repository at https://github.com/pkimpel/retro-b5500/, click the green Code button, and select Download ZIP from the drop-down menu. From the resulting zip file, extract just the \emulator, \tools, and \webUI directories into the \resources\app subdirectory. Of course, you will also need at least the B5500 SYSTEM tape image in order to initialize the disk and load the system software, as described in the Getting Started wiki page.

Also into the \resources\app subdirectory, copy the files package.json and main.js that you created as described above. Then all you have to do is double-click electron.exe in the top-level directory, and retro-b5500 will start up automatically with the console screen displayed, and works as usual after that -- as far as I know! Even the hyperlinks on the main console screen work -- they open up additional windows for the linked content.

It is also possible to package up the application-specific files into a *.asar archive and run out of that, but I didn't bother with that route.

When you're running an application in a "packaged" environment (as opposed to via the "electron" command installed by npm on your development machine), the application's local storage goes into a directory named according to the application name given in \resources\app\package.json, which in my case looks like this:

{
  "name": "electro-b5500",
  "version": "1.0.6",
  "description": "Electron version of retro-b5500 1.06",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Paul Kimpel, Nigel Williams",
  "license": "MIT"
}

Although of course the details depend on how you answered the questions when you performed "npm init" to create package.json in the first place. Presumably you can just edit package.json directly if you want to change those details afterward.

And because I called the app "electro-b5500", the local storage now goes into:

C:\Users\My Name\AppData\Roaming\electro-b5500\IndexedDB\file__0.indexeddb.leveldb\*

One disappointing limitation I've discovered is that although the IndexedDB files created by the Chrome browser look similar to those created by Electron (with CURRENT, LOCK, MANIFEST-000001, etc.), you can't just copy your old Chrome IndexedDB files into the Electron directory and expect retro-b5500 to use them. It just doesn't "see" them. It doesn't work the other way, either -- you can't copy the Electron-created IndexedDB files into the Chrome directory and expect retro-b5500 running in a browser to see them.

So that's my little adventure with Electron.js, Node.js and retro-b5500. As I say, I was totally blown away by the fact that this "just worked." And I am the furthest thing imaginable from a modern-day Web developer.

No comments:

Post a Comment