In preparation for an upcoming webinar I decided to build a sizeable Angular 2 project. I’ve always been fascinated with the Commodore 64 and the 6502 chipset is not a tough one to emulate. I also wrote it the first time several years back, so I had a good baseline of code to draw from. Thus the Angular 2 6502 emulator was born.
Building a Console that Works
The first thing I needed was a decent console to give information to the user. The console itself isn’t too difficult. A service holds an array of strings, splices them when it gets beyond a certain size, and exposes a method to clear them.
Here is the basic code:
In a nutshell, this library provides a different mechanism for dealing with asynchronous workflows and streams. You essentially end up observing a collection and are handed off “items” like an iterator and then can deal with it as you choose.
You can see that emitting an event is easy, but what does it look like consuming it?
Interacting with the DOM
The component for the console simply data-binds the console messages to a bunch of text contained inside of a div element. If that’s all it had to do there would be no need for an event emitter. Unfortunately because new items are appended at the bottom, the div can quickly fill up with scroll bars and new messages are no longer in view.
Fixing this is simple. First, the component needs to know when the contents of the div may have changed (via the event fired from the service). Second, it simply needs to interact with the DOM element so it can scroll into view.
How do we reference the element associated with an Angular component? Take a look at this source:
There are just two steps needed to reference the element. First, import the ElementRef class. Second, include it on the constructor so it is injected. You probably noticed that the component doesn’t do anything with it in the constructor. This is because the constructor is called before the UI has been wired up and rendered, so there is nothing inside of the element.
So how do you know when the element is ready?
Angular 2 Lifecycle
Angular 2 components have a lifecycle and if certain methods are present, they are called during a specific phase. I provided a high level list at an Angular 2 talk I gave. In this case, the component implements a method that is called after the view is initialized. Once initialized, it’s possible to grab the child div because it’s rendered.
A nice feature of TypeScript is auto-completion and documentation. By casting the element to the HTMLDivElement type I get a clearly typed list of APIs available for that element. The component subscribes to the console events (notice it uses a de-bounce rate of 100ms so if a ton of messages are sent at once, it will still only fire 10 times a second to avoid locking the UI). When the event fires, a property on the div is set and that will force it to scroll to the most recent message.
A similar lifecycle method is used in the main app to indicate everything is initialized.
Creating the Display
The next challenge was creating a graphical display. I decided to implement the emulator using a memory-mapped, palette-based display. This means a block of memory is reserved to represent “pixels” of the display, and when a value is set on a specific address, the number corresponds to a palette entry of red, green and blue values.
If you’re curious you can see the code for the algorithm I use to generate the palette . It basically builds up a distinct set of red, green, and blue values and then sorts them based on their luminosity based on a well known equation. Each item has a hexadecimal rendition and the last five palette slots are manually built as shades of gray.
The display service simply keeps track of a pixel buffer that represents the display, then makes a callback to the component when a value changes.
The display component is a little more involved. It holds an array of values that represent rectangles that will be drawn as a “pixel.” These are literally scalable vector graphics-based rectangles inside an svg element. The entries hold x and y offsets from the upper corner of the display, the width and height (you can adjust this if you like), and the current fill color for that pixel.
The component sets a callback on the display service to be notified of any changes. In this case, a callback is used because it is up to 50x faster than using an event emitter (I tested this). The callback ensures the request is in the valid range of memory and is a byte of data, then cross-references the palette to set the proper fill value.
Finally, this is all rendered by Angular as an array of rectangles. Angular’s dirty tracking will check when the fill is updated and update the attribute. Here is the HTML for the display:
The CPU State
The emulator CPU provides the simplest possible mechanism for emulating the operation of instructions. It contains the memory registers, the stack, a program counter (the area of memory that the next machine instruction will be read from), and tracks things like how many instructions per second it is able to execute.
I decided to take a straightforward approach, and have the CPU manage memory, registers, stack, and program counter but create “self-aware” operation classes that would actually “do work.”
For that reason the CPU itself doesn’t have any logic like loading or adding accumulators. Instead, it knows how to look at memory, update memory (including triggering a call to the display service), set and reset flags, pop addresses and handle various modes for addressing memory.
It has instructions to run, step through code line-by-line for the debug mode, and can be halted. When halted or in an error state, the only recovery is to reset it. Basically the workflow for the cpu is to look at the operation at the program counter, ask it to execute itself, then update the program counter and grab the next instruction.
The executeBatch() function is where I did most of the optimization. Running the program until it stops would lock the UI, and running a single instruction per event loop was slow. The CPU therefore compromises by running up to 255 instructions before it uses setTimeout to allow other events to queue.
(This is an area where using animation frames might make sense, but I haven’t explored that avenue yet).
Angular 1.x users will note I can call setTimeout directly and not rely on a $timeout service. Angular 2 is able to detect when I’ve modified the data model even from within an asynchronous function due to the magic of ZoneJS .
Loading Op Codes
An operation is fairly simple. It exposes how it handles addresses, the numeric value of the instruction, the name, how many bytes the instruction and any parameters take up, and can compile itself to text.
There is an execute method that uses the CPU to do work.
Operation codes derive from a base class:
And then implement themselves like this:
The example snippet is an operation that adds a value to the accumulator (a special memory register) with a value. It will set the carry bit if there is an overflow, and it is in immediate mode which means the byte after the instruction contains the value to add (as opposed to obtaining it from a memory location, for example). When executed, it uses the utility methods on the OpCodes class to add with carry and passes in the instance of the cpu and the address it is at.
The OpCodes utility will pop the instruction, inspect the address mode, pull the value, perform the add, and update the program counter with the cpu’s help.
Because each operation has multiple addressing modes, and there is one class per combination of op code and address mode, there end up being several hundred classes. This presents a challenge from the perspective of wiring up the op codes into an array unless they “self-register.” In my original emulator, that’s exactly what they did, but with modern TypeScript there’s an even better way!
TypeScript decorators are heavily used by Angular 2. I decided to create one of my own. First, I created an array of operations to export and make available to the CPU, compiler, and other components that need it. Next, I created a function to serve as a class decorator. It simply takes the target (which is the class, or more precisely the function constructor) then creates the instance and stores it in an array.
With that simple step, because the signature is correct, I can now use that function as a decorator and simply adorn each class that should register as an op code with the @IsOpCode attribute.
Take a look and see for yourself! Now when I implement the other op codes I haven’t finished yet, they will be automatically registered if I decorate them.
Aside from the individual op code definitions, the most logic exists in the compiler class . This is because it is a full two-pass assembly compiler that parses labels and handles both decimal and hexadecimal entries as well as label addition and memory syntax.
The compiler has to be able to take a pass and lay out how much memory each instruction takes so it can assign memory addresses to labels, then use that for “label math” (when the programmer adds or subtracts and offset from a label), then parse the code and get the instructions and memory addresses written out correctly.
Needless to say, a lot of regular expressions are involved. You can look at the source code linked earlier to see how I used interfaces and broke the compiler steps into methods to make it easier to organize all of the steps required.
The CPU Display
The CPU was the easiest component to build. It simply exposes the CPU service:
And then binds to values and cpu methods directly:
There is a compiler component that interacts with the compiler class, and uses forms to collect data. If you go beyond straight data-binding, forms are actually quite interesting.
Forms in Angular 2
The compiler component uses Angular 2’s form builder to expose the form for entering code, loading code, and setting the program counter. The form builder is used to create a group of controls. Each has a name, an initial value, and a set of validators. In the case of the program counter, a “required” validator is combined with a customer validator that parses to ensure it is a valid hexadecimal address.
You can see that in the “pcValidator” method. Passed validations return null, failed return … well, whatever you want except null. I reference the control group to create individual references for each control, so I can use the value of the control for compiling, setting the program counter, etc.
The controls have their own method to update values (such as loading the decompiled code into the corresponding control). I also use it to load source code for some of the pre-built programs I included. Unlike Angular 1.x, the Http service uses RxJS to return its results.
HTTP in Angular 2
The component will import Http and should also import the Rx library to handle the special observable results. After that, it’s fairly straightforward.
The get call is issued and returns an observable that you can begin chaining methods onto. In this example, I’ve mapped the result to text (I’m not getting a JSON object but the entire contents of a text file) and then I subscribe.
The subscription has methods for when the next item appears, when an exception is thrown, and when the observable stream reaches the end. I leverage this to grab the loaded data and set it onto the control and then send a console message when it’s loaded.
The Final Results
After everything is said and done I was very pleased with how quickly I was able to leverage the existing code and migrate it to Angular 2 and the more current version of TypeScript. A few things I noted along the way:
- Dynamic static properties like arrays aren’t really a good idea in the modular/asynchronous world – instead of having a static array for the op codes, I made an instance and exported it so it could be imported by other modules
- Interfaces are useful for annotations but not very useful for dependency injection in Angular 2 – I found if I didn’t want to use magic strings, I had to still reference the implementation and use the @Inject decorator to make it work if I wanted to declare variables by their interface type
- Performance surprised me (in a good way)
I’d like to elaborate on that last bullet. In the original implementation, I had a display service that explicitly held an array of svg elements and set the fill attribute on them directly when a value changed. With the Angular 2 version, I rely completely on Angular’s dirty tracking and let Angular re-render the element (or set the attribute) when the values change. Despite that difference, the improved data-binding is incredible and the new app keeps pace with the old one based on instructions per second.
Over all it was a great and fun experience. I still have some op codes to add to the mix and my binary coded decimal (BDC) mode is broken, so if you want to tinker, I do accept pull requests.
Until next time,
转载本站任何文章请注明：转载至神刀安全网，谢谢神刀安全网 » Lessons Learned (Re) Writing a 6502 Emulator in TypeScript with Angular 2 and RxJs