ciaran2 / pyGB

An emulator for the Nintendo GameBoy written in Python.
MIT License
0 stars 1 forks source link

New-style classes and Cartridge + metaclass #3

Closed zstewar1 closed 8 years ago

zstewar1 commented 8 years ago

This set of changes does two things.

First, it changes all classes to explicitly inherit from object. In Python 3, this does nothing, but in Python 2, this changes these classes from old-style classes to new-style classes. This changes some edge cases in python's data model such as removing __getslice__ in favor of calling __getitem__ with a slice object as the index parameter. This improves 2/3 compatibility and just generally makes things better.

The other change is to the cartridge module. This changes the selection of Cartridge classes to be done through inheritance and metaclass programming instead of creating a function that maually selects the appropriate class of cartridge.

Before this change, cartridges were selected by this function:

def load_rom_from_file(path):
  with open(path, "rb") as f:
    romstring = f.read()

  cart_type = romstring[0x147]

  if cart_type in MBC1Cartridge.ids:
    return MBC1Cartridge(romstring)
  else:
    raise ValueError("Cartridge of unsupported type.")

Note how the function manually checks the ids in MBC1Cartridge. This means that as the rest of the cartridge types are developed, each would have to be added to this function manually.

This function is changed to this:

def load_rom_from_file(path):
  with open(path, "rb") as f:
    romstring = f.read()

  return Cartridge(romstring)

Now all the work of determining the correct type of cartridge to construct has been moved to within the Cartridge class. load_rom_from_file doesn't need to know anything about the contents of the rom.

The way this works is in to parts. First is the metaclass CartridgeMeta. This metaclass implements a registry so that any object hierarchy which uses it as its metaclass will automatically have a class-level variable called _cartridge_registry added to it, and any subclasses of that base class which define the class-level variable ids will automatically be added to the registry. The metaclass also prevents accidents where the programmer specifies the same cartridge type ID twice, by raising an error if a class attempts to register an id that has already been registered.

Then the __new__ method of Cartridge is defined to check in its internal registry, provided by the metaclass, whenever an instance of Cartridge is requested. Instead of returning a base Cartridge object, the subclass which matches the id in the romstring is returned, and if none match, an error is raised. If no romstring is passed, a DummyCartridge is returned.

Additionally, some unit tests are provided for the Cartridge metaclass/slection system.

This has been tested in both Python 2 and 3.

Note that this request creates a dependency on the "six" module from pypi. This module proves a number of helpers for Python 2/3 compatibility.