Introduce protocol-neutral controller state
This commit is contained in:
parent
df515934ab
commit
b11ae076a8
28 changed files with 833 additions and 501 deletions
|
|
@ -12,21 +12,20 @@
|
|||
#include <pico/multicore.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include <uni.h>
|
||||
#ifdef SWITCH_PICO_ADAPTER_FEASIBILITY
|
||||
#include "adapter_usb_mode.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kStickMidpoint = 32768;
|
||||
constexpr int32_t kAxisMinimum = -512;
|
||||
constexpr int32_t kAxisMaximum = 511;
|
||||
constexpr int32_t kTriggerMaximum = 1023;
|
||||
constexpr int32_t kTriggerThreshold = (kTriggerMaximum * 35) / 100;
|
||||
constexpr uint16_t kSwitchHostRumbleDurationMs = 50;
|
||||
#ifdef SWITCH_PICO_ADAPTER_FEASIBILITY
|
||||
// XInput vibration is stateful: it remains active until XInputSetState sends
|
||||
// a new magnitude. Use the longest Bluepad32 duration and stop explicitly on
|
||||
// the zero-magnitude packet.
|
||||
constexpr uint16_t kRumbleDurationMs = UINT16_MAX;
|
||||
#else
|
||||
constexpr uint16_t kRumbleDurationMs = 50;
|
||||
// XInput vibration is stateful and remains active until XInputSetState sends
|
||||
// a new magnitude.
|
||||
constexpr uint16_t kXInputHostRumbleDurationMs = UINT16_MAX;
|
||||
#endif
|
||||
constexpr uint32_t kRumblePollIntervalMs = 5;
|
||||
constexpr uint8_t kSlotCount = BLUEPAD32_INPUT_BACKEND_SLOT_COUNT;
|
||||
|
|
@ -100,7 +99,7 @@ enum class ConnectionPolicyState {
|
|||
struct RumbleEnvelope {
|
||||
uint8_t slot;
|
||||
uint32_t connection_generation;
|
||||
SwitchRumbleOutput rumble;
|
||||
ControllerRumbleOutput rumble;
|
||||
};
|
||||
struct FeedbackEnvelope {
|
||||
uint32_t connection_generation;
|
||||
|
|
@ -111,7 +110,7 @@ struct FeedbackEnvelope {
|
|||
|
||||
|
||||
struct BackendSlot {
|
||||
SwitchInputState state;
|
||||
ControllerState state;
|
||||
// Non-null with active=false is a connected device still becoming ready.
|
||||
uni_hid_device_t* device;
|
||||
uint32_t state_generation;
|
||||
|
|
@ -154,13 +153,17 @@ bool g_pairing_window_open = false;
|
|||
bool g_status_led_on = false;
|
||||
Bluepad32PairingSnapshot g_pairing_snapshot{};
|
||||
|
||||
SwitchInputState make_neutral_state() {
|
||||
SwitchInputState state{};
|
||||
state.lx = kStickMidpoint;
|
||||
state.ly = kStickMidpoint;
|
||||
state.rx = kStickMidpoint;
|
||||
state.ry = kStickMidpoint;
|
||||
return state;
|
||||
uint16_t host_rumble_duration_ms() {
|
||||
#ifdef SWITCH_PICO_ADAPTER_FEASIBILITY
|
||||
if (adapter_host_probe_mode() == AdapterUsbMode::kXInput) {
|
||||
return kXInputHostRumbleDurationMs;
|
||||
}
|
||||
#endif
|
||||
return kSwitchHostRumbleDurationMs;
|
||||
}
|
||||
|
||||
ControllerState make_neutral_state() {
|
||||
return controller_neutral_state();
|
||||
}
|
||||
|
||||
bool valid_slot(uint8_t slot) {
|
||||
|
|
@ -226,7 +229,7 @@ ConnectionStatus compute_connection_status() {
|
|||
}
|
||||
|
||||
void publish_device_state(uint8_t slot, uni_hid_device_t* device,
|
||||
const SwitchInputState& state) {
|
||||
const ControllerState& state) {
|
||||
critical_section_enter_blocking(&g_state_lock);
|
||||
BackendSlot& target = g_slots[slot];
|
||||
if (target.active && target.device == device) {
|
||||
|
|
@ -263,14 +266,28 @@ constexpr int32_t clamp_axis(int32_t value) {
|
|||
return value;
|
||||
}
|
||||
|
||||
constexpr uint16_t scale_stick(int32_t value) {
|
||||
constexpr int16_t scale_axis(int32_t value) {
|
||||
value = clamp_axis(value);
|
||||
if (value <= 0) {
|
||||
return static_cast<uint16_t>(
|
||||
(static_cast<int64_t>(value - kAxisMinimum) * kStickMidpoint) / -kAxisMinimum);
|
||||
return static_cast<int16_t>(
|
||||
(static_cast<int64_t>(value) * -INT16_MIN) /
|
||||
-kAxisMinimum);
|
||||
}
|
||||
return static_cast<int16_t>(
|
||||
(static_cast<int64_t>(value) * INT16_MAX) /
|
||||
kAxisMaximum);
|
||||
}
|
||||
|
||||
constexpr uint16_t scale_trigger(int32_t value) {
|
||||
if (value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (value >= kTriggerMaximum) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return static_cast<uint16_t>(
|
||||
kStickMidpoint + (static_cast<int64_t>(value) * (UINT16_MAX - kStickMidpoint)) / kAxisMaximum);
|
||||
(static_cast<int64_t>(value) * UINT16_MAX) /
|
||||
kTriggerMaximum);
|
||||
}
|
||||
|
||||
constexpr int16_t clamp_int16(int64_t value) {
|
||||
|
|
@ -301,9 +318,11 @@ constexpr int16_t convert_gyro(int64_t q10_value) {
|
|||
return clamp_int16(divide_round_nearest(q10_value * kNumeratorScale, kDenominator));
|
||||
}
|
||||
|
||||
static_assert(scale_stick(-512) == 0);
|
||||
static_assert(scale_stick(0) == 32768);
|
||||
static_assert(scale_stick(511) == UINT16_MAX);
|
||||
static_assert(scale_axis(-512) == INT16_MIN);
|
||||
static_assert(scale_axis(0) == 0);
|
||||
static_assert(scale_axis(511) == INT16_MAX);
|
||||
static_assert(scale_trigger(0) == 0);
|
||||
static_assert(scale_trigger(1023) == UINT16_MAX);
|
||||
static_assert(convert_accel(8192) == 4096);
|
||||
static_assert(convert_accel(-8192) == -4096);
|
||||
static_assert(convert_gyro(1024) == 14);
|
||||
|
|
@ -318,10 +337,10 @@ bool has_motion(const uni_gamepad_t& gamepad) {
|
|||
return false;
|
||||
}
|
||||
|
||||
SwitchInputState map_gamepad(const uni_gamepad_t& gamepad,
|
||||
bool swap_abxy,
|
||||
bool motion_enabled) {
|
||||
SwitchInputState state = make_neutral_state();
|
||||
ControllerState map_gamepad(const uni_gamepad_t& gamepad,
|
||||
bool swap_abxy,
|
||||
bool motion_enabled) {
|
||||
ControllerState state = make_neutral_state();
|
||||
|
||||
state.dpad_up = (gamepad.dpad & DPAD_UP) != 0;
|
||||
state.dpad_down = (gamepad.dpad & DPAD_DOWN) != 0;
|
||||
|
|
@ -329,46 +348,52 @@ SwitchInputState map_gamepad(const uni_gamepad_t& gamepad,
|
|||
state.dpad_right = (gamepad.dpad & DPAD_RIGHT) != 0;
|
||||
|
||||
// Bluepad32's A/B/X/Y are positional: south/east/west/north.
|
||||
state.button_b = (gamepad.buttons & BUTTON_A) != 0;
|
||||
state.button_a = (gamepad.buttons & BUTTON_B) != 0;
|
||||
state.button_y = (gamepad.buttons & BUTTON_X) != 0;
|
||||
state.button_x = (gamepad.buttons & BUTTON_Y) != 0;
|
||||
state.button_south = (gamepad.buttons & BUTTON_A) != 0;
|
||||
state.button_east = (gamepad.buttons & BUTTON_B) != 0;
|
||||
state.button_west = (gamepad.buttons & BUTTON_X) != 0;
|
||||
state.button_north = (gamepad.buttons & BUTTON_Y) != 0;
|
||||
if (swap_abxy) {
|
||||
bool temporary = state.button_a;
|
||||
state.button_a = state.button_b;
|
||||
state.button_b = temporary;
|
||||
temporary = state.button_x;
|
||||
state.button_x = state.button_y;
|
||||
state.button_y = temporary;
|
||||
bool temporary = state.button_east;
|
||||
state.button_east = state.button_south;
|
||||
state.button_south = temporary;
|
||||
temporary = state.button_north;
|
||||
state.button_north = state.button_west;
|
||||
state.button_west = temporary;
|
||||
}
|
||||
state.button_l = (gamepad.buttons & BUTTON_SHOULDER_L) != 0;
|
||||
state.button_r = (gamepad.buttons & BUTTON_SHOULDER_R) != 0;
|
||||
state.button_zl = (gamepad.buttons & BUTTON_TRIGGER_L) != 0 || gamepad.brake >= kTriggerThreshold;
|
||||
state.button_zr = (gamepad.buttons & BUTTON_TRIGGER_R) != 0 || gamepad.throttle >= kTriggerThreshold;
|
||||
state.button_l3 = (gamepad.buttons & BUTTON_THUMB_L) != 0;
|
||||
state.button_r3 = (gamepad.buttons & BUTTON_THUMB_R) != 0;
|
||||
state.button_left_shoulder = (gamepad.buttons & BUTTON_SHOULDER_L) != 0;
|
||||
state.button_right_shoulder = (gamepad.buttons & BUTTON_SHOULDER_R) != 0;
|
||||
state.left_trigger =
|
||||
(gamepad.buttons & BUTTON_TRIGGER_L) != 0
|
||||
? UINT16_MAX
|
||||
: scale_trigger(gamepad.brake);
|
||||
state.right_trigger =
|
||||
(gamepad.buttons & BUTTON_TRIGGER_R) != 0
|
||||
? UINT16_MAX
|
||||
: scale_trigger(gamepad.throttle);
|
||||
state.button_left_stick = (gamepad.buttons & BUTTON_THUMB_L) != 0;
|
||||
state.button_right_stick = (gamepad.buttons & BUTTON_THUMB_R) != 0;
|
||||
|
||||
state.button_minus = (gamepad.misc_buttons & MISC_BUTTON_SELECT) != 0;
|
||||
state.button_plus = (gamepad.misc_buttons & MISC_BUTTON_START) != 0;
|
||||
state.button_home = (gamepad.misc_buttons & MISC_BUTTON_SYSTEM) != 0;
|
||||
state.button_select = (gamepad.misc_buttons & MISC_BUTTON_SELECT) != 0;
|
||||
state.button_start = (gamepad.misc_buttons & MISC_BUTTON_START) != 0;
|
||||
state.button_system = (gamepad.misc_buttons & MISC_BUTTON_SYSTEM) != 0;
|
||||
state.button_capture = (gamepad.misc_buttons & MISC_BUTTON_CAPTURE) != 0;
|
||||
|
||||
state.lx = scale_stick(gamepad.axis_x);
|
||||
state.ly = scale_stick(gamepad.axis_y);
|
||||
state.rx = scale_stick(gamepad.axis_rx);
|
||||
state.ry = scale_stick(gamepad.axis_ry);
|
||||
state.left_stick_x = scale_axis(gamepad.axis_x);
|
||||
state.left_stick_y = scale_axis(gamepad.axis_y);
|
||||
state.right_stick_x = scale_axis(gamepad.axis_rx);
|
||||
state.right_stick_y = scale_axis(gamepad.axis_ry);
|
||||
|
||||
if (motion_enabled && has_motion(gamepad)) {
|
||||
// Dependency patches normalize both arrays to SDL3 PlayStation axes.
|
||||
SwitchImuSample sample{};
|
||||
ControllerMotionSample sample{};
|
||||
sample.accel_x = convert_accel(-static_cast<int64_t>(gamepad.accel[2]));
|
||||
sample.accel_y = convert_accel(-static_cast<int64_t>(gamepad.accel[0]));
|
||||
sample.accel_z = convert_accel(gamepad.accel[1]);
|
||||
sample.gyro_x = convert_gyro(-static_cast<int64_t>(gamepad.gyro[2]));
|
||||
sample.gyro_y = convert_gyro(-static_cast<int64_t>(gamepad.gyro[0]));
|
||||
sample.gyro_z = convert_gyro(gamepad.gyro[1]);
|
||||
state.imu_sample_count = 3;
|
||||
for (SwitchImuSample& destination : state.imu_samples) {
|
||||
state.motion_sample_count = 3;
|
||||
for (ControllerMotionSample& destination : state.motion_samples) {
|
||||
destination = sample;
|
||||
}
|
||||
}
|
||||
|
|
@ -753,7 +778,7 @@ void process_rumble_timer(btstack_timer_source_t* timer) {
|
|||
envelope.rumble.low_frequency_magnitude == 0 &&
|
||||
envelope.rumble.high_frequency_magnitude == 0;
|
||||
device->report_parser.play_dual_rumble(
|
||||
device, 0, stop ? 0 : kRumbleDurationMs,
|
||||
device, 0, stop ? 0 : host_rumble_duration_ms(),
|
||||
envelope.rumble.high_frequency_magnitude,
|
||||
envelope.rumble.low_frequency_magnitude);
|
||||
}
|
||||
|
|
@ -1084,7 +1109,8 @@ void bluepad32_input_backend_pairing_snapshot(
|
|||
}
|
||||
|
||||
|
||||
bool bluepad32_input_backend_snapshot(uint8_t slot_index, SwitchInputState* out) {
|
||||
bool bluepad32_input_backend_snapshot(uint8_t slot_index,
|
||||
ControllerState* out) {
|
||||
if (out == nullptr || !valid_slot(slot_index)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1100,7 +1126,7 @@ bool bluepad32_input_backend_snapshot(uint8_t slot_index, SwitchInputState* out)
|
|||
critical_section_exit(&g_state_lock);
|
||||
|
||||
if (generation == g_consumed_generation[slot_index]) {
|
||||
out->imu_sample_count = 0;
|
||||
out->motion_sample_count = 0;
|
||||
}
|
||||
g_last_snapshot_generation[slot_index] = generation;
|
||||
return controller_active;
|
||||
|
|
@ -1113,8 +1139,8 @@ void bluepad32_input_backend_report_sent(uint8_t slot_index) {
|
|||
g_consumed_generation[slot_index] = g_last_snapshot_generation[slot_index];
|
||||
}
|
||||
|
||||
void bluepad32_input_backend_queue_rumble(uint8_t slot_index,
|
||||
const SwitchRumbleOutput& rumble) {
|
||||
void bluepad32_input_backend_queue_rumble(
|
||||
uint8_t slot_index, const ControllerRumbleOutput& rumble) {
|
||||
if (!g_initialized || !valid_slot(slot_index)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue