Describe the bug
This is a bit of a tricky situation. For toys like the Solace Pro with multiple ways of "actuating" (notably either a frequency with steps, or a specific position with a time to get there, the famous hw_position_with_duration), it ends up being declared twice there:
|
let scalar_attrs: Vec<ClientGenericDeviceMessageAttributesV3> = features |
|
.iter() |
|
.flat_map(|feature| { |
|
let mut actuator_vec = vec![]; |
|
if let Some(output_map) = feature.output() { |
|
let mut create_actuator = |
|
|actuator_type, actuator: &DeviceFeatureOutputValueProperties| { |
|
let attrs = ClientGenericDeviceMessageAttributesV3 { |
|
feature_descriptor: feature.description().to_owned(), |
|
actuator_type, |
|
step_count: actuator.step_count(), |
|
index: 0, |
|
}; |
|
actuator_vec.push(attrs) |
|
}; |
|
if let Some(x) = output_map.constrict().as_ref() { |
|
create_actuator(OutputType::Constrict, x) |
|
} |
|
if let Some(x) = output_map.temperature().as_ref() { |
|
create_actuator(OutputType::Temperature, x) |
|
} |
|
if let Some(x) = output_map.led().as_ref() { |
|
create_actuator(OutputType::Led, x) |
|
} |
|
if let Some(x) = output_map.oscillate().as_ref() { |
|
create_actuator(OutputType::Oscillate, x) |
|
} |
|
if let Some(x) = output_map.position().as_ref() { |
|
create_actuator(OutputType::Position, x) |
|
} |
|
if let Some(x) = output_map.rotate().as_ref() { |
|
create_actuator(OutputType::Rotate, x) |
|
} |
|
if let Some(x) = output_map.spray().as_ref() { |
|
create_actuator(OutputType::Spray, x) |
|
} |
|
if let Some(x) = output_map.vibrate().as_ref() { |
|
create_actuator(OutputType::Vibrate, x) |
|
} |
|
} |
|
actuator_vec |
|
}) |
|
.collect(); |
|
|
|
// We have to calculate rotation attributes seperately, since they're a combination of |
|
// feature type and message in >= v4. |
|
let rotate_attrs: Vec<ClientGenericDeviceMessageAttributesV3> = features |
|
.iter() |
|
.flat_map(|feature| { |
|
let mut actuator_vec = vec![]; |
|
if let Some(output_map) = feature.output() |
|
&& let Some(actuator) = output_map.rotate() |
|
&& *actuator.value().start() < 0 |
|
{ |
|
let actuator_type = OutputType::Rotate; |
|
let attrs = ClientGenericDeviceMessageAttributesV3 { |
|
feature_descriptor: feature.description().to_owned(), |
|
actuator_type, |
|
step_count: actuator.step_count(), |
|
index: 0, |
|
}; |
|
actuator_vec.push(attrs) |
|
} |
|
actuator_vec |
|
}) |
|
.collect(); |
|
|
|
let linear_attrs: Vec<ClientGenericDeviceMessageAttributesV3> = features |
|
.iter() |
|
.flat_map(|feature| { |
|
let mut actuator_vec = vec![]; |
|
if let Some(output_map) = feature.output() |
|
&& let Some(actuator) = output_map.hw_position_with_duration() |
|
{ |
|
let actuator_type = OutputType::Position; |
|
let attrs = ClientGenericDeviceMessageAttributesV3 { |
|
feature_descriptor: feature.description().to_owned(), |
|
actuator_type, |
|
step_count: actuator.step_count(), |
|
index: 0, |
|
}; |
|
actuator_vec.push(attrs) |
|
} |
|
actuator_vec |
|
}) |
|
.collect(); |
Which then means that client apps (especially games) with poor UX will just control a whole toy without allowing to enable or disable each actuator. This is especially true for the ones using the older version of the protocol, such as
https://github.com/DeviantdVeloper/ButtplugUE. And in the end, the toy gets confused, receive both message almost at the same time and just ends up stuttering, not moving, or moving in weird ways.
Expected behavior
This is also a tricky part. Exposing only the scalar would mean that better apps cannot use advanced timing and positioning, while only exposing the linear means bad apps won't be able to use the toy. I think the best would be to offer a configuration option to choose which feature to expose for the older protocols.
Actual behavior
All the features are mapped in their respective categories, even if targeting the same movement, ending up messing with the toy and not really being usable.
Additional context
Toys having multiple features don't seem that common, to be honest. But in the case of the Solace Pro, it really prevents using most of the experiences, unfortunately... (I wish the integration was better, but well, it's difficult to ask everyone to fix that...)
Describe the bug
This is a bit of a tricky situation. For toys like the Solace Pro with multiple ways of "actuating" (notably either a frequency with steps, or a specific position with a time to get there, the famous
hw_position_with_duration), it ends up being declared twice there:buttplug/crates/buttplug_server/src/message/v3/client_device_message_attributes.rs
Lines 245 to 330 in 8ecb259
Which then means that client apps (especially games) with poor UX will just control a whole toy without allowing to enable or disable each actuator. This is especially true for the ones using the older version of the protocol, such as https://github.com/DeviantdVeloper/ButtplugUE. And in the end, the toy gets confused, receive both message almost at the same time and just ends up stuttering, not moving, or moving in weird ways.
Expected behavior
This is also a tricky part. Exposing only the scalar would mean that better apps cannot use advanced timing and positioning, while only exposing the linear means bad apps won't be able to use the toy. I think the best would be to offer a configuration option to choose which feature to expose for the older protocols.
Actual behavior
All the features are mapped in their respective categories, even if targeting the same movement, ending up messing with the toy and not really being usable.
Additional context
Toys having multiple features don't seem that common, to be honest. But in the case of the Solace Pro, it really prevents using most of the experiences, unfortunately... (I wish the integration was better, but well, it's difficult to ask everyone to fix that...)