Skip to content

1. IO Features

Whirvis Ardenaur edited this page Jul 7, 2023 · 5 revisions

In Ketill, an I/O feature describes a capability of an I/O device.

The term "feature" is somewhat abstract. But, it usually refers to a physical feature or a core component of the device. For example, a key on a keyboard or the position of a mouse cursor. The key is a physical object in the real world, whereas the mouse cursor position is the data from a sensor on the mouse.

All I/O features are made up of:

  1. A human readable ID, for configurations and debugging.
  2. An I/O flow, to determine when the adapter should interact with the state.
  3. An I/O state, the contain the feature data.

The ID of an I/O feature allows for a feature or its state to be fetched from a device via a string. This comes in handy for configuration files. For this reason, all features on a device must have unique IDs. There can be any number of features with the same ID in one program, so long as they are present on different devices.

The flow of an I/O feature determines when its state shall be read from or written to. There are three flows to choose from:

Flow Behavior
IoFlow.IN Interact with the state when the device is queried.
IoFlow.OUT Interact with the state when the device is updated.
IoFlow.TWO_WAY Combination of IoFlow.IN and IoFlow.OUT.

Before creating an IoFeature, there must be a corresponding IoState for it. The state contains the current value for a feature on a device. Its contents can be anything. For example, the state of a button would be if it is currently pressed; while the state of an analog stick would be its current position. Furthermore, all I/O states must have an internal data type. The internal data of a state is hidden from the user, but can be accessed by the device, its adapter, and the state itself. Internal data exists primarily for communication between the state and the adapter.

To create an IoState, there must first be an internal data type. The internal data type can be anything, except for another IoState (unless the internal data for a state is the state itself, more on that later). The internal data type for IoButton can be seen below.

public final class IoButtonInternals {
    public boolean pressed;
}

Notice again how the pressed field is public and not protected or package-private.

With that out of the way, the IoButton state class can be created.

import io.ketill.IoDevice
import io.ketill.IoFeature
import io.ketill.IoState

public final class IoButtonState
        extends IoState<IoButtonInternals> {

    /* note: IoButton extends IoFeature */
    public IoButtonState(@NotNull IoDevice device,
                         @NotNull IoButton button) {
        super(device, button, new IoButtonInternals());
    }

    @Override
    protected void reset() {
        internals.pressed = false;
    }

}

All I/O states have a parent device and a feature which they represent. Here, since this state is meant for IoButton, the constructor for IoButtonState takes in an IoButton instead of an IoFeature.

With that done, the IoFeature class can now be created.

import org.jetbrains.annotations.NotNull;

import io.ketill.IoDevice;
import io.ketill.IoFeature;

public final class IoButton extends IoFeature<IoButtonState> {

    public IoButton(@NotNull String id) {
        super(id, IoFlow.IN);
    }

    @Override
    protected @NotNull IoButtonState createState(@NotNull IoDevice device) {
        return new IoButtonState(device, this);
    }

}

As stated before, all I/O features have an ID and a flow. Since the feature is a button (which is input), its flow is set as IoFlow.IN. If the feature were something like a rumble motor, its flow would be IoFlow.OUT. If it were a combination of both, then it would use IoFlow.TWO_WAY. However, this is extremely uncommon. The function createState() creates a new instance of the state for a device. The returned state must be owned by the given device and the feature instance. If these requirements are not met, an IoFeatureException will be thrown.

And voila! We now have a fully functional I/O feature.

We can now add the feature to our own devices.

import org.jetbrains.annotations.NotNull;

import io.ketill.IoButton;
import io.ketill.IoButtonState;
import io.ketill.IoDevice;

public class MegaDriveController extends IoDevice {

    @IoFeature.BuiltIn
    public static final @NotNull IoButton
        BUTTON_A      = new IoButton("a"),
        BUTTON_B      = new IoButton("b"),
        BUTTON_C      = new IoButton("c"),
        BUTTON_X      = new IoButton("x"),
        BUTTON_Y      = new IoButton("y"),
        BUTTON_Z      = new IoButton("z"),
        BUTTON_START  = new IoButton("start"),
        BUTTON_UP     = new IoButton("up"),
        BUTTON_DOWN   = new IoButton("down"),
        BUTTON_LEFT   = new IoButton("left"),
        BUTTON_RIGHT  = new IoButton("right");

    @IoState.BuiltIn
    public final @NotNull IoButtonState
        a      = this.addFeature(BUTTON_A),
        b      = this.addFeature(BUTTON_B),
        c      = this.addFeature(BUTTON_C),
        x      = this.addFeature(BUTTON_X),
        y      = this.addFeature(BUTTON_Y),
        z      = this.addFeature(BUTTON_Z),
        start  = this.addFeature(BUTTON_START),
        up     = this.addFeature(BUTTON_UP),
        down   = this.addFeature(BUTTON_DOWN),
        left   = this.addFeature(BUTTON_LEFT),
        right  = this.addFeature(BUTTON_RIGHT);

    public MegaDriveController() {
        super("megadrive");
    }

}
Clone this wiki locally