Implement land, terrain, environment, and sound managers (#70)
This commit is contained in:
497
crates/libremetaverse/src/sound_manager.rs
Normal file
497
crates/libremetaverse/src/sound_manager.rs
Normal file
@@ -0,0 +1,497 @@
|
||||
//! 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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user