input: synchronize controller state updates

This commit is contained in:
Arthur Cuesta 2026-06-08 18:18:36 -03:00
parent db7685adeb
commit 62b0fee5fb
3 changed files with 86 additions and 74 deletions

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <sstream>
#include <unordered_set>
#include <SDL3/SDL.h>
@ -20,6 +19,8 @@ namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
static constexpr u64 MaxQueueAgeUs = 16'000;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
@ -93,6 +94,7 @@ void State::UpdateAxisSmoothing() {
GameController::GameController() : m_states_queue(64) {}
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
std::lock_guard lg(m_state_mutex);
*isConnected = m_connected;
*connectedCount = m_connected_count;
*state = m_state;
@ -100,6 +102,7 @@ void GameController::ReadState(State* state, bool* isConnected, int* connectedCo
int GameController::ReadStates(State* states, int states_num, bool* isConnected,
int* connectedCount) {
std::lock_guard lg(m_state_mutex);
*isConnected = m_connected;
*connectedCount = m_connected_count;
@ -107,16 +110,6 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
return 0;
}
std::lock_guard lg(m_states_queue_mutex);
if (states_num == 1) {
auto o_state = m_states_queue.PopLatest();
if (!o_state) {
return 0;
}
states[0] = *o_state;
return 1;
}
int ret_num = 0;
for (int i = 0; i < states_num; i++) {
auto o_state = m_states_queue.Pop();
@ -129,47 +122,33 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
}
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
std::lock_guard lg(m_state_mutex);
m_state.OnButton(button, is_pressed);
PushState();
PushStateLocked();
}
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
std::lock_guard lg(m_state_mutex);
m_state.OnAxis(axis, value, smooth);
m_state.UpdateAxisSmoothing();
PushState();
}
void GameController::Gyro(int id) {
m_state.OnGyro(gyro_buf);
}
void GameController::Acceleration(int id) {
m_state.OnAccel(accel_buf);
PushStateLocked();
}
void GameController::UpdateGyro(const float gyro[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::scoped_lock l(m_state_mutex);
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
}
void GameController::UpdateAcceleration(const float acceleration[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::scoped_lock l(m_state_mutex);
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
}
void GameController::PollState() {
m_state.UpdateAxisSmoothing();
m_state.OnGyro(gyro_buf);
m_state.OnAccel(accel_buf);
PushState();
std::lock_guard lg(m_state_mutex);
PushStateLocked();
}
void GameController::UpdateAxisSmoothing() {
m_state.UpdateAxisSmoothing();
PushState();
}
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
void GameController::SetLightBarRGB(u8 const r, u8 const g, u8 const b) {
if (override_colour.has_value()) {
return;
}
@ -179,6 +158,10 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
}
}
void GameController::SetLightBarRGB(Colour const c) {
SetLightBarRGB(c.r, c.g, c.b);
}
Colour GameController::GetLightBarRGB() {
return colour;
}
@ -189,6 +172,22 @@ void GameController::PollLightColour() {
}
}
void GameControllers::ResetLightbarColors() {
for (auto& c : controllers) {
auto const* u = UserManagement.GetUserByID(c->user_id);
if (!u || !c->m_sdl_gamepad) {
continue;
}
auto const i = u->user_color - 1;
if (i < 0 || i > 3) {
continue;
}
auto const& col = g_user_colours[i];
c->override_colour = std::nullopt;
c->SetLightBarRGB(col);
}
}
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_sdl_gamepad != nullptr) {
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
@ -199,9 +198,10 @@ bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
std::lock_guard lg(m_state_mutex);
bool was_pressed = m_state.touchpad[0].state || m_state.touchpad[1].state;
m_state.OnTouchpad(touchIndex, touchDown, x, y);
PushState();
PushStateLocked();
if (!m_state.touchpad[0].state && !m_state.touchpad[1].state && was_pressed) {
last_touch_down_timestamp = 0;
} else if ((m_state.touchpad[0].state || m_state.touchpad[1].state) && !was_pressed) {
@ -251,6 +251,21 @@ void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& accele
orientation.y, orientation.z, orientation.w);
}
void GameController::ConnectController(SDL_Gamepad* pad) {
std::scoped_lock l(m_state_mutex);
m_states_queue.Clear();
m_sdl_gamepad = pad;
m_connected_count = 1;
m_connected = true;
}
void GameController::DisconnectController() {
std::scoped_lock l(m_state_mutex);
m_states_queue.Clear();
m_sdl_gamepad = nullptr;
m_connected_count = 0;
m_connected = false;
}
bool is_first_check = true;
void GameControllers::TryOpenSDLControllers() {
@ -279,16 +294,9 @@ void GameControllers::TryOpenSDLControllers() {
}
if (!still_connected) {
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
UserManagement.LogoutUser(u);
SDL_CloseGamepad(pad);
controllers[i]->m_sdl_gamepad = nullptr;
controllers[i]->DisconnectController();
controllers[i]->user_id = -1;
controllers[i]->m_connected = false;
controllers[i]->m_connected_count = 0;
{
std::scoped_lock l(controllers[i]->m_states_queue_mutex);
controllers[i]->m_states_queue.Clear();
}
slot_taken[i] = false;
}
}
@ -313,18 +321,12 @@ void GameControllers::TryOpenSDLControllers() {
// Player N won't be registered at all
}
auto* c = controllers[i];
c->m_sdl_gamepad = pad;
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
SDL_GetGamepadID(pad));
c->user_id = u->user_id;
c->m_connected = true;
c->m_connected_count = 1;
{
std::scoped_lock l(c->m_states_queue_mutex);
c->m_states_queue.Clear();
}
slot_taken[i] = true;
c->user_id = u->user_id;
UserManagement.LoginUser(u, i + 1);
c->ConnectController(pad);
if (EmulatorSettings.IsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
c->gyro_poll_rate =
@ -352,6 +354,7 @@ void GameControllers::TryOpenSDLControllers() {
if (controller_count - move_count == 0) {
auto u = UserManagement.GetUserByPlayerIndex(1);
controllers[0]->user_id = u->user_id;
controllers[0]->ConnectController(nullptr);
UserManagement.LoginUser(u, 1);
}
}
@ -408,9 +411,15 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
m_last_update = lastUpdate;
}
void GameController::PushState() {
std::lock_guard lg(m_states_queue_mutex);
void GameController::PushStateLocked() {
m_state.UpdateAxisSmoothing();
m_state.OnGyro(gyro_buf);
m_state.OnAccel(accel_buf);
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
const u64 oldest_allowed =
m_state.time > MaxQueueAgeUs ? m_state.time - MaxQueueAgeUs : 0;
m_states_queue.DiscardWhile(
[oldest_allowed](const State& state) { return state.time < oldest_allowed; });
m_states_queue.Push(m_state);
}

View File

@ -3,9 +3,7 @@
#pragma once
#include <array>
#include <mutex>
#include <optional>
#include <utility>
#include <vector>
@ -45,6 +43,12 @@ struct TouchpadEntry {
struct Colour {
u8 r, g, b;
};
static constexpr Input::Colour g_user_colours[4]{
{0, 0, 255}, // blue
{255, 0, 0}, // red
{0, 255, 0}, // green
{255, 0, 255}, // pink
};
struct State {
private:
@ -104,14 +108,12 @@ public:
return std::move(m_storage[index]);
}
std::optional<T> PopLatest() {
if (m_size == 0) {
return {};
template <typename Predicate>
void DiscardWhile(Predicate predicate) {
while (m_size > 0 && predicate(m_storage[m_begin])) {
m_begin = (m_begin + 1) % m_storage.size();
m_size -= 1;
}
const size_t index = (m_begin + m_size - 1) % m_storage.size();
m_begin = 0;
m_size = 0;
return std::move(m_storage[index]);
}
void Clear() {
@ -131,19 +133,19 @@ class GameController {
public:
GameController();
virtual ~GameController() = default;
void ConnectController(SDL_Gamepad* pad);
void DisconnectController();
void ReadState(State* state, bool* isConnected, int* connectedCount);
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
void Button(Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
void Axis(Input::Axis axis, int value, bool smooth = true);
void Gyro(int id);
void Acceleration(int id);
void UpdateGyro(const float gyro[3]);
void UpdateAcceleration(const float acceleration[3]);
void PollState();
void UpdateAxisSmoothing();
void SetLightBarRGB(u8 r, u8 g, u8 b);
void SetLightBarRGB(u8 const r, u8 const g, u8 const b);
void SetLightBarRGB(Colour const c);
Colour GetLightBarRGB();
void PollLightColour();
bool SetVibration(u8 smallMotor, u8 largeMotor);
@ -171,10 +173,11 @@ public:
u64 last_touch_down_timestamp = 0;
private:
void PushState();
// m_state_mutex must be held by the caller.
void PushStateLocked();
bool m_connected = true;
int m_connected_count = 1;
bool m_connected = false;
int m_connected_count = 0;
u8 m_touch_count = 0;
u8 m_secondary_touch_count = 0;
u8 m_previous_touchnum = 0;
@ -186,7 +189,7 @@ private:
State m_state;
std::mutex m_states_queue_mutex;
std::mutex m_state_mutex;
RingBufferQueue<State> m_states_queue;
};
@ -196,10 +199,7 @@ class GameControllers {
public:
GameControllers()
: controllers({new GameController(), new GameController(), new GameController(),
new GameController(), new GameController()}) {
controllers[4]->m_connected = false;
controllers[4]->m_connected_count = 0;
};
new GameController(), new GameController()}) {};
virtual ~GameControllers() = default;
GameController* operator[](const size_t& i) const {
if (i > 4) {
@ -224,6 +224,7 @@ public:
controllers[i]->SetLightBarRGB(r, g, b);
controllers[i]->override_colour = {r, g, b};
}
void ResetLightbarColors();
};
} // namespace Input

View File

@ -267,6 +267,7 @@ void WindowSDL::WaitEvent() {
break;
}
controllers[i]->user_id = u->user_id;
controllers[i]->ConnectController(controllers[i]->m_sdl_gamepad);
UserManagement.LoginUser(u, i + 1);
break;
}
@ -277,6 +278,7 @@ void WindowSDL::WaitEvent() {
for (int i = 3; i >= 0; i--) {
if (controllers[i]->user_id != -1) {
UserManagement.LogoutUser(UserManagement.GetUserByID(controllers[i]->user_id));
controllers[i]->DisconnectController();
controllers[i]->user_id = -1;
break;
}