Artic Base: Add Artic Controller support (#195)

This commit is contained in:
PabloMK7 2024-07-16 22:00:21 +02:00 committed by GitHub
parent 9de19ff7a1
commit 55748d7d1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 741 additions and 158 deletions

View file

@ -41,7 +41,8 @@ enum class IntSetting(
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0),
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0);
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0);
override var int: Int = defaultValue
@ -69,7 +70,8 @@ enum class IntSetting(
DEBUG_RENDERER,
CPU_JIT,
ASYNC_CUSTOM_LOADING,
AUDIO_INPUT_TYPE
AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View file

@ -626,6 +626,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
}
add(HeaderSetting(R.string.miscellaneous))
add(
SwitchSetting(
IntSetting.USE_ARTIC_BASE_CONTROLLER,
R.string.use_artic_base_controller,
R.string.use_artic_base_controller_desc,
IntSetting.USE_ARTIC_BASE_CONTROLLER.key,
IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
)
)
}
}

View file

@ -128,6 +128,8 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT));
ReadSetting("Controls", Settings::values.use_artic_base_controller);
// Core
ReadSetting("Core", Settings::values.use_cpu_jit);
ReadSetting("Core", Settings::values.cpu_clock_percentage);

View file

@ -86,6 +86,9 @@ udp_input_port=
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
udp_pad_index=
# Use Artic Controller when connected to Artic Base Server. (Default 0)
use_artic_base_controller=
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)

View file

@ -665,5 +665,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string>
<string name="delay_render_thread">Retrasa el hilo de dibujado del juego</string>
<string name="delay_render_thread_description">Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos.</string>
<string name="miscellaneous">Misceláneo</string>
<string name="use_artic_base_controller">Usar Artic Controller cuando se está conectado a Artic Base Server</string>
<string name="use_artic_base_controller_desc">Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado.</string>
</resources>

View file

@ -700,5 +700,8 @@
<string name="quicksave_saving">Saving…</string>
<string name="quickload_loading">Loading…</string>
<string name="quickload_not_found">No Quicksave available.</string>
<string name="miscellaneous">Miscellaneous</string>
<string name="use_artic_base_controller">Use Artic Controller when connected to Artic Base Server</string>
<string name="use_artic_base_controller_desc">Use the controls provided by Artic Base Server when connected to it instead of the configured input device.</string>
</resources>

View file

@ -327,6 +327,8 @@ void Config::ReadCameraValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
ReadBasicSetting(Settings::values.use_artic_base_controller);
int num_touch_from_button_maps =
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@ -924,6 +926,8 @@ void Config::SaveCameraValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
WriteBasicSetting(Settings::values.use_artic_base_controller);
WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0);
qt_config->beginWriteArray(QStringLiteral("profiles"));
for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) {

View file

@ -29,7 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
system{system_}, is_powered_on{system.IsPoweredOn()},
general_tab{std::make_unique<ConfigureGeneral>(this)},
system_tab{std::make_unique<ConfigureSystem>(system, this)},
input_tab{std::make_unique<ConfigureInput>(this)},
input_tab{std::make_unique<ConfigureInput>(system, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
graphics_tab{
std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this)},

View file

@ -16,6 +16,7 @@
#include "citra_qt/configuration/configure_input.h"
#include "citra_qt/configuration/configure_motion_touch.h"
#include "common/param_package.h"
#include "core/core.h"
#include "ui_configure_input.h"
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
@ -145,8 +146,8 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return QObject::tr("[unknown]");
}
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
: QWidget(parent), system(_system), ui(std::make_unique<Ui::ConfigureInput>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@ -400,6 +401,9 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::ApplyConfiguration() {
Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked();
std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.current_input_profile.buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
@ -444,6 +448,10 @@ QList<QKeySequence> ConfigureInput::GetUsedKeyboardKeys() {
}
void ConfigureInput::LoadConfiguration() {
ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue());
ui->use_artic_controller->setEnabled(!system.IsPoweredOn());
std::transform(Settings::values.current_input_profile.buttons.begin(),
Settings::values.current_input_profile.buttons.end(), buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });

View file

@ -30,7 +30,7 @@ class ConfigureInput : public QWidget {
Q_OBJECT
public:
explicit ConfigureInput(QWidget* parent = nullptr);
explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr);
~ConfigureInput() override;
/// Save all button configurations to settings file
@ -50,6 +50,7 @@ signals:
void InputKeysChanged(QList<QKeySequence> new_key_list);
private:
Core::System& system;
std::unique_ptr<Ui::ConfigureInput> ui;
std::unique_ptr<QTimer> timeout_timer;

View file

@ -841,6 +841,13 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_artic_controller">
<property name="text">
<string>Use Artic Controller when connected to Artic Base Server</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View file

@ -83,6 +83,7 @@ void LogSettings() {
LOG_INFO(Config, "Citra Configuration:");
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue());
log_setting("Renderer_UseGLES", values.use_gles.GetValue());
log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue()));
log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue());

View file

@ -425,6 +425,7 @@ struct Values {
int current_input_profile_index; ///< The current input profile index
std::vector<InputProfile> input_profiles; ///< The list of input profiles
std::vector<TouchFromButtonMap> touch_from_button_maps;
Setting<bool> use_artic_base_controller{false, "use_artic_base_controller"};
SwitchableSetting<bool> enable_gamemode{true, "enable_gamemode"};

View file

@ -20,6 +20,8 @@
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/hid_spvr.h"
#include "core/hle/service/hid/hid_user.h"
#include "core/hle/service/ir/ir_rst.h"
#include "core/hle/service/ir/ir_user.h"
#include "core/hle/service/service.h"
#include "core/movie.h"
@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) {
}
SERIALIZE_IMPL(Module)
ArticBaseController::ArticBaseController(
const std::shared_ptr<Network::ArticBase::Client>& client) {
udp_stream =
client->NewUDPStream("ArticController", sizeof(ArticBaseController::ControllerData),
std::chrono::milliseconds(2));
if (udp_stream.get()) {
udp_stream->Start();
}
}
ArticBaseController::ControllerData ArticBaseController::GetControllerData() {
if (udp_stream.get() && udp_stream->IsReady()) {
auto data = udp_stream->GetLastPacket();
if (data.size() == sizeof(ControllerData)) {
u32 id = *reinterpret_cast<u32*>(data.data());
if ((id - last_packet_id) < (std::numeric_limits<u32>::max() / 2)) {
last_packet_id = id;
memcpy(&last_controller_data, data.data(), data.size());
}
}
}
return last_controller_data;
}
constexpr float accelerometer_coef = 512.0f; // measured from hw test result
constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
@ -111,96 +139,151 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
LoadInputDevices();
using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus());
state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
float circle_pad_x_f, circle_pad_y_f;
std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
if (artic_controller.get() && artic_controller->IsReady()) {
constexpr u32 HID_VALID_KEYS = 0xF0003FFF;
constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20);
// xperia64: 0x9A seems to be the calibrated limit of the circle pad
// Verified by using Input Redirector with very large-value digital inputs
// on the circle pad and calibrating using the system settings application
constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
// These are rounded rather than truncated on actual hardware
s16 circle_pad_new_x = static_cast<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_new_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_x =
(circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) /
CIRCLE_PAD_AVERAGING;
s16 circle_pad_y =
(circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) /
CIRCLE_PAD_AVERAGING;
circle_pad_old_x.erase(circle_pad_old_x.begin());
circle_pad_old_x.push_back(circle_pad_new_x);
circle_pad_old_y.erase(circle_pad_old_y.begin());
circle_pad_old_y.push_back(circle_pad_new_y);
state.hex = data.pad & HID_VALID_KEYS;
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
s16 circle_pad_x = data.c_pad_x;
s16 circle_pad_y = data.c_pad_y;
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
state.circle_up.Assign(direction.up);
state.circle_down.Assign(direction.down);
state.circle_left.Assign(direction.left);
state.circle_right.Assign(direction.right);
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
mem->pad.current_state.hex = state.hex;
mem->pad.index = next_pad_index;
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
mem->pad.current_state.hex = state.hex;
mem->pad.index = next_pad_index;
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
// Get the previous Pad state
u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size();
PadState old_state = mem->pad.entries[last_entry_index].current_state;
// Get the previous Pad state
u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size();
PadState old_state = mem->pad.entries[last_entry_index].current_state;
// Compute bitmask with 1s for bits different from the old state
PadState changed = {{(state.hex ^ old_state.hex)}};
// Compute bitmask with 1s for bits different from the old state
PadState changed = {{(state.hex ^ old_state.hex)}};
// Get the current Pad entry
PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index];
// Get the current Pad entry
PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index];
// Update entry properties
pad_entry.current_state.hex = state.hex;
pad_entry.delta_additions.hex = changed.hex & state.hex;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
pad_entry.circle_pad_x = circle_pad_x;
pad_entry.circle_pad_y = circle_pad_y;
// Update entry properties
pad_entry.current_state.hex = state.hex;
pad_entry.delta_additions.hex = changed.hex & state.hex;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
pad_entry.circle_pad_x = circle_pad_x;
pad_entry.circle_pad_y = circle_pad_y;
// If we just updated index 0, provide a new timestamp
if (mem->pad.index == 0) {
mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks;
mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks();
// If we just updated index 0, provide a new timestamp
if (mem->pad.index == 0) {
mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks;
mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks();
}
mem->touch.index = next_touch_index;
next_touch_index = (next_touch_index + 1) % mem->touch.entries.size();
// Get the current touch entry
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
bool pressed = (data.pad & LIBCTRU_TOUCH_KEY) != 0;
touch_entry.x = static_cast<u16>(data.touch_x);
touch_entry.y = static_cast<u16>(data.touch_y);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
} else {
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus());
state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
float circle_pad_x_f, circle_pad_y_f;
std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
// xperia64: 0x9A seems to be the calibrated limit of the circle pad
// Verified by using Input Redirector with very large-value digital inputs
// on the circle pad and calibrating using the system settings application
constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position
// These are rounded rather than truncated on actual hardware
s16 circle_pad_new_x = static_cast<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_new_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_x = (circle_pad_new_x +
std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) /
CIRCLE_PAD_AVERAGING;
s16 circle_pad_y = (circle_pad_new_y +
std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) /
CIRCLE_PAD_AVERAGING;
circle_pad_old_x.erase(circle_pad_old_x.begin());
circle_pad_old_x.push_back(circle_pad_new_x);
circle_pad_old_y.erase(circle_pad_old_y.begin());
circle_pad_old_y.push_back(circle_pad_new_y);
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
state.circle_up.Assign(direction.up);
state.circle_down.Assign(direction.down);
state.circle_left.Assign(direction.left);
state.circle_right.Assign(direction.right);
mem->pad.current_state.hex = state.hex;
mem->pad.index = next_pad_index;
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
// Get the previous Pad state
u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size();
PadState old_state = mem->pad.entries[last_entry_index].current_state;
// Compute bitmask with 1s for bits different from the old state
PadState changed = {{(state.hex ^ old_state.hex)}};
// Get the current Pad entry
PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index];
// Update entry properties
pad_entry.current_state.hex = state.hex;
pad_entry.delta_additions.hex = changed.hex & state.hex;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
pad_entry.circle_pad_x = circle_pad_x;
pad_entry.circle_pad_y = circle_pad_y;
// If we just updated index 0, provide a new timestamp
if (mem->pad.index == 0) {
mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks;
mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks();
}
mem->touch.index = next_touch_index;
next_touch_index = (next_touch_index + 1) % mem->touch.entries.size();
// Get the current touch entry
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
bool pressed = false;
float x, y;
std::tie(x, y, pressed) = touch_device->GetStatus();
if (!pressed && touch_btn_device) {
std::tie(x, y, pressed) = touch_btn_device->GetStatus();
}
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
}
mem->touch.index = next_touch_index;
next_touch_index = (next_touch_index + 1) % mem->touch.entries.size();
// Get the current touch entry
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
bool pressed = false;
float x, y;
std::tie(x, y, pressed) = touch_device->GetStatus();
if (!pressed && touch_btn_device) {
std::tie(x, y, pressed) = touch_btn_device->GetStatus();
}
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
// supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being
// converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8).
@ -231,19 +314,27 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la
mem->accelerometer.index = next_accelerometer_index;
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
Common::Vec3<float> accel;
std::tie(accel, std::ignore) = motion_device->GetStatus();
accel *= accelerometer_coef;
// TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
// The time stretch formula should be like
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
AccelerometerDataEntry& accelerometer_entry =
mem->accelerometer.entries[mem->accelerometer.index];
accelerometer_entry.x = static_cast<s16>(accel.x);
accelerometer_entry.y = static_cast<s16>(accel.y);
accelerometer_entry.z = static_cast<s16>(accel.z);
if (artic_controller.get() && artic_controller->IsReady()) {
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
accelerometer_entry.x = data.accel_x;
accelerometer_entry.y = data.accel_y;
accelerometer_entry.z = data.accel_z;
} else {
Common::Vec3<float> accel;
std::tie(accel, std::ignore) = motion_device->GetStatus();
accel *= accelerometer_coef;
// TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
// The time stretch formula should be like
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
accelerometer_entry.x = static_cast<s16>(accel.x);
accelerometer_entry.y = static_cast<s16>(accel.y);
accelerometer_entry.z = static_cast<s16>(accel.z);
}
system.Movie().HandleAccelerometerStatus(accelerometer_entry);
@ -278,13 +369,21 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late)
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
Common::Vec3<float> gyro;
std::tie(std::ignore, gyro) = motion_device->GetStatus();
double stretch = system.perf_stats->GetLastFrameTimeScale();
gyro *= gyroscope_coef * static_cast<float>(stretch);
gyroscope_entry.x = static_cast<s16>(gyro.x);
gyroscope_entry.y = static_cast<s16>(gyro.y);
gyroscope_entry.z = static_cast<s16>(gyro.z);
if (artic_controller.get() && artic_controller->IsReady()) {
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
gyroscope_entry.x = data.gyro_x;
gyroscope_entry.y = data.gyro_y;
gyroscope_entry.z = data.gyro_z;
} else {
Common::Vec3<float> gyro;
std::tie(std::ignore, gyro) = motion_device->GetStatus();
double stretch = system.perf_stats->GetLastFrameTimeScale();
gyro *= gyroscope_coef * static_cast<float>(stretch);
gyroscope_entry.x = static_cast<s16>(gyro.x);
gyroscope_entry.y = static_cast<s16>(gyro.y);
gyroscope_entry.z = static_cast<s16>(gyro.z);
}
system.Movie().HandleGyroscopeStatus(gyroscope_entry);
@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_EnableAccelerometer");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
++hid->enable_accelerometer_count;
// Schedules the accelerometer update event if the accelerometer was just enabled
@ -324,15 +440,29 @@ void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
hid->accelerometer_update_event);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_DisableAccelerometer");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
--hid->enable_accelerometer_count;
// Unschedules the accelerometer update event if the accelerometer was just disabled
@ -340,15 +470,29 @@ void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_EnableGyroscope");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
++hid->enable_gyroscope_count;
// Schedules the gyroscope update event if the gyroscope was just enabled
@ -356,15 +500,29 @@ void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_DisableGyroscope");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
--hid->enable_gyroscope_count;
// Unschedules the gyroscope update event if the gyroscope was just disabled
@ -372,9 +530,6 @@ void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
@ -382,25 +537,90 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(gyroscope_coef);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_GetGyroRawToDpsCoef");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
rb.Push(0.f);
return;
}
Result res = Result{static_cast<u32>(resp->GetMethodResult())};
if (res.IsError()) {
rb.Push(res);
rb.Push(0.f);
return;
}
auto coef = resp->GetResponseFloat(0);
if (!coef.has_value()) {
rb.Push(ResultUnknown);
rb.Push(0.f);
return;
}
rb.Push(res);
rb.Push(*coef);
} else {
rb.Push(ResultSuccess);
rb.Push(gyroscope_coef);
}
}
void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
rb.Push(ResultSuccess);
const s16 param_unit = 6700; // an approximate value taken from hw
GyroscopeCalibrateParam param = {
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
};
rb.PushRaw(param);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
GyroscopeCalibrateParam param;
LOG_WARNING(Service_HID, "(STUBBED) called");
auto req = artic_client->NewRequest("HIDUSER_GetGyroCalibrateParam");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
rb.PushRaw(param);
return;
}
Result res = Result{static_cast<u32>(resp->GetMethodResult())};
if (res.IsError()) {
rb.Push(res);
rb.PushRaw(param);
return;
}
auto param_buf = resp->GetResponseBuffer(0);
if (!param_buf.has_value() || param_buf->second != sizeof(param)) {
rb.Push(ResultUnknown);
rb.PushRaw(param);
return;
}
memcpy(&param, param_buf->first, sizeof(param));
rb.Push(res);
rb.PushRaw(param);
} else {
rb.Push(ResultSuccess);
const s16 param_unit = 6700; // an approximate value taken from hw
GyroscopeCalibrateParam param = {
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
};
rb.PushRaw(param);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
}
void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
@ -454,6 +674,24 @@ Module::Module(Core::System& system) : system(system) {
timing.ScheduleEvent(pad_update_ticks, pad_update_event);
}
void Module::UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
artic_controller = std::make_shared<ArticBaseController>(client);
if (!artic_controller->IsCreated()) {
artic_controller.reset();
} else {
auto ir_user = system.ServiceManager().GetService<Service::IR::IR_USER>("ir:USER");
if (ir_user.get()) {
ir_user->UseArticController(artic_controller);
}
auto ir_rst = system.ServiceManager().GetService<Service::IR::IR_RST>("ir:rst");
if (ir_rst.get()) {
ir_rst->UseArticController(artic_controller);
}
}
}
void Module::ReloadInputDevices() {
is_device_reload_pending.store(true);
}

View file

@ -14,13 +14,11 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/input.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
#include "network/artic_base/artic_base_client.h"
namespace Kernel {
class Event;
@ -199,6 +197,44 @@ struct DirectionState {
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use.
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
class ArticBaseController {
public:
struct ControllerData {
u32 index{};
u32 pad{};
s16 c_pad_x{};
s16 c_pad_y{};
u16 touch_x{};
u16 touch_y{};
s16 c_stick_x{};
s16 c_stick_y{};
s16 accel_x{};
s16 accel_y{};
s16 accel_z{};
s16 gyro_x{};
s16 gyro_y{};
s16 gyro_z{};
};
static_assert(sizeof(ControllerData) == 0x20, "Incorrect ControllerData size");
ArticBaseController(const std::shared_ptr<Network::ArticBase::Client>& client);
bool IsCreated() {
return udp_stream.get();
}
bool IsReady() {
return udp_stream.get() ? udp_stream->IsReady() : false;
}
ControllerData GetControllerData();
private:
std::shared_ptr<Network::ArticBase::Client::UDPStream> udp_stream;
u32 last_packet_id{};
ControllerData last_controller_data{};
};
class Module final {
public:
explicit Module(Core::System& system);
@ -296,6 +332,8 @@ public:
std::shared_ptr<Module> hid;
};
void UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client);
void ReloadInputDevices();
const PadState& GetState() const;
@ -355,6 +393,9 @@ private:
std::unique_ptr<Input::TouchDevice> touch_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::shared_ptr<ArticBaseController> artic_controller;
std::shared_ptr<Network::ArticBase::Client> artic_client;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;

View file

@ -6,6 +6,7 @@
#include "common/alignment.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/extra_hid.h"
#include "core/movie.h"
@ -230,23 +231,47 @@ void ExtraHID::SendHIDStatus() {
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
constexpr u32 ZL_BUTTON = (1 << 14);
constexpr u32 ZR_BUTTON = (1 << 15);
constexpr int C_STICK_CENTER = 0x800;
// TODO(wwylele): this value is not accurately measured. We currently assume that the axis can
// take values in the whole range of a 12-bit integer.
constexpr int C_STICK_RADIUS = 0x7FF;
float x, y;
std::tie(x, y) = c_stick->GetStatus();
ExtraHIDResponse response{};
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign(!zl->GetStatus());
response.buttons.zr_not_held.Assign(!zr->GetStatus());
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
if (artic_controller.get() && artic_controller->IsReady()) {
Service::HID::ArticBaseController::ControllerData data =
artic_controller->GetControllerData();
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
response.c_stick.c_stick_x.Assign(static_cast<u32>(
(static_cast<float>(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS +
C_STICK_CENTER));
response.c_stick.c_stick_y.Assign(static_cast<u32>(
(static_cast<float>(data.c_stick_y) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS +
C_STICK_CENTER));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign((data.pad & ZL_BUTTON) == 0);
response.buttons.zr_not_held.Assign((data.pad & ZR_BUTTON) == 0);
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
} else {
float x, y;
std::tie(x, y) = c_stick->GetStatus();
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign(!zl->GetStatus());
response.buttons.zr_not_held.Assign(!zr->GetStatus());
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
}
movie.HandleExtraHidResponse(response);

View file

@ -19,6 +19,10 @@ class Timing;
class Movie;
} // namespace Core
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR {
struct ExtraHIDResponse {
@ -54,6 +58,10 @@ public:
/// Requests input devices reload from current settings. Called when the input settings change.
void RequestInputDevicesReload();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
artic_controller = ac;
}
private:
void SendHIDStatus();
void HandleConfigureHIDPollingRequest(std::span<const u8> request);
@ -70,6 +78,8 @@ private:
std::unique_ptr<Input::AnalogDevice> c_stick;
std::atomic<bool> is_device_reload_pending;
std::shared_ptr<Service::HID::ArticBaseController> artic_controller = nullptr;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& hid_period;

View file

@ -72,25 +72,41 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) {
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000;
PadState state;
state.zl.Assign(zl_button->GetStatus());
state.zr.Assign(zr_button->GetStatus());
s16 c_stick_x, c_stick_y;
// Get current c-stick position and update c-stick direction
float c_stick_x_f, c_stick_y_f;
std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus();
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS);
if (artic_controller.get() && artic_controller->IsReady()) {
Service::HID::ArticBaseController::ControllerData data =
artic_controller->GetControllerData();
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
state.hex = data.pad & VALID_EXTRAHID_KEYS;
if (!raw_c_stick) {
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
state.c_stick_up.Assign(direction.up);
state.c_stick_down.Assign(direction.down);
state.c_stick_left.Assign(direction.left);
state.c_stick_right.Assign(direction.right);
c_stick_x = data.c_stick_x;
c_stick_y = data.c_stick_y;
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
} else {
state.zl.Assign(zl_button->GetStatus());
state.zr.Assign(zr_button->GetStatus());
// Get current c-stick position and update c-stick direction
float c_stick_x_f, c_stick_y_f;
std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus();
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS);
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
if (!raw_c_stick) {
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
state.c_stick_up.Assign(direction.up);
state.c_stick_down.Assign(direction.down);
state.c_stick_left.Assign(direction.left);
state.c_stick_right.Assign(direction.right);
}
}
// TODO (wwylele): implement raw C-stick data for raw_c_stick = true

View file

@ -21,6 +21,10 @@ namespace Core {
struct TimingEventType;
};
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR {
union PadState {
@ -42,6 +46,10 @@ public:
~IR_RST();
void ReloadInputDevices();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
artic_controller = ac;
}
private:
/**
* GetHandles service function
@ -88,6 +96,8 @@ private:
bool raw_c_stick{false};
int update_period{0};
std::shared_ptr<Service::HID::ArticBaseController> artic_controller = nullptr;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;

View file

@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() {
extra_hid->RequestInputDevicesReload();
}
void IR_USER::UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
if (extra_hid.get()) {
extra_hid->UseArticController(ac);
}
}
IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {}
IRDevice::~IRDevice() = default;

View file

@ -14,6 +14,10 @@ class Event;
class SharedMemory;
} // namespace Kernel
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR {
class BufferManager;
@ -57,6 +61,8 @@ public:
void ReloadInputDevices();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac);
private:
/**
* InitializeIrNopShared service function

View file

@ -27,6 +27,7 @@
#include "core/hle/service/cfg/cfg_u.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/hid/hid_user.h"
#include "core/loader/artic.h"
#include "core/loader/smdh.h"
#include "core/memory.h"
@ -361,6 +362,13 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
amapp->UseArticClient(client);
}
if (Settings::values.use_artic_base_controller.GetValue()) {
auto hid_user = system.ServiceManager().GetService<Service::HID::User>("hid:USER");
if (hid_user.get()) {
hid_user->GetModule()->UseArticClient(client);
}
}
ParseRegionLockoutInfo(ncch_program_id);
return ResultStatus::Success;

View file

@ -121,6 +121,81 @@ Client::Request::Request(u32 request_id, const std::string& method, size_t max_p
std::min<size_t>(request_packet.method.size(), method.size()));
}
void Client::UDPStream::Start() {
thread_run = true;
handle_thread = std::thread(&Client::UDPStream::Handle, this);
}
void Client::UDPStream::Handle() {
struct sockaddr_in* servaddr = reinterpret_cast<sockaddr_in*>(serv_sockaddr_in.data());
socklen_t serv_sockaddr_len = static_cast<socklen_t>(serv_sockaddr_in.size());
memcpy(servaddr, client.GetServerAddr().data(), client.GetServerAddr().size());
servaddr->sin_port = htons(port);
main_socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (main_socket == static_cast<SocketHolder>(-1) || !thread_run) {
LOG_ERROR(Network, "Failed to create socket");
return;
}
if (!SetNonBlock(main_socket, true) || !thread_run) {
closesocket(main_socket);
LOG_ERROR(Network, "Cannot set non-blocking socket mode");
return;
}
// Limit receive buffer so that packets don't get qeued and are dropped instead.
int buffer_size_int = static_cast<int>(buffer_size);
if (::setsockopt(main_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&buffer_size_int),
sizeof(buffer_size_int)) ||
!thread_run) {
closesocket(main_socket);
LOG_ERROR(Network, "Cannot change receive buffer size");
return;
}
// Send data to server so that it knows client address.
char zero = '\0';
int send_res =
::sendto(main_socket, &zero, sizeof(char), 0,
reinterpret_cast<struct sockaddr*>(serv_sockaddr_in.data()), serv_sockaddr_len);
if (send_res < 0 || !thread_run) {
closesocket(main_socket);
LOG_ERROR(Network, "Cannot send data to socket");
return;
}
ready = true;
std::vector<u8> buffer(buffer_size);
while (thread_run) {
std::chrono::steady_clock::time_point before = std::chrono::steady_clock::now();
int packet_size = ::recvfrom(
main_socket, reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size()), 0,
reinterpret_cast<struct sockaddr*>(serv_sockaddr_in.data()), &serv_sockaddr_len);
if (packet_size > 0) {
if (client.report_traffic_callback) {
client.report_traffic_callback(packet_size);
}
buffer.resize(packet_size);
{
std::scoped_lock l(current_buffer_mutex);
current_buffer = buffer;
}
}
auto elapsed = std::chrono::steady_clock::now() - before;
std::unique_lock lk(thread_cv_mutex);
thread_cv.wait_for(lk, elapsed < read_interval ? (read_interval - elapsed)
: std::chrono::microseconds(50));
}
ready = false;
closesocket(main_socket);
}
Client::~Client() {
StopImpl(false);
@ -182,6 +257,7 @@ bool Client::Connect() {
servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr;
servaddr.sin_port = htons(port);
freeaddrinfo(addrinfo);
memcpy(last_sockaddr_in.data(), &servaddr, last_sockaddr_in.size());
if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) {
closesocket(main_socket);
@ -249,15 +325,15 @@ bool Client::Connect() {
std::string str_port;
std::stringstream ss_port(worker_ports.value());
while (std::getline(ss_port, str_port, ',')) {
int port = str_to_int(str_port);
if (port < 0 || port > static_cast<int>(USHRT_MAX)) {
int port_curr = str_to_int(str_port);
if (port_curr < 0 || port_curr > static_cast<int>(USHRT_MAX)) {
shutdown(main_socket, SHUT_RDWR);
closesocket(main_socket);
LOG_ERROR(Network, "Couldn't parse server worker ports");
SignalCommunicationError();
return false;
}
ports.push_back(static_cast<u16>(port));
ports.push_back(static_cast<u16>(port_curr));
}
if (ports.empty()) {
shutdown(main_socket, SHUT_RDWR);
@ -294,6 +370,29 @@ bool Client::Connect() {
return true;
}
std::shared_ptr<Client::UDPStream> Client::NewUDPStream(
const std::string stream_id, size_t buffer_size,
const std::chrono::milliseconds& read_interval) {
auto req = NewRequest("#" + stream_id);
auto resp = Send(req);
if (!resp.has_value()) {
return nullptr;
}
auto port_udp = resp->GetResponseS32(0);
if (!port_udp.has_value()) {
return nullptr;
}
udp_streams.push_back(std::make_shared<UDPStream>(*this, static_cast<u16>(*port_udp),
buffer_size, read_interval));
return udp_streams.back();
}
void Client::StopImpl(bool from_error) {
bool expected = false;
if (!stopped.compare_exchange_strong(expected, true))
@ -303,6 +402,10 @@ void Client::StopImpl(bool from_error) {
SendSimpleRequest("STOP");
}
for (auto it = udp_streams.begin(); it != udp_streams.end(); it++) {
it->get()->Stop();
}
if (ping_thread.joinable()) {
std::scoped_lock l2(ping_cv_mutex);
ping_run = false;

View file

@ -60,6 +60,61 @@ public:
std::vector<std::pair<const void*, size_t>> pending_big_buffers;
};
class UDPStream {
public:
std::vector<u8> GetLastPacket() {
std::scoped_lock l(current_buffer_mutex);
return current_buffer;
}
bool IsReady() {
return ready;
}
void Start();
void Stop() {
if (thread_run && handle_thread.joinable()) {
std::scoped_lock l2(thread_cv_mutex);
thread_run = false;
thread_cv.notify_one();
}
}
UDPStream(Client& _client, u16 _port, size_t _buffer_size,
const std::chrono::milliseconds& _read_interval)
: client(_client), port(_port), buffer_size(_buffer_size),
read_interval(_read_interval) {}
~UDPStream() {
Stop();
if (handle_thread.joinable()) {
handle_thread.join();
}
}
private:
void Handle();
Client& client;
u16 port;
size_t buffer_size;
std::chrono::milliseconds read_interval;
std::array<u8, 16> serv_sockaddr_in{};
bool ready = false;
std::mutex current_buffer_mutex;
std::vector<u8> current_buffer;
SocketHolder main_socket = -1;
std::thread handle_thread;
std::condition_variable thread_cv;
std::mutex thread_cv_mutex;
std::atomic<bool> thread_run = true;
};
friend class UDPStream;
Client(const std::string& _address, u16 _port) : address(_address), port(_port) {
SocketManager::EnableSockets();
}
@ -76,6 +131,10 @@ public:
return Request(GetNextRequestID(), method, max_parameter_count);
}
std::shared_ptr<UDPStream> NewUDPStream(
const std::string stream_id, size_t buffer_size,
const std::chrono::milliseconds& read_interval = std::chrono::milliseconds(0));
void Stop() {
StopImpl(false);
}
@ -97,11 +156,17 @@ public:
report_artic_event_callback = callback;
}
// Returns the server address as a sockaddr_in struct
const std::array<u8, 16>& GetServerAddr() {
return last_sockaddr_in;
}
private:
static constexpr const int SERVER_VERSION = 1;
static constexpr const int SERVER_VERSION = 2;
std::string address;
u16 port;
std::array<u8, 16> last_sockaddr_in;
SocketHolder main_socket = -1;
std::atomic<u32> currRequestID;
@ -124,7 +189,7 @@ private:
std::thread ping_thread;
std::condition_variable ping_cv;
std::mutex ping_cv_mutex;
bool ping_run = true;
std::atomic<bool> ping_run = true;
void StopImpl(bool from_error);
@ -145,6 +210,8 @@ private:
const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0));
std::optional<std::string> SendSimpleRequest(const std::string& method);
std::vector<std::shared_ptr<UDPStream>> udp_streams;
class Handler {
public:
Handler(Client& _client, u32 _addr, u16 _port, int _id);
@ -242,6 +309,14 @@ public:
return *reinterpret_cast<u64*>(buf->first);
}
std::optional<float> GetResponseFloat(u32 buffer_id) const {
auto buf = GetResponseBuffer(buffer_id);
if (!buf.has_value() || buf->second != sizeof(float)) {
return std::nullopt;
}
return *reinterpret_cast<float*>(buf->first);
}
private:
friend class Client;
friend class Client::Handler;