From 502fbb4b60e4cb576fa9e76138d2ad74de2e5018 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Tue, 3 Mar 2026 18:35:16 +0100 Subject: [PATCH 01/17] async_manager upgrades - AsyncManager is now a trait. The used async runtime is determined by the feature flag, but users can also set a global async manager at runtime if they want to use a custom async runtime or if the default one doesn't work for their use case. - All async_manager::spawn calls now take a tracing::Span parameter to ensure proper tracability of async tasks. - The span names for each task may not be perfect atm, but should still be better than none. - The `spawn_with_handle` function has been removed since it was never used. - Since sleep functions depend on the async runtime, they have been moved to the AsyncManager trait as well. - Moved to using tracing::Instrument directly instead of the tracing_futures crate. - Move usages of tokio::spawn to async_manager::spawn, except in intiface-engine which is assumed to be tokio runtime only. Unsure how to handle nintendo joycons, since it has feature gates, so it has been left mostly alone. LLM disclosure: LLMs were very mildly used to assist with this refactor, primarily just autocomplete for changing the spawn and sleep calls. I have tested the changes using cargo test and a simple in-process client connection - i have not been able to make wasm work, but i have tested a custom AsyncManager implementation. --- crates/buttplug_client/Cargo.toml | 1 - crates/buttplug_client/src/lib.rs | 31 ++-- .../src/in_process_connector.rs | 33 ++-- crates/buttplug_core/Cargo.toml | 2 + .../src/connector/remote_connector.rs | 34 ++-- .../src/connector/transport/stream.rs | 3 +- .../src/util/async_manager/dummy.rs | 40 ++--- .../src/util/async_manager/mod.rs | 72 +++++++- .../src/util/async_manager/tokio.rs | 41 ++--- .../src/util/async_manager/wasm.rs | 30 ++++ .../src/util/async_manager/wasm_bindgen.rs | 38 ---- crates/buttplug_core/src/util/mod.rs | 5 - crates/buttplug_server/Cargo.toml | 1 - .../src/device/device_handle.rs | 97 ++++++----- .../buttplug_server/src/device/device_task.rs | 26 +-- .../src/device/hardware/communication.rs | 28 +-- .../src/device/protocol_impl/cowgirl_cone.rs | 4 +- .../src/device/protocol_impl/fredorch.rs | 6 +- .../device/protocol_impl/fredorch_rotary.rs | 17 +- .../src/device/protocol_impl/hgod.rs | 17 +- .../src/device/protocol_impl/kgoal_boost.rs | 124 ++++++------- .../protocol_impl/lovense/lovense_stroker.rs | 15 +- .../src/device/protocol_impl/lovense/mod.rs | 5 +- .../device/protocol_impl/nintendo_joycon.rs | 62 +++---- .../src/device/protocol_impl/xuanhuan.rs | 9 +- .../src/device/server_device_manager.rs | 3 +- .../server_device_manager_event_loop.rs | 57 +++--- crates/buttplug_server/src/ping_timer.rs | 20 ++- crates/buttplug_server/src/server.rs | 15 +- crates/buttplug_server/src/server_builder.rs | 20 ++- .../src/btleplug_comm_manager.rs | 22 ++- .../src/btleplug_hardware.rs | 131 +++++++------- .../src/lovense_connect_service_hardware.rs | 61 ++++--- .../Cargo.toml | 1 - .../src/lovense_dongle_hardware.rs | 60 ++++--- .../src/lovense_hid_dongle_comm_manager.rs | 10 +- .../src/serialport_hardware.rs | 48 ++--- .../src/websocket_server_comm_manager.rs | 164 +++++++++--------- .../src/websocket_server_hardware.rs | 52 +++--- .../src/xinput_hardware.rs | 12 +- .../buttplug_tests/tests/test_serializers.rs | 47 ++--- .../tests/util/channel_transport.rs | 75 ++++---- .../device_test/client/client_v2/client.rs | 29 ++-- .../client/client_v2/in_process_connector.rs | 2 +- .../util/device_test/client/client_v2/mod.rs | 27 +-- .../device_test/client/client_v3/client.rs | 34 ++-- .../connector/in_process_connector.rs | 2 +- .../util/device_test/client/client_v3/mod.rs | 27 +-- .../util/device_test/client/client_v4/mod.rs | 38 ++-- .../connector/channel_transport.rs | 3 +- .../util/test_device_manager/test_device.rs | 54 +++--- .../buttplug_tests/tests/util/test_server.rs | 42 +++-- .../src/websocket_client.rs | 6 +- .../src/websocket_server.rs | 3 +- crates/intiface_engine/src/remote_server.rs | 2 +- 55 files changed, 993 insertions(+), 815 deletions(-) create mode 100644 crates/buttplug_core/src/util/async_manager/wasm.rs delete mode 100644 crates/buttplug_core/src/util/async_manager/wasm_bindgen.rs diff --git a/crates/buttplug_client/Cargo.toml b/crates/buttplug_client/Cargo.toml index 1aed14600..f1b3ffd8d 100644 --- a/crates/buttplug_client/Cargo.toml +++ b/crates/buttplug_client/Cargo.toml @@ -26,7 +26,6 @@ log = "0.4.29" getset = "0.1.6" tokio = { version = "1.49.0", features = ["macros"] } dashmap = { version = "6.1.0" } -tracing-futures = "0.2.5" tracing = "0.1.44" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/crates/buttplug_client/src/lib.rs b/crates/buttplug_client/src/lib.rs index c0ad8415d..664461184 100644 --- a/crates/buttplug_client/src/lib.rs +++ b/crates/buttplug_client/src/lib.rs @@ -20,7 +20,17 @@ use buttplug_core::{ connector::{ButtplugConnector, ButtplugConnectorError}, errors::{ButtplugError, ButtplugHandshakeError}, message::{ - BUTTPLUG_CURRENT_API_MAJOR_VERSION, BUTTPLUG_CURRENT_API_MINOR_VERSION, ButtplugClientMessageV4, ButtplugServerMessageV4, InputType, PingV0, RequestDeviceListV0, RequestServerInfoV4, StartScanningV0, StopCmdV4, StopScanningV0 + BUTTPLUG_CURRENT_API_MAJOR_VERSION, + BUTTPLUG_CURRENT_API_MINOR_VERSION, + ButtplugClientMessageV4, + ButtplugServerMessageV4, + InputType, + PingV0, + RequestDeviceListV0, + RequestServerInfoV4, + StartScanningV0, + StopCmdV4, + StopScanningV0, }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; @@ -43,7 +53,7 @@ use std::{ use strum_macros::Display; use thiserror::Error; use tokio::sync::{Mutex, broadcast, mpsc}; -use tracing_futures::Instrument; +use tracing::info_span; /// Result type used for public APIs. /// @@ -103,7 +113,7 @@ pub enum ButtplugClientError { /// Error converting output command: {} ButtplugOutputCommandConversionError(String), /// Multiple inputs available for {}, must use specific feature - ButtplugMultipleInputAvailableError(InputType) + ButtplugMultipleInputAvailableError(InputType), } /// Enum representing different events that can be emitted by a client. @@ -286,14 +296,11 @@ impl ButtplugClient { // Take the request receiver - if None, a previous connection consumed it and we can't reconnect // without creating a new client (the sender is tied to this receiver) - let request_receiver = self - .request_receiver - .lock() - .await - .take() - .ok_or(ButtplugConnectorError::ConnectorGenericError( + let request_receiver = self.request_receiver.lock().await.take().ok_or( + ButtplugConnectorError::ConnectorGenericError( "Cannot reconnect - request channel already consumed. Create a new client.".to_string(), - ))?; + ), + )?; info!("Connecting to server."); let (connector_sender, connector_receiver) = mpsc::channel(256); @@ -316,8 +323,8 @@ impl ButtplugClient { async_manager::spawn( async move { client_event_loop.run().await; - } - .instrument(tracing::info_span!("Client Loop Span")), + }, + info_span!("ClientLoop").or_current(), ); self.run_handshake().await } diff --git a/crates/buttplug_client_in_process/src/in_process_connector.rs b/crates/buttplug_client_in_process/src/in_process_connector.rs index 95ca314f5..b0f0ce577 100644 --- a/crates/buttplug_client_in_process/src/in_process_connector.rs +++ b/crates/buttplug_client_in_process/src/in_process_connector.rs @@ -29,7 +29,7 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, }; use tokio::sync::mpsc::{Sender, channel}; -use tracing_futures::Instrument; +use tracing::info_span; #[derive(Default)] pub struct ButtplugInProcessClientConnectorBuilder { @@ -116,21 +116,24 @@ impl ButtplugConnector self.server_outbound_sender = message_sender; let server_recv = self.server.server_version_event_stream(); async move { - async_manager::spawn(async move { - info!("Starting In Process Client Connector Event Sender Loop"); - pin_mut!(server_recv); - while let Some(event) = server_recv.next().await { - // If we get an error back, it means the client dropped our event - // handler, so just stop trying. Otherwise, since this is an - // in-process conversion, we can unwrap because we know our - // try_into() will always succeed (which may not be the case with - // remote connections that have different spec versions). - if send.send(event).await.is_err() { - break; + async_manager::spawn( + async move { + info!("Starting In Process Client Connector Event Sender Loop"); + pin_mut!(server_recv); + while let Some(event) = server_recv.next().await { + // If we get an error back, it means the client dropped our event + // handler, so just stop trying. Otherwise, since this is an + // in-process conversion, we can unwrap because we know our + // try_into() will always succeed (which may not be the case with + // remote connections that have different spec versions). + if send.send(event).await.is_err() { + break; + } } - } - info!("Stopping In Process Client Connector Event Sender Loop, due to channel receiver being dropped."); - }.instrument(tracing::info_span!("InProcessClientConnectorEventSenderLoop"))); + info!("Stopping In Process Client Connector Event Sender Loop, due to channel receiver being dropped."); + }, + info_span!("InProcessClientConnectorEventSenderLoop").or_current() + ); connected.store(true, Ordering::Relaxed); Ok(()) }.boxed() diff --git a/crates/buttplug_core/Cargo.toml b/crates/buttplug_core/Cargo.toml index 17671948a..4babb9ad4 100644 --- a/crates/buttplug_core/Cargo.toml +++ b/crates/buttplug_core/Cargo.toml @@ -52,3 +52,5 @@ strum_macros = "0.27.2" strum = "0.27.2" derive_builder = "0.20.2" enum_dispatch = "0.3" +async-trait = "0.1.89" +tracing = "0.1.44" diff --git a/crates/buttplug_core/src/connector/remote_connector.rs b/crates/buttplug_core/src/connector/remote_connector.rs index dc328534a..984cfa93f 100644 --- a/crates/buttplug_core/src/connector/remote_connector.rs +++ b/crates/buttplug_core/src/connector/remote_connector.rs @@ -24,6 +24,7 @@ use futures::{FutureExt, future::BoxFuture, select}; use log::*; use std::marker::PhantomData; use tokio::sync::mpsc::{Receiver, Sender, channel}; +use tracing::info_span; enum ButtplugRemoteConnectorMessage where @@ -234,21 +235,24 @@ where // If we connect successfully, we get back the channel from the transport // to send outgoing messages and receieve incoming events, all serialized. Ok(()) => { - async_manager::spawn(async move { - remote_connector_event_loop::< - TransportType, - SerializerType, - OutboundMessageType, - InboundMessageType, - >( - connector_outgoing_receiver, - connector_incoming_sender, - transport, - transport_outgoing_sender, - transport_incoming_receiver, - ) - .await - }); + async_manager::spawn( + async move { + remote_connector_event_loop::< + TransportType, + SerializerType, + OutboundMessageType, + InboundMessageType, + >( + connector_outgoing_receiver, + connector_incoming_sender, + transport, + transport_outgoing_sender, + transport_incoming_receiver, + ) + .await + }, + info_span!("ButtplugRemoteConnectorEventLoop").or_current(), + ); Ok(()) } Err(e) => Err(e), diff --git a/crates/buttplug_core/src/connector/transport/stream.rs b/crates/buttplug_core/src/connector/transport/stream.rs index 8e6676e4f..d4a81c1ec 100644 --- a/crates/buttplug_core/src/connector/transport/stream.rs +++ b/crates/buttplug_core/src/connector/transport/stream.rs @@ -21,6 +21,7 @@ use futures::{ FutureExt, future::{self, BoxFuture}, }; +use tracing::info_span; use std::sync::Arc; use tokio::{ @@ -88,7 +89,7 @@ impl ButtplugConnectorTransport for ButtplugStreamTransport { } } } - }); + }, info_span!("ButtplugStreamTransportEventLoop").or_current()); Ok(()) }.boxed() } diff --git a/crates/buttplug_core/src/util/async_manager/dummy.rs b/crates/buttplug_core/src/util/async_manager/dummy.rs index 1368e110d..4881776da 100644 --- a/crates/buttplug_core/src/util/async_manager/dummy.rs +++ b/crates/buttplug_core/src/util/async_manager/dummy.rs @@ -5,31 +5,29 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use futures::{ - future::{Future, RemoteHandle}, - task::{FutureObj, Spawn, SpawnError}, -}; +use async_trait::async_trait; +use futures::task::FutureObj; -#[derive(Default)] +#[derive(Default, Debug)] pub struct DummyAsyncManager {} -impl Spawn for DummyAsyncManager { - fn spawn_obj(&self, _: FutureObj<'static, ()>) -> Result<(), SpawnError> { - unimplemented!("Dummy executor can't actually spawn!") +#[async_trait] +impl super::AsyncManager for DummyAsyncManager { + fn spawn(&self, _future: FutureObj<'static, ()>) { + unimplemented!( + "No async runtime available. Please set a global async manager using set_global_async_manager or enable tokio-runtime or wasm feature" + ); } -} -pub fn spawn(_: Fut) -where - Fut: Future + Send + 'static, -{ - unimplemented!("Dummy executor can't actually spawn!") -} + async fn sleep(&self, _duration: std::time::Duration) { + unimplemented!( + "No async runtime available. Please set a global async manager using set_global_async_manager or enable tokio-runtime or wasm feature" + ); + } -pub fn spawn_with_handle(_: Fut) -> Result, SpawnError> -where - Fut: Future + Send + 'static, - Fut::Output: Send, -{ - unimplemented!("Dummy executor can't actually spawn!") + async fn sleep_until(&self, _deadline: std::time::Instant) { + unimplemented!( + "No async runtime available. Please set a global async manager using set_global_async_manager or enable tokio-runtime or wasm feature" + ); + } } diff --git a/crates/buttplug_core/src/util/async_manager/mod.rs b/crates/buttplug_core/src/util/async_manager/mod.rs index 38bd98496..25917e6b6 100644 --- a/crates/buttplug_core/src/util/async_manager/mod.rs +++ b/crates/buttplug_core/src/util/async_manager/mod.rs @@ -5,16 +5,78 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use std::sync::OnceLock; + +use async_trait::async_trait; +use futures::task::FutureObj; +use tracing::Span; + cfg_if::cfg_if! { if #[cfg(feature = "wasm")] { - mod wasm_bindgen; - pub use self::wasm_bindgen::{WasmBindgenAsyncManager as AsyncManager, spawn, spawn_with_handle}; + mod wasm; + use self::wasm::{WasmAsyncManager as DefaultAsyncManager}; } else if #[cfg(feature = "tokio-runtime")] { mod tokio; - pub use self::tokio::{TokioAsyncManager as AsyncManager, spawn, spawn_with_handle}; + use self::tokio::{TokioAsyncManager as DefaultAsyncManager}; } else { mod dummy; - pub use dummy::{DummyAsyncManager as AsyncManager, spawn, spawn_with_handle}; - //std::compile_error!("Please choose a runtime feature: tokio-runtime, wasm-bindgen-runtime, dummy-runtime"); + use dummy::{DummyAsyncManager as DefaultAsyncManager}; } } + +static GLOBAL_ASYNC_MANAGER: OnceLock> = OnceLock::new(); + +pub fn set_global_async_manager(manager: Box) { + log::info!("Setting global async manager to {:?}", manager); + GLOBAL_ASYNC_MANAGER + .set(manager) + .expect("Global async manager can only be set once."); +} + +fn get_global_async_manager() -> &'static Box { + GLOBAL_ASYNC_MANAGER.get_or_init(|| { + let default_manager = DefaultAsyncManager::default(); + log::info!( + "No global async manager set, using {:?} according to feature flag.", + default_manager + ); + Box::new(default_manager) + }) +} + +/// The `AsyncManager` is a trait that abstracts over the async runtime used by Buttplug. +/// It is similar to [futures::task::Spawn] but also includes sleep functions since they also depend on the used async runtime. +/// It also forces instumentation of tracing spans for all spawned tasks. +/// This usually does not need to be used in user code, but is public to allow users to implement their own async runtimes if needed. +#[async_trait] +pub trait AsyncManager: std::fmt::Debug + Send + Sync { + /// Spawns a future onto the async runtime. The future must be `Send` and `'static` since it may be spawned onto a different thread. + /// The span parameter should be used to instrument the future with a tracing span. + fn spawn(&self, future: FutureObj<'static, ()>, span: Span); + async fn sleep(&self, duration: std::time::Duration); + async fn sleep_until(&self, deadline: std::time::Instant); +} + +/// Spawns a future onto the global async manager. +pub fn spawn(future: F, span: Span) +where + F: Future + Send + 'static, +{ + let async_manager = get_global_async_manager(); + + async_manager.spawn(Box::new(future).into(), span); +} + +/// Sleeps for the specified duration using the global async manager. +pub async fn sleep(duration: std::time::Duration) { + let async_manager = get_global_async_manager(); + + async_manager.sleep(duration).await; +} + +/// Sleeps until the specified deadline using the global async manager. +pub async fn sleep_until(deadline: std::time::Instant) { + let async_manager = get_global_async_manager(); + + async_manager.sleep_until(deadline).await; +} diff --git a/crates/buttplug_core/src/util/async_manager/tokio.rs b/crates/buttplug_core/src/util/async_manager/tokio.rs index b52c14d0e..7903ea4a4 100644 --- a/crates/buttplug_core/src/util/async_manager/tokio.rs +++ b/crates/buttplug_core/src/util/async_manager/tokio.rs @@ -5,37 +5,24 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use futures::{ - future::{Future, RemoteHandle}, - task::{FutureObj, Spawn, SpawnError, SpawnExt}, -}; -use tokio; +use async_trait::async_trait; +use futures::task::FutureObj; +use tracing::{Instrument, Span}; -#[derive(Default)] +#[derive(Default, Debug)] pub struct TokioAsyncManager {} -impl Spawn for TokioAsyncManager { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - tokio::spawn(future); - Ok(()) +#[async_trait] +impl super::AsyncManager for TokioAsyncManager { + fn spawn(&self, future: FutureObj<'static, ()>, span: Span) { + tokio::task::spawn(future.instrument(span)); } -} -pub fn spawn(future: Fut) -where - Fut: Future + Send + 'static, -{ - // SAFETY: TokioAsyncManager::spawn_obj always returns Ok(()). The Result type is only - // present to satisfy the Spawn trait interface. - TokioAsyncManager::default() - .spawn(future) - .expect("TokioAsyncManager::spawn_obj always returns Ok") -} + async fn sleep(&self, duration: std::time::Duration) { + tokio::time::sleep(duration).await; + } -pub fn spawn_with_handle(future: Fut) -> Result, SpawnError> -where - Fut: Future + Send + 'static, - Fut::Output: Send, -{ - TokioAsyncManager::default().spawn_with_handle(future) + async fn sleep_until(&self, deadline: std::time::Instant) { + tokio::time::sleep_until(deadline.into()).await; + } } diff --git a/crates/buttplug_core/src/util/async_manager/wasm.rs b/crates/buttplug_core/src/util/async_manager/wasm.rs new file mode 100644 index 000000000..79561ffe9 --- /dev/null +++ b/crates/buttplug_core/src/util/async_manager/wasm.rs @@ -0,0 +1,30 @@ +// Buttplug Rust Source Code File - See https://buttplug.io for more info. +// +// Copyright 2016-2026 Nonpolynomial Labs LLC. All rights reserved. +// +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root +// for full license information. + +use async_trait::async_trait; +use futures::task::FutureObj; + +use wasm_bindgen_futures::spawn_local; +use wasm_timer::tokio::{sleep, sleep_until}; + +#[derive(Default, Debug)] +pub struct WasmAsyncManager {} + +#[async_trait] +impl super::AsyncManager for WasmAsyncManager { + fn spawn(&self, future: FutureObj<'static, ()>) { + spawn_local(future); + } + + async fn sleep(&self, duration: std::time::Duration) { + sleep(duration).await; + } + + async fn sleep_until(&self, deadline: std::time::Instant) { + sleep_until(deadline).await; + } +} diff --git a/crates/buttplug_core/src/util/async_manager/wasm_bindgen.rs b/crates/buttplug_core/src/util/async_manager/wasm_bindgen.rs deleted file mode 100644 index 2851e1099..000000000 --- a/crates/buttplug_core/src/util/async_manager/wasm_bindgen.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Buttplug Rust Source Code File - See https://buttplug.io for more info. -// -// Copyright 2016-2026 Nonpolynomial Labs LLC. All rights reserved. -// -// Licensed under the BSD 3-Clause license. See LICENSE file in the project root -// for full license information. - -use futures::{ - future::{Future, RemoteHandle}, - task::{FutureObj, Spawn, SpawnError, SpawnExt}, -}; - -use wasm_bindgen_futures::spawn_local; - -#[derive(Default)] -pub struct WasmBindgenAsyncManager {} - -impl Spawn for WasmBindgenAsyncManager { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - spawn_local(future); - Ok(()) - } -} - -pub fn spawn(future: Fut) -where - Fut: Future + 'static, -{ - spawn_local(future); -} - -pub fn spawn_with_handle(future: Fut) -> Result, SpawnError> -where - Fut: Future + Send + 'static, - Fut::Output: Send, -{ - WasmBindgenAsyncManager::default().spawn_with_handle(future) -} diff --git a/crates/buttplug_core/src/util/mod.rs b/crates/buttplug_core/src/util/mod.rs index a8f87820a..63cdc8040 100644 --- a/crates/buttplug_core/src/util/mod.rs +++ b/crates/buttplug_core/src/util/mod.rs @@ -12,8 +12,3 @@ pub mod async_manager; pub mod json; pub mod range_serialize; pub mod stream; - -#[cfg(not(feature = "wasm"))] -pub use tokio::time::sleep; -#[cfg(feature = "wasm")] -pub use wasmtimer::tokio::sleep; diff --git a/crates/buttplug_server/Cargo.toml b/crates/buttplug_server/Cargo.toml index f8c153131..bdd90fe29 100644 --- a/crates/buttplug_server/Cargo.toml +++ b/crates/buttplug_server/Cargo.toml @@ -33,7 +33,6 @@ log = "0.4.29" getset = "0.1.6" tokio = { version = "1.49.0", features = ["macros"] } dashmap = { version = "6.1.0" } -tracing-futures = "0.2.5" tracing = "0.1.44" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/crates/buttplug_server/src/device/device_handle.rs b/crates/buttplug_server/src/device/device_handle.rs index e2f711fbc..0908f43ab 100644 --- a/crates/buttplug_server/src/device/device_handle.rs +++ b/crates/buttplug_server/src/device/device_handle.rs @@ -16,18 +16,32 @@ use buttplug_core::{ ButtplugResultFuture, errors::{ButtplugDeviceError, ButtplugError}, message::{ - self, ButtplugMessage, ButtplugServerMessageV4, DeviceFeature, DeviceMessageInfoV4, - InputCommandType, InputType, OutputType, OutputValue, StopCmdV4, + self, + ButtplugMessage, + ButtplugServerMessageV4, + DeviceFeature, + DeviceMessageInfoV4, + InputCommandType, + InputType, + OutputType, + OutputValue, + StopCmdV4, }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; use buttplug_server_device_config::{ - DeviceConfigurationManager, ServerDeviceDefinition, UserDeviceIdentifier, + DeviceConfigurationManager, + ServerDeviceDefinition, + UserDeviceIdentifier, }; use dashmap::DashMap; use futures::future::{self, BoxFuture, FutureExt}; -use tokio::sync::{mpsc::{channel, Sender}, oneshot}; +use tokio::sync::{ + mpsc::{Sender, channel}, + oneshot, +}; use tokio_stream::StreamExt; +use tracing::info_span; use uuid::Uuid; use crate::{ @@ -43,7 +57,7 @@ use crate::{ use super::{ InternalDeviceEvent, - device_task::{spawn_device_task, DeviceTaskConfig}, + device_task::{DeviceTaskConfig, spawn_device_task}, hardware::{Hardware, HardwareCommand, HardwareConnector, HardwareEvent}, protocol::{ProtocolHandler, ProtocolKeepaliveStrategy, ProtocolSpecializer}, }; @@ -554,9 +568,7 @@ pub(super) async fn build_device_handle( strategy, ProtocolKeepaliveStrategy::RepeatLastPacketStrategyWithTiming(_) )) - && let Err(e) = device_handle - .stop(&StopCmdV4::default()) - .await + && let Err(e) = device_handle.stop(&StopCmdV4::default()).await { return Err(ButtplugDeviceError::DeviceConnectionError(format!( "Error setting up keepalive: {e}" @@ -568,44 +580,47 @@ pub(super) async fn build_device_handle( // to the device manager event loop via the provided sender. let event_stream = device_handle.event_stream(); let identifier = device_handle.identifier().clone(); - async_manager::spawn(async move { - futures::pin_mut!(event_stream); - loop { - let event = futures::StreamExt::next(&mut event_stream).await; - match event { - Some(DeviceEvent::Disconnected(id)) => { - if device_event_sender - .send(InternalDeviceEvent::Disconnected(id)) - .await - .is_err() - { - info!( - "Device event sender closed for device {:?}, stopping event forwarding.", - identifier - ); - break; + async_manager::spawn( + async move { + futures::pin_mut!(event_stream); + loop { + let event = futures::StreamExt::next(&mut event_stream).await; + match event { + Some(DeviceEvent::Disconnected(id)) => { + if device_event_sender + .send(InternalDeviceEvent::Disconnected(id)) + .await + .is_err() + { + info!( + "Device event sender closed for device {:?}, stopping event forwarding.", + identifier + ); + break; + } } - } - Some(DeviceEvent::Notification(id, msg)) => { - if device_event_sender - .send(InternalDeviceEvent::Notification(id, msg)) - .await - .is_err() - { - info!( - "Device event sender closed for device {:?}, stopping event forwarding.", - identifier - ); + Some(DeviceEvent::Notification(id, msg)) => { + if device_event_sender + .send(InternalDeviceEvent::Notification(id, msg)) + .await + .is_err() + { + info!( + "Device event sender closed for device {:?}, stopping event forwarding.", + identifier + ); + break; + } + } + None => { + // Stream ended (device likely disconnected) break; } } - None => { - // Stream ended (device likely disconnected) - break; - } } - } - }); + }, + info_span!("DeviceEventForwardingTask").or_current(), + ); Ok(device_handle) } diff --git a/crates/buttplug_server/src/device/device_task.rs b/crates/buttplug_server/src/device/device_task.rs index 3ed84f3f3..9cbb6f1ad 100644 --- a/crates/buttplug_server/src/device/device_task.rs +++ b/crates/buttplug_server/src/device/device_task.rs @@ -12,15 +12,12 @@ //! - Keepalive packet management //! - Hardware disconnect detection -use std::{collections::VecDeque, sync::Arc, time::Duration}; +use std::{collections::VecDeque, sync::Arc, time::{Duration, Instant}}; -use buttplug_core::util::{self, async_manager}; +use buttplug_core::util::async_manager; use futures::future; -use tokio::{ - select, - sync::mpsc::Receiver, - time::Instant, -}; +use tokio::{select, sync::mpsc::Receiver}; +use tracing::info_span; use super::{ hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareWriteCmd}, @@ -52,9 +49,12 @@ pub fn spawn_device_task( config: DeviceTaskConfig, mut command_receiver: Receiver>, ) { - async_manager::spawn(async move { - run_device_task(hardware, config, &mut command_receiver).await; - }); + async_manager::spawn( + async move { + run_device_task(hardware, config, &mut command_receiver).await; + }, + info_span!("DeviceTask"), + ); } /// Run the device communication task (internal implementation). @@ -98,9 +98,9 @@ async fn run_device_task( // Calculate keepalive timeout let keepalive_fut = async { if let Some(duration) = strategy_duration { - util::sleep(duration).await; + async_manager::sleep(duration).await; } else if requires_keepalive { - util::sleep(Duration::from_secs(5)).await; // iOS Bluetooth default + async_manager::sleep(Duration::from_secs(5)).await; // iOS Bluetooth default } else { future::pending::<()>().await; } @@ -109,7 +109,7 @@ async fn run_device_task( // Calculate batch flush timeout (only if we're batching) let batch_fut = async { match batch_deadline { - Some(deadline) => tokio::time::sleep_until(deadline).await, + Some(deadline) => async_manager::sleep_until(deadline).await, None => future::pending::<()>().await, } }; diff --git a/crates/buttplug_server/src/device/hardware/communication.rs b/crates/buttplug_server/src/device/hardware/communication.rs index a9d521977..95b712f03 100644 --- a/crates/buttplug_server/src/device/hardware/communication.rs +++ b/crates/buttplug_server/src/device/hardware/communication.rs @@ -8,7 +8,7 @@ use crate::device::hardware::HardwareConnector; use async_trait::async_trait; use buttplug_core::{ - util::{async_manager, sleep}, + util::async_manager, {ButtplugResultFuture, errors::ButtplugDeviceError}, }; use futures::future::{self, FutureExt}; @@ -17,6 +17,7 @@ use std::{sync::Arc, time::Duration}; use thiserror::Error; use tokio::sync::mpsc::Sender; use tokio_util::sync::CancellationToken; +use tracing::info_span; #[derive(Debug)] pub enum HardwareCommunicationManagerEvent { @@ -95,18 +96,21 @@ impl HardwareCommunicationManager self.cancellation_token = Some(token); let duration = self.comm_manager.rescan_wait_duration(); async move { - async_manager::spawn(async move { - loop { - if let Err(err) = comm_manager.scan().await { - error!("Timed Device Communication Manager Failure: {}", err); - break; + async_manager::spawn( + async move { + loop { + if let Err(err) = comm_manager.scan().await { + error!("Timed Device Communication Manager Failure: {}", err); + break; + } + tokio::select! { + _ = async_manager::sleep(duration) => continue, + _ = child_token.cancelled() => break, + } } - tokio::select! { - _ = sleep(duration) => continue, - _ = child_token.cancelled() => break, - } - } - }); + }, + info_span!("TimedRetryCommunicationManagerScanLoop").or_current(), + ); Ok(()) } .boxed() diff --git a/crates/buttplug_server/src/device/protocol_impl/cowgirl_cone.rs b/crates/buttplug_server/src/device/protocol_impl/cowgirl_cone.rs index c9201e95b..dfeedd0b6 100644 --- a/crates/buttplug_server/src/device/protocol_impl/cowgirl_cone.rs +++ b/crates/buttplug_server/src/device/protocol_impl/cowgirl_cone.rs @@ -15,7 +15,7 @@ use crate::device::{ }, }; use async_trait::async_trait; -use buttplug_core::{errors::ButtplugDeviceError, util::sleep}; +use buttplug_core::{errors::ButtplugDeviceError, util::async_manager}; use buttplug_server_device_config::{ Endpoint, ProtocolCommunicationSpecifier, @@ -47,7 +47,7 @@ impl ProtocolInitializer for CowgirlConeInitializer { false, )) .await?; - sleep(Duration::from_millis(3000)).await; + async_manager::sleep(Duration::from_millis(3000)).await; Ok(Arc::new(CowgirlCone::default())) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/fredorch.rs b/crates/buttplug_server/src/device/protocol_impl/fredorch.rs index 2c03bffbd..9b85fbeea 100644 --- a/crates/buttplug_server/src/device/protocol_impl/fredorch.rs +++ b/crates/buttplug_server/src/device/protocol_impl/fredorch.rs @@ -15,7 +15,7 @@ use crate::device::{ }, }; use async_trait::async_trait; -use buttplug_core::{errors::ButtplugDeviceError, util::sleep}; +use buttplug_core::{errors::ButtplugDeviceError, util::async_manager}; use buttplug_server_device_config::{ Endpoint, ProtocolCommunicationSpecifier, @@ -137,7 +137,7 @@ impl ProtocolInitializer for FredorchInitializer { ); } } - _ = sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { + _ = async_manager::sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { // Or not? } } @@ -169,7 +169,7 @@ impl ProtocolInitializer for FredorchInitializer { ); } } - _ = sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { + _ = async_manager::sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { return Err( ButtplugDeviceError::ProtocolSpecificError( "Fredorch".to_owned(), diff --git a/crates/buttplug_server/src/device/protocol_impl/fredorch_rotary.rs b/crates/buttplug_server/src/device/protocol_impl/fredorch_rotary.rs index f63111b94..efa6ff8d0 100644 --- a/crates/buttplug_server/src/device/protocol_impl/fredorch_rotary.rs +++ b/crates/buttplug_server/src/device/protocol_impl/fredorch_rotary.rs @@ -14,10 +14,7 @@ use crate::device::{ }, }; use async_trait::async_trait; -use buttplug_core::{ - errors::ButtplugDeviceError, - util::{async_manager, sleep}, -}; +use buttplug_core::{errors::ButtplugDeviceError, util::async_manager}; use buttplug_server_device_config::{ Endpoint, ProtocolCommunicationSpecifier, @@ -33,6 +30,7 @@ use std::{ time::Duration, }; use tokio::select; +use tracing::info_span; use uuid::{Uuid, uuid}; const FREDORCH_COMMAND_TIMEOUT_MS: u64 = 100; @@ -106,7 +104,7 @@ impl ProtocolInitializer for FredorchRotaryInitializer { ); } } - _ = sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { + _ = async_manager::sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => { // The after the password check, we won't get anything } } @@ -183,7 +181,7 @@ async fn speed_update_handler( } } - sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).await; + async_manager::sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).await; } info!("FredorchRotary control loop exiting, most likely due to device disconnection."); } @@ -194,9 +192,10 @@ impl FredorchRotary { let target_speed = Arc::new(AtomicU8::new(0)); let current_speed_clone = current_speed.clone(); let target_speed_clone = target_speed.clone(); - async_manager::spawn(async move { - speed_update_handler(device, current_speed_clone, target_speed_clone).await - }); + async_manager::spawn( + async move { speed_update_handler(device, current_speed_clone, target_speed_clone).await }, + info_span!("FredorchRotaryControlLoop").or_current(), + ); Self { current_speed, target_speed, diff --git a/crates/buttplug_server/src/device/protocol_impl/hgod.rs b/crates/buttplug_server/src/device/protocol_impl/hgod.rs index 1beecd570..0b22e302b 100644 --- a/crates/buttplug_server/src/device/protocol_impl/hgod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/hgod.rs @@ -15,10 +15,7 @@ use crate::device::{ }, }; use async_trait::async_trait; -use buttplug_core::{ - errors::ButtplugDeviceError, - util::{async_manager, sleep}, -}; +use buttplug_core::{errors::ButtplugDeviceError, util::async_manager}; use buttplug_server_device_config::{ Endpoint, ProtocolCommunicationSpecifier, @@ -32,6 +29,7 @@ use std::{ }, time::Duration, }; +use tracing::info_span; use uuid::{Uuid, uuid}; // Time between Hgod update commands, in milliseconds. @@ -63,9 +61,12 @@ impl Hgod { let last_command = Arc::new(AtomicU8::new(0)); let last_command_clone = last_command.clone(); - async_manager::spawn(async move { - send_hgod_updates(hardware, last_command_clone).await; - }); + async_manager::spawn( + async move { + send_hgod_updates(hardware, last_command_clone).await; + }, + info_span!("Hgod::send_hgod_updates").or_current(), + ); Self { last_command } } @@ -92,7 +93,7 @@ async fn send_hgod_updates(device: Arc, data: Arc) { ); break; } - sleep(Duration::from_millis(HGOD_COMMAND_DELAY_MS)).await; + async_manager::sleep(Duration::from_millis(HGOD_COMMAND_DELAY_MS)).await; } } diff --git a/crates/buttplug_server/src/device/protocol_impl/kgoal_boost.rs b/crates/buttplug_server/src/device/protocol_impl/kgoal_boost.rs index b4a6a205d..2137f7774 100644 --- a/crates/buttplug_server/src/device/protocol_impl/kgoal_boost.rs +++ b/crates/buttplug_server/src/device/protocol_impl/kgoal_boost.rs @@ -31,6 +31,7 @@ use std::{ }, }; use tokio::sync::broadcast; +use tracing::info_span; use uuid::Uuid; generic_protocol_setup!(KGoalBoost, "kgoal-boost"); @@ -90,69 +91,76 @@ impl ProtocolHandler for KGoalBoost { let stream_sensors = stream_sensors.clone(); info!("Starting Kgoal subscription"); // If we subscribe successfully, we need to set up our event handler. - async_manager::spawn(async move { - let mut cached_values = vec![0u32, 0u32]; - while let Ok(info) = hardware_stream.recv().await { - let subscribed_sensors = stream_sensors.load(Ordering::Relaxed); - // If we have no receivers, quit. - if sender.receiver_count() == 0 || subscribed_sensors == 0 { - return; - } - if let HardwareEvent::Notification(_, endpoint, data) = info - && endpoint == Endpoint::RxPressure - { - if data.len() < 7 { - // Not even sure how this would happen, error and continue on. - error!("KGoal Boost data not expected length!"); - continue; - } - // Extract our two pressure values. - let normalized = (data[3] as u32) << 8 | data[4] as u32; - let unnormalized = (data[5] as u32) << 8 | data[6] as u32; - info!( - "Kgoal Reading {} {} {}", - subscribed_sensors, normalized, unnormalized - ); - if (subscribed_sensors & (1 << 0)) > 0 - && cached_values[0] != normalized - && sender - .send( - InputReadingV4::new( - device_index, - 0, - buttplug_core::message::InputTypeReading::Pressure(InputValue::new( - normalized, - )), - ) - .into(), - ) - .is_err() - { - debug!("Hardware device listener for KGoal Boost shut down, returning from task."); + async_manager::spawn( + async move { + let mut cached_values = vec![0u32, 0u32]; + while let Ok(info) = hardware_stream.recv().await { + let subscribed_sensors = stream_sensors.load(Ordering::Relaxed); + // If we have no receivers, quit. + if sender.receiver_count() == 0 || subscribed_sensors == 0 { return; } - if (subscribed_sensors & (1 << 1)) > 0 - && cached_values[1] != unnormalized - && sender - .send( - InputReadingV4::new( - device_index, - 1, - buttplug_core::message::InputTypeReading::Pressure(InputValue::new( - unnormalized, - )), - ) - .into(), - ) - .is_err() + if let HardwareEvent::Notification(_, endpoint, data) = info + && endpoint == Endpoint::RxPressure { - debug!("Hardware device listener for KGoal Boost shut down, returning from task."); - return; + if data.len() < 7 { + // Not even sure how this would happen, error and continue on. + error!("KGoal Boost data not expected length!"); + continue; + } + // Extract our two pressure values. + let normalized = (data[3] as u32) << 8 | data[4] as u32; + let unnormalized = (data[5] as u32) << 8 | data[6] as u32; + info!( + "Kgoal Reading {} {} {}", + subscribed_sensors, normalized, unnormalized + ); + if (subscribed_sensors & (1 << 0)) > 0 + && cached_values[0] != normalized + && sender + .send( + InputReadingV4::new( + device_index, + 0, + buttplug_core::message::InputTypeReading::Pressure(InputValue::new( + normalized, + )), + ) + .into(), + ) + .is_err() + { + debug!( + "Hardware device listener for KGoal Boost shut down, returning from task." + ); + return; + } + if (subscribed_sensors & (1 << 1)) > 0 + && cached_values[1] != unnormalized + && sender + .send( + InputReadingV4::new( + device_index, + 1, + buttplug_core::message::InputTypeReading::Pressure(InputValue::new( + unnormalized, + )), + ) + .into(), + ) + .is_err() + { + debug!( + "Hardware device listener for KGoal Boost shut down, returning from task." + ); + return; + } + cached_values = vec![normalized, unnormalized]; } - cached_values = vec![normalized, unnormalized]; } - } - }); + }, + info_span!("KGoalBoost::handle_input_subscribe_cmd event handler").or_current(), + ); } stream_sensors.store( stream_sensors.load(Ordering::Relaxed) | (1 << feature_index), diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense/lovense_stroker.rs b/crates/buttplug_server/src/device/protocol_impl/lovense/lovense_stroker.rs index beb60046d..49468d6cc 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense/lovense_stroker.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense/lovense_stroker.rs @@ -12,7 +12,7 @@ use crate::device::{ use buttplug_core::{ errors::ButtplugDeviceError, message::InputReadingV4, - util::{async_manager, sleep}, + util::async_manager, }; use buttplug_server_device_config::Endpoint; use futures::future::BoxFuture; @@ -23,6 +23,7 @@ use std::{ }, time::Duration, }; +use tracing::info_span; use uuid::{Uuid, uuid}; const LOVENSE_STROKER_PROTOCOL_UUID: Uuid = uuid!("a97fc354-5561-459a-bc62-110d7c2868ac"); @@ -35,10 +36,10 @@ pub struct LovenseStroker { impl LovenseStroker { pub fn new(hardware: Arc, need_range_zerod: bool) -> Self { let linear_info = Arc::new((AtomicU32::new(0), AtomicU32::new(0))); - async_manager::spawn(update_linear_movement( - hardware.clone(), - linear_info.clone(), - )); + async_manager::spawn( + update_linear_movement(hardware.clone(), linear_info.clone()), + info_span!("LovenseStroker::update_linear_movement").or_current(), + ); Self { linear_info, need_range_zerod, @@ -119,7 +120,7 @@ async fn update_linear_movement(device: Arc, linear_info: Arc<(AtomicU // If we aren't going anywhere, just pause then restart if current_position == last_goal_position { - sleep(Duration::from_millis(100)).await; + async_manager::sleep(Duration::from_millis(100)).await; continue; } @@ -144,6 +145,6 @@ async fn update_linear_movement(device: Arc, linear_info: Arc<(AtomicU if device.write_value(&hardware_cmd).await.is_err() { return; } - sleep(Duration::from_millis(100)).await; + async_manager::sleep(Duration::from_millis(100)).await; } } diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs index 168efcb9f..8fa4bf9ca 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs @@ -26,8 +26,7 @@ use crate::device::{ use async_trait::async_trait; use buttplug_core::{ errors::ButtplugDeviceError, - message::{self, InputReadingV4, InputTypeReading, InputValue, OutputType}, - util::sleep, + message::{self, InputReadingV4, InputTypeReading, InputValue, OutputType}, util::async_manager, }; use buttplug_server_device_config::{ Endpoint, @@ -134,7 +133,7 @@ impl ProtocolIdentifier for LovenseIdentifier { ); } } - _ = sleep(Duration::from_millis(LOVENSE_COMMAND_TIMEOUT_MS)).fuse() => { + _ = async_manager::sleep(Duration::from_millis(LOVENSE_COMMAND_TIMEOUT_MS)).fuse() => { count += 1; if count > LOVENSE_COMMAND_RETRY { warn!("Lovense Device timed out while getting DeviceType info. ({} retries)", LOVENSE_COMMAND_RETRY); diff --git a/crates/buttplug_server/src/device/protocol_impl/nintendo_joycon.rs b/crates/buttplug_server/src/device/protocol_impl/nintendo_joycon.rs index c823ff0c8..3a707eca4 100644 --- a/crates/buttplug_server/src/device/protocol_impl/nintendo_joycon.rs +++ b/crates/buttplug_server/src/device/protocol_impl/nintendo_joycon.rs @@ -22,6 +22,7 @@ use buttplug_server_device_config::{ ServerDeviceDefinition, UserDeviceIdentifier, }; +use tracing::info_span; use std::{ sync::{ Arc, @@ -263,36 +264,39 @@ impl NintendoJoycon { let notifier_clone = notifier.clone(); let is_stopped = Arc::new(AtomicBool::new(false)); let is_stopped_clone = is_stopped.clone(); - async_manager::spawn(async move { - #[cfg(feature = "wasm")] - use buttplug_core::util; - loop { - if is_stopped_clone.load(Ordering::Relaxed) { - return; - } - let amp = speed_val_clone.load(Ordering::Relaxed) as f32 / 1000f32; - let rumble = if amp > 0.001 { - Rumble::new(200.0f32, amp) - } else { - Rumble::stop() - }; - - if let Err(_) = - send_command_raw(hardware.clone(), 1, 16, 0, &[], Some(rumble), Some(rumble)).await - { - error!("Joycon command failed, exiting update loop"); - break; - } - #[cfg(not(feature = "wasm"))] - let _ = tokio::time::timeout(Duration::from_millis(15), notifier_clone.notified()).await; - - // If we're using WASM, we can't use tokio's timeout due to lack of time library in WASM. - // I'm also too lazy to make this a select. So, this'll do. We can't even access this - // protocol in a web context yet since there's no WebHID comm manager yet. + async_manager::spawn( + async move { #[cfg(feature = "wasm")] - util::sleep(Duration::from_millis(15)).await; - } - }); + use buttplug_core::util; + loop { + if is_stopped_clone.load(Ordering::Relaxed) { + return; + } + let amp = speed_val_clone.load(Ordering::Relaxed) as f32 / 1000f32; + let rumble = if amp > 0.001 { + Rumble::new(200.0f32, amp) + } else { + Rumble::stop() + }; + + if let Err(_) = + send_command_raw(hardware.clone(), 1, 16, 0, &[], Some(rumble), Some(rumble)).await + { + error!("Joycon command failed, exiting update loop"); + break; + } + #[cfg(not(feature = "wasm"))] + let _ = tokio::time::timeout(Duration::from_millis(15), notifier_clone.notified()).await; + + // If we're using WASM, we can't use tokio's timeout due to lack of time library in WASM. + // I'm also too lazy to make this a select. So, this'll do. We can't even access this + // protocol in a web context yet since there's no WebHID comm manager yet. + #[cfg(feature = "wasm")] + async_manager::sleep(Duration::from_millis(15)).await; + } + }, + info_span!("NintendoJoyconControlLoop").or_current(), + ); Self { //packet_number: Arc::new(AtomicU8::new(0)), speed_val, diff --git a/crates/buttplug_server/src/device/protocol_impl/xuanhuan.rs b/crates/buttplug_server/src/device/protocol_impl/xuanhuan.rs index 65834fa99..6dfe2932d 100644 --- a/crates/buttplug_server/src/device/protocol_impl/xuanhuan.rs +++ b/crates/buttplug_server/src/device/protocol_impl/xuanhuan.rs @@ -15,10 +15,7 @@ use crate::device::{ }, }; use async_trait::async_trait; -use buttplug_core::{ - errors::ButtplugDeviceError, - util::{async_manager, sleep}, -}; +use buttplug_core::{errors::ButtplugDeviceError, util::async_manager}; use buttplug_server_device_config::{ Endpoint, ProtocolCommunicationSpecifier, @@ -32,6 +29,7 @@ use std::{ }, time::Duration, }; +use tracing::info_span; use uuid::{Uuid, uuid}; const XUANHUAN_PROTOCOL_ID: Uuid = uuid!("e9f9f8ab-4fd5-4573-a4ec-ab542568849b"); @@ -70,7 +68,7 @@ async fn vibration_update_handler(device: Arc, command_holder: Arc { // Comm manager finished before we completed bringup - ignore for now - debug!( - "Hardware Comm Manager finished before scanning was fully started, ignoring" - ); + debug!("Hardware Comm Manager finished before scanning was fully started, ignoring"); } ScanningState::Active => { // Check if all hardware has actually stopped @@ -291,27 +288,35 @@ impl ServerDeviceManagerEventLoop { // Clone sender again for the forwarding task that build_device_handle will spawn let device_event_sender_for_forwarding = self.device_event_sender.clone(); - async_manager::spawn(async move { - match build_device_handle( - device_config_manager, - creator, - protocol_specializers, - device_event_sender_for_forwarding, - ).await { - Ok(device_handle) => { - if device_event_sender_clone - .send(InternalDeviceEvent::Connected(device_handle)) - .await - .is_err() { - error!("Device manager disappeared before connection established, device will be dropped."); + async_manager::spawn( + async move { + match build_device_handle( + device_config_manager, + creator, + protocol_specializers, + device_event_sender_for_forwarding, + ) + .await + { + Ok(device_handle) => { + if device_event_sender_clone + .send(InternalDeviceEvent::Connected(device_handle)) + .await + .is_err() + { + error!( + "Device manager disappeared before connection established, device will be dropped." + ); + } + } + Err(e) => { + error!("Device errored while trying to connect: {:?}", e); } - }, - Err(e) => { - error!("Device errored while trying to connect: {:?}", e); } - } - connecting_devices.remove(&address); - }.instrument(span)); + connecting_devices.remove(&address); + }, + span.or_current(), + ); } } } @@ -364,7 +369,11 @@ impl ServerDeviceManagerEventLoop { // Note: The device event forwarding task is now spawned in build_device_handle(), // so we no longer need to create it here. - info!("Assigning index {} to {}", device_index, device_handle.name()); + info!( + "Assigning index {} to {}", + device_index, + device_handle.name() + ); self.device_map.insert(device_index, device_handle.clone()); let device_update_message: ButtplugServerMessageV4 = self.generate_device_list().into(); diff --git a/crates/buttplug_server/src/ping_timer.rs b/crates/buttplug_server/src/ping_timer.rs index fdf2c9489..9a7b3f7bd 100644 --- a/crates/buttplug_server/src/ping_timer.rs +++ b/crates/buttplug_server/src/ping_timer.rs @@ -5,13 +5,14 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use buttplug_core::util::{async_manager, sleep}; +use buttplug_core::util::async_manager; use futures::Future; use std::{sync::Arc, time::Duration}; use tokio::{ select, sync::{Mutex, mpsc}, }; +use tracing::info_span; pub enum PingMessage { Ping, @@ -33,7 +34,7 @@ async fn ping_timer( let mut pinged = false; loop { select! { - _ = sleep(Duration::from_millis(max_ping_time.into())) => { + _ = async_manager::sleep(Duration::from_millis(max_ping_time.into())) => { if started { if !pinged { // Ping timeout occurred - call the callback @@ -70,11 +71,14 @@ impl Drop for PingTimer { // This cannot block, otherwise it will throw in WASM contexts on // destruction. We must use send(), not blocking_send(). let sender = self.ping_msg_sender.clone(); - async_manager::spawn(async move { - if sender.send(PingMessage::End).await.is_err() { - debug!("Receiver does not exist, assuming ping timer event loop already dead."); - } - }); + async_manager::spawn( + async move { + if sender.send(PingMessage::End).await.is_err() { + debug!("Receiver does not exist, assuming ping timer event loop already dead."); + } + }, + info_span!("PingTimer::drop").or_current(), + ); } } @@ -92,7 +96,7 @@ impl PingTimer { if max_ping_time > 0 { let callback = Arc::new(Mutex::new(on_ping_timeout)); let fut = ping_timer(max_ping_time, receiver, callback); - async_manager::spawn(fut); + async_manager::spawn(fut, info_span!("ping_timer").or_current()); } Self { max_ping_time, diff --git a/crates/buttplug_server/src/server.rs b/crates/buttplug_server/src/server.rs index 3debf7ef6..d41a9bacb 100644 --- a/crates/buttplug_server/src/server.rs +++ b/crates/buttplug_server/src/server.rs @@ -43,15 +43,11 @@ use futures::{ }; use std::{ fmt, - sync::{ - Arc, - RwLock, - }, + sync::{Arc, RwLock}, }; use tokio::sync::broadcast; use tokio_stream::StreamExt; -use tracing::info_span; -use tracing_futures::Instrument; +use tracing::{Instrument, info_span}; /// Connection state for the ButtplugServer. /// Replaces separate connected/client_name/spec_version fields with explicit states. @@ -215,9 +211,8 @@ impl ButtplugServer { let stop_scanning_fut = self.parse_checked_message( ButtplugCheckedClientMessageV4::StopScanning(StopScanningV0::default()), ); - let stop_fut = self.parse_checked_message(ButtplugCheckedClientMessageV4::StopCmd( - StopCmdV4::default(), - )); + let stop_fut = + self.parse_checked_message(ButtplugCheckedClientMessageV4::StopCmd(StopCmdV4::default())); let state = self.state.clone(); async move { { @@ -255,7 +250,7 @@ impl ButtplugServer { current_version } else { info!("Setting Buttplug Server Message Spec version to {}", v); - v + v }; match msg { ButtplugClientMessageVariant::V4(msg) => { diff --git a/crates/buttplug_server/src/server_builder.rs b/crates/buttplug_server/src/server_builder.rs index d0a3ff65a..4f282e23a 100644 --- a/crates/buttplug_server/src/server_builder.rs +++ b/crates/buttplug_server/src/server_builder.rs @@ -19,6 +19,7 @@ use buttplug_core::{ use buttplug_server_device_config::DeviceConfigurationManagerBuilder; use std::sync::{Arc, RwLock}; use tokio::sync::broadcast; +use tracing::info_span; /// Configures and creates [ButtplugServer] instances. pub struct ButtplugServerBuilder { @@ -114,14 +115,17 @@ impl ButtplugServerBuilder { *state_guard = ConnectionState::PingedOut; } // Stop all devices (spawn async task since callback is sync) - async_manager::spawn(async move { - if let Err(e) = device_manager_clone - .stop_devices(&StopCmdV4::default()) - .await - { - error!("Could not stop devices on ping timeout: {:?}", e); - } - }); + async_manager::spawn( + async move { + if let Err(e) = device_manager_clone + .stop_devices(&StopCmdV4::default()) + .await + { + error!("Could not stop devices on ping timeout: {:?}", e); + } + }, + info_span!("ButtplugServerBuilder::ping_timeout_callback stop_devices").or_current(), + ); // Send error to output channel if output_sender_clone .send(ButtplugServerMessageV4::Error(message::ErrorV0::from( diff --git a/crates/buttplug_server_hwmgr_btleplug/src/btleplug_comm_manager.rs b/crates/buttplug_server_hwmgr_btleplug/src/btleplug_comm_manager.rs index 957b9a4c5..1a32f1065 100644 --- a/crates/buttplug_server_hwmgr_btleplug/src/btleplug_comm_manager.rs +++ b/crates/buttplug_server_hwmgr_btleplug/src/btleplug_comm_manager.rs @@ -18,6 +18,7 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, }; use tokio::sync::mpsc::{Sender, channel}; +use tracing::info_span; #[derive(Default, Clone)] pub struct BtlePlugCommunicationManagerBuilder { @@ -57,15 +58,18 @@ impl BtlePlugCommunicationManager { let (sender, receiver) = channel(256); let adapter_connected = Arc::new(AtomicBool::new(false)); let adapter_connected_clone = adapter_connected.clone(); - async_manager::spawn(async move { - let mut task = BtleplugAdapterTask::new( - event_sender, - receiver, - adapter_connected_clone, - require_keepalive, - ); - task.run().await; - }); + async_manager::spawn( + async move { + let mut task = BtleplugAdapterTask::new( + event_sender, + receiver, + adapter_connected_clone, + require_keepalive, + ); + task.run().await; + }, + info_span!("BtlePlugCommunicationManager::adapter_task").or_current(), + ); Self { adapter_event_sender: sender, scanning_status: Arc::new(AtomicBool::new(false)), diff --git a/crates/buttplug_server_hwmgr_btleplug/src/btleplug_hardware.rs b/crates/buttplug_server_hwmgr_btleplug/src/btleplug_hardware.rs index 061ff44c0..01d206f2b 100644 --- a/crates/buttplug_server_hwmgr_btleplug/src/btleplug_hardware.rs +++ b/crates/buttplug_server_hwmgr_btleplug/src/btleplug_hardware.rs @@ -44,6 +44,7 @@ use std::{ time::Duration, }; use tokio::{select, sync::broadcast}; +use tracing::info_span; use uuid::Uuid; pub(super) struct BtleplugHardwareConnector { @@ -243,70 +244,73 @@ impl BtlePlugHardware { let event_stream_clone = event_stream.clone(); let address = device.id(); let name_clone = name.to_owned(); - async_manager::spawn(async move { - let mut error_notification = false; - loop { - select! { - notification = notification_stream.next() => { - if let Some(notification) = notification { - let endpoint = if let Some(endpoint) = uuid_map.get(¬ification.uuid) { - *endpoint - } else { - // Only print the error message once. - if !error_notification { + async_manager::spawn( + async move { + let mut error_notification = false; + loop { + select! { + notification = notification_stream.next() => { + if let Some(notification) = notification { + let endpoint = if let Some(endpoint) = uuid_map.get(¬ification.uuid) { + *endpoint + } else { + // Only print the error message once. + if !error_notification { + error!( + "Endpoint for UUID {} not found in map, assuming device has disconnected.", + notification.uuid + ); + error_notification = true; + } + continue; + }; + if event_stream_clone.receiver_count() == 0 { + continue; + } + if let Err(err) = event_stream_clone.send(HardwareEvent::Notification( + format!("{address:?}"), + endpoint, + notification.value, + )) { error!( - "Endpoint for UUID {} not found in map, assuming device has disconnected.", - notification.uuid + "Cannot send notification, device object disappeared: {:?}", + err ); - error_notification = true; + break; } - continue; - }; - if event_stream_clone.receiver_count() == 0 { - continue; - } - if let Err(err) = event_stream_clone.send(HardwareEvent::Notification( - format!("{address:?}"), - endpoint, - notification.value, - )) { - error!( - "Cannot send notification, device object disappeared: {:?}", - err - ); - break; } } - } - adapter_event = adapter_event_stream.next() => { - if let Some(CentralEvent::DeviceDisconnected(addr)) = adapter_event - && address == addr { - info!( - "Device {:?} disconnected", - name_clone - ); - if event_stream_clone.receiver_count() != 0 - && let Err(err) = event_stream_clone - .send(HardwareEvent::Disconnected( - format!("{address:?}") - )) { - error!( - "Cannot send notification, device object disappeared: {:?}", - err - ); - } - // At this point, we have nothing left to do because we can't reconnect a device - // that's been connected. Exit. - break; - } + adapter_event = adapter_event_stream.next() => { + if let Some(CentralEvent::DeviceDisconnected(addr)) = adapter_event + && address == addr { + info!( + "Device {:?} disconnected", + name_clone + ); + if event_stream_clone.receiver_count() != 0 + && let Err(err) = event_stream_clone + .send(HardwareEvent::Disconnected( + format!("{address:?}") + )) { + error!( + "Cannot send notification, device object disappeared: {:?}", + err + ); + } + // At this point, we have nothing left to do because we can't reconnect a device + // that's been connected. Exit. + break; + } + } } } - } - info!( - "Exiting btleplug notification/event loop for device {:?}", - address - ) - }); + info!( + "Exiting btleplug notification/event loop for device {:?}", + address + ) + }, + info_span!("BtlePlugHardware::event_loop").or_current(), + ); Self { device, endpoints, @@ -517,10 +521,13 @@ impl HardwareInternal for BtlePlugHardware { impl Drop for BtlePlugHardware { fn drop(&mut self) { let disconnect_fut = self.disconnect(); - async_manager::spawn(async move { - if let Err(e) = disconnect_fut.await { - error!("Error disconnecting btleplug device: {:?}", e); - } - }); + async_manager::spawn( + async move { + if let Err(e) = disconnect_fut.await { + error!("Error disconnecting btleplug device: {:?}", e); + } + }, + info_span!("BtlePlugHardware::drop").or_current(), + ); } } diff --git a/crates/buttplug_server_hwmgr_lovense_connect/src/lovense_connect_service_hardware.rs b/crates/buttplug_server_hwmgr_lovense_connect/src/lovense_connect_service_hardware.rs index 45cc145f4..84d45f0e9 100644 --- a/crates/buttplug_server_hwmgr_lovense_connect/src/lovense_connect_service_hardware.rs +++ b/crates/buttplug_server_hwmgr_lovense_connect/src/lovense_connect_service_hardware.rs @@ -36,6 +36,7 @@ use std::{ time::Duration, }; use tokio::sync::broadcast; +use tracing::info_span; pub struct LovenseServiceHardwareConnector { http_host: String, @@ -93,33 +94,36 @@ impl LovenseServiceHardware { let host = http_host.to_owned(); let battery_level = Arc::new(AtomicU8::new(100)); let battery_level_clone = battery_level.clone(); - async_manager::spawn(async move { - loop { - // SutekhVRC/VibeCheck patch for delay because Lovense Connect HTTP servers crash (Perma DOS) - tokio::time::sleep(Duration::from_secs(1)).await; - match get_local_info(&host).await { - Some(info) => { - for (_, toy) in info.data.iter() { - if toy.id != toy_id { - continue; - } - if !toy.connected { - let _ = sender_clone.send(HardwareEvent::Disconnected(toy_id.clone())); - info!("Exiting lovense service device connection check loop."); + async_manager::spawn( + async move { + loop { + // SutekhVRC/VibeCheck patch for delay because Lovense Connect HTTP servers crash (Perma DOS) + async_manager::sleep(Duration::from_secs(1)).await; + match get_local_info(&host).await { + Some(info) => { + for (_, toy) in info.data.iter() { + if toy.id != toy_id { + continue; + } + if !toy.connected { + let _ = sender_clone.send(HardwareEvent::Disconnected(toy_id.clone())); + info!("Exiting lovense service device connection check loop."); + break; + } + battery_level_clone.store(toy.battery.clamp(0, 100) as u8, Ordering::Relaxed); break; } - battery_level_clone.store(toy.battery.clamp(0, 100) as u8, Ordering::Relaxed); + } + None => { + let _ = sender_clone.send(HardwareEvent::Disconnected(toy_id.clone())); + info!("Exiting lovense service device connection check loop."); break; } } - None => { - let _ = sender_clone.send(HardwareEvent::Disconnected(toy_id.clone())); - info!("Exiting lovense service device connection check loop."); - break; - } } - } - }); + }, + info_span!("LovenseServiceHardware::connection_check").or_current(), + ); Self { event_sender: device_event_sender, http_host: http_host.to_owned(), @@ -167,12 +171,15 @@ impl HardwareInternal for LovenseServiceHardware { async move { match reqwest::get(command_url).await { Ok(res) => { - async_manager::spawn(async move { - trace!( - "Got http response: {}", - res.text().await.unwrap_or("no response".to_string()) - ); - }); + async_manager::spawn( + async move { + trace!( + "Got http response: {}", + res.text().await.unwrap_or("no response".to_string()) + ); + }, + info_span!("LovenseServiceHardware::write_value_response").or_current(), + ); Ok(()) } Err(err) => { diff --git a/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml b/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml index 1466c6314..c6fcdc324 100644 --- a/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml +++ b/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml @@ -36,7 +36,6 @@ serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" serde_repr = "0.1.20" tokio-util = "0.7.18" -tracing-futures = "0.2.5" [target.'cfg(target_os = "windows")'.dependencies] hidapi = { version = "2.6.4", default-features = false, features = ["windows-native"] } diff --git a/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_dongle_hardware.rs b/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_dongle_hardware.rs index ea0b82f35..26dc68fd3 100644 --- a/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_dongle_hardware.rs +++ b/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_dongle_hardware.rs @@ -33,6 +33,7 @@ use buttplug_server_device_config::{ ProtocolCommunicationSpecifier, }; use futures::future::{self, BoxFuture, FutureExt}; +use tracing::info_span; use std::{ collections::HashMap, fmt::{self, Debug}, @@ -128,39 +129,42 @@ impl LovenseDongleHardware { let address_clone = address.to_owned(); let (device_event_sender, _) = broadcast::channel(256); let device_event_sender_clone = device_event_sender.clone(); - async_manager::spawn(async move { - while let Some(msg) = device_incoming.recv().await { - if msg.func != LovenseDongleMessageFunc::ToyData { - continue; + async_manager::spawn( + async move { + while let Some(msg) = device_incoming.recv().await { + if msg.func != LovenseDongleMessageFunc::ToyData { + continue; + } + let data_str = msg + .data + .expect("USB format shouldn't change") + .data + .expect("USB format shouldn't change"); + if device_event_sender_clone + .send(HardwareEvent::Notification( + address_clone.clone(), + Endpoint::Rx, + data_str.into_bytes(), + )) + .is_err() + { + // This sometimes happens with the serial dongle, not sure why. I + // think it may have to do some sort of connection timing. It seems + // like we can continue through it and be fine? Who knows. God I + // hate the lovense dongle. + error!("Can't send to device event sender, continuing Lovense dongle loop."); + } } - let data_str = msg - .data - .expect("USB format shouldn't change") - .data - .expect("USB format shouldn't change"); + info!("Lovense dongle device disconnected",); if device_event_sender_clone - .send(HardwareEvent::Notification( - address_clone.clone(), - Endpoint::Rx, - data_str.into_bytes(), - )) + .send(HardwareEvent::Disconnected(address_clone.clone())) .is_err() { - // This sometimes happens with the serial dongle, not sure why. I - // think it may have to do some sort of connection timing. It seems - // like we can continue through it and be fine? Who knows. God I - // hate the lovense dongle. - error!("Can't send to device event sender, continuing Lovense dongle loop."); + error!("Device Manager no longer alive, cannot send removed event."); } - } - info!("Lovense dongle device disconnected",); - if device_event_sender_clone - .send(HardwareEvent::Disconnected(address_clone.clone())) - .is_err() - { - error!("Device Manager no longer alive, cannot send removed event."); - } - }); + }, + info_span!("LovenseDongleHardware::device_incoming_loop").or_current(), + ); Self { address: address.to_owned(), device_outgoing, diff --git a/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_hid_dongle_comm_manager.rs b/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_hid_dongle_comm_manager.rs index 23ace40e8..96ae9170c 100644 --- a/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_hid_dongle_comm_manager.rs +++ b/crates/buttplug_server_hwmgr_lovense_dongle/src/lovense_hid_dongle_comm_manager.rs @@ -38,7 +38,7 @@ use tokio::{ }, }; use tokio_util::sync::CancellationToken; -use tracing_futures::Instrument; +use tracing::info_span; fn hid_write_thread( dongle: HidDevice, @@ -193,8 +193,8 @@ impl LovenseHIDDongleCommunicationManager { async_manager::spawn( async move { let _ = dongle_fut.await; - } - .instrument(tracing::info_span!("Lovense HID Dongle Finder Task")), + }, + info_span!("Lovense HID Dongle Finder Task").or_current(), ); let mut machine = create_lovense_dongle_machine(event_sender, machine_receiver, mgr.is_scanning.clone()); @@ -203,8 +203,8 @@ impl LovenseHIDDongleCommunicationManager { while let Some(next) = machine.transition().await { machine = next; } - } - .instrument(tracing::info_span!("Lovense HID Dongle State Machine")), + }, + info_span!("Lovense HID Dongle State Machine").or_current(), ); mgr } diff --git a/crates/buttplug_server_hwmgr_serial/src/serialport_hardware.rs b/crates/buttplug_server_hwmgr_serial/src/serialport_hardware.rs index 5181f16f3..435ce21e0 100644 --- a/crates/buttplug_server_hwmgr_serial/src/serialport_hardware.rs +++ b/crates/buttplug_server_hwmgr_serial/src/serialport_hardware.rs @@ -24,6 +24,7 @@ use buttplug_server_device_config::{Endpoint, ProtocolCommunicationSpecifier, Se use futures::future; use futures::{FutureExt, future::BoxFuture}; use serialport::{SerialPort, SerialPortInfo}; +use tracing::info_span; use std::{ fmt::{self, Debug}, io::ErrorKind, @@ -348,30 +349,33 @@ impl HardwareInternal for SerialPortHardware { let event_sender = self.device_event_sender.clone(); let address = self.address.clone(); async move { - async_manager::spawn(async move { - // TODO There's only one subscribable endpoint on a serial port, so we - // should check to make sure we don't have multiple subscriptions so we - // don't deadlock. - let mut data_receiver_mut = data_receiver.lock().await; - loop { - match data_receiver_mut.recv().await { - Some(data) => { - info!("Got serial data! {:?}", data); - event_sender - .send(HardwareEvent::Notification( - address.clone(), - Endpoint::Tx, - data, - )) - .expect("As long as we're subscribed we should have a listener"); - } - None => { - info!("Data channel closed, ending serial listener task"); - break; + async_manager::spawn( + async move { + // TODO There's only one subscribable endpoint on a serial port, so we + // should check to make sure we don't have multiple subscriptions so we + // don't deadlock. + let mut data_receiver_mut = data_receiver.lock().await; + loop { + match data_receiver_mut.recv().await { + Some(data) => { + info!("Got serial data! {:?}", data); + event_sender + .send(HardwareEvent::Notification( + address.clone(), + Endpoint::Tx, + data, + )) + .expect("As long as we're subscribed we should have a listener"); + } + None => { + info!("Data channel closed, ending serial listener task"); + break; + } } } - } - }); + }, + info_span!("SerialPortHardware::subscribe").or_current(), + ); Ok(()) } .boxed() diff --git a/crates/buttplug_server_hwmgr_websocket/src/websocket_server_comm_manager.rs b/crates/buttplug_server_hwmgr_websocket/src/websocket_server_comm_manager.rs index aa693bb0c..1ab41d264 100644 --- a/crates/buttplug_server_hwmgr_websocket/src/websocket_server_comm_manager.rs +++ b/crates/buttplug_server_hwmgr_websocket/src/websocket_server_comm_manager.rs @@ -17,6 +17,7 @@ use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; use tokio::{net::TcpListener, select, sync::mpsc::Sender}; use tokio_util::sync::CancellationToken; +use tracing::info_span; // Packet format received from external devices. #[derive(Serialize, Deserialize, Debug, Clone, Getters, CopyGetters)] @@ -82,90 +83,93 @@ impl WebsocketServerDeviceCommunicationManager { trace!("Websocket server port created."); let server_cancellation_token = CancellationToken::new(); let child_token = server_cancellation_token.child_token(); - async_manager::spawn(async move { - let base_addr = if listen_on_all_interfaces { - "0.0.0.0" - } else { - "127.0.0.1" - }; - - let addr = format!("{base_addr}:{port}"); - debug!("Trying to listen on {}", addr); - - // Create the event loop and TCP listener we'll accept connections on. - debug!("Socket bound."); - let listener = match TcpListener::bind(&addr).await { - Ok(listener) => listener, - Err(err) => { - error!("Cannot bind websocket server to {}: {:?}.", addr, err); - return; - } - }; - debug!("Listening on: {}", addr); - loop { - select! { - listener_result = listener.accept() => { - let stream = if let Ok((stream, _)) = listener_result { - stream - } else { - error!("Cannot bind websocket server comm manager to address {}.", addr); - return; - }; - info!("Got connection"); - let ws_fut = tokio_tungstenite::accept_async(stream); - let mut ws_stream = match ws_fut.await { - Ok(ws_stream) => ws_stream, - Err(err) => { - error!("Cannot accept socket: {}", err); - continue; - } - }; - // Websockets are different from the rest of the communication managers, in that we have no - // information about the device type when we create the connection, and therefore have to - // wait for the first packet. We'll have to pass our device event sender off to the newly - // created event loop, so that it can fire once the info packet is received. - let sender_clone = sender.clone(); - tokio::spawn(async move { - // TODO Implement a receive timeout here so we don't wait forever - if let Some(Ok(tokio_tungstenite::tungstenite::Message::Text(info_message))) = - ws_stream.next().await - { - let info_packet: WebsocketServerDeviceCommManagerInitInfo = - if let Ok(packet) = serde_json::from_str(&info_message) { - packet - } else { - error!("Did not receive a valid JSON info packet as the first packet, disconnecting."); - if let Err(err) = ws_stream.close(None).await { - error!("Error closing connection: {}", err); - } - return; - }; - if sender_clone - .send(HardwareCommunicationManagerEvent::DeviceFound { - name: format!("Websocket Device {}", info_packet.identifier), - address: info_packet.address.clone(), - creator: Box::new(WebsocketServerHardwareConnector::new( - info_packet, - ws_stream, - )), - }) - .await - .is_err() + async_manager::spawn( + async move { + let base_addr = if listen_on_all_interfaces { + "0.0.0.0" + } else { + "127.0.0.1" + }; + + let addr = format!("{base_addr}:{port}"); + debug!("Trying to listen on {}", addr); + + // Create the event loop and TCP listener we'll accept connections on. + debug!("Socket bound."); + let listener = match TcpListener::bind(&addr).await { + Ok(listener) => listener, + Err(err) => { + error!("Cannot bind websocket server to {}: {:?}.", addr, err); + return; + } + }; + debug!("Listening on: {}", addr); + loop { + select! { + listener_result = listener.accept() => { + let stream = if let Ok((stream, _)) = listener_result { + stream + } else { + error!("Cannot bind websocket server comm manager to address {}.", addr); + return; + }; + info!("Got connection"); + let ws_fut = tokio_tungstenite::accept_async(stream); + let mut ws_stream = match ws_fut.await { + Ok(ws_stream) => ws_stream, + Err(err) => { + error!("Cannot accept socket: {}", err); + continue; + } + }; + // Websockets are different from the rest of the communication managers, in that we have no + // information about the device type when we create the connection, and therefore have to + // wait for the first packet. We'll have to pass our device event sender off to the newly + // created event loop, so that it can fire once the info packet is received. + let sender_clone = sender.clone(); + async_manager::spawn(async move { + // TODO Implement a receive timeout here so we don't wait forever + if let Some(Ok(tokio_tungstenite::tungstenite::Message::Text(info_message))) = + ws_stream.next().await { - error!("Device manager disappeared, exiting."); + let info_packet: WebsocketServerDeviceCommManagerInitInfo = + if let Ok(packet) = serde_json::from_str(&info_message) { + packet + } else { + error!("Did not receive a valid JSON info packet as the first packet, disconnecting."); + if let Err(err) = ws_stream.close(None).await { + error!("Error closing connection: {}", err); + } + return; + }; + if sender_clone + .send(HardwareCommunicationManagerEvent::DeviceFound { + name: format!("Websocket Device {}", info_packet.identifier), + address: info_packet.address.clone(), + creator: Box::new(WebsocketServerHardwareConnector::new( + info_packet, + ws_stream, + )), + }) + .await + .is_err() + { + error!("Device manager disappeared, exiting."); + } + } else { + error!("Did not receive info message as first packet, dropping connection."); } - } else { - error!("Did not receive info message as first packet, dropping connection."); - } - }); - }, - _ = child_token.cancelled() => { - info!("Task token cancelled, assuming websocket server comm manager shutdown."); - break; + }, info_span!("WebsocketServerDeviceCommunicationManager::connection_loop").or_current()); + }, + _ = child_token.cancelled() => { + info!("Task token cancelled, assuming websocket server comm manager shutdown."); + break; + } } } - } - }); + }, + info_span!("WebsocketServerDeviceCommunicationManager::server_loop").or_current(), + ); Self { server_cancellation_token, } diff --git a/crates/buttplug_server_hwmgr_websocket/src/websocket_server_hardware.rs b/crates/buttplug_server_hwmgr_websocket/src/websocket_server_hardware.rs index 8ae1778d4..42ae8b2b9 100644 --- a/crates/buttplug_server_hwmgr_websocket/src/websocket_server_hardware.rs +++ b/crates/buttplug_server_hwmgr_websocket/src/websocket_server_hardware.rs @@ -47,6 +47,7 @@ use tokio::{ time::sleep, }; use tokio_util::sync::CancellationToken; +use tracing::info_span; async fn run_connection_loop( address: &str, @@ -173,7 +174,7 @@ impl WebsocketServerHardwareConnector { let (device_event_sender, _) = broadcast::channel(256); let device_event_sender_clone = device_event_sender.clone(); let address = info.address().clone(); - tokio::spawn(async move { + async_manager::spawn(async move { run_connection_loop( &address, device_event_sender_clone, @@ -182,7 +183,7 @@ impl WebsocketServerHardwareConnector { incoming_broadcaster_clone, ) .await; - }); + }, info_span!("WebsocketServerHardwareConnector::connection_loop").or_current()); Self { info, outgoing_sender, @@ -305,31 +306,34 @@ impl HardwareInternal for WebsocketServerHardware { subscribed.store(true, Ordering::Relaxed); let token = CancellationToken::new(); *(subscribed_token.lock().await) = Some(token.child_token()); - async_manager::spawn(async move { - loop { - select! { - result = data_receiver.recv().fuse() => { - match result { - Ok(data) => { - debug!("Got websocket data! {:?}", data); - // We don't really care if there's no one to send the error to here. - let _ = event_sender - .send(HardwareEvent::Notification( - address.clone(), - Endpoint::Tx, - data, - )); - }, - Err(_) => break, + async_manager::spawn( + async move { + loop { + select! { + result = data_receiver.recv().fuse() => { + match result { + Ok(data) => { + debug!("Got websocket data! {:?}", data); + // We don't really care if there's no one to send the error to here. + let _ = event_sender + .send(HardwareEvent::Notification( + address.clone(), + Endpoint::Tx, + data, + )); + }, + Err(_) => break, + } + }, + _ = token.cancelled().fuse() => { + break; } - }, - _ = token.cancelled().fuse() => { - break; } } - } - info!("Data channel closed, ending websocket server device listener task"); - }); + info!("Data channel closed, ending websocket server device listener task"); + }, + info_span!("WebsocketServerHardware::subscribe_listener").or_current(), + ); Ok(()) } .boxed() diff --git a/crates/buttplug_server_hwmgr_xinput/src/xinput_hardware.rs b/crates/buttplug_server_hwmgr_xinput/src/xinput_hardware.rs index 29d3a4fe3..b4619e695 100644 --- a/crates/buttplug_server_hwmgr_xinput/src/xinput_hardware.rs +++ b/crates/buttplug_server_hwmgr_xinput/src/xinput_hardware.rs @@ -33,6 +33,7 @@ use std::{ }; use tokio::sync::broadcast; use tokio_util::sync::CancellationToken; +use tracing::info_span; pub(super) fn create_address(index: XInputControllerIndex) -> String { index.to_string() @@ -55,7 +56,7 @@ async fn check_gamepad_connectivity( } tokio::select! { _ = cancellation_token.cancelled() => return, - _ = tokio::time::sleep(Duration::from_millis(500)) => continue + _ = async_manager::sleep(Duration::from_millis(500)) => continue } } } @@ -113,9 +114,12 @@ impl XInputHardware { let token = CancellationToken::new(); let child = token.child_token(); let sender = device_event_sender.clone(); - async_manager::spawn(async move { - check_gamepad_connectivity(index, sender, child).await; - }); + async_manager::spawn( + async move { + check_gamepad_connectivity(index, sender, child).await; + }, + info_span!("XInputHardware::check_gamepad_connectivity").or_current(), + ); Self { handle: rusty_xinput::XInputHandle::load_default().expect("The DLL should load as long as we're on windows, and we don't get here if we're not on windows."), index, diff --git a/crates/buttplug_tests/tests/test_serializers.rs b/crates/buttplug_tests/tests/test_serializers.rs index 906f84ec2..5e424526e 100644 --- a/crates/buttplug_tests/tests/test_serializers.rs +++ b/crates/buttplug_tests/tests/test_serializers.rs @@ -29,6 +29,7 @@ use buttplug_server::message::{ }; use std::sync::Arc; use tokio::sync::Notify; +use tracing::info_span; use util::channel_transport::ChannelClientTestHelper; #[tokio::test] @@ -38,13 +39,16 @@ async fn test_garbled_client_rsi_response() { let helper_clone = helper.clone(); let finish_notifier = Arc::new(Notify::new()); let finish_notifier_clone = finish_notifier.clone(); - async_manager::spawn(async move { - helper_clone - .connect_without_reply() - .await - .expect("Test, assuming infallible."); - finish_notifier_clone.notify_waiters(); - }); + async_manager::spawn( + async move { + helper_clone + .connect_without_reply() + .await + .expect("Test, assuming infallible."); + finish_notifier_clone.notify_waiters(); + }, + info_span!("ChannelClientTestHelper::simulate_successful_connect").or_current(), + ); // Just assume we get an RSI message let _ = helper.recv_outgoing().await; // Send back crap. @@ -72,19 +76,22 @@ async fn test_serialized_error_relay() { let helper = Arc::new(ChannelClientTestHelper::new()); helper.simulate_successful_connect().await; let helper_clone = helper.clone(); - async_manager::spawn(async move { - assert!(matches!( - helper_clone.next_client_message().await, - ButtplugClientMessageVariant::V4(ButtplugClientMessageV4::StartScanning(..)) - )); - let mut error_msg = ButtplugServerMessageV4::Error(ErrorV0::from(ButtplugError::from( - ButtplugUnknownError::NoDeviceCommManagers, - ))); - error_msg.set_id(3); - helper_clone - .send_client_incoming(ButtplugServerMessageVariant::V4(error_msg)) - .await; - }); + async_manager::spawn( + async move { + assert!(matches!( + helper_clone.next_client_message().await, + ButtplugClientMessageVariant::V4(ButtplugClientMessageV4::StartScanning(..)) + )); + let mut error_msg = ButtplugServerMessageV4::Error(ErrorV0::from(ButtplugError::from( + ButtplugUnknownError::NoDeviceCommManagers, + ))); + error_msg.set_id(3); + helper_clone + .send_client_incoming(ButtplugServerMessageVariant::V4(error_msg)) + .await; + }, + info_span!("test_serialized_error_relay::client_message_sender").or_current(), + ); assert!(matches!( helper.client().start_scanning().await.unwrap_err(), ButtplugClientError::ButtplugError(ButtplugError::ButtplugUnknownError( diff --git a/crates/buttplug_tests/tests/util/channel_transport.rs b/crates/buttplug_tests/tests/util/channel_transport.rs index f38cdcad5..8b7a4e7b8 100644 --- a/crates/buttplug_tests/tests/util/channel_transport.rs +++ b/crates/buttplug_tests/tests/util/channel_transport.rs @@ -51,6 +51,7 @@ use tokio::sync::{ Notify, mpsc::{Receiver, Sender, channel}, }; +use tracing::info_span; struct ChannelTransport { outside_receiver: Arc>>>, @@ -80,37 +81,40 @@ impl ButtplugConnectorTransport for ChannelTransport { let disconnect_notifier = self.disconnect_notifier.clone(); let outside_sender = self.outside_sender.clone(); let outside_receiver_mutex = self.outside_receiver.clone(); - async_manager::spawn(async move { - let mut outside_receiver = outside_receiver_mutex - .lock() - .await - .take() - .expect("Test, assuming infallible"); - loop { - select! { - _ = disconnect_notifier.notified().fuse() => { - info!("Test requested disconnect."); - return; - } - outgoing = outgoing_receiver.recv().fuse() => { - if let Some(o) = outgoing { - outside_sender.send(o).await.expect("Test, assuming infallible"); - } else { - info!("Test dropped stream, returning"); + async_manager::spawn( + async move { + let mut outside_receiver = outside_receiver_mutex + .lock() + .await + .take() + .expect("Test, assuming infallible"); + loop { + select! { + _ = disconnect_notifier.notified().fuse() => { + info!("Test requested disconnect."); return; } - } - incoming = outside_receiver.recv().fuse() => { - if let Some(i) = incoming { - incoming_sender.send(i).await.expect("Test, assuming infallible"); - } else { - info!("Test dropped stream, returning"); - return; + outgoing = outgoing_receiver.recv().fuse() => { + if let Some(o) = outgoing { + outside_sender.send(o).await.expect("Test, assuming infallible"); + } else { + info!("Test dropped stream, returning"); + return; + } + } + incoming = outside_receiver.recv().fuse() => { + if let Some(i) = incoming { + incoming_sender.send(i).await.expect("Test, assuming infallible"); + } else { + info!("Test dropped stream, returning"); + return; + } } - } - }; - } - }); + }; + } + }, + info_span!("ChannelTransport::connect_loop").or_current(), + ); future::ready(Ok(())).boxed() } @@ -191,12 +195,15 @@ impl ChannelClientTestHelper { .expect("Test, assuming infallible"); let finish_notifier = Arc::new(Notify::new()); let finish_notifier_clone = finish_notifier.clone(); - async_manager::spawn(async move { - if let Err(e) = client_clone.connect(connector).await { - assert!(false, "Error connecting to client: {:?}", e); - } - finish_notifier_clone.notify_waiters(); - }); + async_manager::spawn( + async move { + if let Err(e) = client_clone.connect(connector).await { + assert!(false, "Error connecting to client: {:?}", e); + } + finish_notifier_clone.notify_waiters(); + }, + info_span!("ChannelClientTestHelper::simulate_successful_connect").or_current(), + ); // Wait for RequestServerInfo message assert!(matches!( self.next_client_message().await, diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs index 5bc54712b..af0b4d5a0 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs @@ -20,7 +20,6 @@ use buttplug_core::{ }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; -use futures::channel::oneshot; use buttplug_server::message::{ ButtplugClientMessageV2, ButtplugServerMessageV2, @@ -28,6 +27,7 @@ use buttplug_server::message::{ StopAllDevicesV0, }; use dashmap::DashMap; +use futures::channel::oneshot; use futures::{ Stream, future::{self, BoxFuture}, @@ -39,7 +39,7 @@ use std::sync::{ }; use thiserror::Error; use tokio::sync::{Mutex, broadcast, mpsc, mpsc::error::SendError}; -use tracing::{Level, Span, span}; +use tracing::{Level, Span, info_span, span}; use tracing_futures::Instrument; /// Result type used for public APIs. @@ -168,15 +168,18 @@ impl ButtplugClient { pub fn new(name: &str) -> (Self, mpsc::Receiver) { let (message_sender, message_receiver) = mpsc::channel(256); let (event_stream, _) = broadcast::channel(256); - (Self { - client_name: name.to_owned(), - server_name: Arc::new(Mutex::new(None)), - event_stream, - message_sender, - _client_span: Arc::new(Mutex::new(None)), - connected: Arc::new(AtomicBool::new(false)), - device_map: Arc::new(DashMap::new()), - }, message_receiver) + ( + Self { + client_name: name.to_owned(), + server_name: Arc::new(Mutex::new(None)), + event_stream, + message_sender, + _client_span: Arc::new(Mutex::new(None)), + connected: Arc::new(AtomicBool::new(false)), + device_map: Arc::new(DashMap::new()), + }, + message_receiver, + ) } pub async fn connect( @@ -220,8 +223,8 @@ impl ButtplugClient { async_manager::spawn( async move { client_event_loop.run().await; - } - .instrument(tracing::info_span!("Client Loop Span")), + }, + info_span!("ClientLoop").or_current(), ); self.run_handshake().await } diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs index 7138d18cd..5c458c939 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs @@ -133,7 +133,7 @@ impl ButtplugConnector } } info!("Stopping In Process Client Connector Event Sender Loop, due to channel receiver being dropped."); - }.instrument(tracing::info_span!("InProcessClientConnectorEventSenderLoop"))); + }, tracing::info_span!("InProcessClientConnectorEventSenderLoop")); connected.store(true, Ordering::Relaxed); Ok(()) }.boxed() diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/mod.rs index 1a738edf0..6b15003e1 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/mod.rs @@ -24,6 +24,7 @@ use client::{ButtplugClient, ButtplugClientEvent}; use device::{ButtplugClientDevice, LinearCommand, RotateCommand, VibrateCommand}; use in_process_connector::ButtplugInProcessClientConnectorBuilder; use tokio::sync::Notify; +use tracing::info_span; use super::super::{ super::TestDeviceCommunicationManagerBuilder, @@ -78,10 +79,13 @@ async fn run_test_client_command(command: &TestClientCommand, device: &Arc, - connected: &Arc, - ) -> Self { + fn new(message_sender: mpsc::Sender, connected: &Arc) -> Self { Self { message_sender, connected: connected.clone(), @@ -248,17 +246,17 @@ impl ButtplugClient { let (message_sender, message_receiver) = mpsc::channel(256); let (event_stream, _) = broadcast::channel(256); let connected = Arc::new(AtomicBool::new(false)); - (Self { - client_name: name.to_owned(), - server_name: Arc::new(Mutex::new(None)), - event_stream, - message_sender: Arc::new(ButtplugClientMessageSender::new( - message_sender, - &connected, - )), - connected, - device_map: Arc::new(DashMap::new()), - }, message_receiver) + ( + Self { + client_name: name.to_owned(), + server_name: Arc::new(Mutex::new(None)), + event_stream, + message_sender: Arc::new(ButtplugClientMessageSender::new(message_sender, &connected)), + connected, + device_map: Arc::new(DashMap::new()), + }, + message_receiver, + ) } pub async fn connect( @@ -299,8 +297,8 @@ impl ButtplugClient { async_manager::spawn( async move { client_event_loop.run().await; - } - .instrument(tracing::info_span!("Client Loop Span")), + }, + info_span!("ClientLoop").or_current(), ); self.run_handshake().await } diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs index 3c47feb32..8336a56f2 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs @@ -131,7 +131,7 @@ impl ButtplugConnector } } info!("Stopping In Process Client Connector Event Sender Loop, due to channel receiver being dropped."); - }.instrument(tracing::info_span!("InProcessClientConnectorEventSenderLoop"))); + }, tracing::info_span!("InProcessClientConnectorEventSenderLoop")); connected.store(true, Ordering::Relaxed); Ok(()) }.boxed() diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/mod.rs index 8ef64567b..0b6366a7d 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/mod.rs @@ -25,6 +25,7 @@ use buttplug_core::util::async_manager; use buttplug_server::{ButtplugServer, ButtplugServerBuilder, device::ServerDeviceManagerBuilder}; use buttplug_server_device_config::load_protocol_configs; use tokio::sync::Notify; +use tracing::info_span; use super::super::{ super::TestDeviceCommunicationManagerBuilder, @@ -89,10 +90,13 @@ async fn run_test_client_command(command: &TestClientCommand, device: &Arc, @@ -78,7 +79,7 @@ impl ButtplugConnectorTransport for ChannelTransport { } }; } - }); + }, info_span!("ChannelTransport::message_loop").or_current()); Ok(()) } .boxed() diff --git a/crates/buttplug_tests/tests/util/test_device_manager/test_device.rs b/crates/buttplug_tests/tests/util/test_device_manager/test_device.rs index 2fd9984a3..de15c43a2 100644 --- a/crates/buttplug_tests/tests/util/test_device_manager/test_device.rs +++ b/crates/buttplug_tests/tests/util/test_device_manager/test_device.rs @@ -32,6 +32,7 @@ use std::{ time::Duration, }; use tokio::sync::{Mutex, broadcast, mpsc}; +use tracing::info_span; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TestHardwareNotification { @@ -175,36 +176,39 @@ impl TestDevice { let subscribed_endpoints_clone = subscribed_endpoints.clone(); let read_data = Arc::new(Mutex::new(VecDeque::new())); let read_data_clone = read_data.clone(); - async_manager::spawn(async move { - while let Some(event) = receiver.recv().await { - match event { - TestHardwareEvent::Disconnect => { - event_sender_clone - .send(HardwareEvent::Disconnected(address_clone.clone())) - .expect("Test"); - } - TestHardwareEvent::Notifications(notifications) => { - for notification in notifications { - if subscribed_endpoints_clone.contains(¬ification.endpoint) { - event_sender_clone - .send(HardwareEvent::Notification( - address_clone.clone(), - notification.endpoint, - notification.data.clone(), - )) - .expect("Test"); + async_manager::spawn( + async move { + while let Some(event) = receiver.recv().await { + match event { + TestHardwareEvent::Disconnect => { + event_sender_clone + .send(HardwareEvent::Disconnected(address_clone.clone())) + .expect("Test"); + } + TestHardwareEvent::Notifications(notifications) => { + for notification in notifications { + if subscribed_endpoints_clone.contains(¬ification.endpoint) { + event_sender_clone + .send(HardwareEvent::Notification( + address_clone.clone(), + notification.endpoint, + notification.data.clone(), + )) + .expect("Test"); + } } } - } - TestHardwareEvent::Reads(events) => { - let mut guard = read_data_clone.lock().await; - for read in events { - guard.push_front(HardwareReading::new(read.endpoint, &read.data)); + TestHardwareEvent::Reads(events) => { + let mut guard = read_data_clone.lock().await; + for read in events { + guard.push_front(HardwareReading::new(read.endpoint, &read.data)); + } } } } - } - }); + }, + info_span!("TestDevice::command_loop").or_current(), + ); Self { name: name.to_owned(), diff --git a/crates/buttplug_tests/tests/util/test_server.rs b/crates/buttplug_tests/tests/util/test_server.rs index d8056d3f5..55c3a2342 100644 --- a/crates/buttplug_tests/tests/util/test_server.rs +++ b/crates/buttplug_tests/tests/util/test_server.rs @@ -21,6 +21,7 @@ use log::*; use std::sync::Arc; use thiserror::Error; use tokio::sync::{Notify, mpsc}; +use tracing::info_span; #[derive(Error, Debug)] pub enum ButtplugServerConnectorError { @@ -57,27 +58,30 @@ async fn run_server( trace!("Got message from connector: {:?}", client_message); let server_clone = server.clone(); let connector_clone = shared_connector.clone(); - async_manager::spawn(async move { - if let Err(e) = client_message.is_valid() { - error!("Message not valid: {:?} - Error: {}", client_message, e); - let mut err_msg = ErrorV0::from(ButtplugError::from(e)); - err_msg.set_id(client_message.id()); - let _ = connector_clone.send(ButtplugServerMessageVariant::V3(err_msg.into())).await; - return; - } - match server_clone.parse_message(client_message.clone()).await { - Ok(ret_msg) => { - if connector_clone.send(ret_msg).await.is_err() { - error!("Cannot send reply to server, dropping and assuming remote server thread has exited."); - } - }, - Err(err_msg) => { - if connector_clone.send(err_msg).await.is_err() { - error!("Cannot send reply to server, dropping and assuming remote server thread has exited."); + async_manager::spawn( + async move { + if let Err(e) = client_message.is_valid() { + error!("Message not valid: {:?} - Error: {}", client_message, e); + let mut err_msg = ErrorV0::from(ButtplugError::from(e)); + err_msg.set_id(client_message.id()); + let _ = connector_clone.send(ButtplugServerMessageVariant::V3(err_msg.into())).await; + return; + } + match server_clone.parse_message(client_message.clone()).await { + Ok(ret_msg) => { + if connector_clone.send(ret_msg).await.is_err() { + error!("Cannot send reply to server, dropping and assuming remote server thread has exited."); + } + }, + Err(err_msg) => { + if connector_clone.send(err_msg).await.is_err() { + error!("Cannot send reply to server, dropping and assuming remote server thread has exited."); + } } } - } - }); + }, + info_span!("RemoteServerMessageHandler").or_current() + ); } }, _ = disconnect_notifier.notified().fuse() => { diff --git a/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs b/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs index d5dba5444..2867ea7d5 100644 --- a/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs +++ b/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs @@ -40,7 +40,7 @@ use tokio_tungstenite::{ connect_async_tls_with_config, tungstenite::protocol::Message, }; -use tracing::Instrument; +use tracing::{Instrument, info_span}; use url::Url; pub fn get_rustls_config_dangerous() -> ClientConfig { @@ -287,8 +287,8 @@ impl ButtplugConnectorTransport for ButtplugWebsocketClientTransport { } } } - } - .instrument(tracing::info_span!("Websocket Client I/O Task")), + }, + info_span!("Websocket Client I/O Task").or_current(), ); Ok(()) } diff --git a/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs b/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs index 0c2addbb3..510493fce 100644 --- a/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs +++ b/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs @@ -19,6 +19,7 @@ use buttplug_core::{ util::async_manager, }; use futures::{FutureExt, SinkExt, StreamExt, future::BoxFuture}; +use tracing::info_span; use std::{sync::Arc, time::Duration}; use tokio::{ net::{TcpListener, TcpStream}, @@ -245,7 +246,7 @@ impl ButtplugConnectorTransport for ButtplugWebsocketServerTransport { disconnect_notifier_clone, ) .await; - }); + }, info_span!("ButtplugWebsocketServerTransport::connect").or_current()); Ok(()) } else { Err(ButtplugConnectorError::ConnectorGenericError( diff --git a/crates/intiface_engine/src/remote_server.rs b/crates/intiface_engine/src/remote_server.rs index a619e336b..0c4ce2c52 100644 --- a/crates/intiface_engine/src/remote_server.rs +++ b/crates/intiface_engine/src/remote_server.rs @@ -169,7 +169,7 @@ async fn run_server( } } } - }); + }, tracing::info_span!("RemoteServerMessageHandler").or_current()); } }, _ = disconnect_notifier.notified().fuse() => { From b896a4f73052cf6a4f9e8f0b2056145363d10322 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Tue, 3 Mar 2026 22:52:36 +0100 Subject: [PATCH 02/17] Optimize device configuration loading - Make JSON validation return the parsed value to avoid double parsing. - Allow parsing of hashmaps to DeviceConfigurationManagerBuilder to avoid cloning. - Other techniques to avoid cloning of data during loading. - Add a version.rs file generated by build.rs to avoid parsing the config version from JSON at runtime. --- crates/buttplug_core/src/util/json.rs | 9 +- crates/buttplug_server_device_config/build.rs | 12 +++ .../src/device_config_file/base.rs | 4 +- .../src/device_config_file/device.rs | 16 ++-- .../src/device_config_file/mod.rs | 93 ++++++++++--------- .../src/device_config_manager.rs | 36 ++++--- .../src/identifiers.rs | 7 +- 7 files changed, 95 insertions(+), 82 deletions(-) diff --git a/crates/buttplug_core/src/util/json.rs b/crates/buttplug_core/src/util/json.rs index 52bce0401..e1ddf8b95 100644 --- a/crates/buttplug_core/src/util/json.rs +++ b/crates/buttplug_core/src/util/json.rs @@ -39,14 +39,15 @@ impl JSONValidator { /// # Parameters /// /// - `json_str`: JSON string to validate. - pub fn validate(&self, json_str: &str) -> Result<(), ButtplugSerializerError> { - let check_value = serde_json::from_str(json_str).map_err(|err| { + pub fn validate(&self, json_str: &str) -> Result { + let value = serde_json::from_str(json_str).map_err(|err| { ButtplugSerializerError::JsonSerializerError(format!("Message: {json_str} - Error: {err:?}")) })?; - self.schema.validate(&check_value).map_err(|err| { + self.schema.validate(&value).map_err(|err| { ButtplugSerializerError::JsonSerializerError(format!( "Error during JSON Schema Validation: {err:?}" )) - }) + })?; + Ok(value) } } diff --git a/crates/buttplug_server_device_config/build.rs b/crates/buttplug_server_device_config/build.rs index df558f20a..4b13ff304 100644 --- a/crates/buttplug_server_device_config/build.rs +++ b/crates/buttplug_server_device_config/build.rs @@ -39,6 +39,7 @@ fn main() { // Open version file let mut version: VersionFile = serde_yaml::from_str(&std::fs::read_to_string(VERSION_FILE).unwrap()).unwrap(); + save_rust_version_file(version.version); // Bump minor version version.version.minor += 1; @@ -86,4 +87,15 @@ fn main() { ) .unwrap(); std::fs::write(OUTPUT_FILE, json.as_bytes()).unwrap(); + save_rust_version_file(version.version); +} + +fn save_rust_version_file(version: BuildVersion) { + std::fs::write( + format!("{}/version.rs", std::env::var("OUT_DIR").unwrap()), + format!( + "fn get_internal_config_version() -> ConfigVersion {{ ConfigVersion {{ major: {}, minor: {} }} }}", + version.major, version.minor + ), + ).unwrap(); } diff --git a/crates/buttplug_server_device_config/src/device_config_file/base.rs b/crates/buttplug_server_device_config/src/device_config_file/base.rs index df0a780f4..79822280c 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/base.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/base.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; -use getset::Getters; +use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; use crate::device_config_file::{ @@ -17,7 +17,7 @@ use crate::device_config_file::{ protocol::ProtocolDefinition, }; -#[derive(Deserialize, Serialize, Debug, Getters)] +#[derive(Deserialize, Serialize, Debug, Getters, MutGetters)] #[getset(get_mut = "pub", set = "pub")] pub struct BaseConfigFile { #[getset(get_copy = "pub")] diff --git a/crates/buttplug_server_device_config/src/device_config_file/device.rs b/crates/buttplug_server_device_config/src/device_config_file/device.rs index d1378cc0e..a505772ae 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/device.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/device.rs @@ -16,7 +16,7 @@ use super::feature::{ConfigBaseDeviceFeature, ConfigUserDeviceFeature}; #[derive(Debug, Clone, Getters, CopyGetters, Serialize, Deserialize)] pub struct ConfigBaseDeviceDefinition { #[getset(get = "pub")] - identifier: Option>, + pub identifier: Option>, #[getset(get = "pub")] /// Given name of the device this instance represents. name: String, @@ -31,14 +31,14 @@ pub struct ConfigBaseDeviceDefinition { } impl ConfigBaseDeviceDefinition { - pub fn update_with_configuration(&self, config: ConfigBaseDeviceDefinition) -> Self { + pub fn with_defaults(self, defaults: Option<&ConfigBaseDeviceDefinition>) -> Self { Self { - identifier: config.identifier().clone(), - name: config.name().clone(), - id: config.id(), - protocol_variant: config.protocol_variant.or(self.protocol_variant.clone()), - message_gap_ms: config.message_gap_ms.or(self.message_gap_ms), - features: config.features.or(self.features.clone()), + identifier: self.identifier, + name: self.name, + id: self.id, + protocol_variant: self.protocol_variant.or_else(|| defaults.and_then(|d| d.protocol_variant.clone())), + message_gap_ms: self.message_gap_ms.or_else(|| defaults.and_then(|d| d.message_gap_ms)), + features: self.features.or_else(|| defaults.and_then(|d| d.features.clone())), } } } diff --git a/crates/buttplug_server_device_config/src/device_config_file/mod.rs b/crates/buttplug_server_device_config/src/device_config_file/mod.rs index c12c89841..860598105 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/mod.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/mod.rs @@ -13,9 +13,12 @@ mod user; use base::BaseConfigFile; -use crate::device_config_file::{ - protocol::ProtocolDefinition, - user::{UserConfigDefinition, UserConfigFile, UserDeviceConfigPair}, +use crate::{ + ServerDeviceDefinition, + device_config_file::{ + protocol::ProtocolDefinition, + user::{UserConfigDefinition, UserConfigFile, UserDeviceConfigPair}, + }, }; use super::{BaseDeviceIdentifier, DeviceConfigurationManager, DeviceConfigurationManagerBuilder}; @@ -25,14 +28,16 @@ use buttplug_core::{ }; use dashmap::DashMap; use getset::CopyGetters; -use serde::{Deserialize, Serialize}; -use std::fmt::Display; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use std::{collections::HashMap, fmt::Display}; pub static DEVICE_CONFIGURATION_JSON: &str = include_str!("../../build-config/buttplug-device-config-v4.json"); static DEVICE_CONFIGURATION_JSON_SCHEMA: &str = include_str!("../../device-config-v4/buttplug-device-config-schema-v4.json"); +include!(concat!(env!("OUT_DIR"), "/version.rs")); + #[derive(Deserialize, Serialize, Debug, CopyGetters, Clone, Copy)] #[getset(get_copy = "pub", get_mut = "pub")] struct ConfigVersion { @@ -50,22 +55,16 @@ trait ConfigVersionGetter { fn version(&self) -> ConfigVersion; } -fn get_internal_config_version() -> ConfigVersion { - let config: BaseConfigFile = serde_json::from_str(DEVICE_CONFIGURATION_JSON) - .expect("If this fails, the whole library goes with it."); - config.version() -} - fn load_protocol_config_from_json<'a, T>( config_str: &'a str, skip_version_check: bool, ) -> Result where - T: ConfigVersionGetter + Deserialize<'a>, + T: ConfigVersionGetter + DeserializeOwned, { let config_validator = JSONValidator::new(DEVICE_CONFIGURATION_JSON_SCHEMA); match config_validator.validate(config_str) { - Ok(_) => match serde_json::from_str::(config_str) { + Ok(value) => match serde_json::from_value::(value) { Ok(protocol_config) => { let internal_config_version = get_internal_config_version(); if !skip_version_check && protocol_config.version().major != internal_config_version.major { @@ -92,53 +91,54 @@ fn load_main_config( main_config_str: &Option, skip_version_check: bool, ) -> Result { - if main_config_str.is_some() { - info!("Loading from custom base device configuration...") - } else { - info!("Loading from internal base device configuration...") - } // Start by loading the main config - let main_config = load_protocol_config_from_json::( - main_config_str - .as_ref() - .unwrap_or(&DEVICE_CONFIGURATION_JSON.to_owned()), - skip_version_check, - )?; + let main_config_str = match main_config_str { + Some(config_str) => { + info!("Loading main configuration from string."); + config_str.as_str() + } + None => { + info!("No main configuration provided, loading from internal config."); + DEVICE_CONFIGURATION_JSON + } + }; + let mut main_config = + load_protocol_config_from_json::(main_config_str, skip_version_check)?; info!("Loaded config version {:?}", main_config.version()); - let mut dcm_builder = DeviceConfigurationManagerBuilder::default(); + let protocols = main_config.protocols_mut().take().unwrap_or_default(); - for (protocol_name, protocol_def) in main_config.protocols().clone().unwrap_or_default() { - if let Some(specifiers) = protocol_def.communication() { - dcm_builder.communication_specifier(&protocol_name, specifiers); - } + let mut base_communication_specifiers = HashMap::with_capacity(protocols.len()); + let mut base_device_definitions = HashMap::new(); - let mut default = None; - if let Some(features) = protocol_def.defaults() { - default = Some(features.clone()); - dcm_builder.base_device_definition( - &BaseDeviceIdentifier::new_default(&protocol_name), - &features.clone().into(), - ); + for (protocol_name, protocol_def) in protocols { + let ProtocolDefinition { + communication, + defaults, + configurations, + } = protocol_def; + + if let Some(specifiers) = communication { + base_communication_specifiers.insert(protocol_name.clone(), specifiers); } - for config in protocol_def.configurations() { - if let Some(idents) = config.identifier() { + for mut config in configurations { + let identifier = config.identifier.take(); + let config: ServerDeviceDefinition = config.with_defaults(defaults.as_ref()).into(); + if let Some(idents) = identifier { for config_ident in idents { let ident = BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident); - if let Some(d) = &default { - dcm_builder - .base_device_definition(&ident, &d.update_with_configuration(config.clone()).into()); - } else { - dcm_builder.base_device_definition(&ident, &config.clone().into()); - } + base_device_definitions.insert(ident, config.clone()); } } } } - Ok(dcm_builder) + Ok(DeviceConfigurationManagerBuilder::new( + base_communication_specifiers, + base_device_definitions, + )) } fn load_user_config( @@ -173,7 +173,8 @@ fn load_user_config( for config in protocol_def.configurations() { if let Some(idents) = config.identifier() { for config_ident in idents { - let ident = BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident); + let ident = + BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident.clone()); dcm_builder.base_device_definition(&ident, &config.clone().into()); } } diff --git a/crates/buttplug_server_device_config/src/device_config_manager.rs b/crates/buttplug_server_device_config/src/device_config_manager.rs index 3be966bb3..f900d84ba 100644 --- a/crates/buttplug_server_device_config/src/device_config_manager.rs +++ b/crates/buttplug_server_device_config/src/device_config_manager.rs @@ -32,6 +32,17 @@ pub struct DeviceConfigurationManagerBuilder { } impl DeviceConfigurationManagerBuilder { + pub fn new( + base_communication_specifiers: HashMap>, + base_device_definitions: HashMap, + ) -> Self { + Self { + base_communication_specifiers, + base_device_definitions, + ..Default::default() + } + } + pub fn communication_specifier( &mut self, protocol_name: &str, @@ -95,27 +106,12 @@ impl DeviceConfigurationManagerBuilder { } } - pub fn finish(&mut self) -> Result { - // Build and validate the protocol attributes tree. - let mut attribute_tree_map = HashMap::new(); - - // Add all the defaults first, they won't have parent attributes. - for (ident, attr) in &self.base_device_definitions { - attribute_tree_map.insert(ident.clone(), attr.clone()); - } - - let user_attribute_tree_map = DashMap::new(); - // Finally, add in user configurations, which will have an address. - for kv in &self.user_device_definitions { - user_attribute_tree_map.insert(kv.key().clone(), kv.value().clone()); - } - + pub fn finish(self) -> Result { Ok(DeviceConfigurationManager { - base_communication_specifiers: self.base_communication_specifiers.clone(), - user_communication_specifiers: self.user_communication_specifiers.clone(), - base_device_definitions: attribute_tree_map, - user_device_definitions: user_attribute_tree_map, - //protocol_map, + base_communication_specifiers: self.base_communication_specifiers, + user_communication_specifiers: self.user_communication_specifiers, + base_device_definitions: self.base_device_definitions, + user_device_definitions: self.user_device_definitions, }) } } diff --git a/crates/buttplug_server_device_config/src/identifiers.rs b/crates/buttplug_server_device_config/src/identifiers.rs index 5edafb6af..1eac2e3b1 100644 --- a/crates/buttplug_server_device_config/src/identifiers.rs +++ b/crates/buttplug_server_device_config/src/identifiers.rs @@ -55,8 +55,11 @@ impl BaseDeviceIdentifier { Self::new(protocol, &None) } - pub fn new_with_identifier(protocol: &str, attributes_identifier: &str) -> Self { - Self::new(protocol, &Some(attributes_identifier.to_owned())) + pub fn new_with_identifier(protocol: &str, attributes_identifier: String) -> Self { + Self { + protocol: protocol.to_owned(), + identifier: Some(attributes_identifier), + } } pub fn new(protocol: &str, attributes_identifier: &Option) -> Self { From f62be3ea7a8ad9d29f08788ea6d411a6a5573cb5 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 13:28:47 +0100 Subject: [PATCH 03/17] Memory optimizations: Custom RangeInclusive type, CompactString, and bitflags for InputCommandType. --- crates/buttplug_client/src/device/feature.rs | 8 +- crates/buttplug_core/Cargo.toml | 1 + .../src/message/device_feature.rs | 35 +---- .../buttplug_core/src/message/v4/input_cmd.rs | 89 +++++++++++- crates/buttplug_core/src/message/v4/mod.rs | 2 +- crates/buttplug_core/src/util/mod.rs | 2 +- crates/buttplug_core/src/util/range.rs | 48 +++++++ crates/buttplug_server/Cargo.toml | 2 + .../src/device/device_handle.rs | 7 +- crates/buttplug_server/src/device/protocol.rs | 9 +- .../src/device/protocol_impl/fluffer.rs | 2 +- .../src/device/protocol_impl/hismith.rs | 4 +- .../src/device/protocol_impl/hismith_mini.rs | 2 +- .../src/device/protocol_impl/lovense/mod.rs | 6 +- .../protocol_impl/lovense_connect_service.rs | 2 +- .../src/device/protocol_impl/monsterpub.rs | 5 +- .../src/device/protocol_impl/patoo.rs | 2 +- .../src/device/protocol_impl/prettylove.rs | 2 +- .../src/device/protocol_impl/satisfyer.rs | 4 +- .../device/protocol_impl/thehandy_v3/mod.rs | 2 +- .../src/device/protocol_impl/vibratissimo.rs | 2 +- .../src/device/protocol_impl/youou.rs | 2 +- .../src/device/server_device_manager.rs | 7 +- .../src/message/server_device_attributes.rs | 8 +- .../v3/client_device_message_attributes.rs | 36 ++--- .../v3/server_device_message_attributes.rs | 13 +- .../src/message/v4/spec_enums.rs | 15 +- .../buttplug_server_device_config/Cargo.toml | 3 + .../src/device_config_file/base.rs | 3 +- .../src/device_config_file/device.rs | 7 +- .../src/device_config_file/feature.rs | 10 +- .../src/device_config_file/user.rs | 3 +- .../src/device_config_manager.rs | 18 +-- .../src/device_definitions.rs | 22 +-- .../src/identifiers.rs | 27 ++-- .../src/server_device_feature.rs | 135 +++++++----------- .../src/specifier.rs | 13 +- .../tests/test_device_config.rs | 2 +- .../util/device_test/client/client_v4/mod.rs | 2 +- crates/intiface_engine/src/remote_server.rs | 2 +- examples/src/bin/device_tester.rs | 6 +- 41 files changed, 324 insertions(+), 246 deletions(-) create mode 100644 crates/buttplug_core/src/util/range.rs diff --git a/crates/buttplug_client/src/device/feature.rs b/crates/buttplug_client/src/device/feature.rs index d81fd5605..71d7537d1 100644 --- a/crates/buttplug_client/src/device/feature.rs +++ b/crates/buttplug_client/src/device/feature.rs @@ -76,7 +76,7 @@ impl ClientDeviceFeature { ClientDeviceCommandValue::Percent(f) => self.convert_float_value(feature_output, *f)?, ClientDeviceCommandValue::Steps(i) => *i, }; - if feature_output.step_limit().contains(&value) { + if feature_output.step_limit().contains(value) { Ok(value) } else { Err(ButtplugClientError::ButtplugOutputCommandConversionError( @@ -182,7 +182,7 @@ impl ClientDeviceFeature { pub fn run_input_subscribe(&self, sensor_type: InputType) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) - && sensor.command().contains(&InputCommandType::Subscribe) + && sensor.command().contains(InputCommandType::Subscribe) { let msg = InputCmdV4::new( self.device_index, @@ -202,7 +202,7 @@ impl ClientDeviceFeature { pub fn run_input_unsubscribe(&self, sensor_type: InputType) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) - && sensor.command().contains(&InputCommandType::Subscribe) + && sensor.command().contains(InputCommandType::Subscribe) { let msg = InputCmdV4::new( self.device_index, @@ -222,7 +222,7 @@ impl ClientDeviceFeature { pub fn run_input_read(&self, sensor_type: InputType) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) - && sensor.command().contains(&InputCommandType::Read) + && sensor.command().contains(InputCommandType::Read) { let msg = InputCmdV4::new( self.device_index, diff --git a/crates/buttplug_core/Cargo.toml b/crates/buttplug_core/Cargo.toml index 4babb9ad4..d254c3220 100644 --- a/crates/buttplug_core/Cargo.toml +++ b/crates/buttplug_core/Cargo.toml @@ -54,3 +54,4 @@ derive_builder = "0.20.2" enum_dispatch = "0.3" async-trait = "0.1.89" tracing = "0.1.44" +enumflags2 = "0.7.12" diff --git a/crates/buttplug_core/src/message/device_feature.rs b/crates/buttplug_core/src/message/device_feature.rs index 94658f45b..1ddb43bc7 100644 --- a/crates/buttplug_core/src/message/device_feature.rs +++ b/crates/buttplug_core/src/message/device_feature.rs @@ -5,11 +5,11 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use crate::{message::InputCommandType, util::range_serialize::*}; +use crate::{message::InputCommandTypeFlags, util::range::RangeInclusive}; use derive_builder::Builder; use getset::{CopyGetters, Getters, MutGetters, Setters}; -use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq}; -use std::{collections::HashSet, hash::Hash, ops::RangeInclusive}; +use serde::{Deserialize, Serialize}; +use std::hash::Hash; #[derive( Debug, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter, EnumString, @@ -107,20 +107,6 @@ impl DeviceFeature { } } -fn range_sequence_serialize( - range_vec: &Vec>, - serializer: S, -) -> Result -where - S: Serializer, -{ - let mut seq = serializer.serialize_seq(Some(range_vec.len()))?; - for range in range_vec { - seq.serialize_element(&vec![*range.start(), *range.end()])?; - } - seq.end() -} - pub trait DeviceFeatureOutputLimits { fn step_count(&self) -> u32; fn step_limit(&self) -> RangeInclusive; @@ -130,7 +116,6 @@ pub trait DeviceFeatureOutputLimits { #[serde(rename_all = "PascalCase")] pub struct DeviceFeatureOutputValueProperties { #[getset(get = "pub")] - #[serde(serialize_with = "range_serialize")] value: RangeInclusive, } @@ -142,7 +127,7 @@ impl DeviceFeatureOutputValueProperties { } pub fn step_count(&self) -> u32 { - *self.value.end() as u32 + self.value.end() as u32 } } @@ -159,10 +144,8 @@ impl DeviceFeatureOutputLimits for DeviceFeatureOutputValueProperties { #[serde(rename_all = "PascalCase")] pub struct DeviceFeatureOutputHwPositionWithDurationProperties { #[getset(get = "pub")] - #[serde(serialize_with = "range_serialize")] value: RangeInclusive, #[getset(get = "pub")] - #[serde(serialize_with = "range_serialize")] duration: RangeInclusive, } @@ -175,7 +158,7 @@ impl DeviceFeatureOutputHwPositionWithDurationProperties { } pub fn step_count(&self) -> u32 { - *self.value.end() as u32 + self.value.end() as u32 } } @@ -258,17 +241,13 @@ impl DeviceFeatureOutput { #[serde(rename_all = "PascalCase")] pub struct DeviceFeatureInputProperties { #[getset(get = "pub", get_mut = "pub(super)")] - #[serde(serialize_with = "range_sequence_serialize")] value: Vec>, #[getset(get = "pub")] - command: HashSet, + command: InputCommandTypeFlags, } impl DeviceFeatureInputProperties { - pub fn new( - value: &Vec>, - sensor_commands: &HashSet, - ) -> Self { + pub fn new(value: &Vec>, sensor_commands: InputCommandTypeFlags) -> Self { Self { value: value.clone(), command: sensor_commands.clone(), diff --git a/crates/buttplug_core/src/message/v4/input_cmd.rs b/crates/buttplug_core/src/message/v4/input_cmd.rs index 1b5042354..2d64d802a 100644 --- a/crates/buttplug_core/src/message/v4/input_cmd.rs +++ b/crates/buttplug_core/src/message/v4/input_cmd.rs @@ -5,6 +5,8 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use std::{fmt, ops::{Deref, DerefMut}}; + use crate::message::{ ButtplugDeviceMessage, ButtplugMessage, @@ -13,8 +15,11 @@ use crate::message::{ InputType, }; use getset::CopyGetters; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::{SeqAccess, Visitor}, ser::SerializeSeq}; +use enumflags2::{BitFlags, bitflags}; +#[bitflags] +#[repr(u8)] #[derive(Debug, Display, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, Copy)] pub enum InputCommandType { #[serde(alias = "read")] @@ -83,3 +88,85 @@ impl ButtplugMessageValidator for InputCmdV4 { // TODO Should expected_length always be > 0? } } + +#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)] +pub struct InputCommandTypeFlags(BitFlags); + +impl InputCommandTypeFlags { + pub fn new(flags: BitFlags) -> Self { + Self(flags) + } + + pub fn empty() -> Self { + Self(BitFlags::empty()) + } +} + +impl Deref for InputCommandTypeFlags { + type Target = BitFlags; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for InputCommandTypeFlags { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for InputCommandTypeFlags { + fn from(flags: BitFlags) -> Self { + Self(flags) + } +} + +impl From for BitFlags { + fn from(flags: InputCommandTypeFlags) -> Self { + flags.0 + } +} + +impl Serialize for InputCommandTypeFlags { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for cmd in self.0.iter() { + seq.serialize_element(&cmd)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for InputCommandTypeFlags { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FlagsVisitor; + + impl<'de> Visitor<'de> for FlagsVisitor { + type Value = InputCommandTypeFlags; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an array of InputCommandType values") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut flags = BitFlags::empty(); + while let Some(cmd) = seq.next_element::()? { + flags |= cmd; + } + Ok(InputCommandTypeFlags(flags)) + } + } + + deserializer.deserialize_seq(FlagsVisitor) + } +} diff --git a/crates/buttplug_core/src/message/v4/mod.rs b/crates/buttplug_core/src/message/v4/mod.rs index aa94b7336..4ebcb0c4c 100644 --- a/crates/buttplug_core/src/message/v4/mod.rs +++ b/crates/buttplug_core/src/message/v4/mod.rs @@ -18,7 +18,7 @@ mod stop_cmd; pub use { device_list::DeviceListV4, device_message_info::DeviceMessageInfoV4, - input_cmd::{InputCmdV4, InputCommandType}, + input_cmd::{InputCmdV4, InputCommandType, InputCommandTypeFlags}, input_reading::{InputReadingV4, InputTypeReading, InputValue}, output_cmd::{OutputCmdV4, OutputCommand, OutputHwPositionWithDuration, OutputValue}, request_server_info::RequestServerInfoV4, diff --git a/crates/buttplug_core/src/util/mod.rs b/crates/buttplug_core/src/util/mod.rs index 63cdc8040..162dc4202 100644 --- a/crates/buttplug_core/src/util/mod.rs +++ b/crates/buttplug_core/src/util/mod.rs @@ -10,5 +10,5 @@ pub mod async_manager; pub mod json; -pub mod range_serialize; +pub mod range; pub mod stream; diff --git a/crates/buttplug_core/src/util/range.rs b/crates/buttplug_core/src/util/range.rs new file mode 100644 index 000000000..83da2fc4d --- /dev/null +++ b/crates/buttplug_core/src/util/range.rs @@ -0,0 +1,48 @@ +// Buttplug Rust Source Code File - See https://buttplug.io for more info. +// +// Copyright 2016-2026 Nonpolynomial Labs LLC. All rights reserved. +// +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root +// for full license information. + +use std::fmt; + +use serde::{Deserialize, Serialize}; + +/// A range bounded inclusively below and above (`start..=end`). +/// Use this instead of `std::ops::RangeInclusive` when directly iterating over the range is not required. +/// It uses less memory and doesn't need special serialization. +#[derive(Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct RangeInclusive([T; 2]); + +impl RangeInclusive { + pub fn new(start: T, end: T) -> Self { + Self([start, end]) + } + + pub fn start(&self) -> T { + self.0[0] + } + + pub fn end(&self) -> T { + self.0[1] + } + + pub fn is_empty(&self) -> bool { + self.0[0] > self.0[1] + } + + pub fn contains(&self, value: T) -> bool { + value >= self.0[0] && value <= self.0[1] + } +} + +impl fmt::Debug for RangeInclusive +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{:?}..={:?}]", self.0[0], self.0[1]) + } +} diff --git a/crates/buttplug_server/Cargo.toml b/crates/buttplug_server/Cargo.toml index bdd90fe29..a0780d1c2 100644 --- a/crates/buttplug_server/Cargo.toml +++ b/crates/buttplug_server/Cargo.toml @@ -57,6 +57,8 @@ byteorder = "1.5.0" rand = { version = "0.8" } derive_more = { version = "2.1.1", features = ["from"] } evalexpr = { version = "13.1.0", features = ["rand"] } +litemap = "0.8.1" +compact_str = { version = "0.9.0", features = ["serde"] } [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.3.4", features = ["wasm_js"]} diff --git a/crates/buttplug_server/src/device/device_handle.rs b/crates/buttplug_server/src/device/device_handle.rs index 0908f43ab..bb19b7408 100644 --- a/crates/buttplug_server/src/device/device_handle.rs +++ b/crates/buttplug_server/src/device/device_handle.rs @@ -34,6 +34,7 @@ use buttplug_server_device_config::{ ServerDeviceDefinition, UserDeviceIdentifier, }; +use compact_str::CompactString; use dashmap::DashMap; use futures::future::{self, BoxFuture, FutureExt}; use tokio::sync::{ @@ -139,8 +140,8 @@ impl DeviceHandle { } /// Get the device's name - pub fn name(&self) -> String { - self.definition.name().to_owned() + pub fn name(&self) -> CompactString { + self.definition.name().clone() } /// Get the device's definition (contains features, display name, etc.) @@ -158,7 +159,7 @@ impl DeviceHandle { DeviceMessageInfoV4::new( index, &self.name(), - self.definition.display_name(), + &self.definition.display_name().as_ref().map(|n| n.to_string()), 100, &self .definition diff --git a/crates/buttplug_server/src/device/protocol.rs b/crates/buttplug_server/src/device/protocol.rs index e624b5706..c1adb42c3 100644 --- a/crates/buttplug_server/src/device/protocol.rs +++ b/crates/buttplug_server/src/device/protocol.rs @@ -17,6 +17,7 @@ use buttplug_server_device_config::{ ServerDeviceDefinition, UserDeviceIdentifier, }; +use compact_str::CompactString; use dashmap::DashMap; use super::hardware::HardwareWriteCmd; @@ -156,7 +157,7 @@ impl ProtocolIdentifier for GenericProtocolIdentifier { let device_identifier = UserDeviceIdentifier::new( hardware.address(), &self.protocol_identifier, - &Some(hardware.name().to_owned()), + Some(hardware.name()), ); Ok(( device_identifier, @@ -531,7 +532,7 @@ macro_rules! generic_protocol_initializer_setup { hardware: Arc, _: ProtocolCommunicationSpecifier, ) -> Result<(UserDeviceIdentifier, Box), ButtplugDeviceError> { - Ok((UserDeviceIdentifier::new(hardware.address(), $protocol_identifier, &Some(hardware.name().to_owned())), Box::new([< $protocol_name Initializer >]::default()))) + Ok((UserDeviceIdentifier::new(hardware.address(), $protocol_identifier, Some(hardware.name())), Box::new([< $protocol_name Initializer >]::default()))) } } } @@ -558,8 +559,8 @@ impl ProtocolManager { pub fn protocol_specializers( &self, specifier: &ProtocolCommunicationSpecifier, - base_communication_specifiers: &HashMap>, - user_communication_specifiers: &DashMap>, + base_communication_specifiers: &HashMap>, + user_communication_specifiers: &DashMap>, ) -> Vec { debug!( "Looking for protocol that matches specifier: {:?}", diff --git a/crates/buttplug_server/src/device/protocol_impl/fluffer.rs b/crates/buttplug_server/src/device/protocol_impl/fluffer.rs index e3015a4a8..a8f0afc58 100644 --- a/crates/buttplug_server/src/device/protocol_impl/fluffer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/fluffer.rs @@ -84,7 +84,7 @@ impl ProtocolIdentifier for FlufferIdentifier { UserDeviceIdentifier::new( hardware.address(), "fluffer", - &Some(hardware.name().to_owned()), + Some(hardware.name()), ), Box::new(FlufferInitializer::new(data)), )) diff --git a/crates/buttplug_server/src/device/protocol_impl/hismith.rs b/crates/buttplug_server/src/device/protocol_impl/hismith.rs index 99183c75b..9cb5720c0 100644 --- a/crates/buttplug_server/src/device/protocol_impl/hismith.rs +++ b/crates/buttplug_server/src/device/protocol_impl/hismith.rs @@ -70,13 +70,13 @@ impl ProtocolIdentifier for HismithIdentifier { if !LEGACY_HISMITHS.contains(&identifier.as_str()) { info!("Not a legacy Hismith, using hismith-mini protocol"); return Ok(( - UserDeviceIdentifier::new(hardware.address(), "hismith-mini", &Some(identifier)), + UserDeviceIdentifier::new(hardware.address(), "hismith-mini", Some(&identifier)), Box::new(HismithMiniInitializer::default()), )); } Ok(( - UserDeviceIdentifier::new(hardware.address(), "hismith", &Some(identifier)), + UserDeviceIdentifier::new(hardware.address(), "hismith", Some(&identifier)), Box::new(HismithInitializer::default()), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs b/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs index 3358c4608..d4c8663c5 100644 --- a/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs +++ b/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs @@ -65,7 +65,7 @@ impl ProtocolIdentifier for HismithMiniIdentifier { info!("Hismith Device Identifier: {}", identifier); Ok(( - UserDeviceIdentifier::new(hardware.address(), "hismith-mini", &Some(identifier)), + UserDeviceIdentifier::new(hardware.address(), "hismith-mini", Some(&identifier)), Box::new(HismithMiniInitializer::default()), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs index 8fa4bf9ca..316a4ab12 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs @@ -123,7 +123,7 @@ impl ProtocolIdentifier for LovenseIdentifier { let type_response = std::str::from_utf8(&n).map_err(|_| ButtplugDeviceError::ProtocolSpecificError("lovense".to_owned(), "Lovense device init got back non-UTF8 string.".to_owned()))?.to_owned(); debug!("Lovense Device Type Response: {}", type_response); let ident = lovense_model_resolver(type_response); - return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &Some(ident.clone())), Box::new(LovenseInitializer::new(ident)))); + return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", Some(&ident)), Box::new(LovenseInitializer::new(ident)))); } else { return Err( ButtplugDeviceError::ProtocolSpecificError( @@ -140,9 +140,9 @@ impl ProtocolIdentifier for LovenseIdentifier { let re = Regex::new(r"LVS-([A-Z]+)\d+").expect("Static regex shouldn't fail"); if let Some(caps) = re.captures(hardware.name()) { info!("Lovense Device identified by BLE name"); - return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &Some(caps[1].to_string())), Box::new(LovenseInitializer::new(caps[1].to_string())))); + return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", Some(&caps[1])), Box::new(LovenseInitializer::new(caps[1].to_string())))); }; - return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &None), Box::new(LovenseInitializer::new("".to_string())))); + return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", None), Box::new(LovenseInitializer::new("".to_string())))); } } } diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs index 3dc39cb4d..30f6aa5d2 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs @@ -60,7 +60,7 @@ impl ProtocolIdentifier for LovenseConnectIdentifier { UserDeviceIdentifier::new( hardware.address(), "lovense-connect-service", - &Some(hardware.name().to_owned()), + Some(hardware.name()), ), Box::new(LovenseConnectServiceInitializer::default()), )) diff --git a/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs b/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs index e6aa7bb0a..e1561e33d 100644 --- a/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs +++ b/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs @@ -66,12 +66,11 @@ impl ProtocolIdentifier for MonsterPubIdentifier { "MonsterPub device name is non-UTF8 string.".to_owned(), ) })? - .replace("\0", "") - .to_owned(), + .replace("\0", ""), Err(_) => "Unknown".to_string(), }; return Ok(( - UserDeviceIdentifier::new(hardware.address(), "monsterpub", &Some(ident)), + UserDeviceIdentifier::new(hardware.address(), "monsterpub", Some(&ident)), Box::new(MonsterPubInitializer::default()), )); } diff --git a/crates/buttplug_server/src/device/protocol_impl/patoo.rs b/crates/buttplug_server/src/device/protocol_impl/patoo.rs index 083f41a3c..d9e9d2f1c 100644 --- a/crates/buttplug_server/src/device/protocol_impl/patoo.rs +++ b/crates/buttplug_server/src/device/protocol_impl/patoo.rs @@ -59,7 +59,7 @@ impl ProtocolIdentifier for PatooIdentifier { } let name: String = c[0..i].iter().collect(); Ok(( - UserDeviceIdentifier::new(hardware.address(), "Patoo", &Some(name)), + UserDeviceIdentifier::new(hardware.address(), "Patoo", Some(&name)), Box::new(PatooInitializer::default()), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/prettylove.rs b/crates/buttplug_server/src/device/protocol_impl/prettylove.rs index 53e7de8b3..d5608691a 100644 --- a/crates/buttplug_server/src/device/protocol_impl/prettylove.rs +++ b/crates/buttplug_server/src/device/protocol_impl/prettylove.rs @@ -50,7 +50,7 @@ impl ProtocolIdentifier for PrettyLoveIdentifier { UserDeviceIdentifier::new( hardware.address(), "prettylove", - &Some("Aogu BLE".to_owned()), + Some("Aogu BLE"), ), Box::new(PrettyLoveInitializer::default()), )) diff --git a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs index ebb5904ee..8d20f54e7 100644 --- a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs @@ -67,7 +67,7 @@ impl ProtocolIdentifier for SatisfyerIdentifier { ); return Ok(( - UserDeviceIdentifier::new(hardware.address(), "satisfyer", &Some(device_identifier)), + UserDeviceIdentifier::new(hardware.address(), "satisfyer", Some(&device_identifier)), Box::new(SatisfyerInitializer::default()), )); } @@ -92,7 +92,7 @@ impl ProtocolIdentifier for SatisfyerIdentifier { device_identifier ); return Ok(( - UserDeviceIdentifier::new(hardware.address(), "satisfyer", &Some(device_identifier)), + UserDeviceIdentifier::new(hardware.address(), "satisfyer", Some(&device_identifier)), Box::new(SatisfyerInitializer::default()), )); } diff --git a/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs b/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs index d331ebee9..ef4faf861 100644 --- a/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs @@ -60,7 +60,7 @@ impl ProtocolIdentifier for TheHandyV3Identifier { let bits: Vec<&str> = hardware.name().split('_').collect(); let name = if bits.len() > 2 { bits[1] } else { "unknown" }; Ok(( - UserDeviceIdentifier::new(hardware.address(), "thehandy-v3", &Some(name.to_owned())), + UserDeviceIdentifier::new(hardware.address(), "thehandy-v3", Some(name)), Box::new(TheHandyV3Initializer::default()), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs b/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs index 2f534c957..695e83f9f 100644 --- a/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs +++ b/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs @@ -62,7 +62,7 @@ impl ProtocolIdentifier for VibratissimoIdentifier { let ident = String::from_utf8(result.data().to_vec()).unwrap_or_else(|_| hardware.name().to_owned()); Ok(( - UserDeviceIdentifier::new(hardware.address(), "vibratissimo", &Some(ident)), + UserDeviceIdentifier::new(hardware.address(), "vibratissimo", Some(&ident)), Box::new(VibratissimoInitializer::default()), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/youou.rs b/crates/buttplug_server/src/device/protocol_impl/youou.rs index 814566ea4..d38feaea0 100644 --- a/crates/buttplug_server/src/device/protocol_impl/youou.rs +++ b/crates/buttplug_server/src/device/protocol_impl/youou.rs @@ -51,7 +51,7 @@ impl ProtocolIdentifier for YououIdentifier { _: ProtocolCommunicationSpecifier, ) -> Result<(UserDeviceIdentifier, Box), ButtplugDeviceError> { Ok(( - UserDeviceIdentifier::new(hardware.address(), "Youou", &Some("VX001_".to_owned())), + UserDeviceIdentifier::new(hardware.address(), "Youou", Some("VX001_")), Box::new(YououInitializer::default()), )) } diff --git a/crates/buttplug_server/src/device/server_device_manager.rs b/crates/buttplug_server/src/device/server_device_manager.rs index d321ff0aa..2d48f4c94 100644 --- a/crates/buttplug_server/src/device/server_device_manager.rs +++ b/crates/buttplug_server/src/device/server_device_manager.rs @@ -38,15 +38,16 @@ use buttplug_core::{ util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; use buttplug_server_device_config::{DeviceConfigurationManager, UserDeviceIdentifier}; +use compact_str::CompactString; use dashmap::DashMap; use futures::{ Stream, future::{self, FutureExt}, }; use getset::Getters; +use litemap::LiteMap; use tracing::info_span; use std::{ - collections::BTreeMap, convert::TryFrom, sync::{ Arc, @@ -66,7 +67,7 @@ pub(super) enum DeviceManagerCommand { #[getset(get = "pub")] pub struct ServerDeviceInfo { identifier: UserDeviceIdentifier, - display_name: Option, + display_name: Option, } pub struct ServerDeviceManagerBuilder { @@ -297,7 +298,7 @@ impl ServerDeviceManager { } } - pub(crate) fn feature_map(&self) -> BTreeMap { + pub(crate) fn feature_map(&self) -> LiteMap { self .devices() .iter() diff --git a/crates/buttplug_server/src/message/server_device_attributes.rs b/crates/buttplug_server/src/message/server_device_attributes.rs index 014f7326c..655da6ffd 100644 --- a/crates/buttplug_server/src/message/server_device_attributes.rs +++ b/crates/buttplug_server/src/message/server_device_attributes.rs @@ -11,7 +11,7 @@ use super::v2::ServerDeviceMessageAttributesV2; use buttplug_core::errors::ButtplugError; use buttplug_server_device_config::ServerDeviceFeature; use getset::Getters; -use std::collections::BTreeMap; +use litemap::LiteMap; #[derive(Debug, Getters, Clone)] pub(crate) struct ServerDeviceAttributes { @@ -23,11 +23,11 @@ pub(crate) struct ServerDeviceAttributes { #[getset(get = "pub")] attrs_v3: ServerDeviceMessageAttributesV3, #[getset(get = "pub")] - features: BTreeMap, + features: LiteMap, } impl ServerDeviceAttributes { - pub fn new(features: &BTreeMap) -> Self { + pub fn new(features: &LiteMap) -> Self { let f: Vec = features.values().cloned().collect(); Self { attrs_v3: ServerDeviceMessageAttributesV3::from(f.clone()), @@ -46,6 +46,6 @@ where { fn try_from_client_message( msg: T, - features: &BTreeMap, + features: &LiteMap, ) -> Result; } diff --git a/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs b/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs index f09546ee0..0b6fa4f5a 100644 --- a/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs +++ b/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs @@ -9,16 +9,18 @@ use crate::message::{ v1::NullDeviceMessageAttributesV1, v2::{ClientDeviceMessageAttributesV2, GenericDeviceMessageAttributesV2}, }; -use buttplug_core::message::{ - DeviceFeature, - DeviceFeatureOutputValueProperties, - InputCommandType, - InputType, - OutputType, +use buttplug_core::{ + message::{ + DeviceFeature, + DeviceFeatureOutputValueProperties, + InputCommandType, + InputType, + OutputType, + }, + util::range::RangeInclusive, }; use getset::{Getters, MutGetters, Setters}; use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq}; -use std::ops::RangeInclusive; // This will look almost exactly like ServerDeviceMessageAttributes. However, it will only contain // information we want the client to know, i.e. step counts versus specific step ranges. This is @@ -201,20 +203,6 @@ impl ClientGenericDeviceMessageAttributesV3 { } } -fn range_sequence_serialize( - range_vec: &Vec>, - serializer: S, -) -> Result -where - S: Serializer, -{ - let mut seq = serializer.serialize_seq(Some(range_vec.len()))?; - for range in range_vec { - seq.serialize_element(&vec![*range.start(), *range.end()])?; - } - seq.end() -} - #[derive(Clone, Debug, Serialize, Deserialize, Getters, Setters)] pub struct SensorDeviceMessageAttributesV3 { #[getset(get = "pub")] @@ -224,7 +212,7 @@ pub struct SensorDeviceMessageAttributesV3 { #[serde(rename = "SensorType")] pub(in crate::message) sensor_type: InputType, #[getset(get = "pub")] - #[serde(rename = "SensorRange", serialize_with = "range_sequence_serialize")] + #[serde(rename = "SensorRange")] pub(in crate::message) sensor_range: Vec>, // TODO This needs to actually be part of the device info relayed to the client in spec v4. #[getset(get = "pub")] @@ -294,7 +282,7 @@ impl From> for ClientDeviceMessageAttributesV3 { let mut actuator_vec = vec![]; if let Some(output_map) = feature.output() && let Some(actuator) = output_map.rotate() - && *actuator.value().start() < 0 + && actuator.value().start() < 0 { let actuator_type = OutputType::Rotate; let attrs = ClientGenericDeviceMessageAttributesV3 { @@ -338,7 +326,7 @@ impl From> for ClientDeviceMessageAttributesV3 { // Only convert Battery backwards. Other sensors weren't really built for v3 and we // never recommended using them or implemented much for them. if let Some(battery) = sensor_map.battery() - && battery.command().contains(&InputCommandType::Read) + && battery.command().contains(InputCommandType::Read) { sensor_vec.push(SensorDeviceMessageAttributesV3 { feature_descriptor: feature.description().to_owned(), diff --git a/crates/buttplug_server/src/message/v3/server_device_message_attributes.rs b/crates/buttplug_server/src/message/v3/server_device_message_attributes.rs index 3632f2784..f2e98080f 100644 --- a/crates/buttplug_server/src/message/v3/server_device_message_attributes.rs +++ b/crates/buttplug_server/src/message/v3/server_device_message_attributes.rs @@ -6,11 +6,10 @@ // for full license information. use crate::message::v1::NullDeviceMessageAttributesV1; -use buttplug_core::message::{InputType, OutputType}; +use buttplug_core::{message::{InputType, OutputType}, util::range::RangeInclusive}; use buttplug_server_device_config::ServerDeviceFeature; use getset::{Getters, MutGetters, Setters}; -use std::ops::RangeInclusive; #[derive(Clone, Debug, Default, PartialEq, Eq, Getters, MutGetters, Setters)] #[getset(get = "pub")] @@ -62,7 +61,7 @@ impl From> for ServerDeviceMessageAttributesV3 { let mut create_attribute = |actuator_type, step_count| { let actuator_type = actuator_type; let attrs = ServerGenericDeviceMessageAttributesV3 { - feature_descriptor: feature.description().to_owned(), + feature_descriptor: feature.description().to_string(), actuator_type, step_count, feature: feature.clone(), @@ -109,12 +108,12 @@ impl From> for ServerDeviceMessageAttributesV3 { let mut actuator_vec = vec![]; if let Some(output_map) = feature.output() && let Some(actuator) = output_map.rotate() - && *actuator.value().base().start() < 0 + && actuator.value().base().start() < 0 { let actuator_type = OutputType::Rotate; let step_count = actuator.value().step_count(); let attrs = ServerGenericDeviceMessageAttributesV3 { - feature_descriptor: feature.description().to_owned(), + feature_descriptor: feature.description().to_string(), actuator_type, step_count, feature: feature.clone(), @@ -136,7 +135,7 @@ impl From> for ServerDeviceMessageAttributesV3 { let actuator_type = OutputType::Position; let step_count = actuator.value().step_count(); let attrs = ServerGenericDeviceMessageAttributesV3 { - feature_descriptor: feature.description().to_owned(), + feature_descriptor: feature.description().to_string(), actuator_type, step_count, feature: feature.clone(), @@ -159,7 +158,7 @@ impl From> for ServerDeviceMessageAttributesV3 { // Only convert Battery backwards. Other sensors weren't really built for v3 and we // never recommended using them or implemented much for them. sensor_vec.push(ServerSensorDeviceMessageAttributesV3 { - feature_descriptor: feature.description().to_owned(), + feature_descriptor: feature.description().to_string(), sensor_type: InputType::Battery, sensor_range: battery.value().clone(), feature: feature.clone(), diff --git a/crates/buttplug_server/src/message/v4/spec_enums.rs b/crates/buttplug_server/src/message/v4/spec_enums.rs index 4f649bb85..15a294bcc 100644 --- a/crates/buttplug_server/src/message/v4/spec_enums.rs +++ b/crates/buttplug_server/src/message/v4/spec_enums.rs @@ -26,6 +26,7 @@ use buttplug_core::{ ButtplugClientMessageV4, ButtplugDeviceMessage, ButtplugMessage, PingV0, RequestDeviceListV0, RequestServerInfoV4, StartScanningV0, StopCmdV4, StopScanningV0 }, }; +use litemap::LiteMap; use super::{ checked_input_cmd::CheckedInputCmdV4, @@ -74,7 +75,7 @@ impl_message_enum_traits!(ButtplugCheckedClientMessageV4 { impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( value: ButtplugClientMessageV4, - feature_map: &BTreeMap, + feature_map: &LiteMap, ) -> Result { match value { // Messages that don't need checking @@ -186,7 +187,7 @@ impl TryFrom for ButtplugCheckedClientMessageV4 { impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( msg: ButtplugClientMessageVariant, - features: &BTreeMap, + features: &LiteMap, ) -> Result { let id = msg.id(); let mut converted_msg = match msg { @@ -205,7 +206,7 @@ impl TryFromClientMessage for ButtplugCheckedClien impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( msg: ButtplugClientMessageV0, - features: &BTreeMap, + features: &LiteMap, ) -> Result { // All v0 messages can be converted to v1 messages. Self::try_from_client_message(ButtplugClientMessageV1::from(msg), features) @@ -214,7 +215,7 @@ impl TryFromClientMessage for ButtplugCheckedClientMess fn check_device_index_and_convert( msg: T, - features: &BTreeMap, + features: &LiteMap, ) -> Result where T: ButtplugDeviceMessage + Debug, @@ -233,7 +234,7 @@ where impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( msg: ButtplugClientMessageV1, - features: &BTreeMap, + features: &LiteMap, ) -> Result { // Instead of converting to v2 message attributes then to v4 device features, we move directly // from v0 command messages to v4 device features here. There's no reason to do the middle step. @@ -253,7 +254,7 @@ impl TryFromClientMessage for ButtplugCheckedClientMess impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( msg: ButtplugClientMessageV2, - features: &BTreeMap, + features: &LiteMap, ) -> Result { match msg { // Convert v2 specific queries to v3 generic sensor queries @@ -272,7 +273,7 @@ impl TryFromClientMessage for ButtplugCheckedClientMess impl TryFromClientMessage for ButtplugCheckedClientMessageV4 { fn try_from_client_message( msg: ButtplugClientMessageV3, - features: &BTreeMap, + features: &LiteMap, ) -> Result { match msg { // Convert v1/v2 message attribute commands into device feature commands diff --git a/crates/buttplug_server_device_config/Cargo.toml b/crates/buttplug_server_device_config/Cargo.toml index 5c6c41469..f23d32feb 100644 --- a/crates/buttplug_server_device_config/Cargo.toml +++ b/crates/buttplug_server_device_config/Cargo.toml @@ -34,6 +34,9 @@ jsonschema = { version = "0.38.1", default-features = false } uuid = { version = "1.20.0", features = ["serde", "v4"] } strum_macros = "0.27.2" strum = "0.27.2" +litemap = "0.8.1" +compact_str = { version = "0.9.0", features = ["serde"] } +enumflags2 = "0.7.12" [build-dependencies] serde_yaml = "0.9.34" diff --git a/crates/buttplug_server_device_config/src/device_config_file/base.rs b/crates/buttplug_server_device_config/src/device_config_file/base.rs index 79822280c..8a635cd2f 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/base.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/base.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; +use compact_str::CompactString; use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; @@ -24,7 +25,7 @@ pub struct BaseConfigFile { version: ConfigVersion, #[getset(get = "pub")] #[serde(default, skip_serializing_if = "Option::is_none")] - protocols: Option>, + protocols: Option>, } impl Default for BaseConfigFile { diff --git a/crates/buttplug_server_device_config/src/device_config_file/device.rs b/crates/buttplug_server_device_config/src/device_config_file/device.rs index a505772ae..131f295d0 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/device.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/device.rs @@ -5,6 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use compact_str::CompactString; use getset::{CopyGetters, Getters, MutGetters}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -16,10 +17,10 @@ use super::feature::{ConfigBaseDeviceFeature, ConfigUserDeviceFeature}; #[derive(Debug, Clone, Getters, CopyGetters, Serialize, Deserialize)] pub struct ConfigBaseDeviceDefinition { #[getset(get = "pub")] - pub identifier: Option>, + pub identifier: Option>, #[getset(get = "pub")] /// Given name of the device this instance represents. - name: String, + name: CompactString, #[getset(get_copy = "pub")] id: Uuid, #[getset(get = "pub")] @@ -65,7 +66,7 @@ impl From for ServerDeviceDefinition { pub struct ConfigUserDeviceCustomization { #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] - display_name: Option, + display_name: Option, #[serde(default)] #[getset(get_copy = "pub")] allow: bool, diff --git a/crates/buttplug_server_device_config/src/device_config_file/feature.rs b/crates/buttplug_server_device_config/src/device_config_file/feature.rs index 0dcbac476..32ad88b51 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/feature.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/feature.rs @@ -5,7 +5,6 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use std::ops::RangeInclusive; use crate::{ ButtplugDeviceConfigError, @@ -17,7 +16,8 @@ use crate::{ ServerDeviceFeatureOutputHwPositionWithDurationProperties, ServerDeviceFeatureOutputValueProperties, }; -use buttplug_core::util::range_serialize::option_range_serialize; +use buttplug_core::util::range::RangeInclusive; +use compact_str::CompactString; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -39,7 +39,6 @@ impl BaseFeatureSettings { struct UserDeviceFeatureOutputValueProperties { #[serde( skip_serializing_if = "Option::is_none", - serialize_with = "option_range_serialize" )] value: Option>, #[serde(default)] @@ -72,7 +71,6 @@ impl From<&ServerDeviceFeatureOutputValueProperties> for UserDeviceFeatureOutput struct UserDeviceFeatureOutputPositionProperties { #[serde( skip_serializing_if = "Option::is_none", - serialize_with = "option_range_serialize" )] value: Option>, #[serde(default)] @@ -111,12 +109,10 @@ impl From<&ServerDeviceFeatureOutputPositionProperties> struct UserDeviceFeatureOutputHwPositionWithDurationProperties { #[serde( skip_serializing_if = "Option::is_none", - serialize_with = "option_range_serialize" )] value: Option>, #[serde( skip_serializing_if = "Option::is_none", - serialize_with = "option_range_serialize" )] duration: Option>, #[serde(default)] @@ -242,7 +238,7 @@ pub struct ConfigBaseDeviceFeature { index: u32, #[getset(get = "pub")] #[serde(default)] - description: String, + description: CompactString, #[getset(get = "pub")] #[serde(skip_serializing_if = "Option::is_none")] output: Option, diff --git a/crates/buttplug_server_device_config/src/device_config_file/user.rs b/crates/buttplug_server_device_config/src/device_config_file/user.rs index 46d556494..40210c5ec 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/user.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/user.rs @@ -5,6 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use compact_str::CompactString; use dashmap::DashMap; use getset::{Getters, MutGetters, Setters}; use serde::{Deserialize, Serialize}; @@ -30,7 +31,7 @@ pub struct UserDeviceConfigPair { #[getset(get = "pub", set = "pub", get_mut = "pub")] pub struct UserConfigDefinition { #[serde(skip_serializing_if = "Option::is_none")] - pub protocols: Option>, + pub protocols: Option>, #[serde(rename = "devices", default, skip_serializing_if = "Option::is_none")] pub user_device_configs: Option>, } diff --git a/crates/buttplug_server_device_config/src/device_config_manager.rs b/crates/buttplug_server_device_config/src/device_config_manager.rs index f900d84ba..bcba2dee2 100644 --- a/crates/buttplug_server_device_config/src/device_config_manager.rs +++ b/crates/buttplug_server_device_config/src/device_config_manager.rs @@ -6,6 +6,7 @@ // for full license information. use buttplug_core::errors::ButtplugDeviceError; +use compact_str::CompactString; use dashmap::DashMap; use getset::Getters; use std::{ @@ -25,15 +26,15 @@ use crate::{ #[derive(Default, Clone)] pub struct DeviceConfigurationManagerBuilder { - base_communication_specifiers: HashMap>, - user_communication_specifiers: DashMap>, + base_communication_specifiers: HashMap>, + user_communication_specifiers: DashMap>, base_device_definitions: HashMap, user_device_definitions: DashMap, } impl DeviceConfigurationManagerBuilder { pub fn new( - base_communication_specifiers: HashMap>, + base_communication_specifiers: HashMap>, base_device_definitions: HashMap, ) -> Self { Self { @@ -50,7 +51,7 @@ impl DeviceConfigurationManagerBuilder { ) -> &mut Self { self .base_communication_specifiers - .entry(protocol_name.to_owned()) + .entry(protocol_name.into()) .or_default() .extend(specifier.iter().cloned()); self @@ -74,7 +75,7 @@ impl DeviceConfigurationManagerBuilder { ) -> &mut Self { self .user_communication_specifiers - .entry(protocol_name.to_owned()) + .entry(protocol_name.into()) .or_default() .extend(specifier.iter().cloned()); self @@ -133,13 +134,14 @@ pub struct DeviceConfigurationManager { /// Communication specifiers from the base device config, mapped from protocol name to vector of /// specifiers. Should not change/update during a session. #[getset(get = "pub")] - base_communication_specifiers: HashMap>, + base_communication_specifiers: HashMap>, /// Device definitions from the base device config. Should not change/update during a session. + #[getset(get = "pub")] base_device_definitions: HashMap, /// Communication specifiers provided by the user, mapped from protocol name to vector of /// specifiers. Loaded at session start, may change over life of session. #[getset(get = "pub")] - user_communication_specifiers: DashMap>, + user_communication_specifiers: DashMap>, /// Device definitions from the user device config. Loaded at session start, may change over life /// of session. #[getset(get = "pub")] @@ -171,7 +173,7 @@ impl DeviceConfigurationManager { //self.protocol_map.contains_key(protocol); self .user_communication_specifiers - .entry(protocol.to_owned()) + .entry(protocol.into()) .or_default() .push(specifier.clone()); Ok(()) diff --git a/crates/buttplug_server_device_config/src/device_definitions.rs b/crates/buttplug_server_device_config/src/device_definitions.rs index 27df1832c..59d0cfd36 100644 --- a/crates/buttplug_server_device_config/src/device_definitions.rs +++ b/crates/buttplug_server_device_config/src/device_definitions.rs @@ -5,9 +5,9 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use std::collections::BTreeMap; - +use compact_str::CompactString; use getset::{CopyGetters, Getters}; +use litemap::LiteMap; use uuid::Uuid; use super::server_device_feature::ServerDeviceFeature; @@ -15,17 +15,17 @@ use super::server_device_feature::ServerDeviceFeature; pub struct ServerDeviceDefinition { #[getset(get = "pub")] /// Given name of the device this instance represents. - name: String, + name: CompactString, #[getset(get_copy = "pub")] id: Uuid, #[getset(get_copy = "pub")] base_id: Option, #[getset(get = "pub")] - protocol_variant: Option, + protocol_variant: Option, #[getset(get_copy = "pub")] message_gap_ms: Option, #[getset(get = "pub")] - display_name: Option, + display_name: Option, #[getset(get_copy = "pub")] allow: bool, #[getset(get_copy = "pub")] @@ -37,7 +37,7 @@ pub struct ServerDeviceDefinition { // Older versions of the protocol expect specific ordering, so we need to make sure storage // adheres to that since we do a lot of value iteration elsewhere. #[getset(get = "pub")] - features: BTreeMap, + features: LiteMap, } #[derive(Debug)] @@ -49,7 +49,7 @@ impl ServerDeviceDefinitionBuilder { pub fn new(name: &str, id: &Uuid) -> Self { Self { def: ServerDeviceDefinition { - name: name.to_owned(), + name: name.into(), id: *id, base_id: None, protocol_variant: None, @@ -58,7 +58,7 @@ impl ServerDeviceDefinitionBuilder { allow: false, deny: false, index: 0, - features: BTreeMap::new(), + features: LiteMap::new(), }, } } @@ -78,7 +78,7 @@ impl ServerDeviceDefinitionBuilder { }) .collect(); } else { - value.features = BTreeMap::new(); + value.features = LiteMap::new(); } ServerDeviceDefinitionBuilder { def: value } } @@ -97,13 +97,13 @@ impl ServerDeviceDefinitionBuilder { self } - pub fn display_name(&mut self, name: &Option) -> &mut Self { + pub fn display_name(&mut self, name: &Option) -> &mut Self { self.def.display_name = name.clone(); self } pub fn protocol_variant(&mut self, variant: &str) -> &mut Self { - self.def.protocol_variant = Some(variant.to_owned()); + self.def.protocol_variant = Some(variant.into()); self } diff --git a/crates/buttplug_server_device_config/src/identifiers.rs b/crates/buttplug_server_device_config/src/identifiers.rs index 1eac2e3b1..b3a5c469c 100644 --- a/crates/buttplug_server_device_config/src/identifiers.rs +++ b/crates/buttplug_server_device_config/src/identifiers.rs @@ -5,6 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use compact_str::CompactString; use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; @@ -22,20 +23,20 @@ use serde::{Deserialize, Serialize}; #[getset(get = "pub", get_mut = "pub(crate)")] pub struct UserDeviceIdentifier { /// Name of the protocol used - protocol: String, + protocol: CompactString, /// Internal identifier for the protocol used - identifier: Option, + identifier: Option, /// Address, as possibly serialized by whatever the managing library for the Device Communication Manager is. - address: String, + address: CompactString, } impl UserDeviceIdentifier { /// Creates a new instance - pub fn new(address: &str, protocol: &str, identifier: &Option) -> Self { + pub fn new(address: &str, protocol: &str, identifier: Option<&str>) -> Self { Self { - address: address.to_owned(), - protocol: protocol.to_owned(), - identifier: identifier.clone(), + address: address.into(), + protocol: protocol.into(), + identifier: identifier.map(|s| s.into()) } } } @@ -45,9 +46,9 @@ impl UserDeviceIdentifier { #[getset(get = "pub(crate)", get_mut = "pub(crate)")] pub struct BaseDeviceIdentifier { /// Name of the protocol this device uses to communicate - protocol: String, + protocol: CompactString, /// Some([identifier]) if there's an identifier, otherwise None if default - identifier: Option, + identifier: Option, } impl BaseDeviceIdentifier { @@ -55,16 +56,16 @@ impl BaseDeviceIdentifier { Self::new(protocol, &None) } - pub fn new_with_identifier(protocol: &str, attributes_identifier: String) -> Self { + pub fn new_with_identifier(protocol: &str, attributes_identifier: CompactString) -> Self { Self { - protocol: protocol.to_owned(), + protocol: protocol.into(), identifier: Some(attributes_identifier), } } - pub fn new(protocol: &str, attributes_identifier: &Option) -> Self { + pub fn new(protocol: &str, attributes_identifier: &Option) -> Self { Self { - protocol: protocol.to_owned(), + protocol: protocol.into(), identifier: attributes_identifier.clone(), } } diff --git a/crates/buttplug_server_device_config/src/server_device_feature.rs b/crates/buttplug_server_device_config/src/server_device_feature.rs index 74163ad8a..06e45500e 100644 --- a/crates/buttplug_server_device_config/src/server_device_feature.rs +++ b/crates/buttplug_server_device_config/src/server_device_feature.rs @@ -7,69 +7,34 @@ use crate::ButtplugDeviceConfigError; -use buttplug_core::message::{ - DeviceFeature, - DeviceFeatureInput, - DeviceFeatureInputBuilder, - DeviceFeatureInputProperties, - DeviceFeatureOutput, - DeviceFeatureOutputBuilder, - DeviceFeatureOutputHwPositionWithDurationProperties, - DeviceFeatureOutputValueProperties, - InputCommandType, - InputType, - OutputType, +use buttplug_core::{ + message::{ + DeviceFeature, + DeviceFeatureInput, + DeviceFeatureInputBuilder, + DeviceFeatureInputProperties, + DeviceFeatureOutput, + DeviceFeatureOutputBuilder, + DeviceFeatureOutputHwPositionWithDurationProperties, + DeviceFeatureOutputValueProperties, + InputCommandType, + InputCommandTypeFlags, + InputType, + OutputType, + }, + util::range::RangeInclusive, }; +use compact_str::CompactString; use getset::{CopyGetters, Getters, Setters}; use serde::{ + Deserialize, + Serialize, + Serializer, de::{self, Deserializer, SeqAccess, Visitor}, ser::SerializeSeq, - Deserialize, Serialize, Serializer, }; -use std::{collections::HashSet, fmt, ops::RangeInclusive}; use uuid::Uuid; -/// Serde helper module for serializing/deserializing Vec> as [[start, end], ...] -mod range_vec_serde { - use super::*; - - pub fn serialize(ranges: &Vec>, serializer: S) -> Result - where - S: Serializer, - { - let arrays: Vec<[i32; 2]> = ranges.iter().map(|r| [*r.start(), *r.end()]).collect(); - arrays.serialize(serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - struct RangeVecVisitor; - - impl<'de> Visitor<'de> for RangeVecVisitor { - type Value = Vec>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an array of two-element arrays [[start, end], ...]") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut ranges = Vec::new(); - while let Some([start, end]) = seq.next_element::<[i32; 2]>()? { - ranges.push(start..=end); - } - Ok(ranges) - } - } - - deserializer.deserialize_seq(RangeVecVisitor) - } -} - /// Holds a combination of ranges. Base range is defined in the base device config, user range is /// defined by the user later to be a sub-range of the base range. User range only stores in u32, /// ranges with negatives (i.e. rotate with direction) are considered to be symettric around 0, we @@ -92,7 +57,7 @@ impl RangeWithLimit { pub fn new(base: &RangeInclusive) -> Self { Self { base: base.clone(), - internal_base: RangeInclusive::new(0, *base.end() as u32), + internal_base: RangeInclusive::new(0, base.end() as u32), user: None, } } @@ -100,13 +65,13 @@ impl RangeWithLimit { pub fn new_with_user(base: &RangeInclusive, user: &Option>) -> Self { Self { base: base.clone(), - internal_base: RangeInclusive::new(0, *base.end() as u32), + internal_base: RangeInclusive::new(0, base.end() as u32), user: user.clone(), } } pub fn step_limit(&self) -> RangeInclusive { - if *self.base.start() < 0 { + if self.base.start() < 0 { RangeInclusive::new(-(self.step_count() as i32), self.step_count() as i32) } else { RangeInclusive::new(0, self.step_count() as i32) @@ -115,9 +80,9 @@ impl RangeWithLimit { pub fn step_count(&self) -> u32 { if let Some(user) = &self.user { - *user.end() - *user.start() + user.end() - user.start() } else { - *self.base.end() as u32 + self.base.end() as u32 } } @@ -125,14 +90,14 @@ impl RangeWithLimit { base: &RangeInclusive, user: &Option>, ) -> Result { - let truncated_base = RangeInclusive::new(0, *base.end() as u32); + let truncated_base = RangeInclusive::new(0, base.end() as u32); if let Some(user) = user { if user.is_empty() { Err(ButtplugDeviceConfigError::InvalidUserRange) - } else if *user.start() < *truncated_base.start() - || *user.end() > *truncated_base.end() - || *user.start() > *truncated_base.end() - || *user.end() < *truncated_base.start() + } else if user.start() < truncated_base.start() + || user.end() > truncated_base.end() + || user.start() > truncated_base.end() + || user.end() < truncated_base.start() { Err(ButtplugDeviceConfigError::InvalidUserRange) } else { @@ -160,8 +125,8 @@ impl Serialize for RangeWithLimit { S: Serializer, { let mut seq = serializer.serialize_seq(Some(2))?; - seq.serialize_element(self.base.start())?; - seq.serialize_element(self.base.end())?; + seq.serialize_element(&self.base.start())?; + seq.serialize_element(&self.base.end())?; seq.end() } } @@ -190,7 +155,7 @@ impl<'de> Deserialize<'de> for RangeWithLimit { let end: i32 = seq .next_element()? .ok_or_else(|| de::Error::invalid_length(1, &self))?; - Ok(RangeWithLimit::new(&(start..=end))) + Ok(RangeWithLimit::new(&RangeInclusive::new(start, end))) } } @@ -234,7 +199,7 @@ impl ServerDeviceFeatureOutputValueProperties { }; let current_value = value.unsigned_abs(); let mult = if value < 0 { -1 } else { 1 }; - if value != 0 && range.contains(&(range.start() + current_value)) { + if value != 0 && range.contains(range.start() + current_value) { Ok((range.start() + current_value) as i32 * mult) } else if value == 0 { Ok(0) @@ -291,7 +256,7 @@ impl ServerDeviceFeatureOutputPositionProperties { } else { self.value.internal_base() }; - if range.contains(&(range.start() + input)) { + if range.contains(range.start() + input) { if self.reverse_position { Ok(range.end() - input) } else { @@ -352,7 +317,7 @@ impl ServerDeviceFeatureOutputHwPositionWithDurationProperties { } else { self.value.internal_base() }; - if input > 0 && range.contains(&(range.start() + input)) { + if input > 0 && range.contains(range.start() + input) { if self.reverse_position { Ok(range.end() - input) } else { @@ -429,7 +394,10 @@ impl ServerDeviceFeatureOutput { (self.temperature.is_some(), OutputType::Temperature), (self.led.is_some(), OutputType::Led), (self.position.is_some(), OutputType::Position), - (self.hw_position_with_duration.is_some(), OutputType::HwPositionWithDuration), + ( + self.hw_position_with_duration.is_some(), + OutputType::HwPositionWithDuration, + ), (self.spray.is_some(), OutputType::Spray), ] .into_iter() @@ -556,26 +524,22 @@ impl From for DeviceFeatureOutput { #[derive(Clone, Debug, Getters, Serialize, Deserialize)] #[getset(get = "pub")] pub struct ServerDeviceFeatureInputProperties { - #[serde(with = "range_vec_serde")] value: Vec>, - command: HashSet, + command: InputCommandTypeFlags, } impl ServerDeviceFeatureInputProperties { - pub fn new( - value: &Vec>, - sensor_commands: &HashSet, - ) -> Self { + pub fn new(value: &Vec>, sensor_commands: InputCommandTypeFlags) -> Self { Self { value: value.clone(), - command: sensor_commands.clone(), + command: sensor_commands, } } } impl From<&ServerDeviceFeatureInputProperties> for DeviceFeatureInputProperties { fn from(val: &ServerDeviceFeatureInputProperties) -> Self { - DeviceFeatureInputProperties::new(&val.value, &val.command) + DeviceFeatureInputProperties::new(&val.value, val.command.clone()) } } @@ -623,7 +587,7 @@ impl ServerDeviceFeatureInput { .any(|input| { input .as_ref() - .map_or(false, |i| i.command.contains(&InputCommandType::Subscribe)) + .map_or(false, |i| i.command.contains(InputCommandType::Subscribe)) }) } } @@ -649,7 +613,7 @@ pub struct ServerDeviceFeature { index: u32, #[getset(get = "pub")] #[serde(default)] - description: String, + description: CompactString, #[getset(get_copy = "pub")] #[serde(skip)] id: Uuid, @@ -673,13 +637,14 @@ impl PartialEq for ServerDeviceFeature { } } -impl Eq for ServerDeviceFeature {} +impl Eq for ServerDeviceFeature { +} impl Default for ServerDeviceFeature { fn default() -> Self { Self { index: 0, - description: String::new(), + description: "".into(), id: Uuid::new_v4(), base_id: None, alt_protocol_index: None, @@ -701,7 +666,7 @@ impl ServerDeviceFeature { ) -> Self { Self { index, - description: description.to_owned(), + description: description.into(), id, base_id, alt_protocol_index, diff --git a/crates/buttplug_server_device_config/src/specifier.rs b/crates/buttplug_server_device_config/src/specifier.rs index 2c3f9746c..e85e1c539 100644 --- a/crates/buttplug_server_device_config/src/specifier.rs +++ b/crates/buttplug_server_device_config/src/specifier.rs @@ -6,6 +6,7 @@ // for full license information. use super::Endpoint; +use compact_str::CompactString; use getset::{Getters, MutGetters, Setters}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -85,7 +86,7 @@ impl PartialEq for BluetoothLEManufacturerData { #[getset(get = "pub", set = "pub", get_mut = "pub(crate)")] pub struct BluetoothLESpecifier { /// Set of expected advertised names for this device. - names: HashSet, + names: HashSet, /// Array of possible manufacturer data values. #[serde(default)] manufacturer_data: Vec, @@ -107,8 +108,8 @@ impl PartialEq for BluetoothLESpecifier { // Otherwise, try wildcarded names. for name in &self.names { for other_name in &other.names { - let compare_name: &String; - let mut wildcard: String; + let compare_name: &CompactString; + let mut wildcard: CompactString; if name.ends_with('*') { wildcard = name.clone(); compare_name = other_name; @@ -120,7 +121,7 @@ impl PartialEq for BluetoothLESpecifier { } // Remove asterisk from the end of the wildcard wildcard.pop(); - if compare_name.starts_with(&wildcard) { + if compare_name.starts_with(wildcard.as_str()) { return true; } } @@ -149,7 +150,7 @@ impl PartialEq for BluetoothLESpecifier { impl BluetoothLESpecifier { pub fn new( - names: HashSet, + names: HashSet, manufacturer_data: Vec, advertised_services: HashSet, services: HashMap>, @@ -169,7 +170,7 @@ impl BluetoothLESpecifier { advertised_services: &[Uuid], ) -> BluetoothLESpecifier { let mut name_set = HashSet::new(); - name_set.insert(name.to_string()); + name_set.insert(name.into()); let mut data_vec = vec![]; for (company, data) in manufacturer_data.iter() { data_vec.push(BluetoothLEManufacturerData::new( diff --git a/crates/buttplug_server_device_config/tests/test_device_config.rs b/crates/buttplug_server_device_config/tests/test_device_config.rs index 5abf83bb7..ec75f0f4a 100644 --- a/crates/buttplug_server_device_config/tests/test_device_config.rs +++ b/crates/buttplug_server_device_config/tests/test_device_config.rs @@ -65,7 +65,7 @@ fn test_tcode_device_creation() { .finish() .unwrap(); let device = dcm - .device_definition(&UserDeviceIdentifier::new("COM1", "tcode-v03", &None)) + .device_definition(&UserDeviceIdentifier::new("COM1", "tcode-v03", None)) .unwrap(); assert_eq!(device.name(), "TCode v0.3 (Single Linear Axis)"); } diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs index 9fafede67..ef286e6ce 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs @@ -65,7 +65,7 @@ fn get_scalar_index(device: &ButtplugClientDevice, index: u32) -> &u32 { || fo .rotate() .as_ref() - .is_some_and(|r| r.step_limit().start() >= &0) + .is_some_and(|r| r.step_limit().start() >= 0) }); while let Some((idx, _)) = iter.next() { if offset >= index { diff --git a/crates/intiface_engine/src/remote_server.rs b/crates/intiface_engine/src/remote_server.rs index 0c4ce2c52..1dc4212a9 100644 --- a/crates/intiface_engine/src/remote_server.rs +++ b/crates/intiface_engine/src/remote_server.rs @@ -86,7 +86,7 @@ async fn run_device_event_stream( index: da.1.device_index(), name: da.1.device_name().clone(), identifier: device_info.identifier().clone(), - display_name: device_info.display_name().clone(), + display_name: device_info.display_name().as_ref().map(|s| s.to_string()) }; if remote_event_sender.send(added_event).is_err() { error!( diff --git a/examples/src/bin/device_tester.rs b/examples/src/bin/device_tester.rs index 0480560e7..87cffa402 100644 --- a/examples/src/bin/device_tester.rs +++ b/examples/src/bin/device_tester.rs @@ -104,7 +104,7 @@ async fn device_tester() { feature.feature_index() ); } else if let Some(out) = outs.get(OutputType::Rotate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate((*out.step_limit().end()).into()))); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate((out.step_limit().end()).into()))); println!( "{} ({}) should start rotating on feature {}!", dev.name(), @@ -129,7 +129,7 @@ async fn device_tester() { ); } else if let Some(out) = outs.get(OutputType::Temperature) { cmds.push( - feature.run_output(&ClientDeviceOutputCommand::Temperature((*out.step_limit().end()).into())), + feature.run_output(&ClientDeviceOutputCommand::Temperature((out.step_limit().end()).into())), ); println!( "{} ({}) should start heating on feature {}!", @@ -259,7 +259,7 @@ async fn device_tester() { ); } OutputType::Rotate => { - if output.step_limit().start() >= &0 { + if output.step_limit().start() >= 0 { set_level_and_wait(&dev, feature, &otype, 0.25).await; set_level_and_wait(&dev, feature, &otype, 0.5).await; set_level_and_wait(&dev, feature, &otype, 0.75).await; From 61dfdc0870a3a20e5138834246d756087c078c7f Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 14:50:25 +0100 Subject: [PATCH 04/17] Use Arc for ServerDeviceDefinition in DeviceConfigurationManager::base_device_definitions to avoid cloning the definition for each identifier. --- .../src/device_config_file/mod.rs | 24 +++++++++++++++---- .../src/device_config_manager.rs | 10 ++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/buttplug_server_device_config/src/device_config_file/mod.rs b/crates/buttplug_server_device_config/src/device_config_file/mod.rs index 860598105..af55f8d5a 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/mod.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/mod.rs @@ -29,7 +29,7 @@ use buttplug_core::{ use dashmap::DashMap; use getset::CopyGetters; use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use std::{collections::HashMap, fmt::Display}; +use std::{collections::HashMap, fmt::Display, sync::Arc}; pub static DEVICE_CONFIGURATION_JSON: &str = include_str!("../../build-config/buttplug-device-config-v4.json"); @@ -110,7 +110,22 @@ fn load_main_config( let protocols = main_config.protocols_mut().take().unwrap_or_default(); let mut base_communication_specifiers = HashMap::with_capacity(protocols.len()); - let mut base_device_definitions = HashMap::new(); + let base_device_definitions_capacity = protocols + .iter() + .map(|(_, def)| { + def + .configurations() + .iter() + .map(|config| { + config + .identifier() + .as_ref() + .map_or(0, |idents| idents.len()) + }) + .sum::() + }) + .sum::(); + let mut base_device_definitions = HashMap::with_capacity(base_device_definitions_capacity); for (protocol_name, protocol_def) in protocols { let ProtocolDefinition { @@ -125,11 +140,12 @@ fn load_main_config( for mut config in configurations { let identifier = config.identifier.take(); - let config: ServerDeviceDefinition = config.with_defaults(defaults.as_ref()).into(); + let definition: Arc = + Arc::new(config.with_defaults(defaults.as_ref()).into()); if let Some(idents) = identifier { for config_ident in idents { let ident = BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident); - base_device_definitions.insert(ident, config.clone()); + base_device_definitions.insert(ident, definition.clone()); } } } diff --git a/crates/buttplug_server_device_config/src/device_config_manager.rs b/crates/buttplug_server_device_config/src/device_config_manager.rs index bcba2dee2..c48970240 100644 --- a/crates/buttplug_server_device_config/src/device_config_manager.rs +++ b/crates/buttplug_server_device_config/src/device_config_manager.rs @@ -11,7 +11,7 @@ use dashmap::DashMap; use getset::Getters; use std::{ collections::HashMap, - fmt::{self, Debug}, + fmt::{self, Debug}, sync::Arc }; use uuid::Uuid; @@ -28,14 +28,14 @@ use crate::{ pub struct DeviceConfigurationManagerBuilder { base_communication_specifiers: HashMap>, user_communication_specifiers: DashMap>, - base_device_definitions: HashMap, + base_device_definitions: HashMap>, user_device_definitions: DashMap, } impl DeviceConfigurationManagerBuilder { pub fn new( base_communication_specifiers: HashMap>, - base_device_definitions: HashMap, + base_device_definitions: HashMap>, ) -> Self { Self { base_communication_specifiers, @@ -64,7 +64,7 @@ impl DeviceConfigurationManagerBuilder { ) -> &mut Self { self .base_device_definitions - .insert(identifier.clone(), features.clone()); + .insert(identifier.clone(), Arc::new(features.clone())); self } @@ -137,7 +137,7 @@ pub struct DeviceConfigurationManager { base_communication_specifiers: HashMap>, /// Device definitions from the base device config. Should not change/update during a session. #[getset(get = "pub")] - base_device_definitions: HashMap, + base_device_definitions: HashMap>, /// Communication specifiers provided by the user, mapped from protocol name to vector of /// specifiers. Loaded at session start, may change over life of session. #[getset(get = "pub")] From 946fedea11391928683f098f20018964ce577886 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 15:45:24 +0100 Subject: [PATCH 05/17] Decrease cloning when using ServerDeviceDefinitionBuilder --- .../src/device_config_file/device.rs | 59 ++++++++++-------- .../src/device_config_file/feature.rs | 12 ++-- .../src/device_config_manager.rs | 13 ++-- .../src/device_definitions.rs | 62 +++++++++++++------ .../src/server_device_feature.rs | 12 ++-- 5 files changed, 98 insertions(+), 60 deletions(-) diff --git a/crates/buttplug_server_device_config/src/device_config_file/device.rs b/crates/buttplug_server_device_config/src/device_config_file/device.rs index 131f295d0..bcfa3bc26 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/device.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/device.rs @@ -24,7 +24,7 @@ pub struct ConfigBaseDeviceDefinition { #[getset(get_copy = "pub")] id: Uuid, #[getset(get = "pub")] - protocol_variant: Option, + protocol_variant: Option, #[getset(get_copy = "pub")] message_gap_ms: Option, #[getset(get = "pub")] @@ -37,28 +37,33 @@ impl ConfigBaseDeviceDefinition { identifier: self.identifier, name: self.name, id: self.id, - protocol_variant: self.protocol_variant.or_else(|| defaults.and_then(|d| d.protocol_variant.clone())), - message_gap_ms: self.message_gap_ms.or_else(|| defaults.and_then(|d| d.message_gap_ms)), - features: self.features.or_else(|| defaults.and_then(|d| d.features.clone())), + protocol_variant: self + .protocol_variant + .or_else(|| defaults.and_then(|d| d.protocol_variant.clone())), + message_gap_ms: self + .message_gap_ms + .or_else(|| defaults.and_then(|d| d.message_gap_ms)), + features: self + .features + .or_else(|| defaults.and_then(|d| d.features.clone())), } } } impl From for ServerDeviceDefinition { fn from(val: ConfigBaseDeviceDefinition) -> Self { - let mut builder = ServerDeviceDefinitionBuilder::new(&val.name, &val.id); - if let Some(variant) = val.protocol_variant { - builder.protocol_variant(&variant); - } - if let Some(gap) = val.message_gap_ms { - builder.message_gap_ms(Some(gap)); - } - if let Some(features) = val.features { - for feature in features { - builder.add_feature(&feature.into()); - } - } - builder.finish() + ServerDeviceDefinitionBuilder::new_with_features( + val.name, + val.id, + val + .features + .unwrap_or_default() + .into_iter() + .map(Into::into) + ) + .protocol_variant(val.protocol_variant) + .message_gap_ms(val.message_gap_ms) + .finish() } } @@ -112,22 +117,24 @@ impl ConfigUserDeviceDefinition { &self, base: &ServerDeviceDefinition, ) -> Result { - let mut builder = ServerDeviceDefinitionBuilder::from_base(base, self.id, false); - builder.display_name(&self.user_config.display_name); - builder.message_gap_ms(self.user_config.message_gap_ms); - self.user_config.allow.then(|| builder.allow(true)); - self.user_config.deny.then(|| builder.deny(true)); - builder.index(self.user_config.index); if self.features().len() != base.features().len() { return Err(ButtplugDeviceConfigError::UserFeatureMismatch); } + + let mut builder = ServerDeviceDefinitionBuilder::from_base(base, self.id, false) + .display_name(self.user_config.display_name.clone()) + .message_gap_ms(self.user_config.message_gap_ms) + .allow(self.user_config.allow) + .deny(self.user_config.deny) + .index(self.user_config.index); + for feature in self.features() { if let Some(base_feature) = base .features() .values() .find(|x| x.id() == feature.base_id()) { - builder.add_feature(&feature.with_base_feature(base_feature)?); + builder = builder.add_feature(feature.with_base_feature(base_feature)?); } else { return Err(ButtplugDeviceConfigError::UserFeatureMismatch); } @@ -142,7 +149,9 @@ impl TryFrom<&ServerDeviceDefinition> for ConfigUserDeviceDefinition { fn try_from(value: &ServerDeviceDefinition) -> Result { Ok(Self { id: value.id(), - base_id: value.base_id().ok_or(ButtplugDeviceConfigError::MissingBaseId)?, + base_id: value + .base_id() + .ok_or(ButtplugDeviceConfigError::MissingBaseId)?, features: value .features() .values() diff --git a/crates/buttplug_server_device_config/src/device_config_file/feature.rs b/crates/buttplug_server_device_config/src/device_config_file/feature.rs index 32ad88b51..1753c91fa 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/feature.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/feature.rs @@ -256,12 +256,12 @@ impl From for ServerDeviceFeature { fn from(val: ConfigBaseDeviceFeature) -> Self { ServerDeviceFeature::new( val.index, - &val.description, + val.description, val.id, None, val.feature_settings.alt_protocol_index, - &val.output, - &val.input, + val.output, + val.input, ) } } @@ -293,12 +293,12 @@ impl ConfigUserDeviceFeature { }; Ok(ServerDeviceFeature::new( base_feature.index(), - base_feature.description(), + base_feature.description().clone(), self.id, Some(self.base_id), base_feature.alt_protocol_index(), - &output, - base_feature.input(), + output, + base_feature.input().clone(), )) } } diff --git a/crates/buttplug_server_device_config/src/device_config_manager.rs b/crates/buttplug_server_device_config/src/device_config_manager.rs index c48970240..ca8086e8f 100644 --- a/crates/buttplug_server_device_config/src/device_config_manager.rs +++ b/crates/buttplug_server_device_config/src/device_config_manager.rs @@ -11,7 +11,8 @@ use dashmap::DashMap; use getset::Getters; use std::{ collections::HashMap, - fmt::{self, Debug}, sync::Arc + fmt::{self, Debug}, + sync::Arc, }; use uuid::Uuid; @@ -285,8 +286,9 @@ impl DeviceConfigurationManager { "Protocol + Identifier device config found for {:?}, creating new user device from configuration", identifier ); - let mut builder = ServerDeviceDefinitionBuilder::from_base(definition, Uuid::new_v4(), true); - builder.index(self.device_index(identifier)).finish() + ServerDeviceDefinitionBuilder::from_base(definition, Uuid::new_v4(), true) + .index(self.device_index(identifier)) + .finish() } else if let Some(definition) = self .base_device_definitions .get(&BaseDeviceIdentifier::new(identifier.protocol(), &None)) @@ -295,8 +297,9 @@ impl DeviceConfigurationManager { "Protocol device config found for {:?}, creating new user device from protocol defaults", identifier ); - let mut builder = ServerDeviceDefinitionBuilder::from_base(definition, Uuid::new_v4(), true); - builder.index(self.device_index(identifier)).finish() + ServerDeviceDefinitionBuilder::from_base(definition, Uuid::new_v4(), true) + .index(self.device_index(identifier)) + .finish() } else { return None; }; diff --git a/crates/buttplug_server_device_config/src/device_definitions.rs b/crates/buttplug_server_device_config/src/device_definitions.rs index 59d0cfd36..f657ad955 100644 --- a/crates/buttplug_server_device_config/src/device_definitions.rs +++ b/crates/buttplug_server_device_config/src/device_definitions.rs @@ -46,11 +46,11 @@ pub struct ServerDeviceDefinitionBuilder { } impl ServerDeviceDefinitionBuilder { - pub fn new(name: &str, id: &Uuid) -> Self { + pub fn new(name: CompactString, id: Uuid) -> Self { Self { def: ServerDeviceDefinition { - name: name.into(), - id: *id, + name, + id, base_id: None, protocol_variant: None, message_gap_ms: None, @@ -63,6 +63,32 @@ impl ServerDeviceDefinitionBuilder { } } + pub fn new_with_features( + name: CompactString, + id: Uuid, + features_iter: impl Iterator, + ) -> Self { + // LiteMap's .collect() doesn't take capacity into account + let mut features = LiteMap::with_capacity(features_iter.size_hint().0); + for feature in features_iter { + features.insert(feature.index(), feature); + } + Self { + def: ServerDeviceDefinition { + name, + id, + base_id: None, + protocol_variant: None, + message_gap_ms: None, + display_name: None, + allow: false, + deny: false, + index: 0, + features, + }, + } + } + // Used to create new user definitions from a base definition. pub fn from_base(value: &ServerDeviceDefinition, id: Uuid, with_features: bool) -> Self { let mut value = value.clone(); @@ -87,64 +113,64 @@ impl ServerDeviceDefinitionBuilder { ServerDeviceDefinitionBuilder { def: value.clone() } } - pub fn id(&mut self, id: Uuid) -> &mut Self { + pub fn id(mut self, id: Uuid) -> Self { self.def.id = id; self } - pub fn base_id(&mut self, id: Uuid) -> &mut Self { + pub fn base_id(mut self, id: Uuid) -> Self { self.def.base_id = Some(id); self } - pub fn display_name(&mut self, name: &Option) -> &mut Self { + pub fn display_name(mut self, name: Option) -> Self { self.def.display_name = name.clone(); self } - pub fn protocol_variant(&mut self, variant: &str) -> &mut Self { - self.def.protocol_variant = Some(variant.into()); + pub fn protocol_variant(mut self, variant: Option) -> Self { + self.def.protocol_variant = variant; self } - pub fn message_gap_ms(&mut self, gap: Option) -> &mut Self { + pub fn message_gap_ms(mut self, gap: Option) -> Self { self.def.message_gap_ms = gap; self } - pub fn allow(&mut self, allow: bool) -> &mut Self { + pub fn allow(mut self, allow: bool) -> Self { self.def.allow = allow; self } - pub fn deny(&mut self, deny: bool) -> &mut Self { + pub fn deny(mut self, deny: bool) -> Self { self.def.deny = deny; self } - pub fn index(&mut self, index: u32) -> &mut Self { + pub fn index(mut self, index: u32) -> Self { self.def.index = index; self } - pub fn add_feature(&mut self, feature: &ServerDeviceFeature) -> &mut Self { - self.def.features.insert(feature.index(), feature.clone()); + pub fn add_feature(mut self, feature: ServerDeviceFeature) -> Self { + self.def.features.insert(feature.index(), feature); self } - pub fn replace_feature(&mut self, feature: &ServerDeviceFeature) -> &mut Self { + pub fn replace_feature(mut self, feature: ServerDeviceFeature) -> Self { if let Some((_, f)) = self .def .features .iter_mut() .find(|(_, x)| x.id() == feature.id()) { - *f = feature.clone(); + *f = feature; } self } - pub fn finish(&self) -> ServerDeviceDefinition { - self.def.clone() + pub fn finish(self) -> ServerDeviceDefinition { + self.def } } diff --git a/crates/buttplug_server_device_config/src/server_device_feature.rs b/crates/buttplug_server_device_config/src/server_device_feature.rs index 06e45500e..e50dc9333 100644 --- a/crates/buttplug_server_device_config/src/server_device_feature.rs +++ b/crates/buttplug_server_device_config/src/server_device_feature.rs @@ -657,21 +657,21 @@ impl Default for ServerDeviceFeature { impl ServerDeviceFeature { pub fn new( index: u32, - description: &str, + description: CompactString, id: Uuid, base_id: Option, alt_protocol_index: Option, - output: &Option, - input: &Option, + output: Option, + input: Option, ) -> Self { Self { index, - description: description.into(), + description, id, base_id, alt_protocol_index, - output: output.clone(), - input: input.clone(), + output, + input, } } From 4950a399ccedff6c222ca6767310bed3f4aff486 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 21:48:25 +0100 Subject: [PATCH 06/17] Fix loading of default device definitions in config file --- .../src/device_config_file/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/buttplug_server_device_config/src/device_config_file/mod.rs b/crates/buttplug_server_device_config/src/device_config_file/mod.rs index af55f8d5a..6eb4b8386 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/mod.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/mod.rs @@ -134,10 +134,6 @@ fn load_main_config( configurations, } = protocol_def; - if let Some(specifiers) = communication { - base_communication_specifiers.insert(protocol_name.clone(), specifiers); - } - for mut config in configurations { let identifier = config.identifier.take(); let definition: Arc = @@ -149,6 +145,15 @@ fn load_main_config( } } } + + if let Some(defaults) = defaults { + let ident = BaseDeviceIdentifier::new_default(&protocol_name); + base_device_definitions.insert(ident, Arc::new(defaults.into())); + } + + if let Some(specifiers) = communication { + base_communication_specifiers.insert(protocol_name, specifiers); + } } Ok(DeviceConfigurationManagerBuilder::new( From f6f8ae0cad5d8f1694165b46fbc6288f3dd4392d Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 21:49:03 +0100 Subject: [PATCH 07/17] Add Serialize and Deserialize traits to ServerDeviceDefinition --- crates/buttplug_server_device_config/src/device_definitions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/buttplug_server_device_config/src/device_definitions.rs b/crates/buttplug_server_device_config/src/device_definitions.rs index f657ad955..c7a8848e4 100644 --- a/crates/buttplug_server_device_config/src/device_definitions.rs +++ b/crates/buttplug_server_device_config/src/device_definitions.rs @@ -8,10 +8,11 @@ use compact_str::CompactString; use getset::{CopyGetters, Getters}; use litemap::LiteMap; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::server_device_feature::ServerDeviceFeature; -#[derive(Debug, Clone, Getters, CopyGetters)] +#[derive(Debug, Clone, Getters, CopyGetters, Serialize, Deserialize)] pub struct ServerDeviceDefinition { #[getset(get = "pub")] /// Given name of the device this instance represents. From c4a6f8a03c6a588ec9d449f9f0bcfeb98668b758 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 22:45:58 +0100 Subject: [PATCH 08/17] Remove regex dependency from VibCrafter and Lovense protocol implementations. While the regex crate is normally fulled in anyways by jsonschema, it is possible to use buttplug_server without message serialization (in process connector) and also without load_protocol_configs. In those cases, the regex crate is not needed and removing it from these protocol implementations saves about 100KB of binary size on embedded targets. The regexes in question were also very simple and easily replaced with basic string parsing. If regex is needed in the future for more complex parsing, it should ideally be added as regex_lite. --- .../src/device/protocol_impl/lovense/mod.rs | 16 +++++--- .../src/device/protocol_impl/vibcrafter.rs | 41 +++++++------------ 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs index 316a4ab12..e0b9e5763 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs @@ -35,7 +35,6 @@ use buttplug_server_device_config::{ UserDeviceIdentifier, }; use futures::{FutureExt, future::BoxFuture}; -use regex::Regex; use std::{sync::Arc, time::Duration}; use tokio::select; use uuid::{Uuid, uuid}; @@ -137,11 +136,16 @@ impl ProtocolIdentifier for LovenseIdentifier { count += 1; if count > LOVENSE_COMMAND_RETRY { warn!("Lovense Device timed out while getting DeviceType info. ({} retries)", LOVENSE_COMMAND_RETRY); - let re = Regex::new(r"LVS-([A-Z]+)\d+").expect("Static regex shouldn't fail"); - if let Some(caps) = re.captures(hardware.name()) { - info!("Lovense Device identified by BLE name"); - return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", Some(&caps[1])), Box::new(LovenseInitializer::new(caps[1].to_string())))); - }; + if let Some(pos) = hardware.name().find("LVS-") { + let model = hardware.name()[pos + 4..].to_string(); + if !model.is_empty() { + info!("Lovense Device identified by BLE name: {}", model); + return Ok(( + UserDeviceIdentifier::new(hardware.address(), "lovense", Some(&model)), + Box::new(LovenseInitializer::new(model)), + )); + } + } return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", None), Box::new(LovenseInitializer::new("".to_string())))); } } diff --git a/crates/buttplug_server/src/device/protocol_impl/vibcrafter.rs b/crates/buttplug_server/src/device/protocol_impl/vibcrafter.rs index 2d797d2f3..ae18a8ceb 100644 --- a/crates/buttplug_server/src/device/protocol_impl/vibcrafter.rs +++ b/crates/buttplug_server/src/device/protocol_impl/vibcrafter.rs @@ -33,7 +33,6 @@ use uuid::{Uuid, uuid}; use rand::distributions::Alphanumeric; use rand::{Rng, thread_rng}; -use regex::Regex; use sha2::{Digest, Sha256}; type Aes128EcbEnc = ecb::Encryptor; @@ -101,31 +100,21 @@ impl ProtocolInitializer for VibCrafterInitializer { debug!("VibCrafter authenticated!"); return Ok(Arc::new(VibCrafter::default())); } - let challenge = Regex::new(r"^[a-zA-Z0-9]{4}:([a-zA-Z0-9]+);$") - .expect("This is static and should always compile"); - if let Some(parts) = challenge.captures(decoded.as_str()) { - debug!("VibCrafter challenge {:?}", parts); - if let Some(to_hash) = parts.get(1) { - debug!("VibCrafter to hash {:?}", to_hash); - let mut sha256 = Sha256::new(); - sha256.update(to_hash.as_str().as_bytes()); - let result = &sha256.finalize(); - - let auth_msg = format!("Auth:{:02x}{:02x};", result[0], result[1]); - hardware - .write_value(&HardwareWriteCmd::new( - &[VIBCRAFTER_PROTOCOL_UUID], - Endpoint::Tx, - encrypt(auth_msg), - false, - )) - .await?; - } else { - return Err(ButtplugDeviceError::ProtocolSpecificError( - "VibCrafter".to_owned(), - "VibCrafter didn't provide a valid security handshake".to_owned(), - )); - } + if decoded.len() > 5 && decoded.ends_with(';') && decoded.as_bytes()[4] == b':' { + let to_hash = &decoded[5..decoded.len() - 1]; + let mut sha256 = Sha256::new(); + sha256.update(to_hash.as_bytes()); + let result = &sha256.finalize(); + + let auth_msg = format!("Auth:{:02x}{:02x};", result[0], result[1]); + hardware + .write_value(&HardwareWriteCmd::new( + &[VIBCRAFTER_PROTOCOL_UUID], + Endpoint::Tx, + encrypt(auth_msg), + false, + )) + .await?; } else { return Err(ButtplugDeviceError::ProtocolSpecificError( "VibCrafter".to_owned(), From c69790449b63f05e431bff6c7ad1ee702799b47f Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Wed, 4 Mar 2026 22:47:14 +0100 Subject: [PATCH 09/17] Add serde support to litemap Otherwise ServerDeviceDefinitions can't be serialized --- crates/buttplug_server_device_config/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/buttplug_server_device_config/Cargo.toml b/crates/buttplug_server_device_config/Cargo.toml index f23d32feb..3543b6489 100644 --- a/crates/buttplug_server_device_config/Cargo.toml +++ b/crates/buttplug_server_device_config/Cargo.toml @@ -34,7 +34,7 @@ jsonschema = { version = "0.38.1", default-features = false } uuid = { version = "1.20.0", features = ["serde", "v4"] } strum_macros = "0.27.2" strum = "0.27.2" -litemap = "0.8.1" +litemap = { version = "0.8.1", features = ["serde"] } compact_str = { version = "0.9.0", features = ["serde"] } enumflags2 = "0.7.12" From 80401c2ac40dc50a9d8735820a1aefa90311cf5c Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 7 Mar 2026 15:48:14 +0100 Subject: [PATCH 10/17] Cleanup and small changes that i need for easier integration --- crates/buttplug_client/src/device/feature.rs | 2 +- .../v3/client_device_message_attributes.rs | 2 +- .../src/message/v4/spec_enums.rs | 17 ++++++++++++++--- .../src/identifiers.rs | 2 +- .../src/server_device_feature.rs | 2 -- .../src/websocket_client.rs | 2 +- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/buttplug_client/src/device/feature.rs b/crates/buttplug_client/src/device/feature.rs index 71d7537d1..0b9047f21 100644 --- a/crates/buttplug_client/src/device/feature.rs +++ b/crates/buttplug_client/src/device/feature.rs @@ -37,7 +37,7 @@ use crate::{ device::ClientDeviceCommandValue, }; -#[derive(Getters, CopyGetters, Clone)] +#[derive(Getters, CopyGetters, Clone, Debug)] pub struct ClientDeviceFeature { #[getset(get_copy = "pub")] device_index: u32, diff --git a/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs b/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs index 0b6fa4f5a..ed55b645e 100644 --- a/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs +++ b/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs @@ -20,7 +20,7 @@ use buttplug_core::{ util::range::RangeInclusive, }; use getset::{Getters, MutGetters, Setters}; -use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq}; +use serde::{Deserialize, Serialize}; // This will look almost exactly like ServerDeviceMessageAttributes. However, it will only contain // information we want the client to know, i.e. step counts versus specific step ranges. This is diff --git a/crates/buttplug_server/src/message/v4/spec_enums.rs b/crates/buttplug_server/src/message/v4/spec_enums.rs index 15a294bcc..74d489af9 100644 --- a/crates/buttplug_server/src/message/v4/spec_enums.rs +++ b/crates/buttplug_server/src/message/v4/spec_enums.rs @@ -5,7 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use std::{collections::BTreeMap, fmt::Debug, u32}; +use std::{fmt::Debug, u32}; use crate::message::{ ButtplugClientMessageVariant, @@ -23,7 +23,15 @@ use crate::message::{ use buttplug_core::{ errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, message::{ - ButtplugClientMessageV4, ButtplugDeviceMessage, ButtplugMessage, PingV0, RequestDeviceListV0, RequestServerInfoV4, StartScanningV0, StopCmdV4, StopScanningV0 + ButtplugClientMessageV4, + ButtplugDeviceMessage, + ButtplugMessage, + PingV0, + RequestDeviceListV0, + RequestServerInfoV4, + StartScanningV0, + StopCmdV4, + StopScanningV0, }, }; use litemap::LiteMap; @@ -94,7 +102,10 @@ impl TryFromClientMessage for ButtplugCheckedClientMess } // Messages that need device index checking ButtplugClientMessageV4::StopCmd(m) => { - if m.device_index().map_or(true,|x| feature_map.get(&x).is_some()) { + if m + .device_index() + .map_or(true, |x| feature_map.get(&x).is_some()) + { Ok(ButtplugCheckedClientMessageV4::StopCmd(m)) } else { Err(ButtplugError::from( diff --git a/crates/buttplug_server_device_config/src/identifiers.rs b/crates/buttplug_server_device_config/src/identifiers.rs index b3a5c469c..6a24cdb24 100644 --- a/crates/buttplug_server_device_config/src/identifiers.rs +++ b/crates/buttplug_server_device_config/src/identifiers.rs @@ -43,7 +43,7 @@ impl UserDeviceIdentifier { /// Set of information used for matching devices to their features and related communication protocol. #[derive(Debug, Clone, PartialEq, Eq, Hash, Getters, MutGetters, Serialize, Deserialize)] -#[getset(get = "pub(crate)", get_mut = "pub(crate)")] +#[getset(get = "pub", get_mut = "pub(crate)")] pub struct BaseDeviceIdentifier { /// Name of the protocol this device uses to communicate protocol: CompactString, diff --git a/crates/buttplug_server_device_config/src/server_device_feature.rs b/crates/buttplug_server_device_config/src/server_device_feature.rs index e50dc9333..62be47205 100644 --- a/crates/buttplug_server_device_config/src/server_device_feature.rs +++ b/crates/buttplug_server_device_config/src/server_device_feature.rs @@ -609,13 +609,11 @@ impl From for DeviceFeatureInput { #[serde(default)] pub struct ServerDeviceFeature { #[getset(get_copy = "pub")] - #[serde(skip)] index: u32, #[getset(get = "pub")] #[serde(default)] description: CompactString, #[getset(get_copy = "pub")] - #[serde(skip)] id: Uuid, #[getset(get_copy = "pub")] #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs b/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs index 2867ea7d5..e5b0940ad 100644 --- a/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs +++ b/crates/buttplug_transport_websocket_tungstenite/src/websocket_client.rs @@ -40,7 +40,7 @@ use tokio_tungstenite::{ connect_async_tls_with_config, tungstenite::protocol::Message, }; -use tracing::{Instrument, info_span}; +use tracing::info_span; use url::Url; pub fn get_rustls_config_dangerous() -> ClientConfig { From 9412874b748a15c3eef93442f0ed77abd16dbd20 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Tue, 10 Mar 2026 18:56:21 +0100 Subject: [PATCH 11/17] Decrease binary size by about 100kB --- .../src/device/device_handle.rs | 94 +++++++- crates/buttplug_server/src/device/protocol.rs | 204 +++++++----------- .../protocol_impl/lovense_connect_service.rs | 10 +- 3 files changed, 169 insertions(+), 139 deletions(-) diff --git a/crates/buttplug_server/src/device/device_handle.rs b/crates/buttplug_server/src/device/device_handle.rs index bb19b7408..20cd0be87 100644 --- a/crates/buttplug_server/src/device/device_handle.rs +++ b/crates/buttplug_server/src/device/device_handle.rs @@ -23,6 +23,7 @@ use buttplug_core::{ DeviceMessageInfoV4, InputCommandType, InputType, + OutputCommand, OutputType, OutputValue, StopCmdV4, @@ -159,7 +160,11 @@ impl DeviceHandle { DeviceMessageInfoV4::new( index, &self.name(), - &self.definition.display_name().as_ref().map(|n| n.to_string()), + &self + .definition + .display_name() + .as_ref() + .map(|n| n.to_string()), 100, &self .definition @@ -234,10 +239,14 @@ impl DeviceHandle { }); let identifier = self.identifier.clone(); - let handler_mapped_stream = self.handler.event_stream().map(move |incoming_message| { - let id = identifier.clone(); - DeviceEvent::Notification(id, incoming_message) - }); + let handler_mapped_stream = self + .handler + .clone() + .event_stream() + .map(move |incoming_message| { + let id = identifier.clone(); + DeviceEvent::Notification(id, incoming_message) + }); hardware_stream.merge(handler_mapped_stream) } @@ -253,7 +262,80 @@ impl DeviceHandle { self .last_output_command .insert(msg.feature_id(), msg.clone()); - self.handle_generic_command_result(self.handler.handle_output_cmd(msg)) + let result = self + .handler + .handle_output_cmd(msg) + .unwrap_or_else(|| self.handle_outputcmd_v4_default(msg)); + self.handle_generic_command_result(result) + } + + fn handle_outputcmd_v4_default( + &self, + cmd: &CheckedOutputCmdV4, + ) -> Result, ButtplugDeviceError> { + let output_command = cmd.output_command(); + match output_command { + OutputCommand::Constrict(x) => self.handler.handle_output_constrict_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::Spray(x) => self.handler.handle_output_spray_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::Oscillate(x) => self.handler.handle_output_oscillate_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::Rotate(x) => { + self + .handler + .handle_output_rotate_cmd(cmd.feature_index(), cmd.feature_id(), x.value()) + } + OutputCommand::Vibrate(x) => self.handler.handle_output_vibrate_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::Position(x) => self.handler.handle_output_position_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::Temperature(x) => { + self + .handler + .handle_output_temperature_cmd(cmd.feature_index(), cmd.feature_id(), x.value()) + } + OutputCommand::Led(x) => self.handler.handle_output_led_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value() + .try_into() + .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, + ), + OutputCommand::HwPositionWithDuration(x) => { + self.handler.handle_hw_position_with_duration_cmd( + cmd.feature_index(), + cmd.feature_id(), + x.value(), + x.duration(), + ) + } + } } fn handle_hardware_commands(&self, commands: Vec) -> ButtplugServerResultFuture { diff --git a/crates/buttplug_server/src/device/protocol.rs b/crates/buttplug_server/src/device/protocol.rs index c1adb42c3..9939885fd 100644 --- a/crates/buttplug_server/src/device/protocol.rs +++ b/crates/buttplug_server/src/device/protocol.rs @@ -200,22 +200,7 @@ pub trait ProtocolHandler: Sync + Send { &self, message: &ButtplugDeviceCommandMessageUnionV4, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented(print_type_of(&message)) - } - - // Allow here since this changes between debug/release - #[allow(unused_variables)] - fn command_unimplemented( - &self, - command: &str, - ) -> Result, ButtplugDeviceError> { - #[cfg(debug_assertions)] - unimplemented!("Command not implemented for this protocol"); - #[cfg(not(debug_assertions))] - Err(ButtplugDeviceError::UnhandledCommand(format!( - "Command not implemented for this protocol: {}", - command - ))) + command_unimplemented(print_type_of(&message)) } // The default scalar handler assumes that most devices require discrete commands per feature. If @@ -223,65 +208,9 @@ pub trait ProtocolHandler: Sync + Send { // actuators, they should just implement their own version of this method. fn handle_output_cmd( &self, - cmd: &CheckedOutputCmdV4, - ) -> Result, ButtplugDeviceError> { - let output_command = cmd.output_command(); - match output_command { - OutputCommand::Constrict(x) => self.handle_output_constrict_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::Spray(x) => self.handle_output_spray_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::Oscillate(x) => self.handle_output_oscillate_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::Rotate(x) => { - self.handle_output_rotate_cmd(cmd.feature_index(), cmd.feature_id(), x.value()) - } - OutputCommand::Vibrate(x) => self.handle_output_vibrate_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::Position(x) => self.handle_output_position_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::Temperature(x) => { - self.handle_output_temperature_cmd(cmd.feature_index(), cmd.feature_id(), x.value()) - } - OutputCommand::Led(x) => self.handle_output_led_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value() - .try_into() - .map_err(|_| ButtplugDeviceError::DeviceCommandSignError)?, - ), - OutputCommand::HwPositionWithDuration(x) => self.handle_hw_position_with_duration_cmd( - cmd.feature_index(), - cmd.feature_id(), - x.value(), - x.duration(), - ), - } + _cmd: &CheckedOutputCmdV4, + ) -> Option, ButtplugDeviceError>> { + None } fn handle_output_vibrate_cmd( @@ -290,7 +219,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _speed: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Vibrate Actuator)") + command_unimplemented("OutputCmd (Vibrate Actuator)") } fn handle_output_rotate_cmd( @@ -299,7 +228,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _speed: i32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Rotate Actuator)") + command_unimplemented("OutputCmd (Rotate Actuator)") } fn handle_output_oscillate_cmd( @@ -308,7 +237,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _speed: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Oscillate Actuator)") + command_unimplemented("OutputCmd (Oscillate Actuator)") } fn handle_output_spray_cmd( @@ -317,7 +246,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _level: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Spray Actuator)") + command_unimplemented("OutputCmd (Spray Actuator)") } fn handle_output_constrict_cmd( @@ -326,7 +255,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _level: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Constrict Actuator)") + command_unimplemented("OutputCmd (Constrict Actuator)") } fn handle_output_temperature_cmd( @@ -335,7 +264,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _level: i32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Temperature Actuator)") + command_unimplemented("OutputCmd (Temperature Actuator)") } fn handle_output_led_cmd( @@ -344,7 +273,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _level: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Led Actuator)") + command_unimplemented("OutputCmd (Led Actuator)") } fn handle_output_position_cmd( @@ -353,7 +282,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _position: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Position Actuator)") + command_unimplemented("OutputCmd (Position Actuator)") } fn handle_hw_position_with_duration_cmd( @@ -363,7 +292,7 @@ pub trait ProtocolHandler: Sync + Send { _position: u32, _duration: u32, ) -> Result, ButtplugDeviceError> { - self.command_unimplemented("OutputCmd (Position w/ Duration Actuator)") + command_unimplemented("OutputCmd (Position w/ Duration Actuator)") } fn handle_input_subscribe_cmd( @@ -374,10 +303,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _sensor_type: InputType, ) -> BoxFuture<'_, Result<(), ButtplugDeviceError>> { - future::ready(Err(ButtplugDeviceError::UnhandledCommand( - "Command not implemented for this protocol: InputCmd (Subscribe)".to_string(), - ))) - .boxed() + command_unimplemented_future("InputCmd (Subscribe)") } fn handle_input_unsubscribe_cmd( @@ -387,10 +313,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_id: Uuid, _sensor_type: InputType, ) -> BoxFuture<'_, Result<(), ButtplugDeviceError>> { - future::ready(Err(ButtplugDeviceError::UnhandledCommand( - "Command not implemented for this protocol: InputCmd (Unsubscribe)".to_string(), - ))) - .boxed() + command_unimplemented_future("InputCmd (Unsubscribe)") } fn handle_input_read_cmd( @@ -401,14 +324,10 @@ pub trait ProtocolHandler: Sync + Send { feature_id: Uuid, sensor_type: InputType, ) -> BoxFuture<'_, Result> { - match sensor_type { - InputType::Battery => { - self.handle_battery_level_cmd(device_index, device, feature_index, feature_id) - } - _ => future::ready(Err(ButtplugDeviceError::UnhandledCommand( - "Command not implemented for this protocol: InputCmd (Read)".to_string(), - ))) - .boxed(), + if sensor_type == InputType::Battery { + self.handle_battery_level_cmd(device_index, device, feature_index, feature_id) + } else { + command_unimplemented_future("InputCmd (Read)") } } @@ -428,30 +347,7 @@ pub trait ProtocolHandler: Sync + Send { feature_index: u32, feature_id: Uuid, ) -> BoxFuture<'_, Result> { - // If we have a standardized BLE Battery endpoint, handle that above the - // protocol, as it'll always be the same. - if device.endpoints().contains(&Endpoint::RxBLEBattery) { - debug!("Trying to get battery reading."); - let msg = HardwareReadCmd::new(feature_id, Endpoint::RxBLEBattery, 1, 0); - let fut = device.read_value(&msg); - async move { - let hw_msg = fut.await?; - let battery_level = hw_msg.data()[0] as i32; - let battery_reading = InputReadingV4::new( - device_index, - feature_index, - buttplug_core::message::InputTypeReading::Battery(InputValue::new(battery_level as u8)), - ); - debug!("Got battery reading: {}", battery_level); - Ok(battery_reading) - } - .boxed() - } else { - future::ready(Err(ButtplugDeviceError::UnhandledCommand( - "Command not implemented for this protocol: SensorReadCmd".to_string(), - ))) - .boxed() - } + default_handle_battery_level_cmd(device_index, device, feature_index, feature_id) } fn handle_rssi_level_cmd( @@ -460,10 +356,7 @@ pub trait ProtocolHandler: Sync + Send { _feature_index: u32, _feature_id: Uuid, ) -> BoxFuture<'_, Result<(), ButtplugDeviceError>> { - future::ready(Err(ButtplugDeviceError::UnhandledCommand( - "Command not implemented for this protocol: SensorReadCmd".to_string(), - ))) - .boxed() + command_unimplemented_future("SensorReadCmd") } fn event_stream( @@ -473,6 +366,61 @@ pub trait ProtocolHandler: Sync + Send { } } +// Allow here since this changes between debug/release +#[allow(unused_variables)] +fn command_unimplemented(command: &str) -> Result { + #[cfg(debug_assertions)] + unimplemented!("Command not implemented for this protocol"); + #[cfg(not(debug_assertions))] + Err(ButtplugDeviceError::UnhandledCommand(format!( + "Command not implemented for this protocol: {}", + command + ))) +} + +// Allow here since this changes between debug/release +#[allow(unused_variables)] +fn command_unimplemented_future( + command: &str, +) -> BoxFuture<'static, Result> { + #[cfg(debug_assertions)] + unimplemented!("Command not implemented for this protocol"); + #[cfg(not(debug_assertions))] + future::ready(Err(ButtplugDeviceError::UnhandledCommand(format!( + "Command not implemented for this protocol: {}", + command + )))) + .boxed() +} + +// Free function — one copy shared by all 135 vtables +pub(crate) fn default_handle_battery_level_cmd( + device_index: u32, + device: Arc, + feature_index: u32, + feature_id: Uuid, +) -> BoxFuture<'static, Result> { + if device.endpoints().contains(&Endpoint::RxBLEBattery) { + debug!("Trying to get battery reading."); + let msg = HardwareReadCmd::new(feature_id, Endpoint::RxBLEBattery, 1, 0); + let fut = device.read_value(&msg); + async move { + let hw_msg = fut.await?; + let battery_level = hw_msg.data()[0] as i32; + let battery_reading = InputReadingV4::new( + device_index, + feature_index, + buttplug_core::message::InputTypeReading::Battery(InputValue::new(battery_level as u8)), + ); + debug!("Got battery reading: {}", battery_level); + Ok(battery_reading) + } + .boxed() + } else { + command_unimplemented_future("SensorReadCmd") + } +} + #[macro_export] macro_rules! generic_protocol_setup { ( $protocol_name:ident, $protocol_identifier:tt) => { diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs index 30f6aa5d2..67d1c4516 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs @@ -154,7 +154,7 @@ impl ProtocolHandler for LovenseConnectService { fn handle_output_cmd( &self, cmd: &crate::message::checked_output_cmd::CheckedOutputCmdV4, - ) -> Result, ButtplugDeviceError> { + ) -> Option, ButtplugDeviceError>> { let mut hardware_cmds = vec![]; // We do all of our validity checking during message conversion to checked, so we should be able to skip validity checking here. @@ -179,7 +179,7 @@ impl ProtocolHandler for LovenseConnectService { ) .into(), ); - Ok(hardware_cmds) + Some(Ok(hardware_cmds)) } else if self.thusting_count != 0 && cmd.output_command().as_output_type() == OutputType::Oscillate { @@ -199,7 +199,7 @@ impl ProtocolHandler for LovenseConnectService { ) .into(), ); - Ok(hardware_cmds) + Some(Ok(hardware_cmds)) } else if cmd.output_command().as_output_type() == OutputType::Oscillate { // Only the max has a constriction system, and there's only one, so just parse the first command. /* ~ Sutekh @@ -225,9 +225,9 @@ impl ProtocolHandler for LovenseConnectService { ) .into(), ); - Ok(hardware_cmds) + Some(Ok(hardware_cmds)) } else { - Ok(hardware_cmds) + Some(Ok(hardware_cmds)) } } From 43919c5a50a6740297f41de112be1bcc23b288a9 Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Thu, 12 Mar 2026 23:04:38 +0100 Subject: [PATCH 12/17] Use a match statement for protocol identifier retrieval instead of a HashMap lookup, as the number of protocols is small and this avoids the need for dynamic dispatch and heap allocation when retrieving protocol identifiers. --- crates/buttplug_server/src/device/mod.rs | 1 - crates/buttplug_server/src/device/protocol.rs | 155 ++--- .../src/device/protocol_impl/fluffer.rs | 36 +- .../src/device/protocol_impl/hismith.rs | 25 +- .../src/device/protocol_impl/hismith_mini.rs | 25 +- .../src/device/protocol_impl/lovense/mod.rs | 19 +- .../protocol_impl/lovense_connect_service.rs | 14 +- .../src/device/protocol_impl/mod.rs | 596 +++++------------- .../src/device/protocol_impl/monsterpub.rs | 14 +- .../src/device/protocol_impl/patoo.rs | 14 +- .../src/device/protocol_impl/prettylove.rs | 14 +- .../src/device/protocol_impl/satisfyer.rs | 14 +- .../device/protocol_impl/thehandy_v3/mod.rs | 15 +- .../src/device/protocol_impl/vibratissimo.rs | 14 +- .../src/device/protocol_impl/youou.rs | 14 +- .../server_device_manager_event_loop.rs | 8 +- 16 files changed, 269 insertions(+), 709 deletions(-) diff --git a/crates/buttplug_server/src/device/mod.rs b/crates/buttplug_server/src/device/mod.rs index 93e3d5872..921c6bf3c 100644 --- a/crates/buttplug_server/src/device/mod.rs +++ b/crates/buttplug_server/src/device/mod.rs @@ -119,5 +119,4 @@ pub(crate) enum InternalDeviceEvent { /// A device has disconnected Disconnected(UserDeviceIdentifier), } -pub use protocol_impl::get_default_protocol_map; pub use server_device_manager::{ServerDeviceManager, ServerDeviceManagerBuilder}; diff --git a/crates/buttplug_server/src/device/protocol.rs b/crates/buttplug_server/src/device/protocol.rs index 9939885fd..762223060 100644 --- a/crates/buttplug_server/src/device/protocol.rs +++ b/crates/buttplug_server/src/device/protocol.rs @@ -24,7 +24,7 @@ use super::hardware::HardwareWriteCmd; use crate::{ device::{ hardware::{Hardware, HardwareCommand, HardwareReadCmd}, - protocol_impl::get_default_protocol_map, + protocol_impl::get_protocol_identifier, }, message::{ ButtplugServerDeviceMessage, @@ -133,22 +133,22 @@ pub trait ProtocolInitializer: Sync + Send { ) -> Result, ButtplugDeviceError>; } -pub struct GenericProtocolIdentifier { - handler: Option>, - protocol_identifier: String, +pub struct GenericProtocolIdentifier { + protocol_identifier: &'static str, + marker: std::marker::PhantomData, } -impl GenericProtocolIdentifier { - pub fn new(handler: Arc, protocol_identifier: &str) -> Self { +impl GenericProtocolIdentifier { + pub fn new(protocol_identifier: &'static str) -> Self { Self { - handler: Some(handler), - protocol_identifier: protocol_identifier.to_owned(), + protocol_identifier, + marker: std::marker::PhantomData, } } } #[async_trait] -impl ProtocolIdentifier for GenericProtocolIdentifier { +impl ProtocolIdentifier for GenericProtocolIdentifier { async fn identify( &mut self, hardware: Arc, @@ -161,9 +161,7 @@ impl ProtocolIdentifier for GenericProtocolIdentifier { ); Ok(( device_identifier, - Box::new(GenericProtocolInitializer::new( - self.handler.take().unwrap(), - )), + Box::new(GenericProtocolInitializer::new(Arc::new(T::default()))), )) } } @@ -424,27 +422,15 @@ pub(crate) fn default_handle_battery_level_cmd( #[macro_export] macro_rules! generic_protocol_setup { ( $protocol_name:ident, $protocol_identifier:tt) => { - paste::paste! { - pub mod setup { - use std::sync::Arc; - use $crate::device::protocol::{ - GenericProtocolIdentifier, ProtocolIdentifier, ProtocolIdentifierFactory, - }; - #[derive(Default)] - pub struct [< $protocol_name IdentifierFactory >] {} - - impl ProtocolIdentifierFactory for [< $protocol_name IdentifierFactory >] { - fn identifier(&self) -> &str { - $protocol_identifier - } - - fn create(&self) -> Box { - Box::new(GenericProtocolIdentifier::new( - Arc::new(super::$protocol_name::default()), - self.identifier(), - )) - } - } + pub mod setup { + use $crate::device::protocol::{GenericProtocolIdentifier, ProtocolIdentifier}; + + pub const IDENTIFIER: &str = $protocol_identifier; + + pub fn create_identifier() -> Box { + Box::new(GenericProtocolIdentifier::::new( + IDENTIFIER, + )) } } }; @@ -455,18 +441,12 @@ macro_rules! generic_protocol_initializer_setup { ( $protocol_name:ident, $protocol_identifier:tt) => { paste::paste! { pub mod setup { - use $crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct [< $protocol_name IdentifierFactory >] {} - - impl ProtocolIdentifierFactory for [< $protocol_name IdentifierFactory >] { - fn identifier(&self) -> &str { - $protocol_identifier - } - - fn create(&self) -> Box { - Box::new(super::[< $protocol_name Identifier >]::default()) - } + use $crate::device::protocol::ProtocolIdentifier; + + pub const IDENTIFIER: &str = $protocol_identifier; + + pub fn create_identifier() -> Box { + Box::new(super::[< $protocol_name Identifier >]::default()) } } @@ -490,64 +470,41 @@ macro_rules! generic_protocol_initializer_setup { pub use generic_protocol_initializer_setup; pub use generic_protocol_setup; -pub struct ProtocolManager { - // Map of protocol names to their respective protocol instance factories - protocol_map: HashMap>, -} - -impl Default for ProtocolManager { - fn default() -> Self { - Self { - protocol_map: get_default_protocol_map(), - } - } -} - -impl ProtocolManager { - pub fn protocol_specializers( - &self, - specifier: &ProtocolCommunicationSpecifier, - base_communication_specifiers: &HashMap>, - user_communication_specifiers: &DashMap>, - ) -> Vec { - debug!( - "Looking for protocol that matches specifier: {:?}", - specifier - ); - let mut specializers = vec![]; - let mut update_specializer_map = - |name: &str, specifiers: &Vec| { - if specifiers.contains(specifier) { - info!( - "Found protocol {:?} for user specifier {:?}.", +pub fn get_protocol_specializers( + specifier: &ProtocolCommunicationSpecifier, + base_communication_specifiers: &HashMap>, + user_communication_specifiers: &DashMap>, +) -> Vec { + debug!( + "Looking for protocol that matches specifier: {:?}", + specifier + ); + let mut specializers = vec![]; + let mut update_specializer_map = + |name: &str, specifiers: &Vec| { + if specifiers.contains(specifier) { + info!( + "Found protocol {:?} for user specifier {:?}.", + name, specifier + ); + if let Some(identifier) = get_protocol_identifier(name) { + specializers.push(ProtocolSpecializer::new(specifiers.clone(), identifier)); + } else { + warn!( + "No protocol implementation for {:?} found for specifier {:?}.", name, specifier ); - if self.protocol_map.contains_key(name) { - specializers.push(ProtocolSpecializer::new( - specifiers.clone(), - self - .protocol_map - .get(name) - .expect("already checked existence") - .create(), - )); - } else { - warn!( - "No protocol implementation for {:?} found for specifier {:?}.", - name, specifier - ); - } } - }; - // Loop through both maps, as chaining between DashMap and HashMap gets kinda gross. - for spec in user_communication_specifiers.iter() { - update_specializer_map(spec.key(), spec.value()); - } - for (name, specifiers) in base_communication_specifiers.iter() { - update_specializer_map(name, specifiers); - } - specializers + } + }; + // Loop through both maps, as chaining between DashMap and HashMap gets kinda gross. + for spec in user_communication_specifiers.iter() { + update_specializer_map(spec.key(), spec.value()); + } + for (name, specifiers) in base_communication_specifiers.iter() { + update_specializer_map(name, specifiers); } + specializers } /* diff --git a/crates/buttplug_server/src/device/protocol_impl/fluffer.rs b/crates/buttplug_server/src/device/protocol_impl/fluffer.rs index a8f0afc58..c8f6f79f6 100644 --- a/crates/buttplug_server/src/device/protocol_impl/fluffer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/fluffer.rs @@ -5,9 +5,17 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use crate::device::{ - hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd}, - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, +use crate::{ + device::{ + hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd}, + protocol::{ + ProtocolHandler, + ProtocolIdentifier, + ProtocolInitializer, + ProtocolKeepaliveStrategy, + }, + }, + generic_protocol_setup, }; use aes::Aes128; use async_trait::async_trait; @@ -34,21 +42,7 @@ type Aes128EcbDec = ecb::Decryptor; const FLUFFER_PROTOCOL_UUID: Uuid = uuid!("d3721a71-a81d-461a-b404-8599ce50c00b"); const FLUFFER_KEY: [u8; 16] = *b"jdk#Flu%y6fer32f"; -pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct FlufferIdentifierFactory {} - - impl ProtocolIdentifierFactory for FlufferIdentifierFactory { - fn identifier(&self) -> &str { - "fluffer" - } - - fn create(&self) -> Box { - Box::new(super::FlufferIdentifier::default()) - } - } -} +generic_protocol_setup!(Fluffer, "fluffer"); #[derive(Default)] pub struct FlufferIdentifier {} @@ -81,11 +75,7 @@ impl ProtocolIdentifier for FlufferIdentifier { ); } Ok(( - UserDeviceIdentifier::new( - hardware.address(), - "fluffer", - Some(hardware.name()), - ), + UserDeviceIdentifier::new(hardware.address(), "fluffer", Some(hardware.name())), Box::new(FlufferInitializer::new(data)), )) } diff --git a/crates/buttplug_server/src/device/protocol_impl/hismith.rs b/crates/buttplug_server/src/device/protocol_impl/hismith.rs index 9cb5720c0..728193bd6 100644 --- a/crates/buttplug_server/src/device/protocol_impl/hismith.rs +++ b/crates/buttplug_server/src/device/protocol_impl/hismith.rs @@ -6,9 +6,12 @@ // for full license information. use super::hismith_mini::HismithMiniInitializer; -use crate::device::{ - hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, +use crate::{ + device::{ + hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, + protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, + }, + generic_protocol_setup, }; use async_trait::async_trait; use buttplug_core::errors::ButtplugDeviceError; @@ -23,21 +26,7 @@ use uuid::{Uuid, uuid}; const HISMITH_PROTOCOL_UUID: Uuid = uuid!("e59f9c5d-bb4a-4a9c-ab57-0ceb43af1da7"); -pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct HismithIdentifierFactory {} - - impl ProtocolIdentifierFactory for HismithIdentifierFactory { - fn identifier(&self) -> &str { - "hismith" - } - - fn create(&self) -> Box { - Box::new(super::HismithIdentifier::default()) - } - } -} +generic_protocol_setup!(Hismith, "hismith"); #[derive(Default)] pub struct HismithIdentifier {} diff --git a/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs b/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs index d4c8663c5..212426345 100644 --- a/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs +++ b/crates/buttplug_server/src/device/protocol_impl/hismith_mini.rs @@ -4,9 +4,12 @@ // // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use crate::device::{ - hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, +use crate::{ + device::{ + hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, + protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, + }, + generic_protocol_setup, }; use async_trait::async_trait; use buttplug_core::{errors::ButtplugDeviceError, message::OutputType}; @@ -22,21 +25,7 @@ use uuid::{Uuid, uuid}; const HISMITH_MINI_PROTOCOL_UUID: Uuid = uuid!("94befc1a-9859-4bf6-99ee-5678c89237a7"); const HISMITH_MINI_ROTATE_DIRECTIOM_UUID: Uuid = uuid!("94befc1a-9859-4bf6-99ee-5678c89237a7"); -pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct HismithMiniIdentifierFactory {} - - impl ProtocolIdentifierFactory for HismithMiniIdentifierFactory { - fn identifier(&self) -> &str { - "hismith-mini" - } - - fn create(&self) -> Box { - Box::new(super::HismithMiniIdentifier::default()) - } - } -} +generic_protocol_setup!(HismithMini, "hismith-mini"); #[derive(Default)] pub struct HismithMiniIdentifier {} diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs index e0b9e5763..3539f7a29 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense/mod.rs @@ -26,7 +26,8 @@ use crate::device::{ use async_trait::async_trait; use buttplug_core::{ errors::ButtplugDeviceError, - message::{self, InputReadingV4, InputTypeReading, InputValue, OutputType}, util::async_manager, + message::{self, InputReadingV4, InputTypeReading, InputValue, OutputType}, + util::async_manager, }; use buttplug_server_device_config::{ Endpoint, @@ -50,18 +51,12 @@ const LOVENSE_COMMAND_RETRY: u64 = 5; const LOVENSE_PROTOCOL_UUID: Uuid = uuid!("cfa3fac5-48bb-4d87-817e-a439965956e1"); pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct LovenseIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; + + pub const IDENTIFIER: &str = "lovense"; - impl ProtocolIdentifierFactory for LovenseIdentifierFactory { - fn identifier(&self) -> &str { - "lovense" - } - - fn create(&self) -> Box { - Box::new(super::LovenseIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::LovenseIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs index 67d1c4516..4c2abf209 100644 --- a/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs +++ b/crates/buttplug_server/src/device/protocol_impl/lovense_connect_service.rs @@ -31,18 +31,12 @@ use buttplug_server_device_config::ServerDeviceDefinition; const LOVENSE_CONNECT_UUID: Uuid = uuid!("590bfbbf-c3b7-41ae-9679-485b190ffb87"); pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct LovenseConnectIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for LovenseConnectIdentifierFactory { - fn identifier(&self) -> &str { - "lovense-connect-service" - } + pub const IDENTIFIER: &str = "lovense-connect-service"; - fn create(&self) -> Box { - Box::new(super::LovenseConnectIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::LovenseConnectIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/mod.rs b/crates/buttplug_server/src/device/protocol_impl/mod.rs index ae0e7a5a9..6702e0e5f 100644 --- a/crates/buttplug_server/src/device/protocol_impl/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/mod.rs @@ -5,9 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use std::{collections::HashMap, sync::Arc}; - -use crate::device::protocol::ProtocolIdentifierFactory; +use crate::device::protocol::ProtocolIdentifier; // Utility mods pub mod fleshlight_launch_helper; @@ -125,453 +123,149 @@ pub mod youcups; pub mod youou; pub mod zalo; -pub fn get_default_protocol_map() -> HashMap> { - let mut map = HashMap::new(); - fn add_to_protocol_map( - map: &mut HashMap>, - factory: T, - ) where - T: ProtocolIdentifierFactory + 'static, - { - let factory = Arc::new(factory); - map.insert(factory.identifier().to_owned(), factory); - } - - add_to_protocol_map( - &mut map, - activejoy::setup::ActiveJoyIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - adrienlastic::setup::AdrienLasticIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - amorelie_joy::setup::AmorelieJoyIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, aneros::setup::AnerosIdentifierFactory::default()); - add_to_protocol_map(&mut map, ankni::setup::AnkniIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - bananasome::setup::BananasomeIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - cachito::setup::CachitoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - cowgirl::setup::CowgirlIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - cowgirl_cone::setup::CowgirlConeIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, cupido::setup::CupidoIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - deepsire::setup::DeepSireIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - hismith::setup::HismithIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - hismith_mini::setup::HismithMiniIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, htk_bm::setup::HtkBmIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - thehandy::setup::TheHandyIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - thehandy_v3::setup::TheHandyV3IdentifierFactory::default(), - ); - - add_to_protocol_map( - &mut map, - feelingso::setup::FeelingSoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - fleshy_thrust::setup::FleshyThrustIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - fluffer::setup::FlufferIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, foreo::setup::ForeoIdentifierFactory::default()); - add_to_protocol_map(&mut map, fox::setup::FoxIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - fredorch::setup::FredorchIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - fredorch_rotary::setup::FredorchRotaryIdentifierFactory::default(), - ); - - add_to_protocol_map(&mut map, hgod::setup::HgodIdentifierFactory::default()); - - add_to_protocol_map( - &mut map, - galaku_pump::setup::GalakuPumpIdentifierFactory::default(), - ); - - add_to_protocol_map(&mut map, galaku::setup::GalakuIdentifierFactory::default()); - - add_to_protocol_map(&mut map, itoys::setup::IToysIdentifierFactory::default()); - add_to_protocol_map(&mut map, jejoue::setup::JeJoueIdentifierFactory::default()); - add_to_protocol_map(&mut map, joyhub::setup::JoyHubIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - kiiroo_powershot::setup::KiirooPowerShotIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_prowand::setup::KiirooProWandIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_spot::setup::KiirooSpotIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_v2::setup::KiirooV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_v3::setup::KiirooV3IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_v2_vibrator::setup::KiirooV2VibratorIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_v21::setup::KiirooV21IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - kiiroo_v21_initialized::setup::KiirooV21InitializedIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, kizuna::setup::KizunaIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - lelof1s::setup::LeloF1sIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - lelof1sv2::setup::LeloF1sV2IdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, leten::setup::LetenIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - lelo_harmony::setup::LeloHarmonyIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - libo_elle::setup::LiboElleIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - libo_shark::setup::LiboSharkIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - libo_vibes::setup::LiboVibesIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - lioness::setup::LionessIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, loob::setup::LoobIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - lovehoney_desire::setup::LovehoneyDesireIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - lovedistance::setup::LoveDistanceIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - lovense::setup::LovenseIdentifierFactory::default(), - ); +macro_rules! protocol_match { + ( $var:ident, $( $( $protocol_module:ident )::+ ),* $(,)? ) => { + match $var { + $( + $( $protocol_module )::+::setup::IDENTIFIER => Some($( $protocol_module )::+::setup::create_identifier()), + )* + _ => None, + } + }; +} - add_to_protocol_map( - &mut map, - lovense_connect_service::setup::LovenseConnectIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - lovenuts::setup::LoveNutsIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - luvmazer::setup::LuvmazerIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - magic_motion_v1::setup::MagicMotionV1IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - magic_motion_v2::setup::MagicMotionV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - magic_motion_v3::setup::MagicMotionV3IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - magic_motion_v4::setup::MagicMotionV4IdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, mannuo::setup::ManNuoIdentifierFactory::default()); - add_to_protocol_map(&mut map, maxpro::setup::MaxproIdentifierFactory::default()); - add_to_protocol_map(&mut map, meese::setup::MeeseIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - mizzzee::setup::MizzZeeIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - mizzzee_v2::setup::MizzZeeV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - mizzzee_v3::setup::MizzZeeV3IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - monsterpub::setup::MonsterPubIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - motorbunny::setup::MotorbunnyIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - mysteryvibe::setup::MysteryVibeIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - mysteryvibe_v2::setup::MysteryVibeV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - nexus_revo::setup::NexusRevoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - nextlevelracing::setup::NextLevelRacingIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - nintendo_joycon::setup::NintendoJoyconIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, nobra::setup::NobraIdentifierFactory::default()); - add_to_protocol_map(&mut map, omobo::setup::OmoboIdentifierFactory::default()); - add_to_protocol_map(&mut map, patoo::setup::PatooIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - picobong::setup::PicobongIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - pink_punch::setup::PinkPunchIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - prettylove::setup::PrettyLoveIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - raw_protocol::setup::RawProtocolIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, realov::setup::RealovIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - sakuraneko::setup::SakuranekoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - satisfyer::setup::SatisfyerIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, sensee::setup::SenseeIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - sensee_capsule::setup::SenseeCapsuleIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sensee_v2::setup::SenseeV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_lg389::setup::SexverseLG389IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_v1::setup::SexverseV1IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_v2::setup::SexverseV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_v3::setup::SexverseV3IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_v4::setup::SexverseV4IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - sexverse_v5::setup::SexverseV5IdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, serveu::setup::ServeUIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - svakom::svakom_avaneo::setup::SvakomAvaNeoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_alex::setup::SvakomAlexIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_alex_v2::setup::SvakomAlexV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_barnard::setup::SvakomBarnardIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_barney::setup::SvakomBarneyIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_dice::setup::SvakomDiceIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_dt250a::setup::SvakomDT250AIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_iker::setup::SvakomIkerIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_jordan::setup::SvakomJordanIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_pulse::setup::SvakomPulseIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_sam::setup::SvakomSamIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_sam2::setup::SvakomSam2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_suitcase::setup::SvakomSuitcaseIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_tarax::setup::SvakomTaraXIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v1::setup::SvakomV1IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v2::setup::SvakomV2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v3::setup::SvakomV3IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v4::setup::SvakomV4IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v5::setup::SvakomV5IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - svakom::svakom_v6::setup::SvakomV6IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - synchro::setup::SynchroIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, tryfun::setup::TryFunIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - tryfun_blackhole::setup::TryFunBlackHoleIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - tryfun_meta2::setup::TryFunMeta2IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - tcode_v03::setup::TCodeV03IdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - vibcrafter::setup::VibCrafterIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - vibratissimo::setup::VibratissimoIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - vorze_sa::setup::VorzeSAIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, wetoy::setup::WeToyIdentifierFactory::default()); - add_to_protocol_map(&mut map, wevibe::setup::WeVibeIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - wevibe8bit::setup::WeVibe8BitIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - wevibe_chorus::setup::WeVibeChorusIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, xibao::setup::XibaoIdentifierFactory::default()); - add_to_protocol_map(&mut map, xinput::setup::XInputIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - xiuxiuda::setup::XiuxiudaIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - xuanhuan::setup::XuanhuanIdentifierFactory::default(), - ); - add_to_protocol_map( - &mut map, - youcups::setup::YoucupsIdentifierFactory::default(), - ); - add_to_protocol_map(&mut map, youou::setup::YououIdentifierFactory::default()); - add_to_protocol_map(&mut map, zalo::setup::ZaloIdentifierFactory::default()); - add_to_protocol_map( - &mut map, - kgoal_boost::setup::KGoalBoostIdentifierFactory::default(), - ); - map +pub fn get_protocol_identifier(protocol_identifier: &str) -> Option> { + protocol_match!( + protocol_identifier, + activejoy, + adrienlastic, + amorelie_joy, + aneros, + ankni, + bananasome, + cachito, + cowgirl, + cowgirl_cone, + cupido, + deepsire, + feelingso, + fleshy_thrust, + fluffer, + foreo, + fox, + fredorch, + fredorch_rotary, + galaku, + galaku_pump, + hgod, + hismith, + hismith_mini, + htk_bm, + itoys, + jejoue, + joyhub, + kgoal_boost, + kiiroo_powershot, + kiiroo_prowand, + kiiroo_spot, + kiiroo_v2, + kiiroo_v21, + kiiroo_v21_initialized, + kiiroo_v2_vibrator, + kiiroo_v3, + kizuna, + lelo_harmony, + lelof1s, + lelof1sv2, + leten, + libo_elle, + libo_shark, + libo_vibes, + lioness, + loob, + lovedistance, + lovehoney_desire, + lovense, + lovense_connect_service, + lovenuts, + luvmazer, + magic_motion_v1, + magic_motion_v2, + magic_motion_v3, + magic_motion_v4, + mannuo, + maxpro, + meese, + mizzzee, + mizzzee_v2, + mizzzee_v3, + monsterpub, + motorbunny, + mysteryvibe, + mysteryvibe_v2, + nextlevelracing, + nexus_revo, + nintendo_joycon, + nobra, + omobo, + patoo, + picobong, + pink_punch, + prettylove, + raw_protocol, + realov, + sakuraneko, + satisfyer, + sensee, + sensee_capsule, + sensee_v2, + serveu, + sexverse_lg389, + sexverse_v1, + sexverse_v2, + sexverse_v3, + sexverse_v4, + sexverse_v5, + svakom::svakom_alex, + svakom::svakom_alex_v2, + svakom::svakom_avaneo, + svakom::svakom_barnard, + svakom::svakom_barney, + svakom::svakom_dice, + svakom::svakom_dt250a, + svakom::svakom_iker, + svakom::svakom_jordan, + svakom::svakom_pulse, + svakom::svakom_sam, + svakom::svakom_sam2, + svakom::svakom_suitcase, + svakom::svakom_tarax, + svakom::svakom_v1, + svakom::svakom_v2, + svakom::svakom_v3, + svakom::svakom_v4, + svakom::svakom_v5, + svakom::svakom_v6, + synchro, + tcode_v03, + thehandy, + thehandy_v3, + tryfun, + tryfun_blackhole, + tryfun_meta2, + vibcrafter, + vibratissimo, + vorze_sa, + wetoy, + wevibe, + wevibe8bit, + wevibe_chorus, + xibao, + xinput, + xiuxiuda, + xuanhuan, + youcups, + youou, + zalo + ) } diff --git a/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs b/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs index e1561e33d..83a7b762b 100644 --- a/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs +++ b/crates/buttplug_server/src/device/protocol_impl/monsterpub.rs @@ -23,18 +23,12 @@ use std::sync::{ use uuid::{Uuid, uuid}; pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct MonsterPubIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for MonsterPubIdentifierFactory { - fn identifier(&self) -> &str { - "monsterpub" - } + pub const IDENTIFIER: &str = "monsterpub"; - fn create(&self) -> Box { - Box::new(super::MonsterPubIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::MonsterPubIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/patoo.rs b/crates/buttplug_server/src/device/protocol_impl/patoo.rs index d9e9d2f1c..2975cdc88 100644 --- a/crates/buttplug_server/src/device/protocol_impl/patoo.rs +++ b/crates/buttplug_server/src/device/protocol_impl/patoo.rs @@ -25,18 +25,12 @@ use std::sync::{ use uuid::{Uuid, uuid}; pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct PatooIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for PatooIdentifierFactory { - fn identifier(&self) -> &str { - "patoo" - } + pub const IDENTIFIER: &str = "patoo"; - fn create(&self) -> Box { - Box::new(super::PatooIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::PatooIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/prettylove.rs b/crates/buttplug_server/src/device/protocol_impl/prettylove.rs index d5608691a..086c97d86 100644 --- a/crates/buttplug_server/src/device/protocol_impl/prettylove.rs +++ b/crates/buttplug_server/src/device/protocol_impl/prettylove.rs @@ -21,18 +21,12 @@ use std::sync::Arc; use uuid::Uuid; pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct PrettyLoveIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for PrettyLoveIdentifierFactory { - fn identifier(&self) -> &str { - "prettylove" - } + pub const IDENTIFIER: &str = "prettylove"; - fn create(&self) -> Box { - Box::new(super::PrettyLoveIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::PrettyLoveIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs index 8d20f54e7..0c198ff25 100644 --- a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs @@ -29,18 +29,12 @@ use uuid::{Uuid, uuid}; const SATISFYER_PROTOCOL_UUID: Uuid = uuid!("79a0ed0d-f392-4c48-967e-f4467438c344"); pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct SatisfyerIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for SatisfyerIdentifierFactory { - fn identifier(&self) -> &str { - "satisfyer" - } + pub const IDENTIFIER: &str = "satisfyer"; - fn create(&self) -> Box { - Box::new(super::SatisfyerIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::SatisfyerIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs b/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs index ef4faf861..c009963a9 100644 --- a/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/thehandy_v3/mod.rs @@ -31,19 +31,12 @@ const THEHANDY_V3_PROTOCOL_UUID: Uuid = uuid!("f148e5a6-91fe-4666-944f-2fcec6284 const THEHANDY_V3_SUBSCRIBE_UUID: Uuid = uuid!("55392f4a-7dcb-435f-a33d-38ec4c1d7d7e"); pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; + use crate::device::protocol::ProtocolIdentifier; - #[derive(Default)] - pub struct TheHandyV3IdentifierFactory {} + pub const IDENTIFIER: &str = "thehandy-v3"; - impl ProtocolIdentifierFactory for TheHandyV3IdentifierFactory { - fn identifier(&self) -> &str { - "thehandy-v3" - } - - fn create(&self) -> Box { - Box::new(super::TheHandyV3Identifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::TheHandyV3Identifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs b/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs index 695e83f9f..7d0a5c47e 100644 --- a/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs +++ b/crates/buttplug_server/src/device/protocol_impl/vibratissimo.rs @@ -26,18 +26,12 @@ use uuid::{Uuid, uuid}; const VIBRATISSIMO_PROTOCOL_UUID: Uuid = uuid!("66ef7aa4-1e6a-4067-9066-dcb53c7647f2"); pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct VibratissimoIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for VibratissimoIdentifierFactory { - fn identifier(&self) -> &str { - "vibratissimo" - } + pub const IDENTIFIER: &str = "vibratissimo"; - fn create(&self) -> Box { - Box::new(super::VibratissimoIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::VibratissimoIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/youou.rs b/crates/buttplug_server/src/device/protocol_impl/youou.rs index d38feaea0..de51805e7 100644 --- a/crates/buttplug_server/src/device/protocol_impl/youou.rs +++ b/crates/buttplug_server/src/device/protocol_impl/youou.rs @@ -25,18 +25,12 @@ use std::sync::{ use uuid::Uuid; pub mod setup { - use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; - #[derive(Default)] - pub struct YououIdentifierFactory {} + use crate::device::protocol::ProtocolIdentifier; - impl ProtocolIdentifierFactory for YououIdentifierFactory { - fn identifier(&self) -> &str { - "youou" - } + pub const IDENTIFIER: &str = "youou"; - fn create(&self) -> Box { - Box::new(super::YououIdentifier::default()) - } + pub fn create_identifier() -> Box { + Box::new(super::YououIdentifier::default()) } } diff --git a/crates/buttplug_server/src/device/server_device_manager_event_loop.rs b/crates/buttplug_server/src/device/server_device_manager_event_loop.rs index 9c7ba1ac8..d32f54c02 100644 --- a/crates/buttplug_server/src/device/server_device_manager_event_loop.rs +++ b/crates/buttplug_server/src/device/server_device_manager_event_loop.rs @@ -16,8 +16,7 @@ use crate::device::{ DeviceHandle, InternalDeviceEvent, device_handle::build_device_handle, - hardware::communication::{HardwareCommunicationManager, HardwareCommunicationManagerEvent}, - protocol::ProtocolManager, + hardware::communication::{HardwareCommunicationManager, HardwareCommunicationManagerEvent}, protocol::get_protocol_specializers, }; use dashmap::{DashMap, DashSet}; use futures::{FutureExt, future}; @@ -66,8 +65,6 @@ pub(super) struct ServerDeviceManagerEventLoop { connecting_devices: Arc>, /// Cancellation token for the event loop loop_cancellation_token: CancellationToken, - /// Protocol map, for mapping user definitions to protocols - protocol_manager: ProtocolManager, } impl ServerDeviceManagerEventLoop { @@ -93,7 +90,6 @@ impl ServerDeviceManagerEventLoop { scanning_state: ScanningState::Idle, connecting_devices: Arc::new(DashSet::new()), loop_cancellation_token, - protocol_manager: ProtocolManager::default(), } } @@ -245,7 +241,7 @@ impl ServerDeviceManagerEventLoop { // // We used to do this in build_server_device, but we shouldn't mark devices as actually // connecting until after this happens, so we're moving it back here. - let protocol_specializers = self.protocol_manager.protocol_specializers( + let protocol_specializers = get_protocol_specializers( &creator.specifier(), self.device_config_manager.base_communication_specifiers(), self.device_config_manager.user_communication_specifiers(), From 26cf3b2bf5e237727cb49394cb9f52abb2b9efcf Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 14 Mar 2026 01:23:18 +0100 Subject: [PATCH 13/17] Fix feature flag usage in Cargo.toml files --- crates/buttplug_client/Cargo.toml | 2 +- crates/buttplug_client_in_process/Cargo.toml | 16 ++++++++-------- crates/buttplug_core/Cargo.toml | 4 ++-- .../src/util/async_manager/dummy.rs | 2 +- .../buttplug_core/src/util/async_manager/wasm.rs | 2 +- crates/buttplug_server/Cargo.toml | 2 +- crates/buttplug_server_device_config/Cargo.toml | 4 ++-- crates/buttplug_server_hwmgr_btleplug/Cargo.toml | 2 +- crates/buttplug_server_hwmgr_hid/Cargo.toml | 2 +- .../Cargo.toml | 2 +- .../Cargo.toml | 2 +- crates/buttplug_server_hwmgr_serial/Cargo.toml | 2 +- .../buttplug_server_hwmgr_websocket/Cargo.toml | 2 +- crates/buttplug_server_hwmgr_xinput/Cargo.toml | 2 +- .../Cargo.toml | 2 +- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/buttplug_client/Cargo.toml b/crates/buttplug_client/Cargo.toml index f1b3ffd8d..b47b9aa69 100644 --- a/crates/buttplug_client/Cargo.toml +++ b/crates/buttplug_client/Cargo.toml @@ -19,7 +19,7 @@ doctest = true doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } futures = "0.3.31" thiserror = "2.0.18" log = "0.4.29" diff --git a/crates/buttplug_client_in_process/Cargo.toml b/crates/buttplug_client_in_process/Cargo.toml index 98d5a546a..b3945b005 100644 --- a/crates/buttplug_client_in_process/Cargo.toml +++ b/crates/buttplug_client_in_process/Cargo.toml @@ -30,17 +30,17 @@ websocket-manager=["buttplug_server_hwmgr_websocket"] xinput-manager=["buttplug_server_hwmgr_xinput"] [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_client = { version = "10.0.0", path = "../buttplug_client" } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } -buttplug_server_hwmgr_btleplug = { version = "10.0.0", path = "../buttplug_server_hwmgr_btleplug", optional = true} -buttplug_server_hwmgr_hid = { version = "10.0.0", path = "../buttplug_server_hwmgr_hid", optional = true} -buttplug_server_hwmgr_lovense_connect = { version = "10.0.0", path = "../buttplug_server_hwmgr_lovense_connect", optional = true} -buttplug_server_hwmgr_lovense_dongle = { version = "10.0.0", path = "../buttplug_server_hwmgr_lovense_dongle", optional = true} -buttplug_server_hwmgr_serial = { version = "10.0.0", path = "../buttplug_server_hwmgr_serial", optional = true} -buttplug_server_hwmgr_websocket = { version = "10.0.0", path = "../buttplug_server_hwmgr_websocket", optional = true} -buttplug_server_hwmgr_xinput = { version = "10.0.0", path = "../buttplug_server_hwmgr_xinput", optional = true} +buttplug_server_hwmgr_btleplug = { version = "10.0.0", path = "../buttplug_server_hwmgr_btleplug", optional = true } +buttplug_server_hwmgr_hid = { version = "10.0.0", path = "../buttplug_server_hwmgr_hid", optional = true } +buttplug_server_hwmgr_lovense_connect = { version = "10.0.0", path = "../buttplug_server_hwmgr_lovense_connect", optional = true } +buttplug_server_hwmgr_lovense_dongle = { version = "10.0.0", path = "../buttplug_server_hwmgr_lovense_dongle", optional = true } +buttplug_server_hwmgr_serial = { version = "10.0.0", path = "../buttplug_server_hwmgr_serial", optional = true } +buttplug_server_hwmgr_websocket = { version = "10.0.0", path = "../buttplug_server_hwmgr_websocket", optional = true } +buttplug_server_hwmgr_xinput = { version = "10.0.0", path = "../buttplug_server_hwmgr_xinput", optional = true } futures = "0.3.31" futures-util = "0.3.31" thiserror = "2.0.18" diff --git a/crates/buttplug_core/Cargo.toml b/crates/buttplug_core/Cargo.toml index d254c3220..8c02c0109 100644 --- a/crates/buttplug_core/Cargo.toml +++ b/crates/buttplug_core/Cargo.toml @@ -20,7 +20,7 @@ doc = true [features] default=["tokio-runtime"] -tokio-runtime=["tokio/rt"] +tokio-runtime=["tokio/rt", "tokio/time"] wasm=[] # Only build docs on one platform (linux) @@ -46,7 +46,7 @@ log = "0.4.29" getset = "0.1.6" jsonschema = { version = "0.38.1", default-features = false } cfg-if = "1.0.4" -tokio = { version = "1.49.0", features = ["sync", "time", "macros"] } +tokio = { version = "1.49.0", features = ["sync", "macros"] } async-stream = "0.3.6" strum_macros = "0.27.2" strum = "0.27.2" diff --git a/crates/buttplug_core/src/util/async_manager/dummy.rs b/crates/buttplug_core/src/util/async_manager/dummy.rs index 4881776da..5a8a7df40 100644 --- a/crates/buttplug_core/src/util/async_manager/dummy.rs +++ b/crates/buttplug_core/src/util/async_manager/dummy.rs @@ -13,7 +13,7 @@ pub struct DummyAsyncManager {} #[async_trait] impl super::AsyncManager for DummyAsyncManager { - fn spawn(&self, _future: FutureObj<'static, ()>) { + fn spawn(&self, _future: FutureObj<'static, ()>, _span: tracing::Span) { unimplemented!( "No async runtime available. Please set a global async manager using set_global_async_manager or enable tokio-runtime or wasm feature" ); diff --git a/crates/buttplug_core/src/util/async_manager/wasm.rs b/crates/buttplug_core/src/util/async_manager/wasm.rs index 79561ffe9..e4934e5f4 100644 --- a/crates/buttplug_core/src/util/async_manager/wasm.rs +++ b/crates/buttplug_core/src/util/async_manager/wasm.rs @@ -16,7 +16,7 @@ pub struct WasmAsyncManager {} #[async_trait] impl super::AsyncManager for WasmAsyncManager { - fn spawn(&self, future: FutureObj<'static, ()>) { + fn spawn(&self, future: FutureObj<'static, ()>, _span: tracing::Span) { spawn_local(future); } diff --git a/crates/buttplug_server/Cargo.toml b/crates/buttplug_server/Cargo.toml index a0780d1c2..37108aaf2 100644 --- a/crates/buttplug_server/Cargo.toml +++ b/crates/buttplug_server/Cargo.toml @@ -24,7 +24,7 @@ default=[] wasm=["uuid/js"] [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" futures-util = "0.3.31" diff --git a/crates/buttplug_server_device_config/Cargo.toml b/crates/buttplug_server_device_config/Cargo.toml index 3543b6489..6cbb74a22 100644 --- a/crates/buttplug_server_device_config/Cargo.toml +++ b/crates/buttplug_server_device_config/Cargo.toml @@ -19,7 +19,7 @@ doctest = true doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } futures = "0.3.31" futures-util = "0.3.31" serde = { version = "1.0.228", features = ["derive"] } @@ -42,7 +42,7 @@ enumflags2 = "0.7.12" serde_yaml = "0.9.34" serde_json = "1.0.149" serde = { version = "1.0.228", features = ["derive"] } -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } [dev-dependencies] test-case = "3.3.1" diff --git a/crates/buttplug_server_hwmgr_btleplug/Cargo.toml b/crates/buttplug_server_hwmgr_btleplug/Cargo.toml index 137456d62..bbae3871a 100644 --- a/crates/buttplug_server_hwmgr_btleplug/Cargo.toml +++ b/crates/buttplug_server_hwmgr_btleplug/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_hid/Cargo.toml b/crates/buttplug_server_hwmgr_hid/Cargo.toml index bd0572ed1..d8b84a943 100644 --- a/crates/buttplug_server_hwmgr_hid/Cargo.toml +++ b/crates/buttplug_server_hwmgr_hid/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_lovense_connect/Cargo.toml b/crates/buttplug_server_hwmgr_lovense_connect/Cargo.toml index 55aa206eb..6be18d56f 100644 --- a/crates/buttplug_server_hwmgr_lovense_connect/Cargo.toml +++ b/crates/buttplug_server_hwmgr_lovense_connect/Cargo.toml @@ -27,7 +27,7 @@ targets = [] features = ["default", "unstable"] [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml b/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml index c6fcdc324..8294b43cc 100644 --- a/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml +++ b/crates/buttplug_server_hwmgr_lovense_dongle/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_serial/Cargo.toml b/crates/buttplug_server_hwmgr_serial/Cargo.toml index 28347c09a..871107610 100644 --- a/crates/buttplug_server_hwmgr_serial/Cargo.toml +++ b/crates/buttplug_server_hwmgr_serial/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_websocket/Cargo.toml b/crates/buttplug_server_hwmgr_websocket/Cargo.toml index 98736fec9..fc911edf1 100644 --- a/crates/buttplug_server_hwmgr_websocket/Cargo.toml +++ b/crates/buttplug_server_hwmgr_websocket/Cargo.toml @@ -27,7 +27,7 @@ targets = [] features = ["default", "unstable"] [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_server_hwmgr_xinput/Cargo.toml b/crates/buttplug_server_hwmgr_xinput/Cargo.toml index ded9f8459..3417f0546 100644 --- a/crates/buttplug_server_hwmgr_xinput/Cargo.toml +++ b/crates/buttplug_server_hwmgr_xinput/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } futures = "0.3.31" diff --git a/crates/buttplug_transport_websocket_tungstenite/Cargo.toml b/crates/buttplug_transport_websocket_tungstenite/Cargo.toml index efc0977a4..a960bbd0e 100644 --- a/crates/buttplug_transport_websocket_tungstenite/Cargo.toml +++ b/crates/buttplug_transport_websocket_tungstenite/Cargo.toml @@ -20,7 +20,7 @@ doc = true [dependencies] -buttplug_core = { version = "10.0.0", path = "../buttplug_core" } +buttplug_core = { version = "10.0.0", path = "../buttplug_core", default-features = false } futures = "0.3.31" futures-util = "0.3.31" serde = { version = "1.0.228", features = ["derive"] } From fee1309b4fb2ab8df829fa0c2d79034e308e734e Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 14 Mar 2026 01:30:20 +0100 Subject: [PATCH 14/17] Clean up some dependencies --- crates/buttplug_client_in_process/Cargo.toml | 1 - crates/buttplug_server/Cargo.toml | 1 - crates/buttplug_server/src/message/serializer/mod.rs | 7 ++++--- crates/buttplug_tests/Cargo.toml | 1 - .../tests/util/device_test/client/client_v2/client.rs | 1 - .../tests/util/device_test/client/client_v2/device.rs | 2 +- .../device_test/client/client_v2/in_process_connector.rs | 1 - .../tests/util/device_test/client/client_v3/client.rs | 1 - .../client/client_v3/connector/in_process_connector.rs | 1 - 9 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/buttplug_client_in_process/Cargo.toml b/crates/buttplug_client_in_process/Cargo.toml index b3945b005..ccd393638 100644 --- a/crates/buttplug_client_in_process/Cargo.toml +++ b/crates/buttplug_client_in_process/Cargo.toml @@ -48,5 +48,4 @@ log = "0.4.29" getset = "0.1.6" tokio = { version = "1.49.0", features = ["macros"] } dashmap = { version = "6.1.0" } -tracing-futures = "0.2.5" tracing = "0.1.44" diff --git a/crates/buttplug_server/Cargo.toml b/crates/buttplug_server/Cargo.toml index 37108aaf2..3af56aaa6 100644 --- a/crates/buttplug_server/Cargo.toml +++ b/crates/buttplug_server/Cargo.toml @@ -37,7 +37,6 @@ tracing = "0.1.44" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" jsonschema = { version = "0.38.1", default-features = false } -once_cell = "1.21.3" tokio-stream = "0.1.18" strum_macros = "0.27.2" strum = "0.27.2" diff --git a/crates/buttplug_server/src/message/serializer/mod.rs b/crates/buttplug_server/src/message/serializer/mod.rs index 359854c67..0d278f9c7 100644 --- a/crates/buttplug_server/src/message/serializer/mod.rs +++ b/crates/buttplug_server/src/message/serializer/mod.rs @@ -5,6 +5,8 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. +use std::sync::OnceLock; + use buttplug_core::{ errors::{ButtplugError, ButtplugHandshakeError, ButtplugMessageError}, message::{ @@ -28,7 +30,6 @@ use buttplug_core::{ }, }; use jsonschema::Validator; -use once_cell::sync::OnceCell; use serde::Deserialize; use super::{ @@ -69,14 +70,14 @@ impl ButtplugMessageFinalizer for RequestServerInfoVersion { } pub struct ButtplugServerJSONSerializer { - pub(super) message_version: OnceCell, + pub(super) message_version: OnceLock, validator: Validator, } impl Default for ButtplugServerJSONSerializer { fn default() -> Self { Self { - message_version: OnceCell::new(), + message_version: OnceLock::new(), validator: create_message_validator(), } } diff --git a/crates/buttplug_tests/Cargo.toml b/crates/buttplug_tests/Cargo.toml index 29b1f971f..aa05cc6c3 100644 --- a/crates/buttplug_tests/Cargo.toml +++ b/crates/buttplug_tests/Cargo.toml @@ -22,7 +22,6 @@ uuid = "1.20.0" futures = "0.3.31" tracing = "0.1.44" tracing-subscriber = "0.3.22" -tracing-futures = "0.2.5" tokio-test = "0.4.5" serde = "1.0.228" async-trait = "0.1.89" diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs index af0b4d5a0..47af643af 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs @@ -40,7 +40,6 @@ use std::sync::{ use thiserror::Error; use tokio::sync::{Mutex, broadcast, mpsc, mpsc::error::SendError}; use tracing::{Level, Span, info_span, span}; -use tracing_futures::Instrument; /// Result type used for public APIs. /// diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs index aab576acc..6de1fe9be 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs @@ -40,6 +40,7 @@ use buttplug_server::message::{ use futures::{Stream, future}; use getset::Getters; use log::*; +use tracing::Instrument; use std::{ collections::HashMap, fmt, @@ -49,7 +50,6 @@ use std::{ }, }; use tokio::sync::{broadcast, mpsc}; -use tracing_futures::Instrument; /// Enum for messages going to a [ButtplugClientDevice] instance. #[derive(Clone, Debug)] diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs index 5c458c939..63a3f7b13 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/in_process_connector.rs @@ -28,7 +28,6 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, }; use tokio::sync::mpsc::{Sender, channel}; -use tracing_futures::Instrument; #[derive(Default)] pub struct ButtplugInProcessClientConnectorBuilder { diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs index 5314d297a..155d3b561 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs @@ -39,7 +39,6 @@ use std::sync::{ use thiserror::Error; use tokio::sync::{Mutex, broadcast, mpsc}; use tracing::info_span; -use tracing_futures::Instrument; /// Result type used for public APIs. /// diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs index 8336a56f2..2a54b5e24 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/connector/in_process_connector.rs @@ -28,7 +28,6 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, }; use tokio::sync::mpsc::{Sender, channel}; -use tracing_futures::Instrument; #[derive(Default)] pub struct ButtplugInProcessClientConnectorBuilder { From cd84cf7e98a2db7942aaee5f912319b387117aed Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 14 Mar 2026 16:19:37 +0100 Subject: [PATCH 15/17] Improve consistent around using displaydoc::Display vs strum_macros::Display. --- .../src/connector/transport/mod.rs | 2 +- crates/buttplug_core/src/errors.rs | 5 +++-- crates/buttplug_core/src/lib.rs | 3 --- .../src/message/device_feature.rs | 1 + crates/buttplug_core/src/message/mod.rs | 1 + .../buttplug_core/src/message/v4/input_cmd.rs | 1 + .../src/message/v4/input_reading.rs | 1 + .../src/message/v4/output_cmd.rs | 1 + .../src/message/v4/spec_enums.rs | 1 + .../buttplug_server_device_config/src/lib.rs | 22 ++++++++----------- .../Cargo.toml | 2 -- 11 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/buttplug_core/src/connector/transport/mod.rs b/crates/buttplug_core/src/connector/transport/mod.rs index e0d2ea44e..2d534d022 100644 --- a/crates/buttplug_core/src/connector/transport/mod.rs +++ b/crates/buttplug_core/src/connector/transport/mod.rs @@ -12,7 +12,7 @@ use crate::connector::{ ButtplugConnectorResultFuture, ButtplugSerializedMessage, }; -use displaydoc::Display; +use strum_macros::Display; use futures::future::BoxFuture; use thiserror::Error; use tokio::sync::mpsc::{Receiver, Sender}; diff --git a/crates/buttplug_core/src/errors.rs b/crates/buttplug_core/src/errors.rs index 3c03bce4f..740d48bc1 100644 --- a/crates/buttplug_core/src/errors.rs +++ b/crates/buttplug_core/src/errors.rs @@ -15,6 +15,7 @@ use super::message::{ OutputType, serializer::ButtplugSerializerError, }; +use displaydoc::Display; use futures::future::BoxFuture; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -51,7 +52,7 @@ impl_error_to_future!( /// a remote network connection cannot be established), see /// [crate::connector::ButtplugConnectorError]. -#[derive(Debug, Error, Display, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Display, Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ButtplugHandshakeError { /// Expected either a ServerInfo or Error message, received {0} UnexpectedHandshakeMessageReceived(String), @@ -130,7 +131,7 @@ pub enum ButtplugDeviceError { DeviceCommunicationError(String), /// Device feature only has {0} steps for control, but {1} steps specified. DeviceStepRangeError(i32, i32), - /// Device got {} output command but has no viable outputs + /// Device got {0} output command but has no viable outputs DeviceNoOutputError(OutputType), /// Device got {0} input command but has no viable inputs DeviceNoInputError(InputType), diff --git a/crates/buttplug_core/src/lib.rs b/crates/buttplug_core/src/lib.rs index 602bcf121..46bec0134 100644 --- a/crates/buttplug_core/src/lib.rs +++ b/crates/buttplug_core/src/lib.rs @@ -12,9 +12,6 @@ pub mod errors; pub mod message; pub mod util; -#[macro_use] -extern crate strum_macros; - use errors::ButtplugError; use futures::future::{self, BoxFuture, FutureExt}; diff --git a/crates/buttplug_core/src/message/device_feature.rs b/crates/buttplug_core/src/message/device_feature.rs index 1ddb43bc7..de3ab2596 100644 --- a/crates/buttplug_core/src/message/device_feature.rs +++ b/crates/buttplug_core/src/message/device_feature.rs @@ -10,6 +10,7 @@ use derive_builder::Builder; use getset::{CopyGetters, Getters, MutGetters, Setters}; use serde::{Deserialize, Serialize}; use std::hash::Hash; +use strum_macros::{Display, EnumIter, EnumString}; #[derive( Debug, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter, EnumString, diff --git a/crates/buttplug_core/src/message/mod.rs b/crates/buttplug_core/src/message/mod.rs index b11709c1e..394306b3c 100644 --- a/crates/buttplug_core/src/message/mod.rs +++ b/crates/buttplug_core/src/message/mod.rs @@ -28,6 +28,7 @@ use crate::errors::ButtplugMessageError; use enum_dispatch::enum_dispatch; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::convert::TryFrom; +use strum_macros::Display; use super::errors::ButtplugError; diff --git a/crates/buttplug_core/src/message/v4/input_cmd.rs b/crates/buttplug_core/src/message/v4/input_cmd.rs index 2d64d802a..ee1cacc53 100644 --- a/crates/buttplug_core/src/message/v4/input_cmd.rs +++ b/crates/buttplug_core/src/message/v4/input_cmd.rs @@ -17,6 +17,7 @@ use crate::message::{ use getset::CopyGetters; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::{SeqAccess, Visitor}, ser::SerializeSeq}; use enumflags2::{BitFlags, bitflags}; +use strum_macros::Display; #[bitflags] #[repr(u8)] diff --git a/crates/buttplug_core/src/message/v4/input_reading.rs b/crates/buttplug_core/src/message/v4/input_reading.rs index 52f6665c5..1dd50d80c 100644 --- a/crates/buttplug_core/src/message/v4/input_reading.rs +++ b/crates/buttplug_core/src/message/v4/input_reading.rs @@ -8,6 +8,7 @@ use crate::message::{ButtplugDeviceMessage, ButtplugMessage, ButtplugMessageValidator, InputType}; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; +use strum_macros::Display; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CopyGetters)] #[getset(get_copy = "pub")] diff --git a/crates/buttplug_core/src/message/v4/output_cmd.rs b/crates/buttplug_core/src/message/v4/output_cmd.rs index d85ddaceb..719c42239 100644 --- a/crates/buttplug_core/src/message/v4/output_cmd.rs +++ b/crates/buttplug_core/src/message/v4/output_cmd.rs @@ -17,6 +17,7 @@ use crate::{ }; use getset::CopyGetters; use serde::{Deserialize, Serialize}; +use strum_macros::Display; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CopyGetters)] #[getset(get_copy = "pub")] diff --git a/crates/buttplug_core/src/message/v4/spec_enums.rs b/crates/buttplug_core/src/message/v4/spec_enums.rs index f58f7aac9..6d80ed2f9 100644 --- a/crates/buttplug_core/src/message/v4/spec_enums.rs +++ b/crates/buttplug_core/src/message/v4/spec_enums.rs @@ -22,6 +22,7 @@ use crate::message::{ }; use enum_dispatch::enum_dispatch; use serde::{Deserialize, Serialize}; +use strum_macros::Display; use super::{DeviceListV4, InputReadingV4}; diff --git a/crates/buttplug_server_device_config/src/lib.rs b/crates/buttplug_server_device_config/src/lib.rs index 953d24c87..587d743e0 100644 --- a/crates/buttplug_server_device_config/src/lib.rs +++ b/crates/buttplug_server_device_config/src/lib.rs @@ -159,30 +159,26 @@ pub use endpoint::*; use uuid::Uuid; use thiserror::Error; +use displaydoc::Display; -#[derive(Error, Debug)] +#[derive(Error, Debug, Display)] pub enum ButtplugDeviceConfigError { - /// Conversion to client type not possible with requested property type - #[error("Conversion of {0} to client type not possible with requested property type")] + /// Conversion of {0} to client type not possible with requested property type InvalidOutputTypeConversion(String), /// User set range exceeds bounds of possible configuration range - #[error("User set range exceeds bounds of possible configuration range")] InvalidUserRange, - /// Base range required - #[error("Base range required for all feature outputs")] + /// Base range required for all feature outputs BaseRangeRequired, - /// Base ID not found, cannot match user device/feature to a base device/feature - #[error("Device definition with base id {0} not found")] + /// Device definition with base id {0} not found BaseIdNotFound(Uuid), - #[error("Feature vectors between base and user device definitions do not match")] + /// Feature vectors between base and user device definitions do not match UserFeatureMismatch, - #[error("Output value {0} not in range {1}")] + /// Output value {0} not in range {1} InvalidOutputValue(i32, String), - #[error("Output type {0} not available on device")] + /// Output type {0} not available on device InvalidOutput(OutputType), - #[error("Float value {0} is not 0 < x < 1")] + /// Float value {0} is not 0 < x < 1 InvalidFloatConversion(f64), /// Feature or device is missing required base_id for user config conversion - #[error("Feature or device is missing required base_id for user config conversion")] MissingBaseId, } diff --git a/crates/buttplug_transport_websocket_tungstenite/Cargo.toml b/crates/buttplug_transport_websocket_tungstenite/Cargo.toml index a960bbd0e..f4602f26c 100644 --- a/crates/buttplug_transport_websocket_tungstenite/Cargo.toml +++ b/crates/buttplug_transport_websocket_tungstenite/Cargo.toml @@ -26,8 +26,6 @@ futures-util = "0.3.31" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" serde_repr = "0.1.20" -thiserror = "2.0.18" -displaydoc = "0.2.5" dashmap = { version = "6.1.0", features = ["serde"] } log = "0.4.29" getset = "0.1.6" From 8da8f885182dd85366ce7fea37f5e588da3b4e6e Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 14 Mar 2026 17:11:05 +0100 Subject: [PATCH 16/17] (potentially neeeds protocol version bump???) Removing the manual serialization of original_error makes it possible to compile buttplug without json at all (or at least with json function elided at link time) Care should be taken to ensure that the error_message field is still populated with a useful message, as the original_error field will not be serialized. So basically not sure this actually needs to go upstream. --- crates/buttplug_core/src/message/v0/error.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/buttplug_core/src/message/v0/error.rs b/crates/buttplug_core/src/message/v0/error.rs index 717a81988..f589b9c6c 100644 --- a/crates/buttplug_core/src/message/v0/error.rs +++ b/crates/buttplug_core/src/message/v0/error.rs @@ -45,7 +45,7 @@ pub struct ErrorV0 { #[serde(rename = "ErrorMessage")] #[getset(get = "pub")] error_message: String, - #[serde(skip)] + #[serde(rename = "OriginalError", skip_serializing_if = "Option::is_none")] original_error: Option, } @@ -88,10 +88,6 @@ impl ErrorV0 { if let Some(ref original_error) = self.original_error { original_error.clone() } else { - // Try deserializing what's in the error_message field - if let Ok(deserialized_msg) = serde_json::from_str(&self.error_message) { - return deserialized_msg; - } ButtplugError::from(self.clone()) } } @@ -108,9 +104,7 @@ impl From for ErrorV0 { ButtplugError::ButtplugHandshakeError { .. } => ErrorCode::ErrorHandshake, ButtplugError::ButtplugUnknownError { .. } => ErrorCode::ErrorUnknown, }; - // SAFETY: ButtplugError derives Serialize and contains only serializable fields. - // Serialization failure would indicate a bug in the type definition, not a runtime condition. - let msg = serde_json::to_string(&error).expect("ButtplugError derives Serialize"); + let msg = error.to_string(); ErrorV0::new(code, &msg, Some(error)) } } From 6ad2ec973ff4447ed89a563ee91e060c95f4781e Mon Sep 17 00:00:00 2001 From: Jasmin Bom Date: Sat, 14 Mar 2026 21:35:58 +0100 Subject: [PATCH 17/17] Add memory test with tracing spans and custom allocator to track per-span memory usage. --- crates/buttplug_tests/Cargo.toml | 3 + crates/buttplug_tests/tests/test_memory.rs | 415 +++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 crates/buttplug_tests/tests/test_memory.rs diff --git a/crates/buttplug_tests/Cargo.toml b/crates/buttplug_tests/Cargo.toml index aa05cc6c3..0e5ce9c33 100644 --- a/crates/buttplug_tests/Cargo.toml +++ b/crates/buttplug_tests/Cargo.toml @@ -16,6 +16,7 @@ buttplug_client = { version = "10.0.0", path = "../buttplug_client" } buttplug_client_in_process = { version = "10.0.0", path = "../buttplug_client_in_process", default-features = false} buttplug_server = { version = "10.0.0", path = "../buttplug_server" } buttplug_server_device_config = { version = "10.0.0", path = "../buttplug_server_device_config" } +buttplug_server_hwmgr_btleplug = { version = "10.0.0", path = "../buttplug_server_hwmgr_btleplug" } log = "0.4.29" tokio = { version = "1.49.0", features = ["macros"] } uuid = "1.20.0" @@ -31,3 +32,5 @@ getset = "0.1.6" jsonschema = { version = "0.38.1", default-features = false } test-case = "3.3.1" serde_yaml = "0.9.34" +tabled = "0.20" +humansize = "2" diff --git a/crates/buttplug_tests/tests/test_memory.rs b/crates/buttplug_tests/tests/test_memory.rs new file mode 100644 index 000000000..25226cd51 --- /dev/null +++ b/crates/buttplug_tests/tests/test_memory.rs @@ -0,0 +1,415 @@ +use buttplug_server_device_config::load_protocol_configs; +use humansize::{BINARY, format_size}; +use std::alloc::System; +use std::cell::Cell as TlsCell; +use std::collections::HashMap; +use std::sync::{Mutex, OnceLock}; +use tabled::settings::object::Rows; +use tabled::settings::style::HorizontalLine; +use tabled::{ + builder::Builder, + settings::{ + Alignment, + Modify, + Style, + object::{Cell, Columns}, + span::ColumnSpan, + }, +}; +use tracing::{Level, span}; +use tracing_subscriber::Layer; +use tracing_subscriber::layer::SubscriberExt; + +/// Run `$body` inside a named TRACE span. +/// Sync expr: `in_span!("name", expr)` +/// Sync block: `in_span!("name", { ... })` +/// Async: `in_span!("name", async { ... }).await` +macro_rules! in_span { + ($name:expr, async $body:block) => {{ + use tracing::Instrument; + async $body.instrument(span!(Level::TRACE, $name)) + }}; + ($name:expr, { $($body:tt)* }) => {{ + let _span = span!(Level::TRACE, $name).entered(); + $($body)* + }}; + ($name:expr, $body:expr) => {{ + let _span = span!(Level::TRACE, $name).entered(); + $body + }}; +} + +#[derive(Debug, Clone)] +struct Stats { + alloc: i64, + dealloc: i64, +} + +impl Stats { + fn delta(&self) -> i64 { + self.alloc - self.dealloc + } +} + +impl std::ops::Add for Stats { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + alloc: self.alloc + rhs.alloc, + dealloc: self.dealloc + rhs.dealloc, + } + } +} + +// --------------------------------------------------------------------------- +// Global allocator with per-span memory tracking +// --------------------------------------------------------------------------- + +#[global_allocator] +static GLOBAL: Allocator = Allocator {}; + +struct Allocator; + +// Fixed-size span ID stack stored in TLS – no heap allocation needed. +const STACK_DEPTH: usize = 64; + +thread_local! { + /// Set to true while we are inside span-stat bookkeeping to prevent re-entrant tracking. + static TRACKING: TlsCell = TlsCell::new(false); + /// Stack of active span IDs (index 0 = oldest, depth-1 = innermost). + static SPAN_STACK: TlsCell<[u64; STACK_DEPTH]> = TlsCell::new([0u64; STACK_DEPTH]); + static SPAN_DEPTH: TlsCell = TlsCell::new(0); +} + +/// RAII guard that holds the TRACKING flag for its lifetime. +/// Obtain one via `TrackingGuard::acquire()`; returns `None` when already tracking. +struct TrackingGuard; + +impl TrackingGuard { + fn acquire() -> Option { + TRACKING.with(|t| { + if t.get() { + None + } else { + t.set(true); + Some(TrackingGuard) + } + }) + } +} + +impl Drop for TrackingGuard { + fn drop(&mut self) { + TRACKING.with(|t| t.set(false)); + } +} + +fn push_span(id: u64) { + SPAN_DEPTH.with(|d| { + let depth = d.get(); + if depth < STACK_DEPTH { + SPAN_STACK.with(|s| { + let mut arr = s.get(); + arr[depth] = id; + s.set(arr); + }); + } + d.set(depth + 1); + }); +} + +fn pop_span(id: u64) { + SPAN_DEPTH.with(|d| { + let depth = d.get(); + if depth > 0 { + SPAN_STACK.with(|s| { + let arr = s.get(); + if arr[depth - 1] == id { + d.set(depth - 1); + } + }); + } + }); +} + +fn current_span() -> Option { + SPAN_DEPTH.with(|d| { + let depth = d.get(); + if depth == 0 { + return None; + } + SPAN_STACK.with(|s| { + let id = s.get()[depth - 1]; + if id == 0 { None } else { Some(id) } + }) + }) +} + +// --------------------------------------------------------------------------- +// Per-span statistics +// --------------------------------------------------------------------------- + +struct SpanStats { + name: String, + parent: Option, + own: Stats, +} + +impl SpanStats { + fn total(reg: &SpanRegistry, id: u64) -> Stats { + let s = ®.stats[&id]; + let mut total = s.own.clone(); + for &child_id in ®.order { + if reg.stats[&child_id].parent == Some(id) { + total = total + SpanStats::total(reg, child_id); + } + } + total + } +} + +struct SpanRegistry { + stats: HashMap, + /// Insertion order – used when printing the tree. + order: Vec, +} + +impl SpanRegistry { + fn new() -> Self { + Self { + stats: HashMap::new(), + order: Vec::new(), + } + } +} + +static REGISTRY: OnceLock> = OnceLock::new(); + +fn registry() -> &'static Mutex { + REGISTRY.get_or_init(|| Mutex::new(SpanRegistry::new())) +} + +// --------------------------------------------------------------------------- +// GlobalAlloc impl +// --------------------------------------------------------------------------- + +unsafe impl std::alloc::GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 { + let ptr = unsafe { System.alloc(layout) }; + + if let Some(_guard) = TrackingGuard::acquire() { + if let Some(span_id) = current_span() { + if let Ok(mut reg) = registry().try_lock() { + if let Some(s) = reg.stats.get_mut(&span_id) { + s.own.alloc += layout.size() as i64; + } + } + } + } + + ptr + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) { + unsafe { System.dealloc(ptr, layout) }; + + if let Some(_guard) = TrackingGuard::acquire() { + if let Some(span_id) = current_span() { + if let Ok(mut reg) = registry().try_lock() { + if let Some(s) = reg.stats.get_mut(&span_id) { + s.own.dealloc += layout.size() as i64; + } + } + } + } + } +} + +// --------------------------------------------------------------------------- +// Tracing layer – captures span lifecycle to drive the ID stack +// --------------------------------------------------------------------------- + +struct MemTrackingLayer; + +impl Layer for MemTrackingLayer { + fn on_new_span( + &self, + attrs: &span::Attributes<'_>, + id: &tracing::span::Id, + _ctx: tracing_subscriber::layer::Context<'_, S>, + ) { + let span_id = id.clone().into_u64(); + let parent = current_span(); + + // Hold the guard so HashMap's own allocations aren't attributed anywhere. + let _guard = TrackingGuard::acquire(); + if let Ok(mut reg) = registry().try_lock() { + reg.order.push(span_id); + reg.stats.insert( + span_id, + SpanStats { + name: attrs.metadata().name().to_string(), + parent, + own: Stats { + alloc: 0, + dealloc: 0, + }, + }, + ); + } + } + + fn on_enter(&self, id: &tracing::span::Id, _ctx: tracing_subscriber::layer::Context<'_, S>) { + push_span(id.clone().into_u64()); + } + + fn on_exit(&self, id: &tracing::span::Id, _ctx: tracing_subscriber::layer::Context<'_, S>) { + pop_span(id.clone().into_u64()); + } +} + +// --------------------------------------------------------------------------- +// Tree printer +// --------------------------------------------------------------------------- + +fn format_bytes(bytes: i64) -> String { + format_size(bytes.unsigned_abs(), BINARY) +} + +fn signed_bytes(bytes: i64) -> String { + let sign = if bytes < 0 { "-" } else { "+" }; + format!("{}{}", sign, format_size(bytes.unsigned_abs(), BINARY)) +} + +fn collect_rows(reg: &SpanRegistry, parent: Option, depth: usize, builder: &mut Builder) { + for &id in ®.order { + let s = ®.stats[&id]; + if s.parent != parent { + continue; + } + let total = SpanStats::total(reg, id); + builder.push_record([ + format!("{}{}", " ".repeat(depth), s.name), + format_bytes(s.own.alloc), + format_bytes(s.own.dealloc), + signed_bytes(s.own.delta()), + format_bytes(total.alloc), + format_bytes(total.dealloc), + signed_bytes(total.delta()), + ]); + collect_rows(reg, Some(id), depth + 1, builder); + } +} + +fn print_tree(reg: &SpanRegistry) { + let mut builder = Builder::new(); + builder.push_record(["Span", "Own", "", "", "Total", "", ""]); + builder.push_record(["", "+", "-", "Δ", "+", "-", "Δ"]); + collect_rows(reg, None, 0, &mut builder); + + let table = builder + .build() + .with(Style::sharp().horizontals([(2, HorizontalLine::inherit(Style::modern()))])) + .with(Modify::new(Columns::new(1..)).with(Alignment::right())) + .with(Modify::new(Rows::new(0..2)).with(Alignment::center())) + .with(Modify::new(Columns::first()).with(Alignment::left())) + .with(Modify::new(Cell::new(0, 1)).with(ColumnSpan::new(3))) + .with(Modify::new(Cell::new(0, 4)).with(ColumnSpan::new(3))) + .to_string(); + println!("{table}"); +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[test] +fn test_memory() { + let subscriber = tracing_subscriber::registry().with(MemTrackingLayer); + let _guard = tracing::subscriber::set_default(subscriber); + + tokio::runtime::Builder::new_current_thread() + .enable_all() + .start_paused(true) + .build() + .unwrap() + .block_on(async { + let _client = memory_test().await; + + let reg = registry().lock().unwrap(); + print_tree(®); + }); +} + +async fn memory_test() -> buttplug_client::ButtplugClient { + in_span!("memory_test", { + let dcm = in_span!("DeviceConfigurationManager", { + let builder = in_span!( + "load_protocol_configs", + load_protocol_configs(&None, &None, false).unwrap() + ); + let dcm = in_span!( + "DeviceConfigurationManagerBuilder.finish", + builder.finish().unwrap() + ); + dcm + }); + + let manager = in_span!("ServerDeviceManager", { + let mut builder = in_span!( + "ServerDeviceManagerBuilder::new", + buttplug_server::device::ServerDeviceManagerBuilder::new(dcm) + ); + + in_span!( + "builder.comm_manager(BtlePlugCommunicationManagerBuilder)", + builder.comm_manager( + buttplug_server_hwmgr_btleplug::BtlePlugCommunicationManagerBuilder::default() + ) + ); + + in_span!( + "ServerDeviceManagerBuilder.finish", + builder.finish().unwrap() + ) + }); + + let server = in_span!("ButtplugServerBuilder", { + let builder = buttplug_server::ButtplugServerBuilder::new(manager); + + in_span!("ButtplugServerBuilder.finish", builder.finish().unwrap()) + }); + + let connector = in_span!("ButtplugClientInProcessConnector", { + let mut builder = in_span!( + "ButtplugInProcessClientConnectorBuilder::default", + buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder::default() + ); + + in_span!( + "ButtplugClientInProcessConnectorBuilder.server", + builder.server(server) + ); + + in_span!( + "ButtplugInProcessClientConnectorBuilder.finish", + builder.finish() + ) + }); + + in_span!("ButtplugClient", { + let client = in_span!( + "ButtplugClient::new", + buttplug_client::ButtplugClient::new("Memory Test Client") + ); + + in_span!( + "ButtplugClient.connect", + client.connect(connector).await.unwrap() + ); + + client + }) + }) +}