Files
MetaCrate/crates/libremetaverse/src/grid_manager.rs
Chili Palmer c9a1170a27
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

1128 lines
39 KiB
Rust

//! 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, 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<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
value
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write<T>(value: &RwLock<T>) -> 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<bool, Error> {
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<libremetaverse_types::compat::Object>) -> bool {
obj.as_ref().and_then(|value| value.downcast_ref::<Self>()) == 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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<UUID, Vector3>,
new_entries: Vec<UUID>,
removed_entries: Vec<UUID>,
}
impl CoarseLocationUpdateEventArgs {
pub fn new(
simulator: Simulator,
positions: HashMap<UUID, Vector3>,
new_entries: Vec<UUID>,
removed_entries: Vec<UUID>,
) -> Result<Self, Error> {
Ok(Self {
simulator,
positions,
new_entries,
removed_entries,
})
}
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
pub fn positions(&self) -> HashMap<UUID, Vector3> {
self.positions.clone()
}
pub fn new_entries(&self) -> Vec<UUID> {
self.new_entries.clone()
}
pub fn removed_entries(&self) -> Vec<UUID> {
self.removed_entries.clone()
}
}
#[derive(Clone)]
pub struct GridItemsEventArgs {
type_: GridItemType,
items: Vec<MapItem>,
}
impl GridItemsEventArgs {
pub fn new(type_: GridItemType, items: Vec<MapItem>) -> Result<Self, Error> {
Ok(Self { type_, items })
}
pub fn type_(&self) -> GridItemType {
self.type_
}
pub fn items(&self) -> Vec<MapItem> {
self.items.clone()
}
}
#[derive(Clone)]
pub struct GridLayerEventArgs {
layer: GridLayer,
}
impl GridLayerEventArgs {
pub fn new(layer: GridLayer) -> Result<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<RwLock<HashMap<String, OSD>>>);
impl SimulatorFeatures {
pub fn get(&self, feature: String) -> Result<Option<OSD>, Error> {
Ok(read(&self.0).get(&feature).cloned())
}
pub fn has(&self, feature: String) -> Result<bool, Error> {
Ok(read(&self.0).contains_key(&feature))
}
pub fn set_features(
&self,
_response: Option<libremetaverse_types::compat::HttpResponse>,
response_data: Option<Vec<u8>>,
error: Option<libremetaverse_types::compat::ExternalError>,
) -> 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<CoarseLocationUpdateEventArgs>,
items: EventRegistry<GridItemsEventArgs>,
layer: EventRegistry<GridLayerEventArgs>,
region: EventRegistry<GridRegionEventArgs>,
handle: EventRegistry<RegionHandleReplyEventArgs>,
}
#[derive(Clone)]
struct CachedRegion {
region: GridRegion,
seen: SystemTime,
}
pub(crate) struct GridManagerInner {
client: Arc<GridClient>,
events: GridEvents,
regions: RwLock<HashMap<String, CachedRegion>>,
handles: RwLock<HashMap<u64, CachedRegion>>,
uuids: RwLock<HashMap<UUID, u64>>,
items: RwLock<HashMap<GridItemType, Vec<MapItem>>>,
coarse_positions: RwLock<HashMap<u64, HashMap<UUID, Vector3>>>,
notify: Notify,
raw_subscription: Mutex<Option<Subscription>>,
caps_subscriptions: Mutex<Vec<Subscription>>,
disposed: AtomicBool,
sun_ang_velocity: RwLock<Vector3>,
sun_direction: RwLock<Vector3>,
sun_phase: RwLock<f32>,
time_of_day: RwLock<u64>,
}
#[derive(Clone)]
pub struct GridManager {
pub(crate) inner: Arc<GridManagerInner>,
}
impl GridManager {
pub fn new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
Self::native_new(client.ok_or(Error::ArgumentNull)?)
}
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
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<T: GeneratedPacket>(&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<Option<Uri>, 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<CoarseLocationUpdateEventArgs>,
) -> Subscription {
self.inner.events.coarse.subscribe(h)
}
pub fn subscribe_grid_items(&self, h: EventHandler<GridItemsEventArgs>) -> Subscription {
self.inner.events.items.subscribe(h)
}
pub fn subscribe_grid_layer(&self, h: EventHandler<GridLayerEventArgs>) -> Subscription {
self.inner.events.layer.subscribe(h)
}
pub fn subscribe_grid_region(&self, h: EventHandler<GridRegionEventArgs>) -> Subscription {
self.inner.events.region.subscribe(h)
}
pub fn subscribe_region_handle_reply(
&self,
h: EventHandler<RegionHandleReplyEventArgs>,
) -> 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 = name.as_bytes().to_vec();
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<CancellationToken>,
) -> 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<CancellationToken>,
) -> Result<Option<Option<GridRegion>>, 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)));
}
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<CancellationToken>,
) -> Result<Option<Option<GridRegion>>, 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<CancellationToken>,
) -> Result<Vec<MapItem>, 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<CancellationToken>,
notified: impl std::future::Future<Output = ()>,
) -> 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<String, GridRegion> {
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<u64, GridRegion> {
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<UUID, u64> {
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<GridRegion> {
self.prune();
read(&self.regions)
.get(name)
.map(|value| value.region.clone())
}
fn region_by_handle(&self, handle: u64) -> Option<GridRegion> {
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<GridItemType> {
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()))
);
}
}