2. docs
  3. higan
  4. platform


Platform2019-12-17 13:46:45

Every emulation core in higan communicates with the host operating system / GUI using the Platform API.

To put it another way, emulators call Platform functions.


struct Platform {
  virtual auto attach(Node::Object) -> void;
  virtual auto detach(Node::Object) -> void;
  virtual auto open(Node::Object, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file;
  virtual auto video(Node::Video, const uint32_t* data, uint pitch, uint width, uint height) -> void;
  virtual auto audio(Node::Audio, const double* samples, uint channels) -> void;
  virtual auto input(Node::Input) -> void;

attach(Node.Object) -> void;

Called whenever an emulator attaches a new Node object to its tree. For instance, if a controller is connected to a controller port, this function will be called with a Node::Peripheral object. After, this function will be called for all Node::Input buttons that the controller contains.

A user interface should use this function to assign properties to the object for later use, such as where the storage for a peripheral is located on disk. For instance, if a Node::Peripheral is a cartridge, one might call Object::setProperty("location", "/path/to/game/"). This property can then be read inside Platform::open to locate relevant files for a given object.

detach(Node.Object) -> void;

Called whenever an emulator detacted a Node object from its tree. For instance, when a controller is disconnected or a cartridge is ejected.

An emulator should use this function to save any configuration data to persistent storage. For instance, if this is a controller, Node::serialize() can be used to return a tree containing all Node::Input children, and this can be saved to disk. Then upon the next loading of an emulator core, this data can be restored back to the controller via Node::unserialize. In this way, a user's input settings will not be lost between attach and detach events.

open(Node.Object, string name, vfs.file.mode mode, bool required = false) -> vfs.shared.file;

Called whenever an emulator needs to open a file. Mostly, this is used to load ROM and save RAM data. It is also often called with the root Node::System object to load BIOS images, boot ROMs, etc.

nall/vfs is a virtual file system wrapper which allows the user interface to return handles to actual files on disk, to blocks of memory, etc. The property information set earlier can be used here to locate files and open them.

Valid file modes are read and write. The required flag is simply a hint that if the file is not available, the emulation will not function correctly. If the file is not found, emulation should be aborted and not allowed to continue to run. For instance, if the GBA BIOS is missing, all games will be unplayable.

video(Node.Video, const uint32_t* data, uint pitch, uint width, uint height) -> void;

This is called every time a frame of video has been generated by the emulation core. The user interface should blit the data to the screen here.

The reason for the Node::Video parameter is to support systems with multiple screens, providing a unique identifier and properties about each display. Note that currently, higan does not emulate any systems with more than one screen.

audio(Node.Audio, const double* samples, uint channels) -> void;

This is called every time an audio output device on the emulated system produces one frame of audio.

The reason for the Node::Audio parameter is to support systems where multiple independent speakers (not just channels) exist. Note that currently higan does not emulate any systems with more than one set of speakers.

input(Node.Input) -> void;

This is called whenever an emulator core polls an input peripheral. Generally speaking, this will only happen a few times per frame in bursts, but to optimize for latency and performance, the user interface should keep a timestamp and repoll the actual hardware input devices only when this function hasn't been called for at least 5 milliseconds.

A link between the emulated input and the physical hardware mapped input can be established by setting a property on the Node::Input object.

The Node::Input object should be casted to its derived type (eg Button, Axis, Rumble, etc) and handled appropriately. Eg buttons should set a boolean value inside of the node, axes should have a relative signed integer assigned to the value inside of the node, and rumble should check the enable boolean input inside the node to determine whether to enable or disable the force feedback of the hardware controller associated with said node, if available.