1158 lines
40 KiB
Rust
1158 lines
40 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, 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<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)
|
|
}
|
|
#[must_use]
|
|
pub fn native_coarse_position(
|
|
&self,
|
|
simulator_handle: u64,
|
|
avatar_id: UUID,
|
|
) -> Option<Vector3> {
|
|
read(&self.inner.coarse_positions)
|
|
.get(&simulator_handle)
|
|
.and_then(|positions| positions.get(&avatar_id))
|
|
.copied()
|
|
}
|
|
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 = 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<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)));
|
|
}
|
|
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<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()))
|
|
);
|
|
}
|
|
}
|