blob: 07eb1fd510da174713a2aef46270d247b61fe5f7 [file]
#ifndef _RIVE_WASM_GAMEPAD_WIRE_HPP_
#define _RIVE_WASM_GAMEPAD_WIRE_HPP_
#include "rive/animation/listener_invocation.hpp"
#include <cstdint>
#include <cstring>
#include <optional>
namespace rive
{
/// Host to module layout for callGamepadEvent's invocation payload. Four byte
/// fields only, so the struct is identical on both sides of the wasm
/// boundary; buttonValues then axes follow as floats.
struct GamepadWire
{
static constexpr uint32_t kindConnected = 0;
static constexpr uint32_t kindEvent = 1;
static constexpr uint32_t kindDisconnected = 2;
uint32_t kind = 0;
int32_t deviceId = 0;
uint32_t mapping = 0;
uint32_t buttonMaskLo = 0;
uint32_t buttonMaskHi = 0;
uint32_t changeKind = 0;
uint32_t changeIndex = 0;
float changeValue = 0;
uint32_t hasStandardButtonIntent = 0;
uint32_t standardButton = 0;
uint32_t hasStandardAxisIntent = 0;
uint32_t standardAxis = 0;
uint32_t buttonCount = 0;
uint32_t axisCount = 0;
};
/// Flattens a gamepad invocation into the wire; false for other kinds. The
/// snapshot, null on a disconnect, supplies the floats after the wire.
inline bool packGamepadWire(
const ListenerInvocation& invocation,
GamepadWire& wire,
const GamepadSnapshot*& snapshot)
{
if (const GamepadConnectedInvocation* c = invocation.asGamepadConnected())
{
wire.kind = GamepadWire::kindConnected;
snapshot = &c->snapshot;
}
else if (const GamepadEventInvocation* e = invocation.asGamepadEvent())
{
wire.kind = GamepadWire::kindEvent;
snapshot = &e->fullState;
wire.changeKind = (uint32_t)e->change.kind;
wire.changeIndex = e->change.index;
wire.changeValue = e->change.value;
wire.hasStandardButtonIntent = e->hasStandardButtonIntent ? 1 : 0;
wire.standardButton = (uint32_t)e->standardButton;
wire.hasStandardAxisIntent = e->hasStandardAxisIntent ? 1 : 0;
wire.standardAxis = (uint32_t)e->standardAxis;
}
else if (
const GamepadDisconnectedInvocation* d = invocation
.asGamepadDisconnected())
{
wire.kind = GamepadWire::kindDisconnected;
wire.deviceId = d->deviceId;
}
else
{
return false;
}
if (snapshot != nullptr)
{
wire.deviceId = snapshot->deviceId;
wire.mapping = (uint32_t)snapshot->mapping;
wire.buttonMaskLo = (uint32_t)snapshot->buttonMask;
wire.buttonMaskHi = (uint32_t)(snapshot->buttonMask >> 32);
wire.buttonCount = (uint32_t)snapshot->buttonValues.size();
wire.axisCount = (uint32_t)snapshot->axes.size();
}
return true;
}
inline uint32_t gamepadPayloadSize(const GamepadWire& wire)
{
return (uint32_t)(sizeof(wire) +
(wire.buttonCount + wire.axisCount) * sizeof(float));
}
inline void writeGamepadPayload(
uint8_t* out,
const GamepadWire& wire,
const GamepadSnapshot* snapshot)
{
memcpy(out, &wire, sizeof(wire));
if (snapshot == nullptr)
{
return;
}
uint8_t* values = out + sizeof(wire);
if (wire.buttonCount != 0)
{
memcpy(
values,
snapshot->buttonValues.data(),
wire.buttonCount * sizeof(float));
}
if (wire.axisCount != 0)
{
memcpy(
values + wire.buttonCount * sizeof(float),
snapshot->axes.data(),
wire.axisCount * sizeof(float));
}
}
/// False when byteCount cannot hold the wire and the floats it declares.
inline bool decodeGamepadWire(
const uint8_t* data,
size_t byteCount,
GamepadWire& wire)
{
if (byteCount < sizeof(wire))
{
return false;
}
memcpy(&wire, data, sizeof(wire));
uint64_t floatCount = uint64_t(wire.buttonCount) + wire.axisCount;
return uint64_t(byteCount) >= sizeof(wire) + floatCount * sizeof(float);
}
// The payload may sit at any guest address, so floats are copied, not read.
inline GamepadSnapshot decodeGamepadSnapshot(
const GamepadWire& wire,
const uint8_t* data)
{
GamepadSnapshot snapshot;
snapshot.deviceId = wire.deviceId;
snapshot.mapping = (GamepadMappingKind)wire.mapping;
snapshot.buttonMask = (uint64_t(wire.buttonMaskHi) << 32) | wire.buttonMaskLo;
const uint8_t* values = data + sizeof(wire);
snapshot.buttonValues.resize(wire.buttonCount);
snapshot.axes.resize(wire.axisCount);
if (wire.buttonCount != 0)
{
memcpy(
snapshot.buttonValues.data(),
values,
wire.buttonCount * sizeof(float));
}
if (wire.axisCount != 0)
{
memcpy(
snapshot.axes.data(),
values + wire.buttonCount * sizeof(float),
wire.axisCount * sizeof(float));
}
return snapshot;
}
inline GamepadEventInvocation decodeGamepadEvent(
const GamepadWire& wire,
const uint8_t* data)
{
GamepadEventInvocation event;
event.fullState = decodeGamepadSnapshot(wire, data);
event.change.kind = (GamepadInputChangeKind)wire.changeKind;
event.change.index = (uint8_t)wire.changeIndex;
event.change.value = wire.changeValue;
event.hasStandardButtonIntent = wire.hasStandardButtonIntent != 0;
event.standardButton = (StandardGamepadButton)wire.standardButton;
event.hasStandardAxisIntent = wire.hasStandardAxisIntent != 0;
event.standardAxis = (StandardGamepadAxis)wire.standardAxis;
return event;
}
/// The invocation a decoded wire carries; empty for an unknown kind.
inline std::optional<ListenerInvocation> decodeGamepadInvocation(
const GamepadWire& wire,
const uint8_t* data)
{
switch (wire.kind)
{
case GamepadWire::kindConnected:
return ListenerInvocation::gamepadConnected(
decodeGamepadSnapshot(wire, data));
case GamepadWire::kindEvent:
return ListenerInvocation::gamepadEvent(decodeGamepadEvent(wire, data));
case GamepadWire::kindDisconnected:
return ListenerInvocation::gamepadDisconnected(wire.deviceId);
}
return std::nullopt;
}
} // namespace rive
#endif