498 lines
15 KiB
Rust
498 lines
15 KiB
Rust
//! 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<T>(value: &Mutex<T>) -> 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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<AttachedSoundEventArgs>,
|
|
gain: EventRegistry<AttachedSoundGainChangeEventArgs>,
|
|
preload: EventRegistry<PreloadSoundEventArgs>,
|
|
trigger: EventRegistry<SoundTriggerEventArgs>,
|
|
}
|
|
pub(crate) struct SoundManagerInner {
|
|
client: Arc<GridClient>,
|
|
events: SoundEvents,
|
|
raw_subscription: Mutex<Option<Subscription>>,
|
|
disposed: AtomicBool,
|
|
}
|
|
#[derive(Clone)]
|
|
pub struct SoundManager {
|
|
pub(crate) inner: Arc<SoundManagerInner>,
|
|
}
|
|
impl SoundManager {
|
|
pub fn new(client: GridClient) -> Result<Self, Error> {
|
|
Self::native_new(Arc::new(client))
|
|
}
|
|
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
|
|
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<AttachedSoundEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.attached.subscribe(handler)
|
|
}
|
|
pub fn subscribe_attached_sound_gain_change(
|
|
&self,
|
|
handler: EventHandler<AttachedSoundGainChangeEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.gain.subscribe(handler)
|
|
}
|
|
pub fn subscribe_preload_sound(
|
|
&self,
|
|
handler: EventHandler<PreloadSoundEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.preload.subscribe(handler)
|
|
}
|
|
pub fn subscribe_sound_trigger(
|
|
&self,
|
|
handler: EventHandler<SoundTriggerEventArgs>,
|
|
) -> 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)))
|
|
);
|
|
}
|
|
}
|