//! Sound trigger routing and simulator sound event decoding. #![allow(clippy::missing_errors_doc)] #![allow(clippy::must_use_candidate)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::too_many_arguments)] #![allow(clippy::collapsible_if)] // Packet decoding and payload bounds remain distinct checks. #![allow(clippy::unnecessary_wraps)] // Mapped constructors have fixed fallible signatures. use crate::agent_manager::EventRegistry; use crate::packet_catalog::{GeneratedPacket, PacketType}; use crate::{Error, GridClient, Simulator}; use libremetaverse_types::compat::{EventHandler, Subscription}; use libremetaverse_types::{SoundFlags, UUID, Vector3}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; const MAX_SOUND_PACKET_BYTES: usize = 1024 * 1024; fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { value .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) } #[derive(Clone)] pub struct AttachedSoundEventArgs { sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, gain: f32, flags: SoundFlags, } impl AttachedSoundEventArgs { pub fn new( sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, gain: f32, flags: SoundFlags, ) -> Result { Ok(Self { sim, sound_id, owner_id, object_id, gain, flags, }) } pub fn simulator(&self) -> Simulator { self.sim.clone() } pub fn sound_id(&self) -> UUID { self.sound_id } pub fn owner_id(&self) -> UUID { self.owner_id } pub fn object_id(&self) -> UUID { self.object_id } pub fn gain(&self) -> f32 { self.gain } pub fn flags(&self) -> SoundFlags { self.flags } } #[derive(Clone)] pub struct AttachedSoundGainChangeEventArgs { sim: Simulator, object_id: UUID, gain: f32, } impl AttachedSoundGainChangeEventArgs { pub fn new(sim: Simulator, object_id: UUID, gain: f32) -> Result { Ok(Self { sim, object_id, gain, }) } pub fn simulator(&self) -> Simulator { self.sim.clone() } pub fn object_id(&self) -> UUID { self.object_id } pub fn gain(&self) -> f32 { self.gain } } #[derive(Clone)] pub struct PreloadSoundEventArgs { sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, } impl PreloadSoundEventArgs { pub fn new( sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, ) -> Result { Ok(Self { sim, sound_id, owner_id, object_id, }) } pub fn simulator(&self) -> Simulator { self.sim.clone() } pub fn sound_id(&self) -> UUID { self.sound_id } pub fn owner_id(&self) -> UUID { self.owner_id } pub fn object_id(&self) -> UUID { self.object_id } } #[derive(Clone)] pub struct SoundTriggerEventArgs { sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, parent_id: UUID, gain: f32, region_handle: u64, position: Vector3, } impl SoundTriggerEventArgs { pub fn new( sim: Simulator, sound_id: UUID, owner_id: UUID, object_id: UUID, parent_id: UUID, gain: f32, region_handle: u64, position: Vector3, ) -> Result { Ok(Self { sim, sound_id, owner_id, object_id, parent_id, gain, region_handle, position, }) } pub fn simulator(&self) -> Simulator { self.sim.clone() } pub fn sound_id(&self) -> UUID { self.sound_id } pub fn owner_id(&self) -> UUID { self.owner_id } pub fn object_id(&self) -> UUID { self.object_id } pub fn parent_id(&self) -> UUID { self.parent_id } pub fn gain(&self) -> f32 { self.gain } pub fn region_handle(&self) -> u64 { self.region_handle } pub fn position(&self) -> Vector3 { self.position } } #[derive(Default)] struct SoundEvents { attached: EventRegistry, gain: EventRegistry, preload: EventRegistry, trigger: EventRegistry, } pub(crate) struct SoundManagerInner { client: Arc, events: SoundEvents, raw_subscription: Mutex>, disposed: AtomicBool, } #[derive(Clone)] pub struct SoundManager { pub(crate) inner: Arc, } impl SoundManager { pub fn new(client: GridClient) -> Result { Self::native_new(Arc::new(client)) } pub(crate) fn native_new(client: Arc) -> Result { if let Some(inner) = client.cached_sound_manager_inner() { return Ok(Self { inner }); } let inner = Arc::new(SoundManagerInner { client: Arc::clone(&client), events: SoundEvents::default(), raw_subscription: Mutex::new(None), disposed: AtomicBool::new(false), }); let weak = Arc::downgrade(&inner); *mutex(&inner.raw_subscription) = Some(client.network().subscribe_raw_packet(Arc::new( move |event| { if let Some(inner) = weak.upgrade() { inner.handle(event); } }, ))); Ok(Self { inner: client.install_sound_manager_inner(inner), }) } pub fn subscribe_attached_sound( &self, handler: EventHandler, ) -> Subscription { self.inner.events.attached.subscribe(handler) } pub fn subscribe_attached_sound_gain_change( &self, handler: EventHandler, ) -> Subscription { self.inner.events.gain.subscribe(handler) } pub fn subscribe_preload_sound( &self, handler: EventHandler, ) -> Subscription { self.inner.events.preload.subscribe(handler) } pub fn subscribe_sound_trigger( &self, handler: EventHandler, ) -> Subscription { self.inner.events.trigger.subscribe(handler) } pub fn dispose(&self) -> Result<(), Error> { self.inner.disposed.store(true, Ordering::Release); mutex(&self.inner.raw_subscription).take(); Ok(()) } pub fn play_sound(&self, sound_id: UUID) -> Result<(), Error> { let mut client = (*self.inner.client).clone(); let position = client.self_().sim_position(); self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0) } pub fn send_sound_trigger_with_uuid_vector3( &self, sound_id: UUID, position: Vector3, ) -> Result<(), Error> { self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0) } pub fn send_sound_trigger_with_uuid_vector3_single( &self, sound_id: UUID, position: Vector3, gain: f32, ) -> Result<(), Error> { let sim = self .inner .client .network() .current_sim() .ok_or(Error::InvalidOperation)?; self.send_to_sim(sound_id, &sim, sim.handle, position, gain) } pub fn send_sound_trigger_with_uuid_simulator_vector3_single( &self, sound_id: UUID, sim: Simulator, position: Vector3, gain: f32, ) -> Result<(), Error> { self.send_to_sim(sound_id, &sim, sim.handle, position, gain) } pub fn send_sound_trigger_with_uuid_u_int64_vector3_single( &self, sound_id: UUID, handle: u64, position: Vector3, gain: f32, ) -> Result<(), Error> { let sim = self .inner .client .network() .current_sim() .ok_or(Error::InvalidOperation)?; self.send_to_sim(sound_id, &sim, handle, position, gain) } fn send_to_sim( &self, sound_id: UUID, sim: &Simulator, handle: u64, position: Vector3, gain: f32, ) -> Result<(), Error> { if self.inner.disposed.load(Ordering::Acquire) { return Err(Error::InvalidOperation); } if sound_id == UUID::zero() || !gain.is_finite() || !position.x.is_finite() || !position.y.is_finite() || !position.z.is_finite() { return Err(Error::Argument); } let mut packet = crate::packets::SoundTriggerPacket::new_with_constructor()?; packet.sound_data.sound_id = sound_id; packet.sound_data.object_id = UUID::zero(); packet.sound_data.owner_id = UUID::zero(); packet.sound_data.parent_id = UUID::zero(); packet.sound_data.handle = handle; packet.sound_data.position = position; packet.sound_data.gain = gain.clamp(0.0, 1.0); let bytes = packet.to_bytes_with_method()?; sim.native_send_packet_data( bytes.clone(), i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, PacketType::SoundTrigger, false, ) } } impl SoundManagerInner { fn handle(&self, event: crate::network_manager::RawPacketReceivedEventArgs) { if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_SOUND_PACKET_BYTES { return; } let mut position = 0; match event.packet_type { PacketType::AttachedSound => { if let Ok(packet) = crate::packets::AttachedSoundPacket::new_from_bytes(&event.data, &mut position) { let value = packet.data_block; self.events.attached.emit(AttachedSoundEventArgs { sim: event.simulator, sound_id: value.sound_id, owner_id: value.owner_id, object_id: value.object_id, gain: value.gain, flags: SoundFlags(value.flags), }); } } PacketType::AttachedSoundGainChange => { if let Ok(packet) = crate::packets::AttachedSoundGainChangePacket::new_from_bytes( &event.data, &mut position, ) { self.events.gain.emit(AttachedSoundGainChangeEventArgs { sim: event.simulator, object_id: packet.data_block.object_id, gain: packet.data_block.gain, }); } } PacketType::PreloadSound => { if let Ok(packet) = crate::packets::PreloadSoundPacket::new_from_bytes(&event.data, &mut position) { if packet.data_block.len() <= 65_535 { for value in packet.data_block { self.events.preload.emit(PreloadSoundEventArgs { sim: event.simulator.clone(), sound_id: value.sound_id, owner_id: value.owner_id, object_id: value.object_id, }); } } } } PacketType::SoundTrigger => { if let Ok(packet) = crate::packets::SoundTriggerPacket::new_from_bytes(&event.data, &mut position) { let value = packet.sound_data; self.events.trigger.emit(SoundTriggerEventArgs { sim: event.simulator, sound_id: value.sound_id, owner_id: value.owner_id, object_id: value.object_id, parent_id: value.parent_id, gain: value.gain, region_handle: value.handle, position: value.position, }); } } _ => {} } } } #[cfg(test)] mod tests { use super::*; #[test] fn sound_trigger_rejects_zero_ids_and_non_finite_gain() { let client = GridClient::new().unwrap(); let manager = SoundManager::new(client).unwrap(); assert!( manager .send_sound_trigger_with_uuid_u_int64_vector3_single( UUID::zero(), 0, Vector3::default(), 1.0 ) .is_err() ); } #[test] fn event_args_preserve_gain_and_queue_identity() { let client = GridClient::new().unwrap(); let sim = Simulator::new(client, "127.0.0.1:13".parse().unwrap(), 0, None, None).unwrap(); let id = UUID::random().unwrap(); let value = AttachedSoundEventArgs::new(sim, id, UUID::zero(), UUID::zero(), 0.25, SoundFlags(2)) .unwrap(); assert_eq!(value.sound_id(), id); assert_eq!(value.gain(), 0.25); assert_eq!(value.flags(), SoundFlags(2)); } #[test] fn attached_sound_packet_preserves_ids_gain_and_queue_flag() { let client = GridClient::new().unwrap(); let sim = Simulator::new( client.clone(), "127.0.0.1:13".parse().unwrap(), 0, None, None, ) .unwrap(); let manager = SoundManager::new(client).unwrap(); let observed = Arc::new(Mutex::new(None)); let captured = Arc::clone(&observed); let _subscription = manager.subscribe_attached_sound(Arc::new(move |event| { *mutex(&captured) = Some(( event.sound_id(), event.owner_id(), event.object_id(), event.gain(), event.flags(), )); })); let sound_id = UUID::random().unwrap(); let owner_id = UUID::random().unwrap(); let object_id = UUID::random().unwrap(); let mut packet = crate::packets::AttachedSoundPacket::new_with_constructor().unwrap(); packet.data_block.sound_id = sound_id; packet.data_block.owner_id = owner_id; packet.data_block.object_id = object_id; packet.data_block.gain = 0.375; packet.data_block.flags = 2; manager .inner .handle(crate::network_manager::RawPacketReceivedEventArgs { packet_type: PacketType::AttachedSound, data: packet.to_bytes_with_method().unwrap(), simulator: sim, }); assert_eq!( mutex(&observed).clone(), Some((sound_id, owner_id, object_id, 0.375, SoundFlags(2))) ); } }