@@ -12,6 +12,7 @@ use std::{
1212 collections:: HashMap ,
1313 fmt:: { self , Debug } ,
1414 sync:: Arc ,
15+ sync:: RwLock ,
1516} ;
1617use uuid:: Uuid ;
1718
@@ -21,8 +22,8 @@ use crate::{
2122 ProtocolCommunicationSpecifier ,
2223 ServerDeviceDefinition ,
2324 ServerDeviceDefinitionBuilder ,
24- UserDeviceIdentifier ,
2525 SimulatedDeviceConfigEntry ,
26+ UserDeviceIdentifier ,
2627 device_config_file:: { SimulatedDeviceArchetype , SimulatedDeviceFeatureSummary } ,
2728} ;
2829
@@ -164,7 +165,7 @@ impl DeviceConfigurationManagerBuilder {
164165 user_communication_specifiers : self . user_communication_specifiers . clone ( ) ,
165166 base_device_definitions : attribute_tree_map,
166167 user_device_definitions : user_attribute_tree_map,
167- simulated_devices : self . simulated_devices . clone ( ) ,
168+ simulated_devices_store : RwLock :: new ( self . simulated_devices . clone ( ) ) ,
168169 //protocol_map,
169170 } )
170171 }
@@ -200,8 +201,7 @@ pub struct DeviceConfigurationManager {
200201 #[ getset( get = "pub" ) ]
201202 user_device_definitions : DashMap < UserDeviceIdentifier , ServerDeviceDefinition > ,
202203 /// Simulated device configurations from the user config.
203- #[ getset( get = "pub" ) ]
204- simulated_devices : Vec < SimulatedDeviceConfigEntry > ,
204+ simulated_devices_store : RwLock < Vec < SimulatedDeviceConfigEntry > > ,
205205}
206206
207207impl Debug for DeviceConfigurationManager {
@@ -239,10 +239,76 @@ impl DeviceConfigurationManager {
239239 user_communication_specifiers : DashMap :: new ( ) ,
240240 base_device_definitions,
241241 user_device_definitions : DashMap :: new ( ) ,
242- simulated_devices : Vec :: new ( ) ,
242+ simulated_devices_store : RwLock :: new ( Vec :: new ( ) ) ,
243243 }
244244 }
245245
246+ pub fn simulated_devices ( & self ) -> Vec < SimulatedDeviceConfigEntry > {
247+ self
248+ . simulated_devices_store
249+ . read ( )
250+ . expect ( "Simulated device config lock should not be poisoned" )
251+ . clone ( )
252+ }
253+
254+ fn valid_simulated_archetypes ( & self ) -> std:: collections:: HashSet < String > {
255+ self
256+ . base_communication_specifiers
257+ . get ( "simulated" )
258+ . into_iter ( )
259+ . flat_map ( |specifiers| specifiers. iter ( ) )
260+ . filter_map ( |spec| {
261+ if let ProtocolCommunicationSpecifier :: Simulated ( sim) = spec {
262+ Some ( sim. names ( ) . iter ( ) . cloned ( ) )
263+ } else {
264+ None
265+ }
266+ } )
267+ . flatten ( )
268+ . collect ( )
269+ }
270+
271+ pub fn add_simulated_device (
272+ & self ,
273+ device : SimulatedDeviceConfigEntry ,
274+ ) -> Result < ( ) , ButtplugDeviceError > {
275+ let valid_archetypes = self . valid_simulated_archetypes ( ) ;
276+ if !valid_archetypes. contains ( & device. identifier ) {
277+ return Err ( ButtplugDeviceError :: DeviceConfigurationError ( format ! (
278+ "Invalid simulated device archetype '{}'. Valid archetypes: {:?}" ,
279+ device. identifier, valid_archetypes
280+ ) ) ) ;
281+ }
282+
283+ let mut simulated_devices = self
284+ . simulated_devices_store
285+ . write ( )
286+ . expect ( "Simulated device config lock should not be poisoned" ) ;
287+ if simulated_devices
288+ . iter ( )
289+ . any ( |existing| existing. address == device. address )
290+ {
291+ return Err ( ButtplugDeviceError :: DeviceConfigurationError ( format ! (
292+ "Duplicate simulated device address '{}' for archetype '{}'" ,
293+ device. address, device. identifier
294+ ) ) ) ;
295+ }
296+
297+ simulated_devices. push ( device) ;
298+ Ok ( ( ) )
299+ }
300+
301+ pub fn remove_simulated_device ( & self , address : & str ) {
302+ let mut simulated_devices = self
303+ . simulated_devices_store
304+ . write ( )
305+ . expect ( "Simulated device config lock should not be poisoned" ) ;
306+ simulated_devices. retain ( |device| device. address != address) ;
307+ self . user_device_definitions . retain ( |identifier, _| {
308+ identifier. protocol ( ) != "simulated" || identifier. address ( ) != address
309+ } ) ;
310+ }
311+
246312 pub fn add_user_communication_specifier (
247313 & self ,
248314 protocol : & str ,
@@ -403,7 +469,9 @@ impl DeviceConfigurationManager {
403469 identifier : & UserDeviceIdentifier ,
404470 ) {
405471 if let Some ( entry) = self
406- . simulated_devices
472+ . simulated_devices_store
473+ . read ( )
474+ . expect ( "Simulated device config lock should not be poisoned" )
407475 . iter ( )
408476 . find ( |d| d. address ( ) == identifier. address ( ) )
409477 {
@@ -449,3 +517,60 @@ impl DeviceConfigurationManager {
449517 . collect ( )
450518 }
451519}
520+
521+ #[ cfg( test) ]
522+ mod tests {
523+ use super :: * ;
524+ use crate :: load_protocol_configs;
525+
526+ #[ test]
527+ fn test_add_simulated_device_validates_archetype_and_address ( ) {
528+ let dcm = load_protocol_configs ( & None , & None , false )
529+ . expect ( "Should load base configs" )
530+ . finish ( )
531+ . expect ( "Should build DCM" ) ;
532+
533+ let entry = SimulatedDeviceConfigEntry :: new ( "simulated-1vibe" , None ) ;
534+ let duplicate = entry. clone ( ) ;
535+
536+ dcm
537+ . add_simulated_device ( entry)
538+ . expect ( "Valid simulated archetype should add" ) ;
539+ assert_eq ! ( dcm. simulated_devices( ) . len( ) , 1 ) ;
540+
541+ let duplicate_result = dcm. add_simulated_device ( duplicate) ;
542+ assert ! ( duplicate_result. is_err( ) ) ;
543+
544+ let invalid_result = dcm. add_simulated_device ( SimulatedDeviceConfigEntry :: new (
545+ "not-a-simulated-device" ,
546+ None ,
547+ ) ) ;
548+ assert ! ( invalid_result. is_err( ) ) ;
549+ }
550+
551+ #[ test]
552+ fn test_remove_simulated_device_removes_matching_user_definition ( ) {
553+ let dcm = load_protocol_configs ( & None , & None , false )
554+ . expect ( "Should load base configs" )
555+ . finish ( )
556+ . expect ( "Should build DCM" ) ;
557+
558+ let entry = SimulatedDeviceConfigEntry :: new ( "simulated-1vibe" , None ) ;
559+ let address = entry. address . clone ( ) ;
560+ let identifier =
561+ UserDeviceIdentifier :: new ( & address, "simulated" , & Some ( entry. identifier . clone ( ) ) ) ;
562+
563+ dcm
564+ . add_simulated_device ( entry)
565+ . expect ( "Valid simulated archetype should add" ) ;
566+ dcm
567+ . device_definition ( & identifier)
568+ . expect ( "Simulated device definition should resolve" ) ;
569+ assert ! ( dcm. user_device_definitions( ) . contains_key( & identifier) ) ;
570+
571+ dcm. remove_simulated_device ( & address) ;
572+
573+ assert ! ( dcm. simulated_devices( ) . is_empty( ) ) ;
574+ assert ! ( !dcm. user_device_definitions( ) . contains_key( & identifier) ) ;
575+ }
576+ }
0 commit comments