WEBVTT 00:00:00.880 --> 00:00:04.520 Hello everyone and welcome to my talk, "The State of Retro Gaming and Emacs." 00:00:06.960 --> 00:00:08.639 First of all, a little bit about myself. 00:00:08.639 --> 00:00:12.000 My name is Vasilij Schneidermann. I'm 28 years old. 00:00:12.000 --> 00:00:14.719 I work as a cyber security consultant at msg systems, 00:00:14.719 --> 00:00:17.359 and test other people's web applications 00:00:17.359 --> 00:00:20.160 and review the source code for security problems. 00:00:20.160 --> 00:00:22.080 You can reach me by email. 00:00:22.080 --> 00:00:25.039 I have my own self-hosted git repositories, 00:00:25.039 --> 00:00:28.160 and I have a blog where you can occasionally find new posts by me 00:00:28.160 --> 00:00:32.160 on all kinds of things, not just Emacs things. 00:00:32.160 --> 00:00:34.559 The motivation about this one... 00:00:34.559 --> 00:00:37.600 I found that Emacs is the ultimate procrastination machine, 00:00:37.600 --> 00:00:39.600 and there are lots of fun demonstrations. 00:00:39.600 --> 00:00:41.200 I'll go over a few of them. 00:00:41.200 --> 00:00:45.840 For example, someone made a thing to order salad for himself online, 00:00:45.840 --> 00:00:48.239 so he doesn't have to walk over to the shop. 00:00:48.239 --> 00:00:51.760 There's plenty of IRC bots. There's some game things. 00:00:51.760 --> 00:00:55.600 There's an emulator for the Z-machine which you can use to play zork. 00:00:55.600 --> 00:00:57.440 And so I asked myself, at this point, 00:00:57.440 --> 00:00:59.920 can you actually emulate retro games at 60fps? 00:00:59.920 --> 00:01:02.079 I looked around a bit and found some projects, 00:01:02.079 --> 00:01:06.159 but none that were actually able to do it at 60fps. 00:01:06.159 --> 00:01:08.000 So I set out to do my own one, 00:01:08.000 --> 00:01:09.200 and looked out for a console 00:01:09.200 --> 00:01:11.119 that you can actually emulate at that speed, 00:01:11.119 --> 00:01:14.690 using Emacs with its very, very limited rendering. 00:01:16.320 --> 00:01:19.200 And here's the project, chip8.el. 00:01:19.200 --> 00:01:20.560 It's pretty much finished. 00:01:20.560 --> 00:01:24.000 It clocks into under 1000 source lines of code. 00:01:24.000 --> 00:01:26.159 It supports the superchip 8 extensions. 00:01:26.159 --> 00:01:27.280 It runs at full speed. 00:01:27.280 --> 00:01:29.600 All games behave okay, as far as I'm concerned, 00:01:29.600 --> 00:01:31.680 and yeah, I'm pretty happy with it. 00:01:31.680 --> 00:01:34.479 It's very much the hello world of emulation, 00:01:34.479 --> 00:01:40.880 and I might, maybe, do some other emulation projects in the future. 00:01:40.880 --> 00:01:43.360 Now, for the section which is the longest: 00:01:43.360 --> 00:01:45.439 bunch of fun facts about chip8.el 00:01:45.439 --> 00:01:49.200 which I've learned during this project. 00:01:49.200 --> 00:01:51.759 So what the hell is chip8 anyway? 00:01:51.759 --> 00:01:54.960 First of all, unlike many other emulation game things, 00:01:54.960 --> 00:01:56.799 it's not a console, but a VM. 00:01:56.799 --> 00:02:00.000 It was designed for easy porting of home computer games. 00:02:00.000 --> 00:02:02.320 It wasn't terribly successful, 00:02:02.320 --> 00:02:05.439 but there's still a small community of enthusiasts writing games for it, 00:02:05.439 --> 00:02:09.119 and there are even a few demos. 00:02:09.119 --> 00:02:11.039 This VM has system specs. 00:02:11.039 --> 00:02:14.720 It has a very, very simple 8-bit cpu with 16 registers, 00:02:14.720 --> 00:02:17.280 and 36 fixed-size instructions. 00:02:17.280 --> 00:02:19.680 You have a whole 4 kilobyte of RAM. 00:02:19.680 --> 00:02:22.080 You have a stack with 16 return addresses. 00:02:22.080 --> 00:02:25.760 The resolution is 64 by 32 black/white pixels. 00:02:25.760 --> 00:02:28.000 Rendering is done by drawing sprites. 00:02:28.000 --> 00:02:29.200 These are drawn in XOR mode, 00:02:29.200 --> 00:02:31.840 meaning that if you draw a sprite and set a bit, 00:02:31.840 --> 00:02:35.040 it just flips over from black to white or white to black. 00:02:35.040 --> 00:02:39.360 For sound, you have a monotone buzzer that can just beep at one frequency. 00:02:39.360 --> 00:02:43.120 Most unusually, there's a hexadecimal keypad as input, 00:02:43.120 --> 00:02:48.480 so the keys are basically zero to nine and a to f. 00:02:48.480 --> 00:02:50.720 So how does this whole thing work? 00:02:50.720 --> 00:02:52.400 It runs at an unspecified speed. 00:02:52.400 --> 00:02:53.040 You'll probably have to do some fine-tuning 00:02:53.040 --> 00:02:56.080 to find the speed you're happy with. 00:02:56.080 --> 00:02:58.080 Sound and delay timers exist. 00:02:58.080 --> 00:03:01.120 They count down at 60fps down to 0. 00:03:01.120 --> 00:03:05.120 This is done so that you can play a sound at some specific time. 00:03:05.120 --> 00:03:07.840 The game itself is loaded with a fixed offset into RAM. 00:03:07.840 --> 00:03:10.480 The program counter is set to exactly that offset, 00:03:10.480 --> 00:03:11.920 and from there it enters the game loop 00:03:11.920 --> 00:03:13.280 where it decodes an instruction, 00:03:13.280 --> 00:03:14.800 executes it for the side effects, 00:03:14.800 --> 00:03:18.130 and just loops and does this ad infinitum. 00:03:19.599 --> 00:03:22.720 So the game loop was the first thing where we ran into problems. 00:03:22.720 --> 00:03:25.120 The usual game approach is to do stuff, 00:03:25.120 --> 00:03:26.640 figure out how long to wait, 00:03:26.640 --> 00:03:29.280 wait for exactly that much, and repeat. 00:03:29.280 --> 00:03:31.680 This doesn't work well in Emacs at all, because, well, 00:03:31.680 --> 00:03:34.959 user input, basically. 00:03:34.959 --> 00:03:37.760 Emacs is designed to just do whatever it needs to do 00:03:37.760 --> 00:03:39.040 whenever you enter user input 00:03:39.040 --> 00:03:42.319 instead of doing things at one specific time. 00:03:42.319 --> 00:03:46.640 If you try to do interruptable sleep, well, you get unpredictable behavior. 00:03:46.640 --> 00:03:50.480 For example, it can be the timer doesn't run at all at the next time 00:03:50.480 --> 00:03:52.560 because you've accidentally cancelled it. 00:03:52.560 --> 00:03:55.120 If you do uninterruptable sleep, it freezes instead , 00:03:55.120 --> 00:03:56.720 which isn't what we want either. 00:03:56.720 --> 00:04:00.560 So I went for timers, which forced me to do inversion of control, 00:04:00.560 --> 00:04:02.560 meaning that I have to write code in the style 00:04:02.560 --> 00:04:04.879 where it just calls timer, 00:04:04.879 --> 00:04:06.560 and this allows this input to happen 00:04:06.560 --> 00:04:11.040 and for things to progress at roughly the speed I want to. 00:04:11.040 --> 00:04:14.159 So there's the timer function which is called at 60fps 00:04:14.159 --> 00:04:17.359 and I have to be very careful to not do too much in it. 00:04:17.359 --> 00:04:21.305 And, say, this function executes CPU cycles, 00:04:21.305 --> 00:04:26.479 decrement the sound/delay registers, and redraw the screen. 00:04:26.479 --> 00:04:28.800 So to map this whole system to Emacs Lisp, 00:04:28.800 --> 00:04:31.199 I've used just integers and vectors 00:04:31.199 --> 00:04:33.120 which contain even more integers. 00:04:33.120 --> 00:04:35.040 This is used for the RAM, registers, 00:04:35.040 --> 00:04:37.040 return stack, key state, screen, 00:04:37.040 --> 00:04:38.508 and so on and so forth. 00:04:38.508 --> 00:04:41.520 Basically, what you would do if you were writing C. 00:04:41.520 --> 00:04:43.360 All of this is stored in global variables. 00:04:43.360 --> 00:04:45.600 I'm not using any lists at all. 00:04:45.600 --> 00:04:48.400 As a side effect, there's no consing going on at all. 00:04:48.400 --> 00:04:50.080 There are no extra objects created 00:04:50.080 --> 00:04:53.199 which would trigger garbage collection processes. 00:04:53.199 --> 00:04:55.600 Getting this right was rather tricky, actually, 00:04:55.600 --> 00:04:58.240 and there were some hidden garbage collection problems 00:04:58.240 --> 00:05:01.759 which I had to resolve over time. 00:05:01.759 --> 00:05:03.759 So, decoding instructions. 00:05:03.759 --> 00:05:06.800 For this, you have to know that all instructions are two bytes long, 00:05:06.800 --> 00:05:08.880 and the arguments are encoded inside them. 00:05:08.880 --> 00:05:11.440 For example, the jump to address instruction 00:05:11.440 --> 00:05:15.120 is encoded as one and three hex digits. 00:05:15.120 --> 00:05:18.400 The type is extracted masking with #xF000 00:05:18.400 --> 00:05:20.400 and then shifting it by 12 bits. 00:05:20.400 --> 00:05:23.520 Mask means you perform the binary AND. 00:05:23.520 --> 00:05:28.400 You can do the same with the argument by masking with #0xFFF and no shift. 00:05:28.400 --> 00:05:30.560 If you do this long enough, you'll find common patterns. 00:05:30.560 --> 00:05:32.639 For example, addresses are always encoded like this 00:05:32.639 --> 00:05:34.880 using the last three nibbles. 00:05:34.880 --> 00:05:36.160 In the code, you'll find a big cond 00:05:36.160 --> 00:05:40.070 which dispatches on the type and executes it for the side effects. 00:05:41.440 --> 00:05:45.919 For testing, I've initially just executed the ROM until I've hit C-g, 00:05:45.919 --> 00:05:49.039 and then use the debug command to render the screen to a buffer. 00:05:49.039 --> 00:05:53.199 Later on, I found tiny ROMs that just display a static test screen, 00:05:53.199 --> 00:05:57.280 for example, logo, and looked whether it looked right. 00:05:57.280 --> 00:05:58.800 I added instructions as needed 00:05:58.800 --> 00:06:00.720 and went through more and more and more ROMs. 00:06:00.720 --> 00:06:04.000 And later I wrote a unit test suite as a safety net. 00:06:04.000 --> 00:06:07.840 This unit test suite, it just sets up an empty emulator state, 00:06:07.840 --> 00:06:09.199 executes some instructions, 00:06:09.199 --> 00:06:14.880 and then looks whether the expected side effects have happened. 00:06:14.880 --> 00:06:18.319 For debugging, I usually use edebug, but this was super ineffective, 00:06:18.319 --> 00:06:21.600 because, well, you don't really want to step through big cons 00:06:21.600 --> 00:06:23.680 doing side effects for every single cycle, 00:06:23.680 --> 00:06:26.880 when it can take like 100 cycles for things to happen. 00:06:26.880 --> 00:06:29.680 Therefore I've set up logging. 00:06:29.680 --> 00:06:32.639 Whenever I logged something and couldn't figure out the error, 00:06:32.639 --> 00:06:37.039 I compared my log output with the instrumented version of another emulator, 00:06:37.039 --> 00:06:40.479 and if the logs diverge, then I have figured out where the bug lies 00:06:40.479 --> 00:06:42.720 and could look deeper into it. 00:06:42.720 --> 00:06:44.960 Future project idea might be a chip 8 debugger, 00:06:44.960 --> 00:06:49.440 but I doubt I'll ever go into it. 00:06:49.440 --> 00:06:51.759 For analysis, I initially wrote a disassembler, 00:06:51.759 --> 00:06:54.400 which is a very simple thing but super tedious, 00:06:54.400 --> 00:06:56.639 especially if you wanted to add advanced functionality, 00:06:56.639 --> 00:06:58.720 for example, analysis or thinking of what part is data, 00:06:58.720 --> 00:07:00.000 what part is code. 00:07:00.000 --> 00:07:03.360 I had this great idea for using the radare 2 framework 00:07:03.360 --> 00:07:06.479 and adding analysis and disassembly plug-in for it. 00:07:06.479 --> 00:07:08.400 So I looked into this. Found, okay, 00:07:08.400 --> 00:07:10.319 you can write plugins in C 00:07:10.319 --> 00:07:12.639 but also in Python, so I wrote one in Python, 00:07:12.639 --> 00:07:14.720 and then discovered there's actually an existing one in core, 00:07:14.720 --> 00:07:18.400 which you have to enable explicitly by passing an extra argument. 00:07:18.400 --> 00:07:21.680 I've tried it and found it's not exactly as good as my own one, 00:07:21.680 --> 00:07:24.160 so I improved this one and submitted pull requests 00:07:24.160 --> 00:07:26.610 until it was at the same level. 00:07:28.080 --> 00:07:30.720 Rendering was the trickiest part of this whole thing, 00:07:30.720 --> 00:07:34.319 because, well, I decided against using a library. 00:07:34.319 --> 00:07:37.120 Not like there would have been any usable library for this. 00:07:37.120 --> 00:07:40.880 My usual approach of creating SVG files was too expensive. 00:07:40.880 --> 00:07:45.120 It just created too much garbage and took too long time. 00:07:45.120 --> 00:07:47.360 I then tried creating mutating strings. 00:07:47.360 --> 00:07:52.479 This was either too expensive, just like SVGs, or too complicated. 00:07:52.479 --> 00:07:57.280 I tried changing SVG tiles, which created gaps between the lines. 00:07:57.280 --> 00:08:00.720 Then I tried to create an xpm file which was backed by a bool vector 00:08:00.720 --> 00:08:02.400 and mutating this bool vector, 00:08:02.400 --> 00:08:04.000 but the image caching effect 00:08:04.000 --> 00:08:06.479 made it just every nth frame to appear, 00:08:06.479 --> 00:08:08.879 which wasn't good either. 00:08:08.879 --> 00:08:11.440 Then I had the idea to just use plain text 00:08:11.440 --> 00:08:13.120 and paint the individual characters 00:08:13.120 --> 00:08:14.800 with a different background color. 00:08:14.800 --> 00:08:17.120 This had perfect, perfect performance. 00:08:17.120 --> 00:08:19.280 There were many optimization attempts until I got there, 00:08:19.280 --> 00:08:21.199 and it was very, very stressful. 00:08:21.199 --> 00:08:26.160 I wasn't sure whether I would ever get to accept the performance at all. 00:08:26.160 --> 00:08:28.560 For sound you only need to do a single beep, 00:08:28.560 --> 00:08:31.280 so technically, it shouldn't be difficult to emulate it. 00:08:31.280 --> 00:08:33.039 However, doing this is hard because 00:08:33.039 --> 00:08:37.200 Emacs officially only supports synchronous playback of sounds. 00:08:37.200 --> 00:08:41.360 But there's also Emacs process, which you can launch in asynchronous way. 00:08:41.360 --> 00:08:44.720 So I looked into it and found that mplayer has a slave mode 00:08:44.720 --> 00:08:48.640 and mpv supports listing on the fifo for commands. 00:08:48.640 --> 00:08:53.760 So I've created a pipe, started a paused MPV in loop mode, 00:08:53.760 --> 00:08:56.560 and always send in pause and unpause command to the FIFO, 00:08:56.560 --> 00:08:58.000 and that way I could control 00:08:58.000 --> 00:09:02.640 when to start beeping and stop beeping. 00:09:02.640 --> 00:09:04.160 So yeah, that's it so far. 00:09:04.160 --> 00:09:07.200 It was a very educational experience. 00:09:07.200 --> 00:09:10.320 I have tried out a bunch of games which were, 00:09:10.320 --> 00:09:14.320 well, I almost say the worst ports of classic games I've ever tried. 00:09:14.320 --> 00:09:15.680 It wasn't terribly fun to play them, 00:09:15.680 --> 00:09:18.555 but was fun to improve the emulator 00:09:18.555 --> 00:09:21.760 until, well, things worked good enough. 00:09:21.760 --> 00:09:25.120 I've learned a lot about how computers work at this level, 00:09:25.120 --> 00:09:28.880 so, maybe, maybe I'll in the future make another emulator, 00:09:28.880 --> 00:09:34.000 but I'm not sure whether anything more advanced, like an Intel 8080 emulator, 00:09:34.000 --> 00:09:36.560 will actually run in Emacs fast enough, 00:09:36.560 --> 00:09:37.839 but it's still an interesting idea, 00:09:37.839 --> 00:09:40.800 because then you could actually have an OS inside Emacs 00:09:40.800 --> 00:09:43.120 and fulfill that one specific meme. 00:09:43.120 --> 00:09:45.440 But if I try to do most serious stuff, 00:09:45.440 --> 00:09:47.040 I'll probably use Chicken Scheme, 00:09:47.040 --> 00:09:49.920 which is my preferred language for serious projects, 00:09:49.920 --> 00:09:53.279 and write a NES game emulator. 00:09:53.279 --> 00:09:57.839 And that's it. Thank you.