Swift8

27 Jun

I’ve rewritten my Chip8 emulator in Swift.

I’m still learning Swift so I won’t say that it is the right way to do an emulator in Swift. But it is fully functional and does take advantage of some sweet Swift features; like switching on tuples while ignore values:

switch opcodeTuple {
case (0xF, _, 6, 5):
// do stuff for Fx65
}

This helps to make the opcode switch statement nice and clean.

Swift has strong type safety…actually Swift has REALLY strong type safety. For example, if you have an unsigned 8-bit integer (UInt8, used throughout Chip8) and you want to store that in an Int you have to explicity convert it

let myInt = Int(my8bitInt)

This can be a bit of a pain. Don’t get me wrong, I think type safety is a good thing. I’ve been bitten by type conversion “magic” in languages like JavaScript before. But this seems like overkill. I can kind of see the case for this type safety when converting from a larger bit integer to a smaller one (UInt16 to UInt8 for example); since the larger one wouldn’t fit and you want to be certain you’re aware of what’s going on there. But for conversion from a smaller to larger? The smaller integer will always fit into the larger type integer. Maybe this is improved in Swift 2, I haven’t checked yet.

The main hassle I ran into while rewriting it in Swift was accessing array elements using UInt8 and UInt16. Luckily there is an easy solution to this.

extension Array {
  subscript (index: UInt8) -> T {
    get {
      return self[ Int(index) ]
    }
    set {
      self[ Int(index) ] = newValue
    }
  }
  
  subscript (index: UInt16) -> T {
    get {
      return self[ Int(index) ]
    }
    set {
      self[ Int(index) ] = newValue
    }
  }
}

That small extension on Array allows us to both get and set elements in Array using UInt8 and UInt16. This comes in handy when accessing elements in the Chip8 memory (array of UInt8) using a Chip8 register (UInt8) as the index.

One other issue I ran into is overflow. In C overflow isn’t protected. If you have a UInt8, set it to 255 (the max it can hold), and then add 3 to it, it will overflow and become 2. Meaning, once UInt8 goes past it’s max value it just starts back over again at 0. If your subtracting underflow does the same thing (but backwards).

In Swift however, integers are protected from overflow and underflow. The compiler is helpful with this and tries to catch it during compile time and warn you.

warning

But in some cases (common in Chip8) you’re adding two unknown integers together, so it’s impossible to catch it during compile time. Instead, you’ll notice during runtime, because your app will crash (bad app, bad!).

crash

Chip8 expects overflow and underflow, and luckily this is easy to handle in Swift. Just use the &+ operator instead of + (or &- instead of -) wherever you need overflow / underflow.

I would still like to move the code to a more modern Swift style. Right now it feels a bit too rooted in C. Breaking out the main Chip8 emulator class into parts like say an ALU extension might be a nice start. Also, instead of using UInt8 and UInt16 it might make sense to have custom types that represent the Chip8 architecture better like a Chip8 Nibble struct and Chip8 Byte struct? I’ll keep thinking about it.

Here’s the repo on GitHub.