mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
More multi-controller fixes, better controller polling logic, clean up dead relay code
This commit is contained in:
4
packages/server/Cargo.lock
generated
4
packages/server/Cargo.lock
generated
@@ -5079,9 +5079,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vimputti"
|
||||
version = "0.1.4"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb370ee43e3ee4ca5329886e64dc5b27c83dc8cced5a63c2418777dac9a41a8"
|
||||
checksum = "6440b3684270f355fb89193bfb0de957686119626b8b207f21d91024a892d05c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"libc",
|
||||
|
||||
@@ -22,7 +22,7 @@ rand = "0.9"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
vimputti = "0.1.4"
|
||||
vimputti = "0.1.7"
|
||||
chrono = "0.4"
|
||||
prost = "0.14"
|
||||
prost-types = "0.14"
|
||||
|
||||
@@ -29,10 +29,8 @@ impl ControllerInput {
|
||||
client: &vimputti::client::VimputtiClient,
|
||||
) -> Result<Self> {
|
||||
let config = controller_string_to_type(&controller_type)?;
|
||||
Ok(Self {
|
||||
config: config.clone(),
|
||||
device: client.create_device(config).await?,
|
||||
})
|
||||
let device = client.create_device(config.clone()).await?;
|
||||
Ok(Self { config, device })
|
||||
}
|
||||
|
||||
pub fn device_mut(&mut self) -> &mut vimputti::client::VirtualController {
|
||||
@@ -121,9 +119,60 @@ async fn command_loop(
|
||||
slot.session_id == session_id && slot.session_slot == session_slot as u32
|
||||
})
|
||||
.map(|(slot_num, _)| *slot_num);
|
||||
let slot = existing_slot.or_else(|| get_free_slot(&controllers));
|
||||
|
||||
if let Some(slot) = slot {
|
||||
if let Some(existing_slot) = existing_slot {
|
||||
if let Some(controller_slot) = controllers.get_mut(&existing_slot) {
|
||||
let rumble_tx = rumble_tx.clone();
|
||||
let attach_tx = attach_tx.clone();
|
||||
|
||||
controller_slot
|
||||
.controller
|
||||
.device_mut()
|
||||
.on_rumble(move |strong, weak, duration_ms| {
|
||||
let _ = rumble_tx.try_send((
|
||||
existing_slot,
|
||||
strong,
|
||||
weak,
|
||||
duration_ms,
|
||||
data.session_id.clone(),
|
||||
));
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"Failed to register rumble callback for slot {}: {}",
|
||||
existing_slot,
|
||||
e
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Return to attach_tx what slot was assigned
|
||||
let attach_info = ProtoControllerAttach {
|
||||
id: data.id.clone(),
|
||||
session_slot: existing_slot as i32,
|
||||
session_id: session_id.clone(),
|
||||
};
|
||||
|
||||
match attach_tx.send(attach_info).await {
|
||||
Ok(_) => {
|
||||
tracing::info!(
|
||||
"Controller {} re-attached to slot {} (session: {})",
|
||||
data.id,
|
||||
existing_slot,
|
||||
session_id
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to send re-attach info for slot {}: {}",
|
||||
existing_slot,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(slot) = get_free_slot(&controllers) {
|
||||
if let Ok(mut controller) =
|
||||
ControllerInput::new(data.id.clone(), &vimputti_client).await
|
||||
{
|
||||
@@ -133,7 +182,13 @@ async fn command_loop(
|
||||
controller
|
||||
.device_mut()
|
||||
.on_rumble(move |strong, weak, duration_ms| {
|
||||
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms, data.session_id.clone()));
|
||||
let _ = rumble_tx.try_send((
|
||||
slot,
|
||||
strong,
|
||||
weak,
|
||||
duration_ms,
|
||||
data.session_id.clone(),
|
||||
));
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -190,65 +245,10 @@ async fn command_loop(
|
||||
if controllers.remove(&(data.session_slot as u32)).is_some() {
|
||||
tracing::info!("Controller detached from slot {}", data.session_slot);
|
||||
} else {
|
||||
tracing::warn!("No controller found in slot {} to detach", data.session_slot);
|
||||
}
|
||||
}
|
||||
Payload::ControllerButton(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
|
||||
let device = controller.controller.device();
|
||||
device.button(button, data.pressed);
|
||||
device.sync();
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for button event", data.session_slot);
|
||||
}
|
||||
}
|
||||
Payload::ControllerStick(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||
let device = controller.controller.device();
|
||||
if data.stick == 0 {
|
||||
// Left stick
|
||||
device.axis(vimputti::Axis::LeftStickX, data.x);
|
||||
device.sync();
|
||||
device.axis(vimputti::Axis::LeftStickY, data.y);
|
||||
} else if data.stick == 1 {
|
||||
// Right stick
|
||||
device.axis(vimputti::Axis::RightStickX, data.x);
|
||||
device.sync();
|
||||
device.axis(vimputti::Axis::RightStickY, data.y);
|
||||
}
|
||||
device.sync();
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for stick event", data.session_slot);
|
||||
}
|
||||
}
|
||||
Payload::ControllerTrigger(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||
let device = controller.controller.device();
|
||||
if data.trigger == 0 {
|
||||
// Left trigger
|
||||
device.axis(vimputti::Axis::LowerLeftTrigger, data.value);
|
||||
} else if data.trigger == 1 {
|
||||
// Right trigger
|
||||
device.axis(vimputti::Axis::LowerRightTrigger, data.value);
|
||||
}
|
||||
device.sync();
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for trigger event", data.session_slot);
|
||||
}
|
||||
}
|
||||
Payload::ControllerAxis(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||
let device = controller.controller.device();
|
||||
if data.axis == 0 {
|
||||
// dpad x
|
||||
device.axis(vimputti::Axis::DPadX, data.value);
|
||||
} else if data.axis == 1 {
|
||||
// dpad y
|
||||
device.axis(vimputti::Axis::DPadY, data.value);
|
||||
}
|
||||
device.sync();
|
||||
tracing::warn!(
|
||||
"No controller found in slot {} to detach",
|
||||
data.session_slot
|
||||
);
|
||||
}
|
||||
}
|
||||
Payload::ClientDisconnected(data) => {
|
||||
@@ -274,6 +274,125 @@ async fn command_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
Payload::ControllerStateBatch(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.session_slot as u32)) {
|
||||
let device = controller.controller.device();
|
||||
|
||||
// Handle inputs based on update type
|
||||
if data.update_type == 0 {
|
||||
// FULL_STATE: Update all values
|
||||
let _ = device.sync().await;
|
||||
for (btn_code, pressed) in data.button_changed_mask {
|
||||
if let Some(button) = vimputti::Button::from_ev_code(btn_code as u16) {
|
||||
let _ = device.button(button, pressed).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if let Some(x) = data.left_stick_x {
|
||||
let _ = device.axis(vimputti::Axis::LeftStickX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(y) = data.left_stick_y {
|
||||
let _ = device.axis(vimputti::Axis::LeftStickY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(x) = data.right_stick_x {
|
||||
let _ = device.axis(vimputti::Axis::RightStickX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(y) = data.right_stick_y {
|
||||
let _ = device.axis(vimputti::Axis::RightStickY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(value) = data.left_trigger {
|
||||
let _ = device.axis(vimputti::Axis::LowerLeftTrigger, value).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(value) = data.right_trigger {
|
||||
let _ = device.axis(vimputti::Axis::LowerRightTrigger, value).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(x) = data.dpad_x {
|
||||
let _ = device.axis(vimputti::Axis::DPadX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
if let Some(y) = data.dpad_y {
|
||||
let _ = device.axis(vimputti::Axis::DPadY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
} else {
|
||||
// DELTA: Only update changed values
|
||||
if let Some(changed_fields) = data.changed_fields {
|
||||
let _ = device.sync().await;
|
||||
if (changed_fields & (1 << 0)) != 0 {
|
||||
for (btn_code, pressed) in data.button_changed_mask {
|
||||
if let Some(button) =
|
||||
vimputti::Button::from_ev_code(btn_code as u16)
|
||||
{
|
||||
let _ = device.button(button, pressed).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 1)) != 0 {
|
||||
if let Some(x) = data.left_stick_x {
|
||||
let _ = device.axis(vimputti::Axis::LeftStickX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 2)) != 0 {
|
||||
if let Some(y) = data.left_stick_y {
|
||||
let _ = device.axis(vimputti::Axis::LeftStickY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 3)) != 0 {
|
||||
if let Some(x) = data.right_stick_x {
|
||||
let _ = device.axis(vimputti::Axis::RightStickX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 4)) != 0 {
|
||||
if let Some(y) = data.right_stick_y {
|
||||
let _ = device.axis(vimputti::Axis::RightStickY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 5)) != 0 {
|
||||
if let Some(value) = data.left_trigger {
|
||||
let _ =
|
||||
device.axis(vimputti::Axis::LowerLeftTrigger, value).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 6)) != 0 {
|
||||
if let Some(value) = data.right_trigger {
|
||||
let _ =
|
||||
device.axis(vimputti::Axis::LowerRightTrigger, value).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 7)) != 0 {
|
||||
if let Some(x) = data.dpad_x {
|
||||
let _ = device.axis(vimputti::Axis::DPadX, x).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
if (changed_fields & (1 << 8)) != 0 {
|
||||
if let Some(y) = data.dpad_y {
|
||||
let _ = device.axis(vimputti::Axis::DPadY, y).await;
|
||||
let _ = device.sync().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Controller slot {} not found for state batch event",
|
||||
data.session_slot
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
//no-op
|
||||
}
|
||||
|
||||
@@ -102,77 +102,6 @@ pub struct ProtoControllerDetach {
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
}
|
||||
/// ControllerButton message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerButton {
|
||||
/// Session specific slot number (0-3)
|
||||
#[prost(int32, tag="1")]
|
||||
pub session_slot: i32,
|
||||
/// Session ID of the client
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
/// Button code (linux input event code)
|
||||
#[prost(int32, tag="3")]
|
||||
pub button: i32,
|
||||
/// true if pressed, false if released
|
||||
#[prost(bool, tag="4")]
|
||||
pub pressed: bool,
|
||||
}
|
||||
/// ControllerTriggers message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerTrigger {
|
||||
/// Session specific slot number (0-3)
|
||||
#[prost(int32, tag="1")]
|
||||
pub session_slot: i32,
|
||||
/// Session ID of the client
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
/// Trigger number (0 for left, 1 for right)
|
||||
#[prost(int32, tag="3")]
|
||||
pub trigger: i32,
|
||||
/// trigger value (-32768 to 32767)
|
||||
#[prost(int32, tag="4")]
|
||||
pub value: i32,
|
||||
}
|
||||
/// ControllerSticks message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerStick {
|
||||
/// Session specific slot number (0-3)
|
||||
#[prost(int32, tag="1")]
|
||||
pub session_slot: i32,
|
||||
/// Session ID of the client
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
/// Stick number (0 for left, 1 for right)
|
||||
#[prost(int32, tag="3")]
|
||||
pub stick: i32,
|
||||
/// X axis value (-32768 to 32767)
|
||||
#[prost(int32, tag="4")]
|
||||
pub x: i32,
|
||||
/// Y axis value (-32768 to 32767)
|
||||
#[prost(int32, tag="5")]
|
||||
pub y: i32,
|
||||
}
|
||||
/// ControllerAxis message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerAxis {
|
||||
/// Session specific slot number (0-3)
|
||||
#[prost(int32, tag="1")]
|
||||
pub session_slot: i32,
|
||||
/// Session ID of the client
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
/// Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
#[prost(int32, tag="3")]
|
||||
pub axis: i32,
|
||||
/// axis value (-1 to 1)
|
||||
#[prost(int32, tag="4")]
|
||||
pub value: i32,
|
||||
}
|
||||
/// ControllerRumble message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -193,6 +122,86 @@ pub struct ProtoControllerRumble {
|
||||
#[prost(int32, tag="5")]
|
||||
pub duration: i32,
|
||||
}
|
||||
/// ControllerStateBatch - single message containing full or partial controller state
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerStateBatch {
|
||||
/// Session specific slot number (0-3)
|
||||
#[prost(int32, tag="1")]
|
||||
pub session_slot: i32,
|
||||
/// Session ID of the client
|
||||
#[prost(string, tag="2")]
|
||||
pub session_id: ::prost::alloc::string::String,
|
||||
#[prost(enumeration="proto_controller_state_batch::UpdateType", tag="3")]
|
||||
pub update_type: i32,
|
||||
/// Sequence number for packet loss detection
|
||||
#[prost(uint32, tag="4")]
|
||||
pub sequence: u32,
|
||||
/// Button state map (Linux event codes)
|
||||
#[prost(map="int32, bool", tag="5")]
|
||||
pub button_changed_mask: ::std::collections::HashMap<i32, bool>,
|
||||
/// Analog inputs
|
||||
///
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="6")]
|
||||
pub left_stick_x: ::core::option::Option<i32>,
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="7")]
|
||||
pub left_stick_y: ::core::option::Option<i32>,
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="8")]
|
||||
pub right_stick_x: ::core::option::Option<i32>,
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="9")]
|
||||
pub right_stick_y: ::core::option::Option<i32>,
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="10")]
|
||||
pub left_trigger: ::core::option::Option<i32>,
|
||||
/// -32768 to 32767
|
||||
#[prost(int32, optional, tag="11")]
|
||||
pub right_trigger: ::core::option::Option<i32>,
|
||||
/// -1, 0, or 1
|
||||
#[prost(int32, optional, tag="12")]
|
||||
pub dpad_x: ::core::option::Option<i32>,
|
||||
/// -1, 0, or 1
|
||||
#[prost(int32, optional, tag="13")]
|
||||
pub dpad_y: ::core::option::Option<i32>,
|
||||
/// Bitmask indicating which fields have changed
|
||||
/// Bit 0: button_changed_mask, Bit 1: left_stick_x, Bit 2: left_stick_y, etc.
|
||||
#[prost(uint32, optional, tag="14")]
|
||||
pub changed_fields: ::core::option::Option<u32>,
|
||||
}
|
||||
/// Nested message and enum types in `ProtoControllerStateBatch`.
|
||||
pub mod proto_controller_state_batch {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum UpdateType {
|
||||
/// Complete controller state
|
||||
FullState = 0,
|
||||
/// Only changed fields
|
||||
Delta = 1,
|
||||
}
|
||||
impl UpdateType {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
///
|
||||
/// The values are not transformed in any way and thus are considered stable
|
||||
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
UpdateType::FullState => "FULL_STATE",
|
||||
UpdateType::Delta => "DELTA",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"FULL_STATE" => Some(Self::FullState),
|
||||
"DELTA" => Some(Self::Delta),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// WebRTC + signaling
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
@@ -274,7 +283,7 @@ pub struct ProtoMessageBase {
|
||||
pub struct ProtoMessage {
|
||||
#[prost(message, optional, tag="1")]
|
||||
pub message_base: ::core::option::Option<ProtoMessageBase>,
|
||||
#[prost(oneof="proto_message::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25")]
|
||||
#[prost(oneof="proto_message::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20, 21, 22, 23, 24, 25")]
|
||||
pub payload: ::core::option::Option<proto_message::Payload>,
|
||||
}
|
||||
/// Nested message and enum types in `ProtoMessage`.
|
||||
@@ -297,20 +306,15 @@ pub mod proto_message {
|
||||
KeyDown(super::ProtoKeyDown),
|
||||
#[prost(message, tag="8")]
|
||||
KeyUp(super::ProtoKeyUp),
|
||||
/// Controller input types
|
||||
#[prost(message, tag="9")]
|
||||
ControllerAttach(super::ProtoControllerAttach),
|
||||
#[prost(message, tag="10")]
|
||||
ControllerDetach(super::ProtoControllerDetach),
|
||||
#[prost(message, tag="11")]
|
||||
ControllerButton(super::ProtoControllerButton),
|
||||
#[prost(message, tag="12")]
|
||||
ControllerTrigger(super::ProtoControllerTrigger),
|
||||
#[prost(message, tag="13")]
|
||||
ControllerStick(super::ProtoControllerStick),
|
||||
#[prost(message, tag="14")]
|
||||
ControllerAxis(super::ProtoControllerAxis),
|
||||
#[prost(message, tag="15")]
|
||||
ControllerRumble(super::ProtoControllerRumble),
|
||||
#[prost(message, tag="12")]
|
||||
ControllerStateBatch(super::ProtoControllerStateBatch),
|
||||
/// Signaling types
|
||||
#[prost(message, tag="20")]
|
||||
Ice(super::ProtoIce),
|
||||
|
||||
Reference in New Issue
Block a user