//! Grid map discovery, region caches, map items, and simulator feature storage. #![allow(clippy::missing_errors_doc)] #![allow(clippy::must_use_candidate)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_possible_truncation)] // Mapped hash and wire casts match CLR semantics. #![allow(clippy::cast_sign_loss)] // Positive timeout is clamped before conversion. #![allow(clippy::duration_suboptimal_units)] // Cache duration is explicit in protocol seconds. #![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method. #![allow(clippy::too_many_lines)] // Packet dispatch is centralized and bounded per packet. #![allow(clippy::unnecessary_wraps)] // Fixed mapped constructors are fallible. use crate::agent_manager::EventRegistry; use crate::network_manager::RawPacketReceivedEventArgs; use crate::packet_catalog::{GeneratedPacket, PacketType}; use crate::{Error, GridClient, GridItemType, GridLayerType, RegionFlags, SimAccess, Simulator}; use futures_channel::oneshot; use futures_util::future::{Either, select}; use futures_util::pin_mut; use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser}; use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; use libremetaverse_types::{UUID, Utils, Vector3}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, SystemTime}; use tokio::sync::Notify; const MAX_GRID_PACKET_BYTES: usize = 8 * 1024 * 1024; const CACHE_TTL: Duration = Duration::from_secs(15 * 60); fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { value .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { value .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { value .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn wire_string(value: &[u8]) -> String { String::from_utf8_lossy(value.split(|byte| *byte == 0).next().unwrap_or_default()).into_owned() } #[derive(Clone, Debug, Eq, PartialEq)] pub struct GridLayer { pub bottom: i32, pub image_id: UUID, pub left: i32, pub right: i32, pub top: i32, } impl GridLayer { pub fn contains_region(&self, x: i32, y: i32) -> Result { Ok(x >= self.left && x <= self.right && y >= self.bottom && y <= self.top) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct GridRegion { pub access: SimAccess, pub agents: u8, pub map_image_id: UUID, pub name: String, pub region_flags: RegionFlags, pub region_handle: u64, pub water_height: u8, pub x: i32, pub y: i32, } impl GridRegion { pub fn equals(&self, obj: Option) -> bool { obj.as_ref().and_then(|value| value.downcast_ref::()) == Some(self) } pub fn get_hash_code(&self) -> i32 { let folded = self.region_handle ^ (self.region_handle >> 32); folded as i32 } pub fn to_string(&self) -> String { format!("{} ({}, {})", self.name, self.x, self.y) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapItem { pub global_x: u32, pub global_y: u32, pub id: UUID, pub name: String, pub extra: i32, pub extra2: i32, /// Protocol-specific payload retained from the map-item reply. pub data: MapItemData, } /// Native Rust representation of the C# `MapItem` subclass carried by a map /// item reply. Unknown/legacy item kinds retain every wire field in `Raw`. #[derive(Clone, Debug, Eq, PartialEq)] pub enum MapItemData { Telehub(MapTelehub), AgentLocation(MapAgentLocation), LandForSale(MapLandForSale), AdultLandForSale(MapAdultLandForSale), PgEvent(MapPGEvent), MatureEvent(MapMatureEvent), AdultEvent(MapAdultEvent), Raw { id: UUID, name: String, extra: i32, extra2: i32, }, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapAgentLocation { pub avatar_count: i32, pub identifier: String, } impl MapAgentLocation { pub fn new() -> Result { Ok(Self { avatar_count: 0, identifier: String::new(), }) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapLandForSale { pub id: UUID, pub name: String, pub price: i32, pub size: i32, } impl MapLandForSale { pub fn new() -> Result { Ok(Self { id: UUID::zero(), name: String::new(), price: 0, size: 0, }) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapAdultLandForSale { pub id: UUID, pub name: String, pub price: i32, pub size: i32, } impl MapAdultLandForSale { pub fn new() -> Result { Ok(Self { id: UUID::zero(), name: String::new(), price: 0, size: 0, }) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapPGEvent { pub category: crate::DirectoryManagerEventCategories, pub description: String, pub flags: crate::DirectoryManagerEventFlags, } impl MapPGEvent { pub fn new() -> Result { Ok(Self { category: crate::DirectoryManagerEventCategories::All, description: String::new(), flags: crate::DirectoryManagerEventFlags::PG, }) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapMatureEvent { pub category: crate::DirectoryManagerEventCategories, pub description: String, pub flags: crate::DirectoryManagerEventFlags, } impl MapMatureEvent { pub fn new() -> Result { Ok(Self { category: crate::DirectoryManagerEventCategories::All, description: String::new(), flags: crate::DirectoryManagerEventFlags::Mature, }) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MapAdultEvent { pub category: crate::DirectoryManagerEventCategories, pub description: String, pub flags: crate::DirectoryManagerEventFlags, } impl MapAdultEvent { pub fn new() -> Result { Ok(Self { category: crate::DirectoryManagerEventCategories::All, description: String::new(), flags: crate::DirectoryManagerEventFlags::Adult, }) } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct MapTelehub; impl MapTelehub { pub fn new() -> Result { Ok(Self) } } impl MapItem { pub fn local_x(&self) -> u32 { self.global_x & 0xff } pub fn local_y(&self) -> u32 { self.global_y & 0xff } pub fn region_handle(&self) -> u64 { (u64::from(self.global_x & !0xff) << 32) | u64::from(self.global_y & !0xff) } } #[derive(Clone)] pub struct CoarseLocationUpdateEventArgs { simulator: Simulator, positions: HashMap, new_entries: Vec, removed_entries: Vec, } impl CoarseLocationUpdateEventArgs { pub fn new( simulator: Simulator, positions: HashMap, new_entries: Vec, removed_entries: Vec, ) -> Result { Ok(Self { simulator, positions, new_entries, removed_entries, }) } pub fn simulator(&self) -> Simulator { self.simulator.clone() } pub fn positions(&self) -> HashMap { self.positions.clone() } pub fn new_entries(&self) -> Vec { self.new_entries.clone() } pub fn removed_entries(&self) -> Vec { self.removed_entries.clone() } } #[derive(Clone)] pub struct GridItemsEventArgs { type_: GridItemType, items: Vec, } impl GridItemsEventArgs { pub fn new(type_: GridItemType, items: Vec) -> Result { Ok(Self { type_, items }) } pub fn type_(&self) -> GridItemType { self.type_ } pub fn items(&self) -> Vec { self.items.clone() } } #[derive(Clone)] pub struct GridLayerEventArgs { layer: GridLayer, } impl GridLayerEventArgs { pub fn new(layer: GridLayer) -> Result { Ok(Self { layer }) } pub fn layer(&self) -> GridLayer { self.layer.clone() } } #[derive(Clone)] pub struct GridRegionEventArgs { region: GridRegion, } impl GridRegionEventArgs { pub fn new(region: GridRegion) -> Result { Ok(Self { region }) } pub fn region(&self) -> GridRegion { self.region.clone() } } #[derive(Clone)] pub struct RegionHandleReplyEventArgs { region_id: UUID, region_handle: u64, } impl RegionHandleReplyEventArgs { pub fn new(region_id: UUID, region_handle: u64) -> Result { Ok(Self { region_id, region_handle, }) } pub fn region_id(&self) -> UUID { self.region_id } pub fn region_handle(&self) -> u64 { self.region_handle } } #[derive(Clone, Default)] pub struct SimulatorFeatures(Arc>>); impl SimulatorFeatures { pub fn get(&self, feature: String) -> Result, Error> { Ok(read(&self.0).get(&feature).cloned()) } pub fn has(&self, feature: String) -> Result { Ok(read(&self.0).contains_key(&feature)) } pub fn set_features( &self, _response: Option, response_data: Option>, error: Option, ) -> Result<(), Error> { if error.is_some() { return Err(Error::InvalidOperation); } let bytes = response_data.ok_or(Error::ArgumentNull)?; if bytes.len() > MAX_GRID_PACKET_BYTES { return Err(Error::Argument); } let OSD::Map(features) = OSDParser::deserialize_with_bytes(bytes)? else { return Err(Error::Argument); }; *write(&self.0) = features; Ok(()) } } #[derive(Default)] struct GridEvents { coarse: EventRegistry, items: EventRegistry, layer: EventRegistry, region: EventRegistry, handle: EventRegistry, } #[derive(Clone)] struct CachedRegion { region: GridRegion, seen: SystemTime, } pub(crate) struct GridManagerInner { client: Arc, events: GridEvents, regions: RwLock>, handles: RwLock>, uuids: RwLock>, items: RwLock>>, coarse_positions: RwLock>>, notify: Notify, raw_subscription: Mutex>, caps_subscriptions: Mutex>, disposed: AtomicBool, sun_ang_velocity: RwLock, sun_direction: RwLock, sun_phase: RwLock, time_of_day: RwLock, } #[derive(Clone)] pub struct GridManager { pub(crate) inner: Arc, } impl GridManager { pub fn new(client: Option>) -> Result { Self::native_new(client.ok_or(Error::ArgumentNull)?) } pub(crate) fn native_new(client: Arc) -> Result { if let Some(inner) = client.cached_grid_manager_inner() { return Ok(Self { inner }); } let inner = Arc::new(GridManagerInner { client: Arc::clone(&client), events: GridEvents::default(), regions: RwLock::new(HashMap::new()), handles: RwLock::new(HashMap::new()), uuids: RwLock::new(HashMap::new()), items: RwLock::new(HashMap::new()), coarse_positions: RwLock::new(HashMap::new()), notify: Notify::new(), raw_subscription: Mutex::new(None), caps_subscriptions: Mutex::new(Vec::new()), disposed: AtomicBool::new(false), sun_ang_velocity: RwLock::new(Vector3::default()), sun_direction: RwLock::new(Vector3::default()), sun_phase: RwLock::new(0.0), time_of_day: RwLock::new(0), }); 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_packet(event); } }, ))); mutex(&inner.caps_subscriptions).push(client.network().subscribe_caps( "SimulatorFeatures".into(), crate::CapsEventQueueCallback::from_handler(move |_, message, simulator| { if let Ok(map) = message.serialize() && let Ok(bytes) = OSDParser::serialize_llsd_xml_bytes(OSD::Map(map.snapshot())) { let _ = simulator.features.set_features(None, Some(bytes), None); } }), )); Ok(Self { inner: client.install_grid_manager_inner(inner), }) } fn agent_ids(&self) -> (UUID, UUID) { let mut client = (*self.inner.client).clone(); let agent = client.self_(); (agent.agent_id(), agent.session_id()) } fn send(&self, packet: &T, kind: PacketType) -> Result<(), Error> { if self.inner.disposed.load(Ordering::Acquire) { return Err(Error::InvalidOperation); } let sim = self .inner .client .network() .current_sim() .ok_or(Error::InvalidOperation)?; let bytes = packet.encode_packet()?; let result = sim.native_send_packet_data( bytes.clone(), i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, kind, false, ); match result { Err(Error::InvalidOperation) if !sim.connected() => Ok(()), result => result, } } async fn capability( &self, name: &str, token: &CancellationToken, ) -> Result, Error> { let Some(caps) = self .inner .client .network() .current_sim() .and_then(|sim| sim.native_caps()) else { return Ok(None); }; if let Some(uri) = caps.capability_uri(name.to_owned())? { return Ok(Some(uri)); } if caps.seed_request_finished() { return Ok(None); } let (sender, receiver) = oneshot::channel(); let sender = Arc::new(Mutex::new(Some(sender))); let wake = Arc::clone(&sender); let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| { if let Some(sender) = mutex(&wake).take() { let _ = sender.send(()); } }))); if let Some(uri) = caps.capability_uri(name.to_owned())? { return Ok(Some(uri)); } let cancelled = token.cancelled(); pin_mut!(receiver, cancelled); match select(receiver, cancelled).await { Either::Left(_) => caps.capability_uri(name.to_owned()), Either::Right(_) => Err(Error::Cancelled), } } pub fn dispose(&self) -> Result<(), Error> { self.inner.disposed.store(true, Ordering::Release); mutex(&self.inner.raw_subscription).take(); mutex(&self.inner.caps_subscriptions).clear(); Ok(()) } pub fn subscribe_coarse_location_update( &self, h: EventHandler, ) -> Subscription { self.inner.events.coarse.subscribe(h) } #[must_use] pub fn native_coarse_position( &self, simulator_handle: u64, avatar_id: UUID, ) -> Option { read(&self.inner.coarse_positions) .get(&simulator_handle) .and_then(|positions| positions.get(&avatar_id)) .copied() } pub fn subscribe_grid_items(&self, h: EventHandler) -> Subscription { self.inner.events.items.subscribe(h) } pub fn subscribe_grid_layer(&self, h: EventHandler) -> Subscription { self.inner.events.layer.subscribe(h) } pub fn subscribe_grid_region(&self, h: EventHandler) -> Subscription { self.inner.events.region.subscribe(h) } pub fn subscribe_region_handle_reply( &self, h: EventHandler, ) -> Subscription { self.inner.events.handle.subscribe(h) } pub fn request_map_blocks( &self, layer: GridLayerType, min_x: u16, min_y: u16, max_x: u16, max_y: u16, return_non_existent: bool, ) -> Result<(), Error> { if min_x > max_x || min_y > max_y { return Err(Error::Argument); } let (agent, session) = self.agent_ids(); let mut p = crate::packets::MapBlockRequestPacket::new_with_constructor()?; p.agent_data.agent_id = agent; p.agent_data.session_id = session; p.agent_data.flags = (layer as u32) | if return_non_existent { 0x10000 } else { 0 }; p.position_data.min_x = min_x; p.position_data.min_y = min_y; p.position_data.max_x = max_x; p.position_data.max_y = max_y; self.send(&p, PacketType::MapBlockRequest) } pub fn request_mainland_sims(&self, layer: GridLayerType) -> Result<(), Error> { self.request_map_blocks(layer, 0, 0, u16::MAX, u16::MAX, false) } pub fn request_map_region( &self, region_name: String, layer: GridLayerType, ) -> Result<(), Error> { let name = region_name.trim(); if name.is_empty() || name.len() > 255 { return Err(Error::Argument); } let (agent, session) = self.agent_ids(); let mut p = crate::packets::MapNameRequestPacket::new_with_constructor()?; p.agent_data.agent_id = agent; p.agent_data.session_id = session; p.agent_data.flags = layer as u32; p.name_data.name = Utils::string_to_bytes(name.to_ascii_lowercase())?; self.send(&p, PacketType::MapNameRequest) } pub fn request_map_items( &self, region_handle: u64, item: GridItemType, layer: GridLayerType, ) -> Result<(), Error> { let (agent, session) = self.agent_ids(); let mut p = crate::packets::MapItemRequestPacket::new_with_constructor()?; p.agent_data.agent_id = agent; p.agent_data.session_id = session; p.agent_data.flags = layer as u32; p.request_data.region_handle = region_handle; p.request_data.item_type = item as u32; self.send(&p, PacketType::MapItemRequest) } pub fn request_region_handle(&self, region_id: UUID) -> Result<(), Error> { let mut p = crate::packets::RegionHandleRequestPacket::new_with_constructor()?; p.request_block.region_id = region_id; self.send(&p, PacketType::RegionHandleRequest) } pub async fn request_map_layer( &self, layer: GridLayerType, cancellation_token: Option, ) -> Result<(), Error> { let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); token.throw_if_cancellation_requested()?; if let Some(uri) = self.capability("MapLayer", &token).await? { let request = OSD::Map(HashMap::from([( "Flags".to_owned(), OSD::Integer(layer as i32), )])); let (response, bytes) = self .inner .client .native_http_caps_client() .post_with_uri_osd_format_osd_cancellation_token_i_progress( uri, OSDFormat::Xml, request, token.clone(), None, ) .await?; if !response.is_success_status_code() || bytes.len() > MAX_GRID_PACKET_BYTES { return Err(Error::InvalidOperation); } let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else { return Err(Error::Argument); }; let mut reply = crate::messages::linden::MapLayerReplyVariant::new()?; reply.deserialize(OSDMap::new_with_dictionary(values)?)?; for block in reply.layer_data_blocks.into_iter().take(65_535) { self.inner.events.layer.emit(GridLayerEventArgs { layer: GridLayer { bottom: block.bottom, image_id: block.image_id, left: block.left, right: block.right, top: block.top, }, }); } self.inner.notify.notify_waiters(); return Ok(()); } let (agent, session) = self.agent_ids(); let mut p = crate::packets::MapLayerRequestPacket::new_with_constructor()?; p.agent_data.agent_id = agent; p.agent_data.session_id = session; p.agent_data.flags = layer as u32; self.send(&p, PacketType::MapLayerRequest)?; token.throw_if_cancellation_requested() } pub async fn get_grid_region_with_string_grid_layer_type_cancellation_token( &self, name: String, layer: GridLayerType, cancellation_token: Option, ) -> Result>, Error> { let key = name.trim().to_ascii_lowercase(); if key.is_empty() { return Err(Error::Argument); } if let Some(region) = self.inner.region_by_name(&key) { return Ok(Some(Some(region))); } if let Some(simulator) = self .inner .client .network() .current_sim() .filter(|simulator| simulator.name.eq_ignore_ascii_case(&key)) { return Ok(Some(Some(GridRegion { access: simulator.access, agents: 0, map_image_id: UUID::zero(), name: simulator.name.clone(), region_flags: simulator.flags, region_handle: simulator.handle, water_height: simulator.water_height.clamp(0.0, f32::from(u8::MAX)) as u8, x: ((simulator.handle >> 32) / 256) as i32, y: ((simulator.handle & u64::from(u32::MAX)) / 256) as i32, }))); } let notified = self.inner.notify.notified(); self.request_map_region(name, layer)?; if let Some(region) = self.inner.region_by_name(&key) { return Ok(Some(Some(region))); } self.wait_for(cancellation_token, notified).await?; Ok(Some(self.inner.region_by_name(&key))) } pub async fn get_grid_region_with_u_int64_grid_layer_type_cancellation_token( &self, handle: u64, layer: GridLayerType, cancellation_token: Option, ) -> Result>, Error> { if let Some(region) = self.inner.region_by_handle(handle) { return Ok(Some(Some(region))); } let x = u16::try_from((handle >> 32) / 256).map_err(|_| Error::Argument)?; let y = u16::try_from((handle & u64::from(u32::MAX)) / 256).map_err(|_| Error::Argument)?; let notified = self.inner.notify.notified(); self.request_map_blocks(layer, x, y, x, y, true)?; if let Some(region) = self.inner.region_by_handle(handle) { return Ok(Some(Some(region))); } self.wait_for(cancellation_token, notified).await?; Ok(Some(self.inner.region_by_handle(handle))) } pub async fn map_items( &self, region_handle: u64, item: GridItemType, layer: GridLayerType, cancellation_token: Option, ) -> Result, Error> { let notified = self.inner.notify.notified(); self.request_map_items(region_handle, item, layer)?; self.wait_for(cancellation_token, notified).await?; Ok(read(&self.inner.items) .get(&item) .cloned() .unwrap_or_default() .into_iter() .filter(|value| value.region_handle() == region_handle) .collect()) } async fn wait_for( &self, cancellation_token: Option, notified: impl std::future::Future, ) -> Result<(), Error> { let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); token.throw_if_cancellation_requested()?; let millis = self .inner .client .settings_ref() .timing .map_request_timeout .max(1) as u64; tokio::select! { () = notified => Ok(()), () = token.cancelled() => Err(Error::Cancelled), () = tokio::time::sleep(Duration::from_millis(millis)) => Ok(()) } } pub fn regions_read_only(&self) -> HashMap { self.inner.prune(); read(&self.inner.regions) .iter() .map(|(k, v)| (k.clone(), v.region.clone())) .collect() } pub fn regions_by_handle_read_only(&self) -> HashMap { self.inner.prune(); read(&self.inner.handles) .iter() .map(|(k, v)| (*k, v.region.clone())) .collect() } pub fn regions_by_uuid_read_only(&self) -> HashMap { read(&self.inner.uuids).clone() } pub fn sun_ang_velocity(&self) -> Vector3 { *read(&self.inner.sun_ang_velocity) } pub fn set_sun_ang_velocity(&mut self, value: Vector3) { *write(&self.inner.sun_ang_velocity) = value; } pub fn sun_direction(&self) -> Vector3 { *read(&self.inner.sun_direction) } pub fn set_sun_direction(&mut self, value: Vector3) { *write(&self.inner.sun_direction) = value; } pub fn sun_phase(&self) -> f32 { *read(&self.inner.sun_phase) } pub fn set_sun_phase(&mut self, value: f32) { *write(&self.inner.sun_phase) = value; } pub fn time_of_day(&self) -> u64 { *read(&self.inner.time_of_day) } pub fn set_time_of_day(&mut self, value: u64) { *write(&self.inner.time_of_day) = value; } } impl GridManagerInner { fn prune(&self) { let now = self.client.time_provider.get_utc_now(); write(&self.regions) .retain(|_, value| now.duration_since(value.seen).unwrap_or_default() <= CACHE_TTL); write(&self.handles) .retain(|_, value| now.duration_since(value.seen).unwrap_or_default() <= CACHE_TTL); } fn region_by_name(&self, name: &str) -> Option { self.prune(); read(&self.regions) .get(name) .map(|value| value.region.clone()) } fn region_by_handle(&self, handle: u64) -> Option { self.prune(); read(&self.handles) .get(&handle) .map(|value| value.region.clone()) } fn insert_region(&self, region: GridRegion) { let cached = CachedRegion { region: region.clone(), seen: self.client.time_provider.get_utc_now(), }; write(&self.regions).insert(region.name.to_ascii_lowercase(), cached.clone()); write(&self.handles).insert(region.region_handle, cached); self.events.region.emit(GridRegionEventArgs { region }); self.notify.notify_waiters(); } fn handle_packet(&self, event: RawPacketReceivedEventArgs) { if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_GRID_PACKET_BYTES { return; } let mut pos = 0; match event.packet_type { PacketType::MapBlockReply => { if let Ok(packet) = crate::packets::MapBlockReplyPacket::new_from_bytes(&event.data, &mut pos) { for block in packet.data.into_iter().take(65_535) { let global_x = u32::from(block.x) * 256; let global_y = u32::from(block.y) * 256; self.insert_region(GridRegion { access: SimAccess(block.access), agents: block.agents, map_image_id: block.map_image_id, name: wire_string(&block.name), region_flags: RegionFlags(u64::from(block.region_flags)), region_handle: (u64::from(global_x) << 32) | u64::from(global_y), water_height: block.water_height, x: i32::from(block.x), y: i32::from(block.y), }); } } } PacketType::MapLayerReply => { if let Ok(packet) = crate::packets::MapLayerReplyPacket::new_from_bytes(&event.data, &mut pos) { for block in packet.layer_data.into_iter().take(65_535) { self.events.layer.emit(GridLayerEventArgs { layer: GridLayer { bottom: block.bottom as i32, image_id: block.image_id, left: block.left as i32, right: block.right as i32, top: block.top as i32, }, }); } self.notify.notify_waiters(); } } PacketType::MapItemReply => { if let Ok(packet) = crate::packets::MapItemReplyPacket::new_from_bytes(&event.data, &mut pos) && let Some(kind) = grid_item_type(packet.request_data.item_type) { let values: Vec<_> = packet .data .into_iter() .take(65_535) .map(|b| map_item_from_block(kind, b)) .collect(); write(&self.items).insert(kind, values.clone()); self.events.items.emit(GridItemsEventArgs { type_: kind, items: values, }); self.notify.notify_waiters(); } } PacketType::RegionIDAndHandleReply => { if let Ok(packet) = crate::packets::RegionIDAndHandleReplyPacket::new_from_bytes( &event.data, &mut pos, ) { let value = packet.reply_block; write(&self.uuids).insert(value.region_id, value.region_handle); self.events.handle.emit(RegionHandleReplyEventArgs { region_id: value.region_id, region_handle: value.region_handle, }); self.notify.notify_waiters(); } } PacketType::CoarseLocationUpdate => { if let Ok(packet) = crate::packets::CoarseLocationUpdatePacket::new_from_bytes( &event.data, &mut pos, ) { if let Ok(prey_index) = usize::try_from(packet.index.prey) && let Some(prey) = packet.agent_data.get(prey_index) { event.simulator.native_set_prey_id(prey.agent_id); } let positions: HashMap<_, _> = packet .agent_data .into_iter() .zip(packet.location) .take(65_535) .map(|(agent, location)| { ( agent.agent_id, Vector3 { x: f32::from(location.x), y: f32::from(location.y), z: f32::from(location.z) * 4.0, }, ) }) .collect(); let previous = write(&self.coarse_positions) .insert(event.simulator.handle, positions.clone()) .unwrap_or_default(); let new_entries = positions .keys() .filter(|id| !previous.contains_key(id)) .copied() .collect(); let removed_entries = previous .keys() .filter(|id| !positions.contains_key(id)) .copied() .collect(); self.events.coarse.emit(CoarseLocationUpdateEventArgs { simulator: event.simulator, positions, new_entries, removed_entries, }); } } _ => {} } } } fn grid_item_type(value: u32) -> Option { Some(match value { 1 => GridItemType::Telehub, 2 => GridItemType::PgEvent, 3 => GridItemType::MatureEvent, 4 => GridItemType::Popular, 6 => GridItemType::AgentLocations, 7 => GridItemType::LandForSale, 8 => GridItemType::Classified, 9 => GridItemType::AdultEvent, 10 => GridItemType::AdultLandForSale, _ => return None, }) } fn event_category(value: i32) -> crate::DirectoryManagerEventCategories { use crate::DirectoryManagerEventCategories as Category; match value { 18 => Category::Discussion, 19 => Category::Sports, 20 => Category::LiveMusic, 22 => Category::Commercial, 23 => Category::Nightlife, 24 => Category::Games, 25 => Category::Pageants, 26 => Category::Education, 27 => Category::Arts, 28 => Category::Charity, 29 => Category::Miscellaneous, _ => Category::All, } } fn event_flags(value: i32, kind: GridItemType) -> crate::DirectoryManagerEventFlags { use crate::DirectoryManagerEventFlags as Flags; match value { 0 => Flags::PG, 1 => Flags::Mature, 2 => Flags::Adult, _ => match kind { GridItemType::MatureEvent => Flags::Mature, GridItemType::AdultEvent => Flags::Adult, _ => Flags::PG, }, } } fn map_item_from_block( kind: GridItemType, block: crate::packets::MapItemReplyPacketDataBlock, ) -> MapItem { let name = wire_string(&block.name); let data = match kind { GridItemType::Telehub => MapItemData::Telehub(MapTelehub), GridItemType::AgentLocations => MapItemData::AgentLocation(MapAgentLocation { avatar_count: block.extra, identifier: name.clone(), }), GridItemType::LandForSale => MapItemData::LandForSale(MapLandForSale { id: block.id, name: name.clone(), price: block.extra2, size: block.extra, }), GridItemType::AdultLandForSale => MapItemData::AdultLandForSale(MapAdultLandForSale { id: block.id, name: name.clone(), price: block.extra2, size: block.extra, }), GridItemType::PgEvent => MapItemData::PgEvent(MapPGEvent { category: event_category(block.extra), description: name.clone(), flags: event_flags(block.extra2, kind), }), GridItemType::MatureEvent => MapItemData::MatureEvent(MapMatureEvent { category: event_category(block.extra), description: name.clone(), flags: event_flags(block.extra2, kind), }), GridItemType::AdultEvent => MapItemData::AdultEvent(MapAdultEvent { category: event_category(block.extra), description: name.clone(), flags: event_flags(block.extra2, kind), }), GridItemType::Classified | GridItemType::Popular => MapItemData::Raw { id: block.id, name: name.clone(), extra: block.extra, extra2: block.extra2, }, }; MapItem { global_x: block.x, global_y: block.y, id: block.id, name, extra: block.extra, extra2: block.extra2, data, } } #[cfg(test)] mod tests { use super::*; use libremetaverse_types::compat::TimeProvider; use std::sync::atomic::{AtomicU64, Ordering}; #[test] fn map_item_handle_math() { let item = MapItem { global_x: 1025, global_y: 2050, id: UUID::zero(), name: String::new(), extra: 0, extra2: 0, data: MapItemData::Telehub(MapTelehub), }; assert_eq!(item.local_x(), 1); assert_eq!(item.local_y(), 2); assert_eq!(item.region_handle(), (1024_u64 << 32) | 2048); } #[test] fn layers_include_edges() { let layer = GridLayer { bottom: 2, image_id: UUID::zero(), left: 1, right: 3, top: 4, }; assert!(layer.contains_region(1, 4).unwrap()); assert!(!layer.contains_region(4, 4).unwrap()); } #[test] fn map_item_fixture_retains_and_interprets_wire_fields() { let id = UUID::random().unwrap(); let mut block = crate::packets::MapItemReplyPacketDataBlock::new_with_constructor().unwrap(); block.x = 4097; block.y = 8194; block.id = id; block.name = b"Parcel One\0".to_vec(); block.extra = 1024; block.extra2 = 55_000; let item = map_item_from_block(GridItemType::LandForSale, block); assert_eq!((item.global_x, item.global_y), (4097, 8194)); assert_eq!((item.id, item.name.as_str()), (id, "Parcel One")); assert_eq!((item.extra, item.extra2), (1024, 55_000)); assert_eq!( item.data, MapItemData::LandForSale(MapLandForSale { id, name: "Parcel One".to_owned(), price: 55_000, size: 1024, }) ); } #[test] fn region_cache_expiry_uses_injected_clock() { let seconds = Arc::new(AtomicU64::new(1_000)); let clock = Arc::clone(&seconds); let client = GridClient::builder() .with_time_provider(TimeProvider::from_fn(move || { std::time::UNIX_EPOCH + Duration::from_secs(clock.load(Ordering::Relaxed)) })) .build() .unwrap(); let manager = GridManager::native_new(Arc::new(client)).unwrap(); manager.inner.insert_region(GridRegion { access: SimAccess::PG, agents: 0, map_image_id: UUID::zero(), name: "Example".into(), region_flags: RegionFlags::NONE, region_handle: (256_u64 << 32) | 512, water_height: 20, x: 1, y: 2, }); assert!(manager.inner.region_by_name("example").is_some()); seconds.store(1_000 + CACHE_TTL.as_secs() + 1, Ordering::Relaxed); assert!(manager.inner.region_by_name("example").is_none()); } #[test] fn simulator_features_preserve_unknown_fields() { let features = SimulatorFeatures::default(); let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([ ("MeshRezEnabled".into(), OSD::Boolean(true)), ("FutureFeature".into(), OSD::String("kept".into())), ]))) .unwrap(); features.set_features(None, Some(body), None).unwrap(); assert!(features.has("FutureFeature".into()).unwrap()); assert_eq!( features.get("FutureFeature".into()).unwrap(), Some(OSD::String("kept".into())) ); } }