From 66fbb87b3f290eb0f7cb9e00e9c00c331ea74efd Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 10 Aug 2026 17:45:08 +0000 Subject: [PATCH] Implement grid and directory discovery (#71) --- README.md | 8 + api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse-types/src/compat.rs | 148 +- crates/libremetaverse/src/caps.rs | 44 +- crates/libremetaverse/src/client_core.rs | 133 ++ .../libremetaverse/src/directory_manager.rs | 1445 +++++++++++++ crates/libremetaverse/src/generated.rs | 1829 +++-------------- crates/libremetaverse/src/grid_manager.rs | 1122 ++++++++++ crates/libremetaverse/src/interest_list.rs | 237 +++ crates/libremetaverse/src/lib.rs | 5 + crates/libremetaverse/src/network_manager.rs | 2 +- crates/libremetaverse/src/slurl.rs | 430 ++++ docs/discovery.md | 43 + tools/generate_api_shims.py | 42 + 14 files changed, 3949 insertions(+), 1541 deletions(-) create mode 100644 crates/libremetaverse/src/directory_manager.rs create mode 100644 crates/libremetaverse/src/grid_manager.rs create mode 100644 crates/libremetaverse/src/interest_list.rs create mode 100644 crates/libremetaverse/src/slurl.rs create mode 100644 docs/discovery.md diff --git a/README.md b/README.md index c4c65d8..d976589 100644 --- a/README.md +++ b/README.md @@ -413,3 +413,11 @@ parcel-versus-region URI rules, and sound packets retain identity, gain, position, and queue flags. Packet/capability selection, state-before-event ordering, cancellation, limits, and live-operation safety are documented in [`docs/land.md`](docs/land.md). + +Grid and directory discovery now use native LLUDP map and search requests, +correlated bounded reply events, expiring region indexes, coarse-location +deltas, and exact region-handle math. SLURL parsing covers location and +application forms with stable escaping, while interest-list modes and unknown +simulator features are preserved through LLSD capabilities. The cache, +pagination, cancellation, and compatibility contracts are documented in +[`docs/discovery.md`](docs/discovery.md). diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index b36c7f2..4adc09e 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | Assembly | Types | Members | Status | |---|---:|---:|---| -| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 290 types / 16,219 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 326 types / 16,464 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | diff --git a/crates/libremetaverse-types/src/compat.rs b/crates/libremetaverse-types/src/compat.rs index fd6f8c9..074246f 100644 --- a/crates/libremetaverse-types/src/compat.rs +++ b/crates/libremetaverse-types/src/compat.rs @@ -1003,7 +1003,153 @@ pub struct Span(pub PhantomData); pub struct RandomSource; -pub struct IAsyncEnumerable(pub PhantomData T>); +struct AsyncEnumerableState { + values: std::collections::VecDeque, + closed: bool, + waiters: Vec, + cancellation: Option, +} + +/// A cloneable, producer-driven equivalent of `IAsyncEnumerable`. +/// +/// Native managers keep a producer clone and publish correlated protocol +/// results. Consumers call [`IAsyncEnumerable::next`] until it returns `None`. +pub struct IAsyncEnumerable(Arc>>); + +impl Clone for IAsyncEnumerable { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} + +impl Default for IAsyncEnumerable { + fn default() -> Self { + Self::new() + } +} + +impl IAsyncEnumerable { + #[must_use] + pub fn new() -> Self { + Self(Arc::new(Mutex::new(AsyncEnumerableState { + values: std::collections::VecDeque::new(), + closed: false, + waiters: Vec::new(), + cancellation: None, + }))) + } + + #[must_use] + pub fn with_cancellation(cancellation: CancellationToken) -> Self { + let value = Self::new(); + value + .0 + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .cancellation = Some(cancellation); + value + } + + /// Publishes one item and wakes pending consumers. + pub fn push(&self, value: T) { + let waiters = { + let mut state = self + .0 + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if state + .cancellation + .as_ref() + .is_some_and(CancellationToken::is_cancellation_requested) + { + state.closed = true; + } + if state.closed { + return; + } + state.values.push_back(value); + std::mem::take(&mut state.waiters) + }; + for waiter in waiters { + waiter.wake(); + } + } + + /// Completes the sequence after already queued items are consumed. + pub fn close(&self) { + let waiters = { + let mut state = self + .0 + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + state.closed = true; + std::mem::take(&mut state.waiters) + }; + for waiter in waiters { + waiter.wake(); + } + } + + #[must_use] + pub fn next(&self) -> AsyncEnumerableNext { + let cancellation = self + .0 + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .cancellation + .clone() + .map(|token| token.cancelled()); + AsyncEnumerableNext { + state: Arc::clone(&self.0), + registered: false, + cancellation, + } + } +} + +pub struct AsyncEnumerableNext { + state: Arc>>, + registered: bool, + cancellation: Option, +} + +impl Future for AsyncEnumerableNext { + type Output = Option; + + fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { + let should_register = !self.registered; + if let Some(cancelled) = self.cancellation.as_mut() + && Pin::new(cancelled).poll(context).is_ready() + { + self.state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .closed = true; + } + let outcome = { + let mut state = self + .state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(value) = state.values.pop_front() { + Some(Poll::Ready(Some(value))) + } else if state.closed { + Some(Poll::Ready(None)) + } else { + if should_register { + state.waiters.push(context.waker().clone()); + } + None + } + }; + if let Some(outcome) = outcome { + outcome + } else { + self.registered = true; + Poll::Pending + } + } +} pub struct ImmutableDictionary(pub PhantomData); diff --git a/crates/libremetaverse/src/caps.rs b/crates/libremetaverse/src/caps.rs index 323d63c..fe41813 100644 --- a/crates/libremetaverse/src/caps.rs +++ b/crates/libremetaverse/src/caps.rs @@ -375,7 +375,10 @@ async fn run_seed_requests(weak: Weak, cancellation: CancellationToke .await; match response { Ok((response, data)) if response.is_success_status_code() => { - if install_seed_response(&inner, &simulator, data).is_ok() { + if install_seed_response(&inner, &simulator, data, cancellation.clone()) + .await + .is_ok() + { return; } } @@ -392,10 +395,11 @@ async fn run_seed_requests(weak: Weak, cancellation: CancellationToke } } -fn install_seed_response( +async fn install_seed_response( inner: &Arc, simulator: &Simulator, data: Vec, + cancellation: CancellationToken, ) -> Result<(), Error> { let OSD::Map(map) = OSDParser::deserialize_with_bytes(data)? else { return Err(Error::Parse { @@ -439,6 +443,24 @@ fn install_seed_response( queue.start()?; *mutex(&inner.event_queue) = Some(queue); } + + // SimulatorFeatures is a GET capability, not merely an event-queue + // message. Load its initial snapshot before announcing that capability + // discovery is complete, while still allowing grids that omit or reject + // the optional endpoint to finish seed discovery normally. + let simulator_features_uri = read(&inner.caps).get("SimulatorFeatures").cloned(); + if let Some(uri) = simulator_features_uri + && let Ok((response, bytes)) = simulator + .client + .native_http_caps_client() + .get(uri, cancellation.clone(), None) + .await + && response.is_success_status_code() + && bytes.len() <= 8 * 1024 * 1024 + { + let _ = simulator.features.set_features(None, Some(bytes), None); + } + cancellation.throw_if_cancellation_requested()?; inner.emit_capabilities_received(simulator.clone()); Ok(()) } @@ -635,6 +657,10 @@ mod tests { "GetTexture".to_owned(), OSD::Uri(Uri("https://asset.example.test/texture".to_owned())), ), + ( + "SimulatorFeatures".to_owned(), + OSD::Uri(Uri("https://caps.example.test/features".to_owned())), + ), ( "InvalidScheme".to_owned(), OSD::Uri(Uri("file:///private/capability".to_owned())), @@ -654,12 +680,22 @@ mod tests { let release = Arc::clone(&release); async move { let is_seed = request.uri.0.contains("/seed"); + let is_features = request.uri.0.contains("/features"); lock(&recorded).push(request); if is_seed { while !release.load(Ordering::Acquire) { tokio::time::sleep(Duration::from_millis(1)).await; } response(200, seed_body()) + } else if is_features { + response( + 200, + OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "MeshRezEnabled".to_owned(), + OSD::Boolean(true), + )]))) + .unwrap(), + ) } else { response(404, Vec::new()) } @@ -708,6 +744,10 @@ mod tests { None ); assert!(caps.event_queue().is_some()); + assert_eq!( + simulator.features.get("MeshRezEnabled".to_owned()).unwrap(), + Some(OSD::Boolean(true)) + ); let deadline = Instant::now() + Duration::from_secs(2); while lock(&requests).len() < 2 && Instant::now() < deadline { diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index a111167..5b8962e 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -123,6 +123,9 @@ struct ClientRuntime { terrain_manager: Mutex>>, sound_manager: Mutex>>, parcel_manager: Mutex>>, + grid_manager: Mutex>>, + directory_manager: Mutex>>, + interest_list_manager: Mutex>>, appearance_manager: Mutex>>, avatar_manager: Mutex>>, animesh_manager: Mutex>>, @@ -162,6 +165,9 @@ impl ClientRuntime { terrain_manager: Mutex::new(None), sound_manager: Mutex::new(None), parcel_manager: Mutex::new(None), + grid_manager: Mutex::new(None), + directory_manager: Mutex::new(None), + interest_list_manager: Mutex::new(None), appearance_manager: Mutex::new(None), avatar_manager: Mutex::new(None), animesh_manager: Mutex::new(None), @@ -252,6 +258,18 @@ impl ClientRuntime { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .take(); + self.grid_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); + self.directory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); + self.interest_list_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); self.http_caps_client .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) @@ -845,6 +863,121 @@ impl GridClient { .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); } + pub(crate) fn cached_grid_manager_inner( + &self, + ) -> Option> { + self.runtime + .grid_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + pub(crate) fn install_grid_manager_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .grid_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Arc::clone(inner); + } + *cached = Some(Arc::clone(&candidate)); + candidate + } + pub(crate) fn native_grid(&self) -> Result { + if let Some(inner) = self.cached_grid_manager_inner() { + Ok(crate::GridManager { inner }) + } else { + crate::grid_manager::GridManager::native_new(Arc::new(self.clone())) + } + } + pub(crate) fn native_set_grid(&self, value: crate::GridManager) { + *self + .runtime + .grid_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); + } + pub(crate) fn cached_directory_manager_inner( + &self, + ) -> Option> { + self.runtime + .directory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + pub(crate) fn install_directory_manager_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .directory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Arc::clone(inner); + } + *cached = Some(Arc::clone(&candidate)); + candidate + } + pub(crate) fn native_directory(&self) -> Result { + if let Some(inner) = self.cached_directory_manager_inner() { + Ok(crate::DirectoryManager { inner }) + } else { + crate::directory_manager::DirectoryManager::native_new(Arc::new(self.clone())) + } + } + pub(crate) fn native_set_directory(&self, value: crate::DirectoryManager) { + *self + .runtime + .directory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); + } + pub(crate) fn cached_interest_list_manager_inner( + &self, + ) -> Option> { + self.runtime + .interest_list_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + pub(crate) fn install_interest_list_manager_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .interest_list_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Arc::clone(inner); + } + *cached = Some(Arc::clone(&candidate)); + candidate + } + pub(crate) fn native_interest_list(&self) -> Result { + if let Some(inner) = self.cached_interest_list_manager_inner() { + Ok(crate::InterestListManager { inner }) + } else { + crate::interest_list::InterestListManager::native_new(Arc::new(self.clone())) + } + } + pub(crate) fn native_set_interest_list(&self, value: crate::InterestListManager) { + *self + .runtime + .interest_list_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); + } + pub(crate) fn native_set_caps_rate_limiter( &mut self, value: crate::caps_http::CapsRateLimiter, diff --git a/crates/libremetaverse/src/directory_manager.rs b/crates/libremetaverse/src/directory_manager.rs new file mode 100644 index 0000000..a1a8bac --- /dev/null +++ b/crates/libremetaverse/src/directory_manager.rs @@ -0,0 +1,1445 @@ +//! Directory search requests, paged reply decoding, and correlated result events. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::cast_possible_truncation)] // Wire coordinates intentionally follow CLR casts. +#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString methods. +#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructors are fallible. +#![allow(clippy::unused_self)] // Helper shares the manager-owned stream construction contract. +#![allow(clippy::too_many_lines)] // Packet dispatch is centralized and bounded by reply type. + +use crate::agent_manager::EventRegistry; +use crate::network_manager::RawPacketReceivedEventArgs; +use crate::packet_catalog::{GeneratedPacket, PacketType}; +use crate::{Error, GridClient}; +use libremetaverse_types::compat::{ + CancellationToken, EventHandler, IAsyncEnumerable, Subscription, +}; +use libremetaverse_types::{UUID, Vector3d}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tokio::sync::Notify; + +const MAX_DIRECTORY_PACKET_BYTES: usize = 8 * 1024 * 1024; +const MAX_QUERY_BYTES: usize = 255; +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(|b| *b == 0).next().unwrap_or_default()).into_owned() +} +fn valid_query(value: &str) -> Result<(), Error> { + if value.len() > MAX_QUERY_BYTES { + Err(Error::Argument) + } else { + Ok(()) + } +} +fn finish_stream( + streams: &Mutex>>, + query_id: UUID, + values: &[T], +) { + if let Some(stream) = mutex(streams).remove(&query_id) { + for value in values { + stream.push(value.clone()); + } + stream.close(); + } +} +fn publish_stream_page( + streams: &Mutex>>, + query_id: UUID, + values: &[T], + terminal: bool, +) { + let stream = mutex(streams).get(&query_id).cloned(); + if let Some(stream) = stream { + for value in values { + stream.push(value.clone()); + } + if terminal { + mutex(streams).remove(&query_id); + stream.close(); + } + } +} +fn close_streams(streams: &Mutex>>) { + for stream in mutex(streams).drain().map(|(_, stream)| stream) { + stream.close(); + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerAgentSearchData { + pub agent_id: UUID, + pub first_name: String, + pub last_name: String, + pub online: bool, +} +impl DirectoryManagerAgentSearchData { + pub fn to_string(&self) -> String { + format!("{} {}", self.first_name, self.last_name) + .trim() + .to_owned() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerClassified { + pub creation_date: SystemTime, + pub expiration_date: SystemTime, + pub flags: crate::DirectoryManagerClassifiedFlags, + pub id: UUID, + pub name: String, + pub price: i32, +} +impl DirectoryManagerClassified { + pub fn to_string(&self) -> String { + self.name.clone() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerDirectoryParcel { + pub actual_area: i32, + pub auction: bool, + pub dwell: f32, + pub for_sale: bool, + pub id: UUID, + pub name: String, + pub sale_price: i32, +} +impl DirectoryManagerDirectoryParcel { + pub fn to_string(&self) -> String { + self.name.clone() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerEventsSearchData { + pub date: String, + pub flags: crate::DirectoryManagerEventFlags, + pub id: u32, + pub name: String, + pub owner: UUID, + pub time: u32, +} +impl DirectoryManagerEventsSearchData { + pub fn to_string(&self) -> String { + self.name.clone() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerGroupSearchData { + pub group_id: UUID, + pub group_name: String, + pub members: i32, +} +impl DirectoryManagerGroupSearchData { + pub fn to_string(&self) -> String { + self.group_name.clone() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerPlacesSearchData { + pub actual_area: i32, + pub billable_area: i32, + pub desc: String, + pub dwell: f32, + pub flags: crate::DirectoryManagerPlacesFlags, + pub global_x: f32, + pub global_y: f32, + pub global_z: f32, + pub name: String, + pub owner_id: UUID, + pub price: i32, + pub sku: String, + pub sim_name: String, + pub snapshot_id: UUID, +} +impl DirectoryManagerPlacesSearchData { + pub fn to_s_lurl(&self) -> Result { + crate::SlurlParser::get_teleport_url( + self.sim_name.clone(), + Some(self.global_x as i32 & 0xff), + Some(self.global_y as i32 & 0xff), + Some(self.global_z as i32), + ) + } + pub fn to_string(&self) -> String { + self.name.clone() + } +} +#[derive(Clone, Debug, PartialEq)] +pub struct DirectoryManagerEventInfo { + pub amount: u32, + pub category: crate::DirectoryManagerEventCategories, + pub cover: u32, + pub creator: UUID, + pub date: String, + pub date_utc: u32, + pub desc: String, + pub duration: u32, + pub flags: crate::DirectoryManagerEventFlags, + pub global_pos: Vector3d, + pub id: u32, + pub name: String, + pub sim_name: String, +} +impl DirectoryManagerEventInfo { + pub fn to_s_lurl(&self) -> Result { + crate::SlurlParser::get_teleport_url( + self.sim_name.clone(), + Some(self.global_pos.x as i32 & 0xff), + Some(self.global_pos.y as i32 & 0xff), + Some(self.global_pos.z as i32), + ) + } + pub fn to_string(&self) -> String { + self.name.clone() + } +} + +macro_rules! correlated_args { + ($name:ident, $item:ty, $field:ident, $get:ident) => { + #[derive(Clone)] + pub struct $name { + query_id: UUID, + $field: Vec<$item>, + } + impl $name { + pub fn new(query_id: UUID, $field: Vec<$item>) -> Result { + Ok(Self { query_id, $field }) + } + pub fn query_id(&self) -> UUID { + self.query_id + } + pub fn $get(&self) -> Vec<$item> { + self.$field.clone() + } + } + }; +} +correlated_args!( + DirEventsReplyEventArgs, + DirectoryManagerEventsSearchData, + matched_events, + matched_events +); +correlated_args!( + DirGroupsReplyEventArgs, + DirectoryManagerGroupSearchData, + matched_groups, + matched_groups +); +correlated_args!( + DirPeopleReplyEventArgs, + DirectoryManagerAgentSearchData, + matched_people, + matched_people +); +correlated_args!( + DirPlacesReplyEventArgs, + DirectoryManagerDirectoryParcel, + matched_parcels, + matched_parcels +); +correlated_args!( + PlacesReplyEventArgs, + DirectoryManagerPlacesSearchData, + matched_places, + matched_places +); + +#[derive(Clone)] +pub struct DirClassifiedsReplyEventArgs { + classifieds: Vec, +} +impl DirClassifiedsReplyEventArgs { + pub fn new(classifieds: Vec) -> Result { + Ok(Self { classifieds }) + } + pub fn classifieds(&self) -> Vec { + self.classifieds.clone() + } +} +#[derive(Clone)] +pub struct DirLandReplyEventArgs { + dir_parcels: Vec, +} +impl DirLandReplyEventArgs { + pub fn new(dir_parcels: Vec) -> Result { + Ok(Self { dir_parcels }) + } + pub fn dir_parcels(&self) -> Vec { + self.dir_parcels.clone() + } +} +#[derive(Clone)] +pub struct EventInfoReplyEventArgs { + matched_event: DirectoryManagerEventInfo, +} +impl EventInfoReplyEventArgs { + pub fn new(matched_event: DirectoryManagerEventInfo) -> Result { + Ok(Self { matched_event }) + } + pub fn matched_event(&self) -> DirectoryManagerEventInfo { + self.matched_event.clone() + } +} + +#[derive(Default)] +struct DirectoryEvents { + classifieds: EventRegistry, + events: EventRegistry, + groups: EventRegistry, + land: EventRegistry, + people: EventRegistry, + dir_places: EventRegistry, + event_info: EventRegistry, + places: EventRegistry, +} +#[derive(Clone, Copy)] +enum SearchStreamKind { + People, + Groups, + Classifieds, + DirPlaces, + Events, + Land, + Places, +} +pub(crate) struct DirectoryManagerInner { + client: Arc, + events: DirectoryEvents, + raw_subscription: Mutex>, + disposed: AtomicBool, + event_info: RwLock>, + event_notify: Notify, + people_streams: Mutex>>, + group_streams: Mutex>>, + classified_streams: Mutex>>, + dir_place_streams: Mutex>>, + event_streams: Mutex>>, + land_streams: Mutex>>, + place_streams: Mutex>>, + find_pages: Mutex>, +} +#[derive(Clone)] +pub struct DirectoryManager { + pub(crate) inner: Arc, +} + +impl DirectoryManager { + pub fn new(client: GridClient) -> Result { + Self::native_new(Arc::new(client)) + } + pub(crate) fn native_new(client: Arc) -> Result { + if let Some(inner) = client.cached_directory_manager_inner() { + return Ok(Self { inner }); + } + let inner = Arc::new(DirectoryManagerInner { + client: Arc::clone(&client), + events: DirectoryEvents::default(), + raw_subscription: Mutex::new(None), + disposed: AtomicBool::new(false), + event_info: RwLock::new(None), + event_notify: Notify::new(), + people_streams: Mutex::new(HashMap::new()), + group_streams: Mutex::new(HashMap::new()), + classified_streams: Mutex::new(HashMap::new()), + dir_place_streams: Mutex::new(HashMap::new()), + event_streams: Mutex::new(HashMap::new()), + land_streams: Mutex::new(HashMap::new()), + place_streams: Mutex::new(HashMap::new()), + find_pages: Mutex::new(HashMap::new()), + }); + 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); + } + }, + ))); + Ok(Self { + inner: client.install_directory_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 result_stream( + &self, + cancellation: CancellationToken, + ) -> IAsyncEnumerable { + IAsyncEnumerable::with_cancellation(cancellation) + } + fn arm_stream_timeout( + &self, + query_id: UUID, + kind: SearchStreamKind, + stream: IAsyncEnumerable, + cancellation: CancellationToken, + ) { + if let Ok(runtime) = tokio::runtime::Handle::try_current() { + let timeout = Duration::from_millis( + self.inner + .client + .settings_ref() + .timing + .map_request_timeout + .max(1) as u64, + ); + let weak = Arc::downgrade(&self.inner); + runtime.spawn(async move { + tokio::select! { + () = cancellation.cancelled() => {}, + () = tokio::time::sleep(timeout) => {}, + } + stream.close(); + if let Some(inner) = weak.upgrade() { + match kind { + SearchStreamKind::People => { + mutex(&inner.people_streams).remove(&query_id); + } + SearchStreamKind::Groups => { + mutex(&inner.group_streams).remove(&query_id); + } + SearchStreamKind::Classifieds => { + mutex(&inner.classified_streams).remove(&query_id); + } + SearchStreamKind::DirPlaces => { + mutex(&inner.dir_place_streams).remove(&query_id); + } + SearchStreamKind::Events => { + mutex(&inner.event_streams).remove(&query_id); + } + SearchStreamKind::Land => { + mutex(&inner.land_streams).remove(&query_id); + } + SearchStreamKind::Places => { + mutex(&inner.place_streams).remove(&query_id); + } + } + mutex(&inner.find_pages).remove(&query_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, + } + } + pub fn dispose(&self) -> Result<(), Error> { + self.inner.disposed.store(true, Ordering::Release); + mutex(&self.inner.raw_subscription).take(); + close_streams(&self.inner.people_streams); + close_streams(&self.inner.group_streams); + close_streams(&self.inner.classified_streams); + close_streams(&self.inner.dir_place_streams); + close_streams(&self.inner.event_streams); + close_streams(&self.inner.land_streams); + close_streams(&self.inner.place_streams); + mutex(&self.inner.find_pages).clear(); + Ok(()) + } + pub fn subscribe_dir_classifieds_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.classifieds.subscribe(h) + } + pub fn subscribe_dir_events_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.events.subscribe(h) + } + pub fn subscribe_dir_groups_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.groups.subscribe(h) + } + pub fn subscribe_dir_land_reply(&self, h: EventHandler) -> Subscription { + self.inner.events.land.subscribe(h) + } + pub fn subscribe_dir_people_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.people.subscribe(h) + } + pub fn subscribe_dir_places_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.dir_places.subscribe(h) + } + pub fn subscribe_event_info_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.event_info.subscribe(h) + } + pub fn subscribe_places_reply(&self, h: EventHandler) -> Subscription { + self.inner.events.places.subscribe(h) + } + + fn start_find( + &self, + text: String, + start: i32, + flags: crate::DirectoryManagerDirFindFlags, + ) -> Result { + valid_query(&text)?; + if start < 0 { + return Err(Error::Argument); + } + let query_id = UUID::random()?; + self.send_find(query_id, text, start, flags)?; + Ok(query_id) + } + fn send_find( + &self, + query_id: UUID, + text: String, + start: i32, + flags: crate::DirectoryManagerDirFindFlags, + ) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::DirFindQueryPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.query_data.query_id = query_id; + p.query_data.query_text = text.into_bytes(); + p.query_data.query_flags = flags.0 as u32; + p.query_data.query_start = start; + self.send(&p, PacketType::DirFindQuery) + } + pub fn start_people_search( + &self, + search_text: String, + query_start: i32, + ) -> Result { + self.start_find( + search_text, + query_start, + crate::DirectoryManagerDirFindFlags::PEOPLE, + ) + } + pub fn start_group_search_with_string_int32( + &self, + search_text: String, + query_start: i32, + ) -> Result { + self.start_group_search_with_string_int32_dir_find_flags( + search_text, + query_start, + crate::DirectoryManagerDirFindFlags::GROUPS, + ) + } + pub fn start_group_search_with_string_int32_dir_find_flags( + &self, + search_text: String, + query_start: i32, + flags: crate::DirectoryManagerDirFindFlags, + ) -> Result { + self.start_find( + search_text, + query_start, + crate::DirectoryManagerDirFindFlags( + flags.0 | crate::DirectoryManagerDirFindFlags::GROUPS.0, + ), + ) + } + pub fn start_events_search_with_string_u_int32( + &self, + search_text: String, + query_start: u32, + ) -> Result { + self.start_events_search_with_string_dir_find_flags_string_u_int32_event_categories( + search_text, + crate::DirectoryManagerDirFindFlags::EVENTS, + "u".into(), + query_start, + crate::DirectoryManagerEventCategories::All, + ) + } + pub fn start_events_search_with_string_dir_find_flags_string_u_int32_event_categories( + &self, + search_text: String, + query_flags: crate::DirectoryManagerDirFindFlags, + event_day: String, + query_start: u32, + category: crate::DirectoryManagerEventCategories, + ) -> Result { + let text = format!("{event_day}|{}|{search_text}", category as i32); + self.start_find( + text, + i32::try_from(query_start).map_err(|_| Error::Argument)?, + crate::DirectoryManagerDirFindFlags( + query_flags.0 | crate::DirectoryManagerDirFindFlags::EVENTS.0, + ), + ) + } + pub fn start_classified_search_with_string(&self, search_text: String) -> Result { + self.start_classified_search_with_string_classified_categories_classified_query_flags( + search_text, + crate::DirectoryManagerClassifiedCategories::Any, + crate::DirectoryManagerClassifiedQueryFlags::ALL, + ) + } + pub fn start_classified_search_with_string_classified_categories_classified_query_flags( + &self, + search_text: String, + category: crate::DirectoryManagerClassifiedCategories, + query_flags: crate::DirectoryManagerClassifiedQueryFlags, + ) -> Result { + valid_query(&search_text)?; + let id = UUID::random()?; + self.send_classified(id, search_text, category, query_flags)?; + Ok(id) + } + fn send_classified( + &self, + id: UUID, + search_text: String, + category: crate::DirectoryManagerClassifiedCategories, + query_flags: crate::DirectoryManagerClassifiedQueryFlags, + ) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::DirClassifiedQueryPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.query_data.query_id = id; + p.query_data.query_text = search_text.into_bytes(); + p.query_data.category = category as u32; + p.query_data.query_flags = query_flags.0 as u32; + p.query_data.query_start = 0; + self.send(&p, PacketType::DirClassifiedQuery) + } + pub fn start_dir_places_search_with_string_int32( + &self, + search_text: String, + query_start: i32, + ) -> Result { + self.start_dir_places_search_with_string_dir_find_flags_parcel_category_int32( + search_text, + crate::DirectoryManagerDirFindFlags::INCLUDE_PG, + crate::ParcelCategory::Any, + query_start, + ) + } + pub fn start_dir_places_search_with_string_dir_find_flags_parcel_category_int32( + &self, + search_text: String, + query_flags: crate::DirectoryManagerDirFindFlags, + category: crate::ParcelCategory, + query_start: i32, + ) -> Result { + valid_query(&search_text)?; + if query_start < 0 { + return Err(Error::Argument); + } + let id = UUID::random()?; + self.send_dir_places(id, search_text, query_flags, category, query_start)?; + Ok(id) + } + fn send_dir_places( + &self, + id: UUID, + search_text: String, + query_flags: crate::DirectoryManagerDirFindFlags, + category: crate::ParcelCategory, + query_start: i32, + ) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::DirPlacesQueryPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.query_data.query_id = id; + p.query_data.query_text = search_text.into_bytes(); + p.query_data.query_flags = query_flags.0 as u32; + p.query_data.category = category as i8; + p.query_data.query_start = query_start; + self.send(&p, PacketType::DirPlacesQuery) + } + pub fn start_land_search_with_search_type_flags( + &self, + type_flags: crate::DirectoryManagerSearchTypeFlags, + ) -> Result<(), Error> { + self.start_land_search_with_search_type_flags_int32_int32_int32(type_flags, 0, 0, 0) + } + pub fn start_land_search_with_search_type_flags_int32_int32_int32( + &self, + type_flags: crate::DirectoryManagerSearchTypeFlags, + price_limit: i32, + area_limit: i32, + query_start: i32, + ) -> Result<(), Error> { + self.start_land_search_with_dir_find_flags_search_type_flags_int32_int32_int32( + crate::DirectoryManagerDirFindFlags::INCLUDE_PG, + type_flags, + price_limit, + area_limit, + query_start, + ) + } + pub fn start_land_search_with_dir_find_flags_search_type_flags_int32_int32_int32( + &self, + find_flags: crate::DirectoryManagerDirFindFlags, + type_flags: crate::DirectoryManagerSearchTypeFlags, + price_limit: i32, + area_limit: i32, + query_start: i32, + ) -> Result<(), Error> { + self.start_land_search(find_flags, type_flags, price_limit, area_limit, query_start)?; + Ok(()) + } + fn start_land_search( + &self, + find_flags: crate::DirectoryManagerDirFindFlags, + type_flags: crate::DirectoryManagerSearchTypeFlags, + price_limit: i32, + area_limit: i32, + query_start: i32, + ) -> Result { + if price_limit < 0 || area_limit < 0 || query_start < 0 { + return Err(Error::Argument); + } + let query_id = UUID::random()?; + self.send_land( + query_id, + find_flags, + type_flags, + price_limit, + area_limit, + query_start, + )?; + Ok(query_id) + } + fn send_land( + &self, + query_id: UUID, + find_flags: crate::DirectoryManagerDirFindFlags, + type_flags: crate::DirectoryManagerSearchTypeFlags, + price_limit: i32, + area_limit: i32, + query_start: i32, + ) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::DirLandQueryPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.query_data.query_id = query_id; + p.query_data.query_flags = find_flags.0 as u32; + p.query_data.search_type = type_flags.0 as u32; + p.query_data.price = price_limit; + p.query_data.area = area_limit; + p.query_data.query_start = query_start; + self.send(&p, PacketType::DirLandQuery) + } + pub fn start_places_search_with_method(&self) -> Result { + self.start_places_search_with_string(String::new()) + } + pub fn start_places_search_with_uuid(&self, group_id: UUID) -> Result { + self.start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid( + crate::DirectoryManagerDirFindFlags::INCLUDE_PG, + crate::ParcelCategory::Any, + String::new(), + String::new(), + group_id, + UUID::random()?, + ) + } + pub fn start_places_search_with_string(&self, search_text: String) -> Result { + self.start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid( + crate::DirectoryManagerDirFindFlags::INCLUDE_PG, + crate::ParcelCategory::Any, + search_text, + String::new(), + UUID::zero(), + UUID::random()?, + ) + } + pub fn start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid( + &self, + find_flags: crate::DirectoryManagerDirFindFlags, + search_category: crate::ParcelCategory, + search_text: String, + simulator_name: String, + group_id: UUID, + transaction_id: UUID, + ) -> Result { + valid_query(&search_text)?; + valid_query(&simulator_name)?; + self.send_places( + find_flags, + search_category, + search_text, + simulator_name, + group_id, + transaction_id, + )?; + Ok(transaction_id) + } + fn send_places( + &self, + find_flags: crate::DirectoryManagerDirFindFlags, + search_category: crate::ParcelCategory, + search_text: String, + simulator_name: String, + group_id: UUID, + transaction_id: UUID, + ) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::PlacesQueryPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.agent_data.query_id = group_id; + p.query_data.query_flags = find_flags.0 as u32; + p.query_data.category = search_category as i8; + p.query_data.query_text = search_text.into_bytes(); + p.query_data.sim_name = simulator_name.into_bytes(); + p.transaction_data.transaction_id = transaction_id; + self.send(&p, PacketType::PlacesQuery) + } + pub fn event_info_request(&self, event_id: u32) -> Result<(), Error> { + let (agent, session) = self.agent_ids(); + let mut p = crate::packets::EventInfoRequestPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.event_data.event_id = event_id; + self.send(&p, PacketType::EventInfoRequest) + } + pub async fn get_event_info( + &self, + event_id: u32, + ct: Option, + ) -> Result>, Error> { + if let Some(value) = read(&self.inner.event_info) + .clone() + .filter(|v| v.id == event_id) + { + return Ok(Some(Some(value))); + } + let notified = self.inner.event_notify.notified(); + self.event_info_request(event_id)?; + let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let ms = self + .inner + .client + .settings_ref() + .timing + .map_request_timeout + .max(1) as u64; + tokio::select! {()=notified=>{},()=token.cancelled()=>return Err(Error::Cancelled),()=tokio::time::sleep(Duration::from_millis(ms))=>{}} + Ok(Some( + read(&self.inner.event_info) + .clone() + .filter(|v| v.id == event_id), + )) + } + pub fn search_people( + &self, + name: String, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + valid_query(&name)?; + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.people_streams).insert(id, stream.clone()); + self.arm_stream_timeout(id, SearchStreamKind::People, stream.clone(), cancellation); + mutex(&self.inner.find_pages).insert( + id, + (name.clone(), crate::DirectoryManagerDirFindFlags::PEOPLE, 0), + ); + if let Err(error) = self.send_find(id, name, 0, crate::DirectoryManagerDirFindFlags::PEOPLE) + { + mutex(&self.inner.people_streams).remove(&id); + mutex(&self.inner.find_pages).remove(&id); + return Err(error); + } + Ok(stream) + } + pub fn search_groups( + &self, + query: String, + flags: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + valid_query(&query)?; + let flags = flags.unwrap_or(crate::DirectoryManagerDirFindFlags::GROUPS); + let flags = crate::DirectoryManagerDirFindFlags( + flags.0 | crate::DirectoryManagerDirFindFlags::GROUPS.0, + ); + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.group_streams).insert(id, stream.clone()); + self.arm_stream_timeout(id, SearchStreamKind::Groups, stream.clone(), cancellation); + mutex(&self.inner.find_pages).insert(id, (query.clone(), flags, 0)); + if let Err(error) = self.send_find(id, query, 0, flags) { + mutex(&self.inner.group_streams).remove(&id); + mutex(&self.inner.find_pages).remove(&id); + return Err(error); + } + Ok(stream) + } + pub fn search_classifieds( + &self, + query: String, + category: Option, + flags: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + valid_query(&query)?; + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.classified_streams).insert(id, stream.clone()); + if let Err(error) = self.send_classified( + id, + query, + category.unwrap_or(crate::DirectoryManagerClassifiedCategories::Any), + flags.unwrap_or(crate::DirectoryManagerClassifiedQueryFlags::ALL), + ) { + mutex(&self.inner.classified_streams).remove(&id); + stream.close(); + return Err(error); + } + self.arm_stream_timeout( + id, + SearchStreamKind::Classifieds, + stream.clone(), + cancellation, + ); + Ok(stream) + } + pub fn search_dir_places( + &self, + query: String, + flags: Option, + category: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + valid_query(&query)?; + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.dir_place_streams).insert(id, stream.clone()); + if let Err(error) = self.send_dir_places( + id, + query, + flags.unwrap_or(crate::DirectoryManagerDirFindFlags::INCLUDE_PG), + category.unwrap_or(crate::ParcelCategory::Any), + 0, + ) { + mutex(&self.inner.dir_place_streams).remove(&id); + stream.close(); + return Err(error); + } + self.arm_stream_timeout( + id, + SearchStreamKind::DirPlaces, + stream.clone(), + cancellation, + ); + Ok(stream) + } + pub fn search_events( + &self, + query: String, + flags: Option, + event_day: Option, + category: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let flags = flags.unwrap_or(crate::DirectoryManagerDirFindFlags::EVENTS); + let flags = crate::DirectoryManagerDirFindFlags( + flags.0 | crate::DirectoryManagerDirFindFlags::EVENTS.0, + ); + let text = format!( + "{}|{}|{query}", + event_day.unwrap_or_else(|| "u".into()), + category.unwrap_or(crate::DirectoryManagerEventCategories::All) as i32 + ); + valid_query(&text)?; + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.event_streams).insert(id, stream.clone()); + self.arm_stream_timeout(id, SearchStreamKind::Events, stream.clone(), cancellation); + mutex(&self.inner.find_pages).insert(id, (text.clone(), flags, 0)); + if let Err(error) = self.send_find(id, text, 0, flags) { + mutex(&self.inner.event_streams).remove(&id); + mutex(&self.inner.find_pages).remove(&id); + return Err(error); + } + Ok(stream) + } + pub fn search_land( + &self, + type_flags: crate::DirectoryManagerSearchTypeFlags, + find_flags: Option, + price_limit: Option, + area_limit: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let price_limit = price_limit.unwrap_or(0); + let area_limit = area_limit.unwrap_or(0); + if price_limit < 0 || area_limit < 0 { + return Err(Error::Argument); + } + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.land_streams).insert(id, stream.clone()); + if let Err(error) = self.send_land( + id, + find_flags.unwrap_or(crate::DirectoryManagerDirFindFlags::INCLUDE_PG), + type_flags, + price_limit, + area_limit, + 0, + ) { + mutex(&self.inner.land_streams).remove(&id); + stream.close(); + return Err(error); + } + self.arm_stream_timeout(id, SearchStreamKind::Land, stream.clone(), cancellation); + Ok(stream) + } + pub fn search_places( + &self, + find_flags: crate::DirectoryManagerDirFindFlags, + category: crate::ParcelCategory, + query: String, + simulator_name: Option, + group_id: Option, + ct: Option, + ) -> Result, Error> { + let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let simulator_name = simulator_name.unwrap_or_default(); + valid_query(&query)?; + valid_query(&simulator_name)?; + let id = UUID::random()?; + let stream = self.result_stream(cancellation.clone()); + mutex(&self.inner.place_streams).insert(id, stream.clone()); + if let Err(error) = self.send_places( + find_flags, + category, + query, + simulator_name, + group_id.unwrap_or_else(UUID::zero), + id, + ) { + mutex(&self.inner.place_streams).remove(&id); + stream.close(); + return Err(error); + } + self.arm_stream_timeout(id, SearchStreamKind::Places, stream.clone(), cancellation); + Ok(stream) + } +} + +impl DirectoryManagerInner { + fn next_find_page( + &self, + query_id: UUID, + count: usize, + ) -> Option<(String, crate::DirectoryManagerDirFindFlags, i32)> { + if count < 100 { + mutex(&self.find_pages).remove(&query_id); + return None; + } + let mut pages = mutex(&self.find_pages); + let (text, flags, start) = pages.get_mut(&query_id)?; + *start = start.saturating_add(100); + Some((text.clone(), *flags, *start)) + } + fn handle_packet(self: &Arc, event: RawPacketReceivedEventArgs) { + if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_DIRECTORY_PACKET_BYTES { + return; + } + let mut pos = 0; + match event.packet_type { + PacketType::DirPeopleReply => { + if let Ok(p) = + crate::packets::DirPeopleReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerAgentSearchData { + agent_id: v.agent_id, + first_name: wire_string(&v.first_name), + last_name: wire_string(&v.last_name), + online: v.online, + }) + .collect::>(); + let next = self.next_find_page(p.query_data.query_id, values.len()); + publish_stream_page( + &self.people_streams, + p.query_data.query_id, + &values, + next.is_none(), + ); + self.events.people.emit(DirPeopleReplyEventArgs { + query_id: p.query_data.query_id, + matched_people: values, + }); + if let Some((text, flags, start)) = next + && (DirectoryManager { + inner: Arc::clone(self), + }) + .send_find(p.query_data.query_id, text, start, flags) + .is_err() + { + mutex(&self.find_pages).remove(&p.query_data.query_id); + publish_stream_page(&self.people_streams, p.query_data.query_id, &[], true); + } + } + } + PacketType::DirGroupsReply => { + if let Ok(p) = + crate::packets::DirGroupsReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerGroupSearchData { + group_id: v.group_id, + group_name: wire_string(&v.group_name), + members: v.members, + }) + .collect::>(); + let next = self.next_find_page(p.query_data.query_id, values.len()); + publish_stream_page( + &self.group_streams, + p.query_data.query_id, + &values, + next.is_none(), + ); + self.events.groups.emit(DirGroupsReplyEventArgs { + query_id: p.query_data.query_id, + matched_groups: values, + }); + if let Some((text, flags, start)) = next + && (DirectoryManager { + inner: Arc::clone(self), + }) + .send_find(p.query_data.query_id, text, start, flags) + .is_err() + { + mutex(&self.find_pages).remove(&p.query_data.query_id); + publish_stream_page(&self.group_streams, p.query_data.query_id, &[], true); + } + } + } + PacketType::DirLandReply => { + if let Ok(p) = + crate::packets::DirLandReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerDirectoryParcel { + actual_area: v.actual_area, + auction: v.auction, + dwell: 0.0, + for_sale: v.for_sale, + id: v.parcel_id, + name: wire_string(&v.name), + sale_price: v.sale_price, + }) + .collect::>(); + finish_stream(&self.land_streams, p.query_data.query_id, &values); + self.events.land.emit(DirLandReplyEventArgs { + dir_parcels: values, + }); + } + } + PacketType::DirPlacesReply => { + if let Ok(p) = + crate::packets::DirPlacesReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let query = p.query_data.first().map_or_else(UUID::zero, |v| v.query_id); + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerDirectoryParcel { + actual_area: 0, + auction: v.auction, + dwell: v.dwell, + for_sale: v.for_sale, + id: v.parcel_id, + name: wire_string(&v.name), + sale_price: 0, + }) + .collect::>(); + finish_stream(&self.dir_place_streams, query, &values); + self.events.dir_places.emit(DirPlacesReplyEventArgs { + query_id: query, + matched_parcels: values, + }); + } + } + PacketType::DirClassifiedReply => { + if let Ok(p) = + crate::packets::DirClassifiedReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerClassified { + creation_date: UNIX_EPOCH + + Duration::from_secs(u64::from(v.creation_date)), + expiration_date: UNIX_EPOCH + + Duration::from_secs(u64::from(v.expiration_date)), + flags: crate::DirectoryManagerClassifiedFlags(v.classified_flags), + id: v.classified_id, + name: wire_string(&v.name), + price: v.price_for_listing, + }) + .collect::>(); + finish_stream(&self.classified_streams, p.query_data.query_id, &values); + self.events.classifieds.emit(DirClassifiedsReplyEventArgs { + classifieds: values, + }); + } + } + PacketType::DirEventsReply => { + if let Ok(p) = + crate::packets::DirEventsReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let values = p + .query_replies + .into_iter() + .take(1024) + .map(|v| DirectoryManagerEventsSearchData { + date: wire_string(&v.date), + flags: event_flags(v.event_flags), + id: v.event_id, + name: wire_string(&v.name), + owner: v.owner_id, + time: v.unix_time, + }) + .collect::>(); + let next = self.next_find_page(p.query_data.query_id, values.len()); + publish_stream_page( + &self.event_streams, + p.query_data.query_id, + &values, + next.is_none(), + ); + self.events.events.emit(DirEventsReplyEventArgs { + query_id: p.query_data.query_id, + matched_events: values, + }); + if let Some((text, flags, start)) = next + && (DirectoryManager { + inner: Arc::clone(self), + }) + .send_find(p.query_data.query_id, text, start, flags) + .is_err() + { + mutex(&self.find_pages).remove(&p.query_data.query_id); + publish_stream_page(&self.event_streams, p.query_data.query_id, &[], true); + } + } + } + PacketType::PlacesReply => { + if let Ok(p) = + crate::packets::PlacesReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let query_id = p.transaction_data.transaction_id; + let values = p + .query_data + .into_iter() + .take(1024) + .map(|v| DirectoryManagerPlacesSearchData { + actual_area: v.actual_area, + billable_area: v.billable_area, + desc: wire_string(&v.desc), + dwell: v.dwell, + flags: if v.flags & 128 != 0 { + crate::DirectoryManagerPlacesFlags::ForSale + } else { + crate::DirectoryManagerPlacesFlags::NotForSale + }, + global_x: v.global_x, + global_y: v.global_y, + global_z: v.global_z, + name: wire_string(&v.name), + owner_id: v.owner_id, + price: v.price, + sku: String::new(), + sim_name: wire_string(&v.sim_name), + snapshot_id: v.snapshot_id, + }) + .collect::>(); + finish_stream(&self.place_streams, query_id, &values); + self.events.places.emit(PlacesReplyEventArgs { + query_id, + matched_places: values, + }); + } + } + PacketType::EventInfoReply => { + if let Ok(p) = + crate::packets::EventInfoReplyPacket::new_from_bytes(&event.data, &mut pos) + { + let v = p.event_data; + let info = DirectoryManagerEventInfo { + amount: v.amount, + category: event_category(&wire_string(&v.category)), + cover: v.cover, + creator: UUID::parse(wire_string(&v.creator)) + .unwrap_or_else(|_| UUID::zero()), + date: wire_string(&v.date), + date_utc: v.date_utc, + desc: wire_string(&v.desc), + duration: v.duration, + flags: event_flags(v.event_flags), + global_pos: v.global_pos, + id: v.event_id, + name: wire_string(&v.name), + sim_name: wire_string(&v.sim_name), + }; + *write(&self.event_info) = Some(info.clone()); + self.events.event_info.emit(EventInfoReplyEventArgs { + matched_event: info, + }); + self.event_notify.notify_waiters(); + } + } + _ => {} + } + } +} +fn event_flags(value: u32) -> crate::DirectoryManagerEventFlags { + match value & 3 { + 1 => crate::DirectoryManagerEventFlags::Mature, + 2 => crate::DirectoryManagerEventFlags::Adult, + _ => crate::DirectoryManagerEventFlags::PG, + } +} +fn event_category(value: &str) -> crate::DirectoryManagerEventCategories { + match value.parse::().unwrap_or_default() { + 18 => crate::DirectoryManagerEventCategories::Discussion, + 19 => crate::DirectoryManagerEventCategories::Sports, + 20 => crate::DirectoryManagerEventCategories::LiveMusic, + 22 => crate::DirectoryManagerEventCategories::Commercial, + 23 => crate::DirectoryManagerEventCategories::Nightlife, + 24 => crate::DirectoryManagerEventCategories::Games, + 25 => crate::DirectoryManagerEventCategories::Pageants, + 26 => crate::DirectoryManagerEventCategories::Education, + 27 => crate::DirectoryManagerEventCategories::Arts, + 28 => crate::DirectoryManagerEventCategories::Charity, + 29 => crate::DirectoryManagerEventCategories::Miscellaneous, + _ => crate::DirectoryManagerEventCategories::All, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use libremetaverse_types::compat::CancellationTokenSource; + #[test] + fn place_slurl_uses_local_coordinates() { + let value = DirectoryManagerPlacesSearchData { + actual_area: 0, + billable_area: 0, + desc: String::new(), + dwell: 0.0, + flags: crate::DirectoryManagerPlacesFlags::NotForSale, + global_x: 1025.0, + global_y: 2050.0, + global_z: 30.0, + name: String::new(), + owner_id: UUID::zero(), + price: 0, + sku: String::new(), + sim_name: "Example Region".into(), + snapshot_id: UUID::zero(), + }; + assert_eq!( + value.to_s_lurl().unwrap(), + "secondlife:///app/teleport/Example%20Region/1/2/30" + ); + } + #[tokio::test] + async fn async_search_results_are_ordered_and_complete() { + let stream = IAsyncEnumerable::new(); + stream.push(DirectoryManagerAgentSearchData { + agent_id: UUID::zero(), + first_name: "Ada".into(), + last_name: "Lovelace".into(), + online: true, + }); + stream.close(); + assert_eq!(stream.next().await.unwrap().to_string(), "Ada Lovelace"); + assert!(stream.next().await.is_none()); + } + #[tokio::test] + async fn async_search_cancellation_wakes_pending_consumer() { + let source = CancellationTokenSource::new(); + let stream = + IAsyncEnumerable::::with_cancellation(source.token()); + let pending = stream.next(); + source.cancel(); + assert!(pending.await.is_none()); + } +} diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 83e17b6..334575f 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -5041,49 +5041,12 @@ impl ClassifiedInfoReplyEventArgs { } /// C# type: `T:LibreMetaverse.CoarseLocationUpdateEventArgs`. -pub struct CoarseLocationUpdateEventArgs; -impl CoarseLocationUpdateEventArgs { - /// C# member: `M:LibreMetaverse.CoarseLocationUpdateEventArgs.#ctor(LibreMetaverse.Simulator,System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,LibreMetaverse.Vector3},System.Collections.Generic.ICollection{LibreMetaverse.UUID},System.Collections.Generic.ICollection{LibreMetaverse.UUID})`. - pub fn new( - simulator: libremetaverse::Simulator, - positions: std::collections::HashMap< - libremetaverse_types::UUID, - libremetaverse_types::Vector3, - >, - new_entries: Vec, - removed_entries: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.CoarseLocationUpdateEventArgs.#ctor(LibreMetaverse.Simulator,System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,LibreMetaverse.Vector3},System.Collections.Generic.ICollection{LibreMetaverse.UUID},System.Collections.Generic.ICollection{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.NewEntries`. - pub fn new_entries(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CoarseLocationUpdateEventArgs.NewEntries" - ) - } - /// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.Positions`. - pub fn positions( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CoarseLocationUpdateEventArgs.Positions" - ) - } - /// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.RemovedEntries`. - pub fn removed_entries(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CoarseLocationUpdateEventArgs.RemovedEntries" - ) - } - /// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.Simulator`. - pub fn simulator(&self) -> libremetaverse::Simulator { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CoarseLocationUpdateEventArgs.Simulator" - ) - } -} +/// C# member: `M:LibreMetaverse.CoarseLocationUpdateEventArgs.#ctor(LibreMetaverse.Simulator,System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,LibreMetaverse.Vector3},System.Collections.Generic.ICollection{LibreMetaverse.UUID},System.Collections.Generic.ICollection{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.NewEntries`. +/// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.Positions`. +/// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.RemovedEntries`. +/// C# member: `P:LibreMetaverse.CoarseLocationUpdateEventArgs.Simulator`. +pub use crate::grid_manager::CoarseLocationUpdateEventArgs; /// C# type: `T:LibreMetaverse.CompressedFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -5424,569 +5387,94 @@ pub enum DictionaryEventAction { } /// C# type: `T:LibreMetaverse.DirClassifiedsReplyEventArgs`. -pub struct DirClassifiedsReplyEventArgs; -impl DirClassifiedsReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirClassifiedsReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.Classified})`. - pub fn new( - classifieds: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirClassifiedsReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.Classified})", - ) - } - /// C# member: `P:LibreMetaverse.DirClassifiedsReplyEventArgs.Classifieds`. - pub fn classifieds(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirClassifiedsReplyEventArgs.Classifieds" - ) - } -} +/// C# member: `M:LibreMetaverse.DirClassifiedsReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.Classified})`. +/// C# member: `P:LibreMetaverse.DirClassifiedsReplyEventArgs.Classifieds`. +pub use crate::directory_manager::DirClassifiedsReplyEventArgs; /// C# type: `T:LibreMetaverse.DirEventsReplyEventArgs`. -pub struct DirEventsReplyEventArgs; -impl DirEventsReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirEventsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.EventsSearchData})`. - pub fn new( - query_id: libremetaverse_types::UUID, - matched_events: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirEventsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.EventsSearchData})", - ) - } - /// C# member: `P:LibreMetaverse.DirEventsReplyEventArgs.MatchedEvents`. - pub fn matched_events(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirEventsReplyEventArgs.MatchedEvents" - ) - } - /// C# member: `P:LibreMetaverse.DirEventsReplyEventArgs.QueryID`. - pub fn query_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.DirEventsReplyEventArgs.QueryID") - } -} +/// C# member: `M:LibreMetaverse.DirEventsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.EventsSearchData})`. +/// C# member: `P:LibreMetaverse.DirEventsReplyEventArgs.MatchedEvents`. +/// C# member: `P:LibreMetaverse.DirEventsReplyEventArgs.QueryID`. +pub use crate::directory_manager::DirEventsReplyEventArgs; /// C# type: `T:LibreMetaverse.DirGroupsReplyEventArgs`. -pub struct DirGroupsReplyEventArgs; -impl DirGroupsReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirGroupsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.GroupSearchData})`. - pub fn new( - query_id: libremetaverse_types::UUID, - matched_groups: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirGroupsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.GroupSearchData})", - ) - } - /// C# member: `P:LibreMetaverse.DirGroupsReplyEventArgs.MatchedGroups`. - pub fn matched_groups(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirGroupsReplyEventArgs.MatchedGroups" - ) - } - /// C# member: `P:LibreMetaverse.DirGroupsReplyEventArgs.QueryID`. - pub fn query_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.DirGroupsReplyEventArgs.QueryID") - } -} +/// C# member: `M:LibreMetaverse.DirGroupsReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.GroupSearchData})`. +/// C# member: `P:LibreMetaverse.DirGroupsReplyEventArgs.MatchedGroups`. +/// C# member: `P:LibreMetaverse.DirGroupsReplyEventArgs.QueryID`. +pub use crate::directory_manager::DirGroupsReplyEventArgs; /// C# type: `T:LibreMetaverse.DirLandReplyEventArgs`. -pub struct DirLandReplyEventArgs; -impl DirLandReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirLandReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})`. - pub fn new( - dir_parcels: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirLandReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})", - ) - } - /// C# member: `P:LibreMetaverse.DirLandReplyEventArgs.DirParcels`. - pub fn dir_parcels(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirLandReplyEventArgs.DirParcels" - ) - } -} +/// C# member: `M:LibreMetaverse.DirLandReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})`. +/// C# member: `P:LibreMetaverse.DirLandReplyEventArgs.DirParcels`. +pub use crate::directory_manager::DirLandReplyEventArgs; /// C# type: `T:LibreMetaverse.DirPeopleReplyEventArgs`. -pub struct DirPeopleReplyEventArgs; -impl DirPeopleReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirPeopleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.AgentSearchData})`. - pub fn new( - query_id: libremetaverse_types::UUID, - matched_people: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirPeopleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.AgentSearchData})", - ) - } - /// C# member: `P:LibreMetaverse.DirPeopleReplyEventArgs.MatchedPeople`. - pub fn matched_people(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirPeopleReplyEventArgs.MatchedPeople" - ) - } - /// C# member: `P:LibreMetaverse.DirPeopleReplyEventArgs.QueryID`. - pub fn query_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.DirPeopleReplyEventArgs.QueryID") - } -} +/// C# member: `M:LibreMetaverse.DirPeopleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.AgentSearchData})`. +/// C# member: `P:LibreMetaverse.DirPeopleReplyEventArgs.MatchedPeople`. +/// C# member: `P:LibreMetaverse.DirPeopleReplyEventArgs.QueryID`. +pub use crate::directory_manager::DirPeopleReplyEventArgs; /// C# type: `T:LibreMetaverse.DirPlacesReplyEventArgs`. -pub struct DirPlacesReplyEventArgs; -impl DirPlacesReplyEventArgs { - /// C# member: `M:LibreMetaverse.DirPlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})`. - pub fn new( - query_id: libremetaverse_types::UUID, - matched_parcels: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirPlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})", - ) - } - /// C# member: `P:LibreMetaverse.DirPlacesReplyEventArgs.MatchedParcels`. - pub fn matched_parcels(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.DirPlacesReplyEventArgs.MatchedParcels" - ) - } - /// C# member: `P:LibreMetaverse.DirPlacesReplyEventArgs.QueryID`. - pub fn query_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.DirPlacesReplyEventArgs.QueryID") - } -} +/// C# member: `M:LibreMetaverse.DirPlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.DirectoryParcel})`. +/// C# member: `P:LibreMetaverse.DirPlacesReplyEventArgs.MatchedParcels`. +/// C# member: `P:LibreMetaverse.DirPlacesReplyEventArgs.QueryID`. +pub use crate::directory_manager::DirPlacesReplyEventArgs; /// C# type: `T:LibreMetaverse.DirectoryManager`. -pub struct DirectoryManager; -impl DirectoryManager { - /// C# member: `E:LibreMetaverse.DirectoryManager.DirClassifiedsReply`. - pub fn subscribe_dir_classifieds_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::DirClassifiedsReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.DirectoryManager.DirClassifiedsReply" - ) - } - /// C# member: `E:LibreMetaverse.DirectoryManager.DirEventsReply`. - pub fn subscribe_dir_events_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::DirEventsReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.DirEventsReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.DirGroupsReply`. - pub fn subscribe_dir_groups_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::DirGroupsReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.DirGroupsReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.DirLandReply`. - pub fn subscribe_dir_land_reply( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.DirLandReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.DirPeopleReply`. - pub fn subscribe_dir_people_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::DirPeopleReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.DirPeopleReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.DirPlacesReply`. - pub fn subscribe_dir_places_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::DirPlacesReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.DirPlacesReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.EventInfoReply`. - pub fn subscribe_event_info_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EventInfoReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.EventInfoReply") - } - /// C# member: `E:LibreMetaverse.DirectoryManager.PlacesReply`. - pub fn subscribe_places_reply( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.DirectoryManager.PlacesReply") - } - /// C# member: `M:LibreMetaverse.DirectoryManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.DirectoryManager.Dispose") - } - /// C# member: `M:LibreMetaverse.DirectoryManager.EventInfoRequest(System.UInt32)`. - pub fn event_info_request(&self, event_id: u32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.EventInfoRequest(System.UInt32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.GetEventInfoAsync(System.UInt32,System.Threading.CancellationToken)`. - pub async fn get_event_info( - &self, - event_id: u32, - ct: Option, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.GetEventInfoAsync(System.UInt32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchClassifiedsAsync(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags,System.Threading.CancellationToken)`. - pub fn search_classifieds( - &self, - query: String, - category: Option, - flags: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchClassifiedsAsync(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchDirPlacesAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Threading.CancellationToken)`. - pub fn search_dir_places( - &self, - query: String, - flags: Option, - category: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerDirectoryParcel, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchDirPlacesAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchEventsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,LibreMetaverse.DirectoryManager.EventCategories,System.Threading.CancellationToken)`. - pub fn search_events( - &self, - query: String, - flags: Option, - event_day: Option, - category: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerEventsSearchData, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchEventsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,LibreMetaverse.DirectoryManager.EventCategories,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchGroupsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.Threading.CancellationToken)`. - pub fn search_groups( - &self, - query: String, - flags: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerGroupSearchData, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchGroupsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchLandAsync(LibreMetaverse.DirectoryManager.SearchTypeFlags,LibreMetaverse.DirectoryManager.DirFindFlags,System.Int32,System.Int32,System.Threading.CancellationToken)`. - pub fn search_land( - &self, - type_flags: libremetaverse::DirectoryManagerSearchTypeFlags, - find_flags: Option, - price_limit: Option, - area_limit: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerDirectoryParcel, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchLandAsync(LibreMetaverse.DirectoryManager.SearchTypeFlags,LibreMetaverse.DirectoryManager.DirFindFlags,System.Int32,System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchPeopleAsync(System.String,System.Threading.CancellationToken)`. - pub fn search_people( - &self, - name: String, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerAgentSearchData, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchPeopleAsync(System.String,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.SearchPlacesAsync(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub fn search_places( - &self, - find_flags: libremetaverse::DirectoryManagerDirFindFlags, - category: libremetaverse::ParcelCategory, - query: String, - simulator_name: Option, - group_id: Option, - ct: Option, - ) -> Result< - libremetaverse_types::compat::IAsyncEnumerable< - libremetaverse::DirectoryManagerPlacesSearchData, - >, - crate::Error, - > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.SearchPlacesAsync(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String)`. - pub fn start_classified_search_with_string( - &self, - search_text: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags)`. - pub fn start_classified_search_with_string_classified_categories_classified_query_flags( - &self, - search_text: String, - category: libremetaverse::DirectoryManagerClassifiedCategories, - query_flags: libremetaverse::DirectoryManagerClassifiedQueryFlags, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Int32)`. - pub fn start_dir_places_search_with_string_dir_find_flags_parcel_category_int32( - &self, - search_text: String, - query_flags: libremetaverse::DirectoryManagerDirFindFlags, - category: libremetaverse::ParcelCategory, - query_start: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,System.Int32)`. - pub fn start_dir_places_search_with_string_int32( - &self, - search_text: String, - query_start: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,System.UInt32,LibreMetaverse.DirectoryManager.EventCategories)`. - pub fn start_events_search_with_string_dir_find_flags_string_u_int32_event_categories( - &self, - search_text: String, - query_flags: libremetaverse::DirectoryManagerDirFindFlags, - event_day: String, - query_start: u32, - category: libremetaverse::DirectoryManagerEventCategories, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,System.UInt32,LibreMetaverse.DirectoryManager.EventCategories)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,System.UInt32)`. - pub fn start_events_search_with_string_u_int32( - &self, - search_text: String, - query_start: u32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,System.UInt32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32)`. - pub fn start_group_search_with_string_int32( - &self, - search_text: String, - query_start: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32,LibreMetaverse.DirectoryManager.DirFindFlags)`. - pub fn start_group_search_with_string_int32_dir_find_flags( - &self, - search_text: String, - query_start: i32, - flags: libremetaverse::DirectoryManagerDirFindFlags, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32,LibreMetaverse.DirectoryManager.DirFindFlags)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)`. - pub fn start_land_search_with_dir_find_flags_search_type_flags_int32_int32_int32( - &self, - find_flags: libremetaverse::DirectoryManagerDirFindFlags, - type_flags: libremetaverse::DirectoryManagerSearchTypeFlags, - price_limit: i32, - area_limit: i32, - query_start: i32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags)`. - pub fn start_land_search_with_search_type_flags( - &self, - type_flags: libremetaverse::DirectoryManagerSearchTypeFlags, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)`. - pub fn start_land_search_with_search_type_flags_int32_int32_int32( - &self, - type_flags: libremetaverse::DirectoryManagerSearchTypeFlags, - price_limit: i32, - area_limit: i32, - query_start: i32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartPeopleSearch(System.String,System.Int32)`. - pub fn start_people_search( - &self, - search_text: String, - query_start: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartPeopleSearch(System.String,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch`. - pub fn start_places_search_with_method( - &self, - ) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.DirectoryManager.StartPlacesSearch") - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid( - &self, - find_flags: libremetaverse::DirectoryManagerDirFindFlags, - search_category: libremetaverse::ParcelCategory, - search_text: String, - simulator_name: String, - group_id: libremetaverse_types::UUID, - transaction_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.UUID)`. - pub fn start_places_search_with_uuid( - &self, - group_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(System.String)`. - pub fn start_places_search_with_string( - &self, - search_text: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.StartPlacesSearch(System.String)", - ) - } -} +/// C# member: `E:LibreMetaverse.DirectoryManager.DirClassifiedsReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.DirEventsReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.DirGroupsReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.DirLandReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.DirPeopleReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.DirPlacesReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.EventInfoReply`. +/// C# member: `E:LibreMetaverse.DirectoryManager.PlacesReply`. +/// C# member: `M:LibreMetaverse.DirectoryManager.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.Dispose`. +/// C# member: `M:LibreMetaverse.DirectoryManager.EventInfoRequest(System.UInt32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.GetEventInfoAsync(System.UInt32,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchClassifiedsAsync(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchDirPlacesAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchEventsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,LibreMetaverse.DirectoryManager.EventCategories,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchGroupsAsync(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchLandAsync(LibreMetaverse.DirectoryManager.SearchTypeFlags,LibreMetaverse.DirectoryManager.DirFindFlags,System.Int32,System.Int32,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchPeopleAsync(System.String,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.SearchPlacesAsync(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartClassifiedSearch(System.String,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.DirectoryManager.ClassifiedQueryFlags)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartDirPlacesSearch(System.String,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,LibreMetaverse.DirectoryManager.DirFindFlags,System.String,System.UInt32,LibreMetaverse.DirectoryManager.EventCategories)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartEventsSearch(System.String,System.UInt32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartGroupSearch(System.String,System.Int32,LibreMetaverse.DirectoryManager.DirFindFlags)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartLandSearch(LibreMetaverse.DirectoryManager.SearchTypeFlags,System.Int32,System.Int32,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartPeopleSearch(System.String,System.Int32)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.DirectoryManager.DirFindFlags,LibreMetaverse.ParcelCategory,System.String,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.DirectoryManager.StartPlacesSearch(System.String)`. +pub use crate::directory_manager::DirectoryManager; /// C# type: `T:LibreMetaverse.DirectoryManager.AgentSearchData`. -pub struct DirectoryManagerAgentSearchData { - /// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.AgentID`. - pub agent_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.FirstName`. - pub first_name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.LastName`. - pub last_name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.Online`. - pub online: bool, -} -impl DirectoryManagerAgentSearchData { - /// C# member: `M:LibreMetaverse.DirectoryManager.AgentSearchData.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.AgentSearchData.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.AgentID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.FirstName`. +/// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.LastName`. +/// C# member: `F:LibreMetaverse.DirectoryManager.AgentSearchData.Online`. +/// C# member: `M:LibreMetaverse.DirectoryManager.AgentSearchData.ToString`. +pub use crate::directory_manager::DirectoryManagerAgentSearchData; /// C# type: `T:LibreMetaverse.DirectoryManager.Classified`. -pub struct DirectoryManagerClassified { - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.CreationDate`. - pub creation_date: std::time::SystemTime, - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.ExpirationDate`. - pub expiration_date: std::time::SystemTime, - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Flags`. - pub flags: libremetaverse::DirectoryManagerClassifiedFlags, - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Price`. - pub price: i32, -} -impl DirectoryManagerClassified { - /// C# member: `M:LibreMetaverse.DirectoryManager.Classified.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.Classified.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.CreationDate`. +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.ExpirationDate`. +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Flags`. +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.ID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Name`. +/// C# member: `F:LibreMetaverse.DirectoryManager.Classified.Price`. +/// C# member: `M:LibreMetaverse.DirectoryManager.Classified.ToString`. +pub use crate::directory_manager::DirectoryManagerClassified; /// C# type: `T:LibreMetaverse.DirectoryManager.ClassifiedCategories`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -6106,30 +5594,15 @@ impl DirectoryManagerDirFindFlags { } /// C# type: `T:LibreMetaverse.DirectoryManager.DirectoryParcel`. -pub struct DirectoryManagerDirectoryParcel { - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ActualArea`. - pub actual_area: i32, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Auction`. - pub auction: bool, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Dwell`. - pub dwell: f32, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ForSale`. - pub for_sale: bool, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.SalePrice`. - pub sale_price: i32, -} -impl DirectoryManagerDirectoryParcel { - /// C# member: `M:LibreMetaverse.DirectoryManager.DirectoryParcel.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.DirectoryParcel.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ActualArea`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Auction`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Dwell`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ForSale`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.ID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.Name`. +/// C# member: `F:LibreMetaverse.DirectoryManager.DirectoryParcel.SalePrice`. +/// C# member: `M:LibreMetaverse.DirectoryManager.DirectoryParcel.ToString`. +pub use crate::directory_manager::DirectoryManagerDirectoryParcel; /// C# type: `T:LibreMetaverse.DirectoryManager.EventCategories`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -6174,88 +5647,39 @@ pub enum DirectoryManagerEventFlags { } /// C# type: `T:LibreMetaverse.DirectoryManager.EventInfo`. -pub struct DirectoryManagerEventInfo { - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Amount`. - pub amount: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Category`. - pub category: libremetaverse::DirectoryManagerEventCategories, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Cover`. - pub cover: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Creator`. - pub creator: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Date`. - pub date: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.DateUTC`. - pub date_utc: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Desc`. - pub desc: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Duration`. - pub duration: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Flags`. - pub flags: libremetaverse::DirectoryManagerEventFlags, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.GlobalPos`. - pub global_pos: libremetaverse_types::Vector3d, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.ID`. - pub id: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.SimName`. - pub sim_name: String, -} -impl DirectoryManagerEventInfo { - /// C# member: `M:LibreMetaverse.DirectoryManager.EventInfo.ToSLurl`. - pub fn to_s_lurl(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.DirectoryManager.EventInfo.ToSLurl") - } - /// C# member: `M:LibreMetaverse.DirectoryManager.EventInfo.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.EventInfo.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Amount`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Category`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Cover`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Creator`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Date`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.DateUTC`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Desc`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Duration`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Flags`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.GlobalPos`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.ID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.Name`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventInfo.SimName`. +/// C# member: `M:LibreMetaverse.DirectoryManager.EventInfo.ToSLurl`. +/// C# member: `M:LibreMetaverse.DirectoryManager.EventInfo.ToString`. +pub use crate::directory_manager::DirectoryManagerEventInfo; /// C# type: `T:LibreMetaverse.DirectoryManager.EventsSearchData`. -pub struct DirectoryManagerEventsSearchData { - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Date`. - pub date: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Flags`. - pub flags: libremetaverse::DirectoryManagerEventFlags, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.ID`. - pub id: u32, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Owner`. - pub owner: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Time`. - pub time: u32, -} -impl DirectoryManagerEventsSearchData { - /// C# member: `M:LibreMetaverse.DirectoryManager.EventsSearchData.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.EventsSearchData.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Date`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Flags`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.ID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Name`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Owner`. +/// C# member: `F:LibreMetaverse.DirectoryManager.EventsSearchData.Time`. +/// C# member: `M:LibreMetaverse.DirectoryManager.EventsSearchData.ToString`. +pub use crate::directory_manager::DirectoryManagerEventsSearchData; /// C# type: `T:LibreMetaverse.DirectoryManager.GroupSearchData`. -pub struct DirectoryManagerGroupSearchData { - /// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.GroupID`. - pub group_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.GroupName`. - pub group_name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.Members`. - pub members: i32, -} -impl DirectoryManagerGroupSearchData { - /// C# member: `M:LibreMetaverse.DirectoryManager.GroupSearchData.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.GroupSearchData.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.GroupID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.GroupName`. +/// C# member: `F:LibreMetaverse.DirectoryManager.GroupSearchData.Members`. +/// C# member: `M:LibreMetaverse.DirectoryManager.GroupSearchData.ToString`. +pub use crate::directory_manager::DirectoryManagerGroupSearchData; /// C# type: `T:LibreMetaverse.DirectoryManager.PlacesFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -6268,50 +5692,23 @@ pub enum DirectoryManagerPlacesFlags { } /// C# type: `T:LibreMetaverse.DirectoryManager.PlacesSearchData`. -pub struct DirectoryManagerPlacesSearchData { - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.ActualArea`. - pub actual_area: i32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.BillableArea`. - pub billable_area: i32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Desc`. - pub desc: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Dwell`. - pub dwell: f32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Flags`. - pub flags: libremetaverse::DirectoryManagerPlacesFlags, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalX`. - pub global_x: f32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalY`. - pub global_y: f32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalZ`. - pub global_z: f32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.OwnerID`. - pub owner_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Price`. - pub price: i32, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SKU`. - pub sku: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SimName`. - pub sim_name: String, - /// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SnapshotID`. - pub snapshot_id: libremetaverse_types::UUID, -} -impl DirectoryManagerPlacesSearchData { - /// C# member: `M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToSLurl`. - pub fn to_s_lurl(&self) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToSLurl", - ) - } - /// C# member: `M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToString" - ) - } -} +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.ActualArea`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.BillableArea`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Desc`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Dwell`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Flags`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalX`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalY`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.GlobalZ`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Name`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.OwnerID`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.Price`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SKU`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SimName`. +/// C# member: `F:LibreMetaverse.DirectoryManager.PlacesSearchData.SnapshotID`. +/// C# member: `M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToSLurl`. +/// C# member: `M:LibreMetaverse.DirectoryManager.PlacesSearchData.ToString`. +pub use crate::directory_manager::DirectoryManagerPlacesSearchData; /// C# type: `T:LibreMetaverse.DirectoryManager.SearchTypeFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -7446,23 +6843,9 @@ impl EstateUsersReplyEventArgs { } /// C# type: `T:LibreMetaverse.EventInfoReplyEventArgs`. -pub struct EventInfoReplyEventArgs; -impl EventInfoReplyEventArgs { - /// C# member: `M:LibreMetaverse.EventInfoReplyEventArgs.#ctor(LibreMetaverse.DirectoryManager.EventInfo)`. - pub fn new( - matched_event: libremetaverse::DirectoryManagerEventInfo, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EventInfoReplyEventArgs.#ctor(LibreMetaverse.DirectoryManager.EventInfo)", - ) - } - /// C# member: `P:LibreMetaverse.EventInfoReplyEventArgs.MatchedEvent`. - pub fn matched_event(&self) -> libremetaverse::DirectoryManagerEventInfo { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EventInfoReplyEventArgs.MatchedEvent" - ) - } -} +/// C# member: `M:LibreMetaverse.EventInfoReplyEventArgs.#ctor(LibreMetaverse.DirectoryManager.EventInfo)`. +/// C# member: `P:LibreMetaverse.EventInfoReplyEventArgs.MatchedEvent`. +pub use crate::directory_manager::EventInfoReplyEventArgs; /// C# type: `T:LibreMetaverse.EventQueueRunningEventArgs`. /// C# member: `M:LibreMetaverse.EventQueueRunningEventArgs.#ctor(LibreMetaverse.Simulator)`. @@ -8358,11 +7741,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Directory`. pub fn directory(&self) -> libremetaverse::DirectoryManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Directory") + /* native client-core implementation */ + self.native_directory() + .unwrap_or_else(|_| panic!("failed to construct DirectoryManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Directory`. pub fn set_directory(&mut self, value: libremetaverse::DirectoryManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Directory") + /* native client-core implementation */ + self.native_set_directory(value) } /// C# member: `P:LibreMetaverse.GridClient.Environment`. pub fn environment(&self) -> libremetaverse::EnvironmentManager { @@ -8393,11 +7779,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Grid`. pub fn grid(&self) -> libremetaverse::GridManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Grid") + /* native client-core implementation */ + self.native_grid() + .unwrap_or_else(|_| panic!("failed to construct GridManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Grid`. pub fn set_grid(&mut self, value: libremetaverse::GridManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Grid") + /* native client-core implementation */ + self.native_set_grid(value) } /// C# member: `P:LibreMetaverse.GridClient.Groups`. pub fn groups(&self) -> libremetaverse::GroupManager { @@ -8419,11 +7808,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.InterestList`. pub fn interest_list(&self) -> libremetaverse::InterestListManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.InterestList") + /* native client-core implementation */ + self.native_interest_list() + .unwrap_or_else(|_| panic!("failed to construct InterestListManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.InterestList`. pub fn set_interest_list(&mut self, value: libremetaverse::InterestListManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.InterestList") + /* native client-core implementation */ + self.native_set_interest_list(value) } /// C# member: `P:LibreMetaverse.GridClient.Inventory`. pub fn inventory(&self) -> libremetaverse::InventoryManager { @@ -8594,63 +7986,24 @@ pub enum GridItemType { } /// C# type: `T:LibreMetaverse.GridItemsEventArgs`. -pub struct GridItemsEventArgs; -impl GridItemsEventArgs { - /// C# member: `M:LibreMetaverse.GridItemsEventArgs.#ctor(LibreMetaverse.GridItemType,System.Collections.Generic.List{LibreMetaverse.MapItem})`. - pub fn new( - type_: libremetaverse::GridItemType, - items: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridItemsEventArgs.#ctor(LibreMetaverse.GridItemType,System.Collections.Generic.List{LibreMetaverse.MapItem})", - ) - } - /// C# member: `P:LibreMetaverse.GridItemsEventArgs.Items`. - pub fn items(&self) -> Vec { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridItemsEventArgs.Items") - } - /// C# member: `P:LibreMetaverse.GridItemsEventArgs.Type`. - pub fn type_(&self) -> libremetaverse::GridItemType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridItemsEventArgs.Type") - } -} +/// C# member: `M:LibreMetaverse.GridItemsEventArgs.#ctor(LibreMetaverse.GridItemType,System.Collections.Generic.List{LibreMetaverse.MapItem})`. +/// C# member: `P:LibreMetaverse.GridItemsEventArgs.Items`. +/// C# member: `P:LibreMetaverse.GridItemsEventArgs.Type`. +pub use crate::grid_manager::GridItemsEventArgs; /// C# type: `T:LibreMetaverse.GridLayer`. -pub struct GridLayer { - /// C# member: `F:LibreMetaverse.GridLayer.Bottom`. - pub bottom: i32, - /// C# member: `F:LibreMetaverse.GridLayer.ImageID`. - pub image_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.GridLayer.Left`. - pub left: i32, - /// C# member: `F:LibreMetaverse.GridLayer.Right`. - pub right: i32, - /// C# member: `F:LibreMetaverse.GridLayer.Top`. - pub top: i32, -} -impl GridLayer { - /// C# member: `M:LibreMetaverse.GridLayer.ContainsRegion(System.Int32,System.Int32)`. - pub fn contains_region(&self, x: i32, y: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridLayer.ContainsRegion(System.Int32,System.Int32)", - ) - } -} +/// C# member: `F:LibreMetaverse.GridLayer.Bottom`. +/// C# member: `F:LibreMetaverse.GridLayer.ImageID`. +/// C# member: `F:LibreMetaverse.GridLayer.Left`. +/// C# member: `F:LibreMetaverse.GridLayer.Right`. +/// C# member: `F:LibreMetaverse.GridLayer.Top`. +/// C# member: `M:LibreMetaverse.GridLayer.ContainsRegion(System.Int32,System.Int32)`. +pub use crate::grid_manager::GridLayer; /// C# type: `T:LibreMetaverse.GridLayerEventArgs`. -pub struct GridLayerEventArgs; -impl GridLayerEventArgs { - /// C# member: `M:LibreMetaverse.GridLayerEventArgs.#ctor(LibreMetaverse.GridLayer)`. - pub fn new(layer: libremetaverse::GridLayer) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridLayerEventArgs.#ctor(LibreMetaverse.GridLayer)", - ) - } - /// C# member: `P:LibreMetaverse.GridLayerEventArgs.Layer`. - pub fn layer(&self) -> libremetaverse::GridLayer { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridLayerEventArgs.Layer") - } -} +/// C# member: `M:LibreMetaverse.GridLayerEventArgs.#ctor(LibreMetaverse.GridLayer)`. +/// C# member: `P:LibreMetaverse.GridLayerEventArgs.Layer`. +pub use crate::grid_manager::GridLayerEventArgs; /// C# type: `T:LibreMetaverse.GridLayerType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -8665,266 +8018,50 @@ pub enum GridLayerType { } /// C# type: `T:LibreMetaverse.GridManager`. -pub struct GridManager; -impl GridManager { - /// C# member: `E:LibreMetaverse.GridManager.CoarseLocationUpdate`. - pub fn subscribe_coarse_location_update( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::CoarseLocationUpdateEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.GridManager.CoarseLocationUpdate" - ) - } - /// C# member: `E:LibreMetaverse.GridManager.GridItems`. - pub fn subscribe_grid_items( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GridManager.GridItems") - } - /// C# member: `E:LibreMetaverse.GridManager.GridLayer`. - pub fn subscribe_grid_layer( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GridManager.GridLayer") - } - /// C# member: `E:LibreMetaverse.GridManager.GridRegion`. - pub fn subscribe_grid_region( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GridManager.GridRegion") - } - /// C# member: `E:LibreMetaverse.GridManager.RegionHandleReply`. - pub fn subscribe_region_handle_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::RegionHandleReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GridManager.RegionHandleReply") - } - /// C# member: `M:LibreMetaverse.GridManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new( - client: Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.GridManager.Dispose") - } - /// C# member: `M:LibreMetaverse.GridManager.GetGridRegionAsync(System.String,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. - pub async fn get_grid_region_with_string_grid_layer_type_cancellation_token( - &self, - name: String, - layer: libremetaverse::GridLayerType, - cancellation_token: Option, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.GetGridRegionAsync(System.String,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.GetGridRegionAsync(System.UInt64,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. - pub async fn get_grid_region_with_u_int64_grid_layer_type_cancellation_token( - &self, - handle: u64, - layer: libremetaverse::GridLayerType, - cancellation_token: Option, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.GetGridRegionAsync(System.UInt64,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.MapItemsAsync(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. - pub async fn map_items( - &self, - region_handle: u64, - item: libremetaverse::GridItemType, - layer: libremetaverse::GridLayerType, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.MapItemsAsync(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestMainlandSims(LibreMetaverse.GridLayerType)`. - pub fn request_mainland_sims( - &self, - layer: libremetaverse::GridLayerType, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestMainlandSims(LibreMetaverse.GridLayerType)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestMapBlocks(LibreMetaverse.GridLayerType,System.UInt16,System.UInt16,System.UInt16,System.UInt16,System.Boolean)`. - pub fn request_map_blocks( - &self, - layer: libremetaverse::GridLayerType, - min_x: u16, - min_y: u16, - max_x: u16, - max_y: u16, - return_non_existent: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestMapBlocks(LibreMetaverse.GridLayerType,System.UInt16,System.UInt16,System.UInt16,System.UInt16,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestMapItems(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType)`. - pub fn request_map_items( - &self, - region_handle: u64, - item: libremetaverse::GridItemType, - layer: libremetaverse::GridLayerType, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestMapItems(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestMapLayerAsync(LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. - pub async fn request_map_layer( - &self, - layer: libremetaverse::GridLayerType, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestMapLayerAsync(LibreMetaverse.GridLayerType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestMapRegion(System.String,LibreMetaverse.GridLayerType)`. - pub fn request_map_region( - &self, - region_name: String, - layer: libremetaverse::GridLayerType, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestMapRegion(System.String,LibreMetaverse.GridLayerType)", - ) - } - /// C# member: `M:LibreMetaverse.GridManager.RequestRegionHandle(LibreMetaverse.UUID)`. - pub fn request_region_handle( - &self, - region_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridManager.RequestRegionHandle(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.GridManager.RegionsByHandleReadOnly`. - pub fn regions_by_handle_read_only( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GridManager.RegionsByHandleReadOnly" - ) - } - /// C# member: `P:LibreMetaverse.GridManager.RegionsByUUIDReadOnly`. - pub fn regions_by_uuid_read_only( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GridManager.RegionsByUUIDReadOnly" - ) - } - /// C# member: `P:LibreMetaverse.GridManager.RegionsReadOnly`. - pub fn regions_read_only( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.RegionsReadOnly") - } - /// C# member: `P:LibreMetaverse.GridManager.SunAngVelocity`. - pub fn sun_ang_velocity(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunAngVelocity") - } - /// Setter for C# member: `P:LibreMetaverse.GridManager.SunAngVelocity`. - pub fn set_sun_ang_velocity(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunAngVelocity") - } - /// C# member: `P:LibreMetaverse.GridManager.SunDirection`. - pub fn sun_direction(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunDirection") - } - /// Setter for C# member: `P:LibreMetaverse.GridManager.SunDirection`. - pub fn set_sun_direction(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunDirection") - } - /// C# member: `P:LibreMetaverse.GridManager.SunPhase`. - pub fn sun_phase(&self) -> f32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunPhase") - } - /// Setter for C# member: `P:LibreMetaverse.GridManager.SunPhase`. - pub fn set_sun_phase(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.SunPhase") - } - /// C# member: `P:LibreMetaverse.GridManager.TimeOfDay`. - pub fn time_of_day(&self) -> u64 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.TimeOfDay") - } - /// Setter for C# member: `P:LibreMetaverse.GridManager.TimeOfDay`. - pub fn set_time_of_day(&mut self, value: u64) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridManager.TimeOfDay") - } -} +/// C# member: `E:LibreMetaverse.GridManager.CoarseLocationUpdate`. +/// C# member: `E:LibreMetaverse.GridManager.GridItems`. +/// C# member: `E:LibreMetaverse.GridManager.GridLayer`. +/// C# member: `E:LibreMetaverse.GridManager.GridRegion`. +/// C# member: `E:LibreMetaverse.GridManager.RegionHandleReply`. +/// C# member: `M:LibreMetaverse.GridManager.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.GridManager.Dispose`. +/// C# member: `M:LibreMetaverse.GridManager.GetGridRegionAsync(System.String,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GridManager.GetGridRegionAsync(System.UInt64,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GridManager.MapItemsAsync(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestMainlandSims(LibreMetaverse.GridLayerType)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestMapBlocks(LibreMetaverse.GridLayerType,System.UInt16,System.UInt16,System.UInt16,System.UInt16,System.Boolean)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestMapItems(System.UInt64,LibreMetaverse.GridItemType,LibreMetaverse.GridLayerType)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestMapLayerAsync(LibreMetaverse.GridLayerType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestMapRegion(System.String,LibreMetaverse.GridLayerType)`. +/// C# member: `M:LibreMetaverse.GridManager.RequestRegionHandle(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.GridManager.RegionsByHandleReadOnly`. +/// C# member: `P:LibreMetaverse.GridManager.RegionsByUUIDReadOnly`. +/// C# member: `P:LibreMetaverse.GridManager.RegionsReadOnly`. +/// C# member: `P:LibreMetaverse.GridManager.SunAngVelocity`. +/// C# member: `P:LibreMetaverse.GridManager.SunDirection`. +/// C# member: `P:LibreMetaverse.GridManager.SunPhase`. +/// C# member: `P:LibreMetaverse.GridManager.TimeOfDay`. +pub use crate::grid_manager::GridManager; /// C# type: `T:LibreMetaverse.GridRegion`. -pub struct GridRegion { - /// C# member: `F:LibreMetaverse.GridRegion.Access`. - pub access: libremetaverse::SimAccess, - /// C# member: `F:LibreMetaverse.GridRegion.Agents`. - pub agents: u8, - /// C# member: `F:LibreMetaverse.GridRegion.MapImageID`. - pub map_image_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.GridRegion.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.GridRegion.RegionFlags`. - pub region_flags: libremetaverse::RegionFlags, - /// C# member: `F:LibreMetaverse.GridRegion.RegionHandle`. - pub region_handle: u64, - /// C# member: `F:LibreMetaverse.GridRegion.WaterHeight`. - pub water_height: u8, - /// C# member: `F:LibreMetaverse.GridRegion.X`. - pub x: i32, - /// C# member: `F:LibreMetaverse.GridRegion.Y`. - pub y: i32, -} -impl GridRegion { - /// C# member: `M:LibreMetaverse.GridRegion.Equals(System.Object)`. - pub fn equals(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.GridRegion.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.GridRegion.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.GridRegion.GetHashCode") - } - /// C# member: `M:LibreMetaverse.GridRegion.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.GridRegion.ToString") - } -} +/// C# member: `F:LibreMetaverse.GridRegion.Access`. +/// C# member: `F:LibreMetaverse.GridRegion.Agents`. +/// C# member: `F:LibreMetaverse.GridRegion.MapImageID`. +/// C# member: `F:LibreMetaverse.GridRegion.Name`. +/// C# member: `F:LibreMetaverse.GridRegion.RegionFlags`. +/// C# member: `F:LibreMetaverse.GridRegion.RegionHandle`. +/// C# member: `F:LibreMetaverse.GridRegion.WaterHeight`. +/// C# member: `F:LibreMetaverse.GridRegion.X`. +/// C# member: `F:LibreMetaverse.GridRegion.Y`. +/// C# member: `M:LibreMetaverse.GridRegion.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.GridRegion.GetHashCode`. +/// C# member: `M:LibreMetaverse.GridRegion.ToString`. +pub use crate::grid_manager::GridRegion; /// C# type: `T:LibreMetaverse.GridRegionEventArgs`. -pub struct GridRegionEventArgs; -impl GridRegionEventArgs { - /// C# member: `M:LibreMetaverse.GridRegionEventArgs.#ctor(LibreMetaverse.GridRegion)`. - pub fn new(region: libremetaverse::GridRegion) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GridRegionEventArgs.#ctor(LibreMetaverse.GridRegion)", - ) - } - /// C# member: `P:LibreMetaverse.GridRegionEventArgs.Region`. - pub fn region(&self) -> libremetaverse::GridRegion { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridRegionEventArgs.Region") - } -} +/// C# member: `M:LibreMetaverse.GridRegionEventArgs.#ctor(LibreMetaverse.GridRegion)`. +/// C# member: `P:LibreMetaverse.GridRegionEventArgs.Region`. +pub use crate::grid_manager::GridRegionEventArgs; /// C# type: `T:LibreMetaverse.Group`. pub struct Group { @@ -10689,63 +9826,14 @@ pub enum InstantMessageOnline { } /// C# type: `T:LibreMetaverse.InterestListManager`. -pub struct InterestListManager; -impl InterestListManager { - /// C# member: `M:LibreMetaverse.InterestListManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InterestListManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.InterestListManager.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.InterestListManager.Dispose") - } - /// C# member: `M:LibreMetaverse.InterestListManager.ResetAsync(System.Threading.CancellationToken)`. - pub async fn reset( - &self, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InterestListManager.ResetAsync(System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.InterestListManager.ResetOnSimAsync(LibreMetaverse.Simulator,System.Threading.CancellationToken)`. - pub async fn reset_on_sim( - &self, - simulator: libremetaverse::Simulator, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InterestListManager.ResetOnSimAsync(LibreMetaverse.Simulator,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.InterestListManager.SetModeAsync(LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)`. - pub async fn set_mode( - &self, - mode: libremetaverse::messages::linden::InterestListMode, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InterestListManager.SetModeAsync(LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.InterestListManager.SetModeOnSimAsync(LibreMetaverse.Simulator,LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)`. - pub async fn set_mode_on_sim( - &self, - simulator: libremetaverse::Simulator, - mode: libremetaverse::messages::linden::InterestListMode, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InterestListManager.SetModeOnSimAsync(LibreMetaverse.Simulator,LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)", - ) - } - /// C# member: `P:LibreMetaverse.InterestListManager.CurrentMode`. - pub fn current_mode(&self) -> libremetaverse::messages::linden::InterestListMode { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InterestListManager.CurrentMode") - } -} +/// C# member: `M:LibreMetaverse.InterestListManager.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.InterestListManager.Dispose`. +/// C# member: `M:LibreMetaverse.InterestListManager.ResetAsync(System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.InterestListManager.ResetOnSimAsync(LibreMetaverse.Simulator,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.InterestListManager.SetModeAsync(LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.InterestListManager.SetModeOnSimAsync(LibreMetaverse.Simulator,LibreMetaverse.Messages.Linden.InterestListMode,System.Threading.CancellationToken)`. +/// C# member: `P:LibreMetaverse.InterestListManager.CurrentMode`. +pub use crate::interest_list::InterestListManager; /// C# type: `T:LibreMetaverse.Inventory`. /// C# member: `E:LibreMetaverse.Inventory.InventoryObjectAdded`. @@ -13655,52 +12743,25 @@ pub struct LslSyntaxLslKeyword { impl LslSyntaxLslKeyword {} /// C# type: `T:LibreMetaverse.MapAdultEvent`. -pub struct MapAdultEvent { - /// C# member: `F:LibreMetaverse.MapAdultEvent.Category`. - pub category: libremetaverse::DirectoryManagerEventCategories, - /// C# member: `F:LibreMetaverse.MapAdultEvent.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.MapAdultEvent.Flags`. - pub flags: libremetaverse::DirectoryManagerEventFlags, -} -impl MapAdultEvent { - /// C# member: `M:LibreMetaverse.MapAdultEvent.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapAdultEvent.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapAdultEvent.Category`. +/// C# member: `F:LibreMetaverse.MapAdultEvent.Description`. +/// C# member: `F:LibreMetaverse.MapAdultEvent.Flags`. +/// C# member: `M:LibreMetaverse.MapAdultEvent.#ctor`. +pub use crate::grid_manager::MapAdultEvent; /// C# type: `T:LibreMetaverse.MapAdultLandForSale`. -pub struct MapAdultLandForSale { - /// C# member: `F:LibreMetaverse.MapAdultLandForSale.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.MapAdultLandForSale.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.MapAdultLandForSale.Price`. - pub price: i32, - /// C# member: `F:LibreMetaverse.MapAdultLandForSale.Size`. - pub size: i32, -} -impl MapAdultLandForSale { - /// C# member: `M:LibreMetaverse.MapAdultLandForSale.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapAdultLandForSale.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapAdultLandForSale.ID`. +/// C# member: `F:LibreMetaverse.MapAdultLandForSale.Name`. +/// C# member: `F:LibreMetaverse.MapAdultLandForSale.Price`. +/// C# member: `F:LibreMetaverse.MapAdultLandForSale.Size`. +/// C# member: `M:LibreMetaverse.MapAdultLandForSale.#ctor`. +pub use crate::grid_manager::MapAdultLandForSale; /// C# type: `T:LibreMetaverse.MapAgentLocation`. -pub struct MapAgentLocation { - /// C# member: `F:LibreMetaverse.MapAgentLocation.AvatarCount`. - pub avatar_count: i32, - /// C# member: `F:LibreMetaverse.MapAgentLocation.Identifier`. - pub identifier: String, -} -impl MapAgentLocation { - /// C# member: `M:LibreMetaverse.MapAgentLocation.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapAgentLocation.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapAgentLocation.AvatarCount`. +/// C# member: `F:LibreMetaverse.MapAgentLocation.Identifier`. +/// C# member: `M:LibreMetaverse.MapAgentLocation.#ctor`. +pub use crate::grid_manager::MapAgentLocation; /// C# type: `T:LibreMetaverse.MapBlock`. pub struct MapBlock { @@ -13753,76 +12814,34 @@ impl MapField { } /// C# type: `T:LibreMetaverse.MapItem`. -pub struct MapItem { - /// C# member: `F:LibreMetaverse.MapItem.GlobalX`. - pub global_x: u32, - /// C# member: `F:LibreMetaverse.MapItem.GlobalY`. - pub global_y: u32, -} -impl MapItem { - /// C# member: `P:LibreMetaverse.MapItem.LocalX`. - pub fn local_x(&self) -> u32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.MapItem.LocalX") - } - /// C# member: `P:LibreMetaverse.MapItem.LocalY`. - pub fn local_y(&self) -> u32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.MapItem.LocalY") - } - /// C# member: `P:LibreMetaverse.MapItem.RegionHandle`. - pub fn region_handle(&self) -> u64 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.MapItem.RegionHandle") - } -} +/// C# member: `F:LibreMetaverse.MapItem.GlobalX`. +/// C# member: `F:LibreMetaverse.MapItem.GlobalY`. +/// C# member: `P:LibreMetaverse.MapItem.LocalX`. +/// C# member: `P:LibreMetaverse.MapItem.LocalY`. +/// C# member: `P:LibreMetaverse.MapItem.RegionHandle`. +pub use crate::grid_manager::MapItem; /// C# type: `T:LibreMetaverse.MapLandForSale`. -pub struct MapLandForSale { - /// C# member: `F:LibreMetaverse.MapLandForSale.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.MapLandForSale.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.MapLandForSale.Price`. - pub price: i32, - /// C# member: `F:LibreMetaverse.MapLandForSale.Size`. - pub size: i32, -} -impl MapLandForSale { - /// C# member: `M:LibreMetaverse.MapLandForSale.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapLandForSale.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapLandForSale.ID`. +/// C# member: `F:LibreMetaverse.MapLandForSale.Name`. +/// C# member: `F:LibreMetaverse.MapLandForSale.Price`. +/// C# member: `F:LibreMetaverse.MapLandForSale.Size`. +/// C# member: `M:LibreMetaverse.MapLandForSale.#ctor`. +pub use crate::grid_manager::MapLandForSale; /// C# type: `T:LibreMetaverse.MapMatureEvent`. -pub struct MapMatureEvent { - /// C# member: `F:LibreMetaverse.MapMatureEvent.Category`. - pub category: libremetaverse::DirectoryManagerEventCategories, - /// C# member: `F:LibreMetaverse.MapMatureEvent.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.MapMatureEvent.Flags`. - pub flags: libremetaverse::DirectoryManagerEventFlags, -} -impl MapMatureEvent { - /// C# member: `M:LibreMetaverse.MapMatureEvent.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapMatureEvent.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapMatureEvent.Category`. +/// C# member: `F:LibreMetaverse.MapMatureEvent.Description`. +/// C# member: `F:LibreMetaverse.MapMatureEvent.Flags`. +/// C# member: `M:LibreMetaverse.MapMatureEvent.#ctor`. +pub use crate::grid_manager::MapMatureEvent; /// C# type: `T:LibreMetaverse.MapPGEvent`. -pub struct MapPGEvent { - /// C# member: `F:LibreMetaverse.MapPGEvent.Category`. - pub category: libremetaverse::DirectoryManagerEventCategories, - /// C# member: `F:LibreMetaverse.MapPGEvent.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.MapPGEvent.Flags`. - pub flags: libremetaverse::DirectoryManagerEventFlags, -} -impl MapPGEvent { - /// C# member: `M:LibreMetaverse.MapPGEvent.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapPGEvent.#ctor") - } -} +/// C# member: `F:LibreMetaverse.MapPGEvent.Category`. +/// C# member: `F:LibreMetaverse.MapPGEvent.Description`. +/// C# member: `F:LibreMetaverse.MapPGEvent.Flags`. +/// C# member: `M:LibreMetaverse.MapPGEvent.#ctor`. +pub use crate::grid_manager::MapPGEvent; /// C# type: `T:LibreMetaverse.MapPacket`. pub struct MapPacket { @@ -13847,13 +12866,8 @@ impl MapPacket { } /// C# type: `T:LibreMetaverse.MapTelehub`. -pub struct MapTelehub; -impl MapTelehub { - /// C# member: `M:LibreMetaverse.MapTelehub.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.MapTelehub.#ctor") - } -} +/// C# member: `M:LibreMetaverse.MapTelehub.#ctor`. +pub use crate::grid_manager::MapTelehub; /// C# type: `T:LibreMetaverse.MappingType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -16796,28 +15810,10 @@ impl PickInfoReplyEventArgs { } /// C# type: `T:LibreMetaverse.PlacesReplyEventArgs`. -pub struct PlacesReplyEventArgs; -impl PlacesReplyEventArgs { - /// C# member: `M:LibreMetaverse.PlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.PlacesSearchData})`. - pub fn new( - query_id: libremetaverse_types::UUID, - matched_places: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.PlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.PlacesSearchData})", - ) - } - /// C# member: `P:LibreMetaverse.PlacesReplyEventArgs.MatchedPlaces`. - pub fn matched_places(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.PlacesReplyEventArgs.MatchedPlaces" - ) - } - /// C# member: `P:LibreMetaverse.PlacesReplyEventArgs.QueryID`. - pub fn query_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.PlacesReplyEventArgs.QueryID") - } -} +/// C# member: `M:LibreMetaverse.PlacesReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.DirectoryManager.PlacesSearchData})`. +/// C# member: `P:LibreMetaverse.PlacesReplyEventArgs.MatchedPlaces`. +/// C# member: `P:LibreMetaverse.PlacesReplyEventArgs.QueryID`. +pub use crate::directory_manager::PlacesReplyEventArgs; /// C# type: `T:LibreMetaverse.PointAtType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -17602,30 +16598,10 @@ impl RegionFlags { } /// C# type: `T:LibreMetaverse.RegionHandleReplyEventArgs`. -pub struct RegionHandleReplyEventArgs; -impl RegionHandleReplyEventArgs { - /// C# member: `M:LibreMetaverse.RegionHandleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64)`. - pub fn new( - region_id: libremetaverse_types::UUID, - region_handle: u64, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.RegionHandleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64)", - ) - } - /// C# member: `P:LibreMetaverse.RegionHandleReplyEventArgs.RegionHandle`. - pub fn region_handle(&self) -> u64 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionHandleReplyEventArgs.RegionHandle" - ) - } - /// C# member: `P:LibreMetaverse.RegionHandleReplyEventArgs.RegionID`. - pub fn region_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionHandleReplyEventArgs.RegionID" - ) - } -} +/// C# member: `M:LibreMetaverse.RegionHandleReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64)`. +/// C# member: `P:LibreMetaverse.RegionHandleReplyEventArgs.RegionHandle`. +/// C# member: `P:LibreMetaverse.RegionHandleReplyEventArgs.RegionID`. +pub use crate::grid_manager::RegionHandleReplyEventArgs; /// C# type: `T:LibreMetaverse.RegionProtocols`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -18589,35 +17565,10 @@ impl SimulatorDataPool { } /// C# type: `T:LibreMetaverse.SimulatorFeatures`. -pub struct SimulatorFeatures; -impl SimulatorFeatures { - /// C# member: `M:LibreMetaverse.SimulatorFeatures.Get(System.String)`. - pub fn get( - &self, - feature: String, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SimulatorFeatures.Get(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SimulatorFeatures.Has(System.String)`. - pub fn has(&self, feature: String) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SimulatorFeatures.Has(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SimulatorFeatures.SetFeatures(System.Net.Http.HttpResponseMessage,System.Byte[],System.Exception)`. - pub fn set_features( - &self, - response: Option, - response_data: Option>, - error: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SimulatorFeatures.SetFeatures(System.Net.Http.HttpResponseMessage,System.Byte[],System.Exception)", - ) - } -} +/// C# member: `M:LibreMetaverse.SimulatorFeatures.Get(System.String)`. +/// C# member: `M:LibreMetaverse.SimulatorFeatures.Has(System.String)`. +/// C# member: `M:LibreMetaverse.SimulatorFeatures.SetFeatures(System.Net.Http.HttpResponseMessage,System.Byte[],System.Exception)`. +pub use crate::grid_manager::SimulatorFeatures; /// C# type: `T:LibreMetaverse.SkeletalBoneInfo`. /// C# member: `F:LibreMetaverse.SkeletalBoneInfo.BoneName`. @@ -18682,175 +17633,29 @@ pub enum SlappCommand { } /// C# type: `T:LibreMetaverse.SlurlParser`. -pub struct SlurlParser; -impl SlurlParser { - /// C# member: `M:LibreMetaverse.SlurlParser.#ctor(System.String)`. - pub fn new(location: Option) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.SlurlParser.#ctor(System.String)") - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetAgentUrl(LibreMetaverse.UUID,System.String)`. - pub fn get_agent_url( - agent_id: libremetaverse_types::UUID, - action: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetAgentUrl(LibreMetaverse.UUID,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetGroupUrl(LibreMetaverse.UUID,System.String)`. - pub fn get_group_url( - group_id: libremetaverse_types::UUID, - action: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetGroupUrl(LibreMetaverse.UUID,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetLoginUrl(System.String,System.String,System.String)`. - pub fn get_login_url( - last_name: Option, - session_id: Option, - location: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetLoginUrl(System.String,System.String,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetObjectImUrl(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.String)`. - pub fn get_object_im_url( - object_id: libremetaverse_types::UUID, - object_name: String, - owner_id: libremetaverse_types::UUID, - group_owned: bool, - slurl: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetObjectImUrl(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetRawLocation`. - pub fn get_raw_location(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.SlurlParser.GetRawLocation") - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetSearchUrl(System.String,System.String)`. - pub fn get_search_url(category: String, search_term: String) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetSearchUrl(System.String,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetSlappUrl(LibreMetaverse.SlappCommand,System.String,System.Collections.Generic.Dictionary{System.String,System.String})`. - pub fn get_slapp_url( - command: libremetaverse::SlappCommand, - path: Option, - query_params: Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetSlappUrl(LibreMetaverse.SlappCommand,System.String,System.Collections.Generic.Dictionary{System.String,System.String})", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetSlurl`. - pub fn get_slurl(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.SlurlParser.GetSlurl") - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetStartLocationUri`. - pub fn get_start_location_uri(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.SlurlParser.GetStartLocationUri") - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetTeleportUrl(System.String,System.Int32,System.Int32,System.Int32)`. - pub fn get_teleport_url( - region: String, - x: Option, - y: Option, - z: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetTeleportUrl(System.String,System.Int32,System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.SlurlParser.GetWorldMapUrl(System.String,System.Int32,System.Int32,System.Int32)`. - pub fn get_world_map_url( - region: String, - x: Option, - y: Option, - z: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SlurlParser.GetWorldMapUrl(System.String,System.Int32,System.Int32,System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.SlurlParser.Command`. - pub fn command(&self) -> libremetaverse::SlappCommand { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Command") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.Command`. - pub fn set_command(&mut self, value: libremetaverse::SlappCommand) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Command") - } - /// C# member: `P:LibreMetaverse.SlurlParser.CommandPath`. - pub fn command_path(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.CommandPath") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.CommandPath`. - pub fn set_command_path(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.CommandPath") - } - /// C# member: `P:LibreMetaverse.SlurlParser.IsApplication`. - pub fn is_application(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.IsApplication") - } - /// C# member: `P:LibreMetaverse.SlurlParser.IsLocation`. - pub fn is_location(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.IsLocation") - } - /// C# member: `P:LibreMetaverse.SlurlParser.QueryParameters`. - pub fn query_parameters(&self) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.QueryParameters") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.QueryParameters`. - pub fn set_query_parameters(&mut self, value: std::collections::HashMap) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.QueryParameters") - } - /// C# member: `P:LibreMetaverse.SlurlParser.Sim`. - pub fn sim(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Sim") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.Sim`. - pub fn set_sim(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Sim") - } - /// C# member: `P:LibreMetaverse.SlurlParser.UriType`. - pub fn uri_type(&self) -> libremetaverse::ViewerUriType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.UriType") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.UriType`. - pub fn set_uri_type(&mut self, value: libremetaverse::ViewerUriType) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.UriType") - } - /// C# member: `P:LibreMetaverse.SlurlParser.X`. - pub fn x(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.X") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.X`. - pub fn set_x(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.X") - } - /// C# member: `P:LibreMetaverse.SlurlParser.Y`. - pub fn y(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Y") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.Y`. - pub fn set_y(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Y") - } - /// C# member: `P:LibreMetaverse.SlurlParser.Z`. - pub fn z(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Z") - } - /// Setter for C# member: `P:LibreMetaverse.SlurlParser.Z`. - pub fn set_z(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.SlurlParser.Z") - } -} +/// C# member: `M:LibreMetaverse.SlurlParser.#ctor(System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetAgentUrl(LibreMetaverse.UUID,System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetGroupUrl(LibreMetaverse.UUID,System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetLoginUrl(System.String,System.String,System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetObjectImUrl(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetRawLocation`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetSearchUrl(System.String,System.String)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetSlappUrl(LibreMetaverse.SlappCommand,System.String,System.Collections.Generic.Dictionary{System.String,System.String})`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetSlurl`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetStartLocationUri`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetTeleportUrl(System.String,System.Int32,System.Int32,System.Int32)`. +/// C# member: `M:LibreMetaverse.SlurlParser.GetWorldMapUrl(System.String,System.Int32,System.Int32,System.Int32)`. +/// C# member: `P:LibreMetaverse.SlurlParser.Command`. +/// C# member: `P:LibreMetaverse.SlurlParser.CommandPath`. +/// C# member: `P:LibreMetaverse.SlurlParser.IsApplication`. +/// C# member: `P:LibreMetaverse.SlurlParser.IsLocation`. +/// C# member: `P:LibreMetaverse.SlurlParser.QueryParameters`. +/// C# member: `P:LibreMetaverse.SlurlParser.Sim`. +/// C# member: `P:LibreMetaverse.SlurlParser.UriType`. +/// C# member: `P:LibreMetaverse.SlurlParser.X`. +/// C# member: `P:LibreMetaverse.SlurlParser.Y`. +/// C# member: `P:LibreMetaverse.SlurlParser.Z`. +pub use crate::slurl::SlurlParser; /// C# type: `T:LibreMetaverse.SoundManager`. /// C# member: `E:LibreMetaverse.SoundManager.AttachedSound`. @@ -56565,60 +55370,12 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.InterestListMessage`. - pub struct InterestListMessage; - impl InterestListMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.InterestListMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.InterestListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.InterestListMessage.Serialize", - ) - } - /// C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.InterestListMode`. - pub fn interest_list_mode(&self) -> libremetaverse::messages::linden::InterestListMode { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Messages.Linden.InterestListMessage.InterestListMode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.InterestListMode`. - pub fn set_interest_list_mode( - &mut self, - value: libremetaverse::messages::linden::InterestListMode, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Messages.Linden.InterestListMessage.InterestListMode" - ) - } - /// C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.Mode`. - pub fn mode(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Messages.Linden.InterestListMessage.Mode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.Mode`. - pub fn set_mode(&mut self, value: String) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Messages.Linden.InterestListMessage.Mode" - ) - } - } - impl libremetaverse::interfaces::IMessage for InterestListMessage {} + /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.InterestListMessage.Serialize`. + /// C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.InterestListMode`. + /// C# member: `P:LibreMetaverse.Messages.Linden.InterestListMessage.Mode`. + pub use crate::interest_list::InterestListMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.InterestListMode`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] diff --git a/crates/libremetaverse/src/grid_manager.rs b/crates/libremetaverse/src/grid_manager.rs new file mode 100644 index 0000000..f6af71c --- /dev/null +++ b/crates/libremetaverse/src/grid_manager.rs @@ -0,0 +1,1122 @@ +//! 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(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) + } + 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 = 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, + ) -> 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))); + } + 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, + ) { + 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())) + ); + } +} diff --git a/crates/libremetaverse/src/interest_list.rs b/crates/libremetaverse/src/interest_list.rs new file mode 100644 index 0000000..4f485f3 --- /dev/null +++ b/crates/libremetaverse/src/interest_list.rs @@ -0,0 +1,237 @@ +//! Capability-backed interest-list mode control. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructor is fallible. + +use crate::{Error, GridClient, Simulator}; +use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; +use libremetaverse_types::compat::CancellationToken; +use std::sync::{Arc, RwLock}; + +#[derive(Clone, Debug)] +pub struct InterestListMessage { + mode: String, + interest_list_mode: crate::messages::linden::InterestListMode, +} +impl InterestListMessage { + pub fn new() -> Result { + Ok(Self { + mode: "default".into(), + interest_list_mode: crate::messages::linden::InterestListMode::Default, + }) + } + pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { + let mode = map + .get("mode") + .map(|value| value.as_string()) + .transpose()? + .unwrap_or_else(|| "default".into()); + self.set_mode(mode); + Ok(()) + } + pub fn serialize(&self) -> Result { + OSDMap::new_with_dictionary(std::collections::HashMap::from([( + "mode".into(), + OSD::String(self.mode.clone()), + )])) + } + pub fn interest_list_mode(&self) -> crate::messages::linden::InterestListMode { + self.interest_list_mode + } + pub fn set_interest_list_mode(&mut self, value: crate::messages::linden::InterestListMode) { + self.interest_list_mode = value; + self.mode = mode_name(value).into(); + } + pub fn mode(&self) -> String { + self.mode.clone() + } + pub fn set_mode(&mut self, value: String) { + self.interest_list_mode = if matches!( + value.to_ascii_lowercase().as_str(), + "360" | "panoramic360" | "panoramic_360" + ) { + crate::messages::linden::InterestListMode::Panoramic360 + } else { + crate::messages::linden::InterestListMode::Default + }; + self.mode = mode_name(self.interest_list_mode).into(); + } +} +impl crate::interfaces::IMessage for InterestListMessage { + fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { + InterestListMessage::deserialize(self, map) + } + fn serialize(&self) -> Result { + InterestListMessage::serialize(self) + } +} +fn mode_name(mode: crate::messages::linden::InterestListMode) -> &'static str { + match mode { + crate::messages::linden::InterestListMode::Default => "default", + crate::messages::linden::InterestListMode::Panoramic360 => "360", + } +} + +pub(crate) struct InterestListManagerInner { + client: Arc, + current_mode: RwLock, +} +#[derive(Clone)] +pub struct InterestListManager { + pub(crate) inner: Arc, +} +impl InterestListManager { + pub fn new(client: GridClient) -> Result { + Self::native_new(Arc::new(client)) + } + pub(crate) fn native_new(client: Arc) -> Result { + if let Some(inner) = client.cached_interest_list_manager_inner() { + return Ok(Self { inner }); + } + let inner = Arc::new(InterestListManagerInner { + client: Arc::clone(&client), + current_mode: RwLock::new(crate::messages::linden::InterestListMode::Default), + }); + Ok(Self { + inner: client.install_interest_list_manager_inner(inner), + }) + } + pub fn dispose(&self) -> Result<(), Error> { + Ok(()) + } + pub fn current_mode(&self) -> crate::messages::linden::InterestListMode { + *self + .inner + .current_mode + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + pub async fn reset(&self, cancellation_token: Option) -> Result<(), Error> { + self.set_mode( + crate::messages::linden::InterestListMode::Default, + cancellation_token, + ) + .await + } + pub async fn set_mode( + &self, + mode: crate::messages::linden::InterestListMode, + cancellation_token: Option, + ) -> Result<(), Error> { + let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let simulations = self.inner.client.network().simulators.snapshot(); + let mut accepted = false; + for simulator in simulations { + if self + .set_mode_on_sim(simulator, mode, Some(token.clone())) + .await? + { + accepted = true; + } + } + if accepted || self.inner.client.network().simulators.is_empty() { + *self + .inner + .current_mode + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = mode; + } + Ok(()) + } + pub async fn reset_on_sim( + &self, + simulator: Simulator, + cancellation_token: Option, + ) -> Result { + self.set_mode_on_sim( + simulator, + crate::messages::linden::InterestListMode::Default, + cancellation_token, + ) + .await + } + pub async fn set_mode_on_sim( + &self, + simulator: Simulator, + mode: crate::messages::linden::InterestListMode, + cancellation_token: Option, + ) -> Result { + let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let Some(caps) = simulator.native_caps() else { + return Ok(false); + }; + let Some(uri) = caps.capability_uri("InterestList".into())? else { + return Ok(false); + }; + let mut message = InterestListMessage::new()?; + message.set_interest_list_mode(mode); + let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(message.serialize()?.snapshot()))?; + let (response, bytes) = self + .inner + .client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + body, + token.clone(), + None, + ) + .await?; + token.throw_if_cancellation_requested()?; + if bytes.len() > 1024 * 1024 { + return Err(Error::Argument); + } + let ok = (200..300).contains(&response.status_code); + if ok { + *self + .inner + .current_mode + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = mode; + } + Ok(ok) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use libremetaverse_types::compat::CancellationTokenSource; + #[test] + fn message_round_trip() { + let mut value = InterestListMessage::new().unwrap(); + value.set_interest_list_mode(crate::messages::linden::InterestListMode::Panoramic360); + let mut copy = InterestListMessage::new().unwrap(); + copy.deserialize(value.serialize().unwrap()).unwrap(); + assert_eq!(copy.mode(), "360"); + assert_eq!( + copy.interest_list_mode(), + crate::messages::linden::InterestListMode::Panoramic360 + ); + } + #[tokio::test] + async fn cancelled_mode_change_does_not_commit() { + let client = GridClient::new().unwrap(); + let manager = InterestListManager::new(client).unwrap(); + let source = CancellationTokenSource::new(); + source.cancel(); + assert_eq!( + manager + .set_mode( + crate::messages::linden::InterestListMode::Panoramic360, + Some(source.token()) + ) + .await, + Err(Error::Cancelled) + ); + assert_eq!( + manager.current_mode(), + crate::messages::linden::InterestListMode::Default + ); + } +} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 461fdcc..9e931b0 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -48,10 +48,15 @@ mod object_material; mod object_model; #[rustfmt::skip] pub mod packet_catalog; +mod directory_manager; +mod grid_manager; +pub use grid_manager::MapItemData; +mod interest_list; mod packet_wire; mod parcel_manager; mod sim_stats; mod skeleton; +mod slurl; mod sound_manager; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod skeleton_catalog; diff --git a/crates/libremetaverse/src/network_manager.rs b/crates/libremetaverse/src/network_manager.rs index 7575df8..5c11254 100644 --- a/crates/libremetaverse/src/network_manager.rs +++ b/crates/libremetaverse/src/network_manager.rs @@ -1135,7 +1135,7 @@ impl Simulator { client, colo_location: String::new(), data_pool: None, - features: SimulatorFeatures, + features: SimulatorFeatures::default(), flags: RegionFlags(0), global_to_local_id: RwLock::new(HashMap::new()), handle, diff --git a/crates/libremetaverse/src/slurl.rs b/crates/libremetaverse/src/slurl.rs new file mode 100644 index 0000000..d124276 --- /dev/null +++ b/crates/libremetaverse/src/slurl.rs @@ -0,0 +1,430 @@ +//! Second Life location and application URI parsing and formatting. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] + +use crate::{Error, SlappCommand, ViewerUriType}; +use libremetaverse_types::UUID; +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Write; + +const DEFAULT_X: i32 = 128; +const DEFAULT_Y: i32 = 128; +const DEFAULT_Z: i32 = 0; +const MAX_URI_BYTES: usize = 1024 * 1024; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SlurlParser { + command: SlappCommand, + command_path: String, + query_parameters: HashMap, + sim: String, + uri_type: ViewerUriType, + x: i32, + y: i32, + z: i32, +} + +fn command_from_str(value: &str) -> SlappCommand { + match value.to_ascii_lowercase().as_str() { + "agent" => SlappCommand::Agent, + "appearance" => SlappCommand::Appearance, + "balance" => SlappCommand::Balance, + "chat" => SlappCommand::Chat, + "classified" => SlappCommand::Classified, + "event" => SlappCommand::Event, + "experience" => SlappCommand::Experience, + "group" => SlappCommand::Group, + "help" => SlappCommand::Help, + "inventory" => SlappCommand::Inventory, + "keybinding" => SlappCommand::Keybinding, + "login" => SlappCommand::Login, + "maptrackavatar" => SlappCommand::MapTrackAvatar, + "objectim" => SlappCommand::ObjectIm, + "openfloater" => SlappCommand::OpenFloater, + "parcel" => SlappCommand::Parcel, + "region" => SlappCommand::Region, + "search" => SlappCommand::Search, + "sharewithavatar" => SlappCommand::ShareWithAvatar, + "teleport" => SlappCommand::Teleport, + "voicecallavatar" => SlappCommand::VoiceCallAvatar, + "wear_folder" => SlappCommand::WearFolder, + "worldmap" => SlappCommand::WorldMap, + _ => SlappCommand::Unknown, + } +} + +fn command_name(command: SlappCommand) -> &'static str { + match command { + SlappCommand::Agent => "agent", + SlappCommand::Appearance => "appearance", + SlappCommand::Balance => "balance", + SlappCommand::Chat => "chat", + SlappCommand::Classified => "classified", + SlappCommand::Event => "event", + SlappCommand::Experience => "experience", + SlappCommand::Group => "group", + SlappCommand::Help => "help", + SlappCommand::Inventory => "inventory", + SlappCommand::Keybinding => "keybinding", + SlappCommand::Login => "login", + SlappCommand::MapTrackAvatar => "maptrackavatar", + SlappCommand::ObjectIm => "objectim", + SlappCommand::OpenFloater => "openfloater", + SlappCommand::Parcel => "parcel", + SlappCommand::Region => "region", + SlappCommand::Search => "search", + SlappCommand::ShareWithAvatar => "sharewithavatar", + SlappCommand::Teleport => "teleport", + SlappCommand::VoiceCallAvatar => "voicecallavatar", + SlappCommand::WearFolder => "wear_folder", + SlappCommand::WorldMap => "worldmap", + SlappCommand::Unknown => "unknown", + } +} + +fn encode(value: &str) -> String { + let mut result = String::with_capacity(value.len()); + for byte in value.bytes() { + if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') { + result.push(char::from(byte)); + } else { + result.push('%'); + let _ = write!(result, "{byte:02X}"); + } + } + result +} + +fn decode(value: &str) -> Result { + let bytes = value.as_bytes(); + let mut output = Vec::with_capacity(bytes.len()); + let mut index = 0; + while index < bytes.len() { + match bytes[index] { + b'%' if index + 2 < bytes.len() => { + let text = std::str::from_utf8(&bytes[index + 1..index + 3]) + .map_err(|_| Error::Argument)?; + output.push(u8::from_str_radix(text, 16).map_err(|_| Error::Argument)?); + index += 3; + } + b'+' => { + output.push(b' '); + index += 1; + } + byte => { + output.push(byte); + index += 1; + } + } + } + String::from_utf8(output).map_err(|_| Error::Argument) +} + +fn parse_query(value: Option<&str>) -> Result, Error> { + let mut result = HashMap::new(); + let Some(value) = value else { + return Ok(result); + }; + for pair in value.split('&').filter(|pair| !pair.is_empty()).take(4096) { + let (key, value) = pair.split_once('=').unwrap_or((pair, "")); + result.insert(decode(key)?, decode(value)?); + } + Ok(result) +} + +fn parse_coordinate(value: Option<&&str>, default: i32) -> Result { + match value.filter(|value| !value.is_empty()) { + Some(value) => value.parse().map_err(|_| Error::Argument), + None => Ok(default), + } +} + +impl SlurlParser { + pub fn new(location: Option) -> Result { + let location = location.ok_or(Error::ArgumentNull)?; + if location.is_empty() || location.len() > MAX_URI_BYTES { + return Err(Error::Argument); + } + if let Some(application) = location.strip_prefix("secondlife:///app/") { + return Self::parse_application(application); + } + let raw = if let Some(value) = location.strip_prefix("secondlife://") { + value + } else if let Some(value) = location + .strip_prefix("http://maps.secondlife.com/secondlife/") + .or_else(|| location.strip_prefix("https://maps.secondlife.com/secondlife/")) + { + value + } else if let Some(value) = location.strip_prefix("uri:") { + let values: Vec<_> = value.split('&').collect(); + return Self::from_location_parts(&values); + } else { + location.as_str() + }; + let values: Vec<_> = raw.trim_matches('/').split('/').collect(); + Self::from_location_parts(&values) + } + + fn from_location_parts(values: &[&str]) -> Result { + let Some(region) = values.first().filter(|value| !value.is_empty()) else { + return Err(Error::Argument); + }; + Ok(Self { + command: SlappCommand::Unknown, + command_path: String::new(), + query_parameters: HashMap::new(), + sim: decode(region)?, + uri_type: ViewerUriType::Location, + x: parse_coordinate(values.get(1), DEFAULT_X)?, + y: parse_coordinate(values.get(2), DEFAULT_Y)?, + z: parse_coordinate(values.get(3), DEFAULT_Z)?, + }) + } + + fn parse_application(application: &str) -> Result { + let (path, query) = application + .split_once('?') + .map_or((application, None), |(path, query)| (path, Some(query))); + let command_path = path.trim_start_matches('/').to_owned(); + let parts: Vec<_> = command_path.split('/').collect(); + let command = command_from_str(parts.first().copied().unwrap_or_default()); + let application_location = + if matches!(command, SlappCommand::Teleport | SlappCommand::WorldMap) { + Some(Self::from_location_parts( + parts.get(1..).unwrap_or_default(), + )?) + } else { + None + }; + let mut result = Self { + command, + command_path, + query_parameters: parse_query(query)?, + sim: String::new(), + uri_type: ViewerUriType::Application, + x: DEFAULT_X, + y: DEFAULT_Y, + z: DEFAULT_Z, + }; + if let Some(location) = application_location { + result.sim = location.sim; + result.x = location.x; + result.y = location.y; + result.z = location.z; + } + Ok(result) + } + + pub fn get_agent_url(agent_id: UUID, action: Option) -> Result { + Self::get_slapp_url( + SlappCommand::Agent, + Some(format!( + "{agent_id}/{}", + encode(action.as_deref().unwrap_or("about")) + )), + None, + ) + } + pub fn get_group_url(group_id: UUID, action: Option) -> Result { + Self::get_slapp_url( + SlappCommand::Group, + Some(format!( + "{group_id}/{}", + encode(action.as_deref().unwrap_or("about")) + )), + None, + ) + } + pub fn get_login_url( + last_name: Option, + session_id: Option, + location: Option, + ) -> Result { + let mut query = HashMap::new(); + if let Some(value) = last_name { + query.insert("last".into(), value); + } + if let Some(value) = session_id { + query.insert("session".into(), value); + } + if let Some(value) = location { + query.insert("location".into(), value); + } + Self::get_slapp_url( + SlappCommand::Login, + None, + (!query.is_empty()).then_some(query), + ) + } + pub fn get_object_im_url( + object_id: UUID, + object_name: String, + owner_id: UUID, + group_owned: bool, + slurl: String, + ) -> Result { + let mut query = HashMap::from([ + ("name".into(), object_name), + ("owner".into(), owner_id.to_string()), + ("slurl".into(), slurl), + ]); + if group_owned { + query.insert("groupowned".into(), "true".into()); + } + Self::get_slapp_url( + SlappCommand::ObjectIm, + Some(object_id.to_string()), + Some(query), + ) + } + pub fn get_raw_location(&self) -> Result { + if !self.is_location() { + return Err(Error::InvalidOperation); + } + Ok(format!("{}/{}/{}/{}", self.sim, self.x, self.y, self.z)) + } + pub fn get_search_url(category: String, search_term: String) -> Result { + Self::get_slapp_url( + SlappCommand::Search, + Some(format!("{}/{}", encode(&category), encode(&search_term))), + None, + ) + } + pub fn get_slapp_url( + command: SlappCommand, + path: Option, + query_params: Option>, + ) -> Result { + if command == SlappCommand::Unknown { + return Err(Error::Argument); + } + let mut result = format!("secondlife:///app/{}", command_name(command)); + if let Some(path) = path.filter(|path| !path.is_empty()) { + result.push('/'); + result.push_str(path.trim_start_matches('/')); + } + if let Some(query) = query_params.filter(|query| !query.is_empty()) { + let ordered: BTreeMap<_, _> = query.into_iter().collect(); + result.push('?'); + for (index, (key, value)) in ordered.iter().enumerate() { + if index != 0 { + result.push('&'); + } + result.push_str(&encode(key)); + result.push('='); + result.push_str(&encode(value)); + } + } + if result.len() > MAX_URI_BYTES { + return Err(Error::Argument); + } + Ok(result) + } + pub fn get_slurl(&self) -> Result { + Ok(format!( + "secondlife://{}/{}/{}/{}/", + encode(&self.sim), + self.x, + self.y, + self.z + )) + } + pub fn get_start_location_uri(&self) -> Result { + Ok(format!("uri:{}&{}&{}&{}", self.sim, self.x, self.y, self.z)) + } + pub fn get_teleport_url( + region: String, + x: Option, + y: Option, + z: Option, + ) -> Result { + Self::location_application(SlappCommand::Teleport, region, x, y, z) + } + pub fn get_world_map_url( + region: String, + x: Option, + y: Option, + z: Option, + ) -> Result { + Self::location_application(SlappCommand::WorldMap, region, x, y, z) + } + fn location_application( + command: SlappCommand, + region: String, + x: Option, + y: Option, + z: Option, + ) -> Result { + if region.is_empty() { + return Err(Error::Argument); + } + Self::get_slapp_url( + command, + Some(format!( + "{}/{}/{}/{}", + encode(®ion), + x.unwrap_or(DEFAULT_X), + y.unwrap_or(DEFAULT_Y), + z.unwrap_or(DEFAULT_Z) + )), + None, + ) + } + + pub fn command(&self) -> SlappCommand { + self.command + } + pub fn set_command(&mut self, value: SlappCommand) { + self.command = value; + } + pub fn command_path(&self) -> String { + self.command_path.clone() + } + pub fn set_command_path(&mut self, value: String) { + self.command_path = value; + } + pub fn is_application(&self) -> bool { + self.uri_type == ViewerUriType::Application + } + pub fn is_location(&self) -> bool { + self.uri_type == ViewerUriType::Location + } + pub fn query_parameters(&self) -> HashMap { + self.query_parameters.clone() + } + pub fn set_query_parameters(&mut self, value: HashMap) { + self.query_parameters = value; + } + pub fn sim(&self) -> String { + self.sim.clone() + } + pub fn set_sim(&mut self, value: String) { + self.sim = value; + } + pub fn uri_type(&self) -> ViewerUriType { + self.uri_type + } + pub fn set_uri_type(&mut self, value: ViewerUriType) { + self.uri_type = value; + } + pub fn x(&self) -> i32 { + self.x + } + pub fn set_x(&mut self, value: i32) { + self.x = value; + } + pub fn y(&self) -> i32 { + self.y + } + pub fn set_y(&mut self, value: i32) { + self.y = value; + } + pub fn z(&self) -> i32 { + self.z + } + pub fn set_z(&mut self, value: i32) { + self.z = value; + } +} diff --git a/docs/discovery.md b/docs/discovery.md new file mode 100644 index 0000000..f4c5811 --- /dev/null +++ b/docs/discovery.md @@ -0,0 +1,43 @@ +# Grid and directory discovery + +`GridClient.Grid`, `GridClient.Directory`, and `GridClient.InterestList` are +client-owned managers. Repeated property access returns handles to the same +state, and disposing the client releases their packet and capability +subscriptions. + +`GridManager` uses the advertised `MapLayer` capability with an LLUDP fallback, +and sends standard map-name, map-block, map-item, and region-handle LLUDP +requests. Replies update the name, handle, and UUID indexes +before observers run. Region names are indexed case-insensitively, repeated +blocks replace older values, and entries expire after fifteen minutes using +the client's injectable clock. Async lookups use `MapRequestTimeout`, observe +the caller or client cancellation token, and return the cache result available +at timeout. Map coordinates use 256-metre region origins; `MapItem` exposes +the corresponding local coordinates and region handle, retains every raw +reply field, and provides typed payloads for telehubs, agent clusters, land +sales, and events. Coarse-location updates report complete current positions +plus added and removed agent IDs. + +`DirectoryManager` sends correlated people, group, event, classified, parcel, +land, place, and event-info requests. Query IDs are random and returned to the +caller, reply pages are capped at 1,024 records, strings are bounded to the +LLUDP variable-field limit, and negative page/price/area inputs are rejected. +Reply events retain the server query or transaction ID so callers can combine +pages without mixing concurrent searches. Maturity, category, sort, price, +area, and ownership flags are sent unchanged. High-level search entry points +start the same real server queries and return live `IAsyncEnumerable` streams +that yield decoded records in reply order until cancellation, timeout, or a +terminal page. Typed reply events remain available for callback-oriented +consumers. + +`SlurlParser` accepts raw locations, `secondlife://` locations, maps URLs, +legacy start locations, and `secondlife:///app` commands. It applies the +reference coordinate defaults, decodes query values, and generates stable, +percent-escaped application and location URLs. Inputs and query counts are +bounded. + +`InterestListManager` posts LLSD to each simulator's `InterestList` +capability. A mode is committed only after a successful response; missing +capabilities return `false` for per-simulator calls. `SimulatorFeatures` +atomically replaces its map after bounded LLSD validation and deliberately +retains unknown feature keys for forward compatibility. diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 6067304..11c9613 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -146,6 +146,42 @@ NATIVE_TYPES = { "T:LibreMetaverse.GroupChatJoinedEventArgs": "crate::agent_messages::GroupChatJoinedEventArgs", "T:LibreMetaverse.RegionExperiencesEventArgs": "crate::agent_messages::RegionExperiencesEventArgs", "T:LibreMetaverse.SetDisplayNameReplyEventArgs": "crate::agent_messages::SetDisplayNameReplyEventArgs", + "T:LibreMetaverse.SlurlParser": "crate::slurl::SlurlParser", + "T:LibreMetaverse.GridManager": "crate::grid_manager::GridManager", + "T:LibreMetaverse.GridRegion": "crate::grid_manager::GridRegion", + "T:LibreMetaverse.GridLayer": "crate::grid_manager::GridLayer", + "T:LibreMetaverse.MapItem": "crate::grid_manager::MapItem", + "T:LibreMetaverse.MapAgentLocation": "crate::grid_manager::MapAgentLocation", + "T:LibreMetaverse.MapLandForSale": "crate::grid_manager::MapLandForSale", + "T:LibreMetaverse.MapAdultLandForSale": "crate::grid_manager::MapAdultLandForSale", + "T:LibreMetaverse.MapPGEvent": "crate::grid_manager::MapPGEvent", + "T:LibreMetaverse.MapMatureEvent": "crate::grid_manager::MapMatureEvent", + "T:LibreMetaverse.MapAdultEvent": "crate::grid_manager::MapAdultEvent", + "T:LibreMetaverse.MapTelehub": "crate::grid_manager::MapTelehub", + "T:LibreMetaverse.CoarseLocationUpdateEventArgs": "crate::grid_manager::CoarseLocationUpdateEventArgs", + "T:LibreMetaverse.GridItemsEventArgs": "crate::grid_manager::GridItemsEventArgs", + "T:LibreMetaverse.GridLayerEventArgs": "crate::grid_manager::GridLayerEventArgs", + "T:LibreMetaverse.GridRegionEventArgs": "crate::grid_manager::GridRegionEventArgs", + "T:LibreMetaverse.RegionHandleReplyEventArgs": "crate::grid_manager::RegionHandleReplyEventArgs", + "T:LibreMetaverse.SimulatorFeatures": "crate::grid_manager::SimulatorFeatures", + "T:LibreMetaverse.DirectoryManager": "crate::directory_manager::DirectoryManager", + "T:LibreMetaverse.DirectoryManager.AgentSearchData": "crate::directory_manager::DirectoryManagerAgentSearchData", + "T:LibreMetaverse.DirectoryManager.Classified": "crate::directory_manager::DirectoryManagerClassified", + "T:LibreMetaverse.DirectoryManager.DirectoryParcel": "crate::directory_manager::DirectoryManagerDirectoryParcel", + "T:LibreMetaverse.DirectoryManager.EventInfo": "crate::directory_manager::DirectoryManagerEventInfo", + "T:LibreMetaverse.DirectoryManager.EventsSearchData": "crate::directory_manager::DirectoryManagerEventsSearchData", + "T:LibreMetaverse.DirectoryManager.GroupSearchData": "crate::directory_manager::DirectoryManagerGroupSearchData", + "T:LibreMetaverse.DirectoryManager.PlacesSearchData": "crate::directory_manager::DirectoryManagerPlacesSearchData", + "T:LibreMetaverse.DirClassifiedsReplyEventArgs": "crate::directory_manager::DirClassifiedsReplyEventArgs", + "T:LibreMetaverse.DirEventsReplyEventArgs": "crate::directory_manager::DirEventsReplyEventArgs", + "T:LibreMetaverse.DirGroupsReplyEventArgs": "crate::directory_manager::DirGroupsReplyEventArgs", + "T:LibreMetaverse.DirLandReplyEventArgs": "crate::directory_manager::DirLandReplyEventArgs", + "T:LibreMetaverse.DirPeopleReplyEventArgs": "crate::directory_manager::DirPeopleReplyEventArgs", + "T:LibreMetaverse.DirPlacesReplyEventArgs": "crate::directory_manager::DirPlacesReplyEventArgs", + "T:LibreMetaverse.EventInfoReplyEventArgs": "crate::directory_manager::EventInfoReplyEventArgs", + "T:LibreMetaverse.PlacesReplyEventArgs": "crate::directory_manager::PlacesReplyEventArgs", + "T:LibreMetaverse.InterestListManager": "crate::interest_list::InterestListManager", + "T:LibreMetaverse.Messages.Linden.InterestListMessage": "crate::interest_list::InterestListMessage", "T:LibreMetaverse.TeleportEventArgs": "crate::agent_movement::TeleportEventArgs", "T:LibreMetaverse.TerrainCompressor": "crate::terrain_codec::TerrainCompressor", "T:LibreMetaverse.TerrainPatch": "crate::terrain_codec::TerrainPatch", @@ -404,6 +440,12 @@ NATIVE_MEMBER_BODIES = { "P:LibreMetaverse.GridClient.Sound#set": "self.native_set_sound(value)", "P:LibreMetaverse.GridClient.Parcels": "self.native_parcels().unwrap_or_else(|_| panic!(\"failed to construct ParcelManager\"))", "P:LibreMetaverse.GridClient.Parcels#set": "self.native_set_parcels(value)", + "P:LibreMetaverse.GridClient.Grid": "self.native_grid().unwrap_or_else(|_| panic!(\"failed to construct GridManager\"))", + "P:LibreMetaverse.GridClient.Grid#set": "self.native_set_grid(value)", + "P:LibreMetaverse.GridClient.Directory": "self.native_directory().unwrap_or_else(|_| panic!(\"failed to construct DirectoryManager\"))", + "P:LibreMetaverse.GridClient.Directory#set": "self.native_set_directory(value)", + "P:LibreMetaverse.GridClient.InterestList": "self.native_interest_list().unwrap_or_else(|_| panic!(\"failed to construct InterestListManager\"))", + "P:LibreMetaverse.GridClient.InterestList#set": "self.native_set_interest_list(value)", "M:LibreMetaverse.Simulator.IsParcelMapFull": "Ok(self.native_is_parcel_map_full())", "M:LibreMetaverse.Simulator.TerrainHeightAtPoint(System.Int32,System.Int32,System.Single@)": "self.native_terrain_height_at_point(x, y, height)", "P:LibreMetaverse.Simulator.ParcelMap": "self.native_parcel_map_snapshot()",