-
Notifications
You must be signed in to change notification settings - Fork 1
1. IO Features
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:
- A human readable ID, for configurations and debugging.
- An I/O flow, to determine when the adapter should interact with the state.
- 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");
}
}
Whirvis Ardenaur (c) 2021-2023