From 52f62d8c39a7c6d697111ea6efaaeb91cebf2d54 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 10 Aug 2026 07:51:17 +0000 Subject: [PATCH] Implement native asset pipeline and cache (#64) --- Cargo.lock | 35 + README.md | 7 + api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse-imaging/src/jpeg2000.rs | 8 +- crates/libremetaverse/Cargo.toml | 4 +- crates/libremetaverse/src/asset_cache.rs | 484 +++++ crates/libremetaverse/src/asset_manager.rs | 1319 ++++++++++++ crates/libremetaverse/src/asset_material.rs | 719 +++++++ crates/libremetaverse/src/asset_models.rs | 676 ++++++ .../src/asset_pipeline_semantics.rs | 133 ++ crates/libremetaverse/src/client_core.rs | 25 + crates/libremetaverse/src/download_manager.rs | 33 +- crates/libremetaverse/src/generated.rs | 1902 +++-------------- .../libremetaverse/src/inventory_manager.rs | 111 + crates/libremetaverse/src/lib.rs | 8 + crates/libremetaverse/src/object_material.rs | 192 ++ crates/libremetaverse/src/transfers.rs | 226 ++ crates/libremetaverse/tests/caps_http.rs | 17 +- docs/assets.md | 49 + tests/api-compile/Cargo.lock | 60 + tools/check_milestone_09_issue_64.py | 62 + tools/generate_api_shims.py | 53 + tools/generate_rust_mapping.py | 6 + 23 files changed, 4511 insertions(+), 1620 deletions(-) create mode 100644 crates/libremetaverse/src/asset_cache.rs create mode 100644 crates/libremetaverse/src/asset_manager.rs create mode 100644 crates/libremetaverse/src/asset_material.rs create mode 100644 crates/libremetaverse/src/asset_models.rs create mode 100644 crates/libremetaverse/src/asset_pipeline_semantics.rs create mode 100644 crates/libremetaverse/src/object_material.rs create mode 100644 crates/libremetaverse/src/transfers.rs create mode 100644 docs/assets.md create mode 100644 tools/check_milestone_09_issue_64.py diff --git a/Cargo.lock b/Cargo.lock index 604fd44..b7ab250 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,16 @@ dependencies = [ "libc", ] +[[package]] +name = "aotuv_lancer_vorbis_sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc4fd1a61860d2f1198b60bedd30910eaffa978f1ee6214dfb24ac70d589225" +dependencies = [ + "cc", + "ogg_next_sys", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -853,7 +863,9 @@ dependencies = [ "os_info", "reqwest", "roxmltree", + "serde_json", "tokio", + "vorbis_rs", ] [[package]] @@ -1298,6 +1310,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "ogg_next_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2d7a48e247c2bb07e633aefb65a38648ea58c7eedd4e4408a5861721ab049b" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2239,6 +2260,20 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vorbis_rs" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c5da94d280f7a27e8c937e9b73df2da3e23a2583f48471fd8fb4c72f9c1933" +dependencies = [ + "aotuv_lancer_vorbis_sys", + "errno", + "getrandom 0.4.3", + "ogg_next_sys", + "thiserror 2.0.20", + "tinyvec", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/README.md b/README.md index f7fb118..53a2a32 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,13 @@ and uses same-directory temporary replacement with rollback. The compatibility and persistence contract is documented in [`docs/inventory.md`](docs/inventory.md). +Native asset models now enforce bounded format decoding, real JPEG 2000 and Ogg +Vorbis codec boundaries, client-owned capability and LLUDP downloads, complete +small/Xfer and two-stage capability uploads, independent cancellation for +deduplicated HTTP subscribers, and atomic size-pruned disk caching. The ownership, +correlation, and corruption contracts are documented in +[`docs/assets.md`](docs/assets.md). + The client-owned `InventoryManager` now implements the packet-level create, update, move, copy, remove, fetch, search, give, rez/derez, script-state, and task-inventory workflows. Callback IDs and concurrent item/task waiters are diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 62d9808..847b1ec 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: 166 types / 14,923 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 197 types / 15,177 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-imaging/src/jpeg2000.rs b/crates/libremetaverse-imaging/src/jpeg2000.rs index f9dddb3..56d86d1 100644 --- a/crates/libremetaverse-imaging/src/jpeg2000.rs +++ b/crates/libremetaverse-imaging/src/jpeg2000.rs @@ -223,10 +223,10 @@ impl J2kCodec { if options.max_encoded_bytes == 0 { return Err(Error::Argument); } - if let J2kCompression::Lossy { compression_ratio } = options.compression { - if !compression_ratio.is_finite() || compression_ratio < 1.0 { - return Err(Error::Argument); - } + if let J2kCompression::Lossy { compression_ratio } = options.compression + && (!compression_ratio.is_finite() || compression_ratio < 1.0) + { + return Err(Error::Argument); } encode_with_openjpeg(image, options) } diff --git a/crates/libremetaverse/Cargo.toml b/crates/libremetaverse/Cargo.toml index 4b7d654..c7aa540 100644 --- a/crates/libremetaverse/Cargo.toml +++ b/crates/libremetaverse/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true description = "Rust rewrite shell for the LibreMetaverse client library" [features] -default = ["dds-bc67"] +default = ["dds-bc67", "jpeg2000"] dds-bc67 = ["dep:bcdec_rs"] jpeg2000 = ["libremetaverse-imaging/jpeg2000"] @@ -24,7 +24,9 @@ mac_address2 = "2.0.2" os_info = { version = "3.15.0", default-features = false } reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] } roxmltree = "0.21.1" +serde_json = "1.0.143" tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time"] } +vorbis_rs = "0.5.5" [dev-dependencies] tokio = { version = "1.47.1", features = ["macros", "net", "rt-multi-thread", "sync", "test-util", "time"] } diff --git a/crates/libremetaverse/src/asset_cache.rs b/crates/libremetaverse/src/asset_cache.rs new file mode 100644 index 0000000..fa25a37 --- /dev/null +++ b/crates/libremetaverse/src/asset_cache.rs @@ -0,0 +1,484 @@ +//! Cross-platform, bounded on-disk asset cache. + +#![allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::collapsible_if, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::needless_pass_by_value, + clippy::single_match_else, + clippy::type_complexity, + clippy::uninlined_format_args, + clippy::unused_async, + clippy::unused_self +)] + +use crate::{Error, GridClient, ImageDownload}; +use libremetaverse_types::UUID; +use libremetaverse_types::compat::{CancellationToken, Object}; +use std::fs::{self, OpenOptions}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +const CACHE_TARGET_PERCENT: i64 = 90; +const ABSOLUTE_CACHE_FILE_LIMIT: u64 = crate::asset_models::MAX_ASSET_BYTES as u64; + +type CacheFilenameHandler = dyn Fn(String, UUID) -> Result + Send + Sync; + +#[derive(Clone)] +pub struct AssetCacheComputeAssetCacheFilenameDelegate { + handler: Arc, +} + +impl std::fmt::Debug for AssetCacheComputeAssetCacheFilenameDelegate { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("AssetCacheComputeAssetCacheFilenameDelegate") + .finish_non_exhaustive() + } +} + +impl AssetCacheComputeAssetCacheFilenameDelegate { + pub fn from_handler( + handler: impl Fn(String, UUID) -> Result + Send + Sync + 'static, + ) -> Self { + Self { + handler: Arc::new(handler), + } + } + + pub fn new(object: Object, method: isize) -> Result { + if method == 0 { + return Err(Error::Argument); + } + object + .downcast_arc::() + .map(|value| (*value).clone()) + .ok_or(Error::Argument) + } + + pub fn invoke(&self, cache_dir: String, asset_id: UUID) -> Result { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + (self.handler)(cache_dir, asset_id) + })) + .map_err(|_| Error::InvalidOperation)? + } + + pub fn begin_invoke( + &self, + cache_dir: String, + asset_id: UUID, + callback: Box, + object: Object, + ) -> Result, Error> { + let result = self.invoke(cache_dir, asset_id)?; + callback(&object); + Ok(Box::new(result)) + } + + pub fn end_invoke( + &self, + result: Box, + ) -> Result { + result + .downcast::() + .map(|value| *value) + .map_err(|_| Error::Argument) + } +} + +struct AssetCacheInner { + client: crate::client_core::ClientWeakHandle, + pruning: AtomicBool, + auto_prune: AtomicBool, + interval_bits: AtomicU64, + last_prune_ms: AtomicU64, +} + +pub struct AssetCache { + inner: Arc, + pub compute_asset_cache_filename: Option, +} + +impl Clone for AssetCache { + fn clone(&self) -> Self { + Self { + inner: Arc::clone(&self.inner), + compute_asset_cache_filename: self.compute_asset_cache_filename.clone(), + } + } +} + +impl std::fmt::Debug for AssetCache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AssetCache") + .field("auto_prune_enabled", &self.auto_prune_enabled()) + .finish_non_exhaustive() + } +} + +impl AssetCache { + fn now_ms() -> u64 { + u64::try_from( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis(), + ) + .unwrap_or(u64::MAX) + } + + pub fn new(client: GridClient) -> Result { + Ok(Self { + inner: Arc::new(AssetCacheInner { + client: client.native_weak_handle(), + pruning: AtomicBool::new(false), + auto_prune: AtomicBool::new(true), + interval_bits: AtomicU64::new((300_000_f64).to_bits()), + last_prune_ms: AtomicU64::new(Self::now_ms()), + }), + compute_asset_cache_filename: None, + }) + } + + fn config(&self) -> Result { + let client = self.inner.client.upgrade().ok_or(Error::InvalidOperation)?; + Ok(client.settings_ref().asset_cache()) + } + + fn cache_dir(&self) -> Result { + let config = self.config()?; + let dir = PathBuf::from(config.dir); + if dir.as_os_str().is_empty() { + return Err(Error::InvalidOperation); + } + Ok(dir) + } + + fn path(&self, id: UUID) -> Result { + let dir = self.cache_dir()?; + if let Some(compute) = &self.compute_asset_cache_filename { + let path = PathBuf::from(compute.invoke(dir.to_string_lossy().into_owned(), id)?); + if path.parent() == Some(dir.as_path()) { + return Ok(path); + } + return Err(Error::Argument); + } + Ok(dir.join(id.to_string())) + } + + fn operational(&self) -> Result { + Ok(self.config()?.enabled) + } + + fn checked_read(&self, path: &Path) -> Result>, Error> { + let metadata = match fs::metadata(path) { + Ok(value) => value, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(_) => return Err(Error::InvalidOperation), + }; + if !metadata.is_file() || metadata.len() == 0 || metadata.len() > ABSOLUTE_CACHE_FILE_LIMIT + { + return Err(Error::Argument); + } + let bytes = fs::read(path).map_err(|_| Error::InvalidOperation)?; + if bytes.len() as u64 != metadata.len() { + return Err(Error::InvalidOperation); + } + Ok(Some(bytes)) + } + + pub fn get_cached_asset_bytes_with_uuid( + &self, + asset_id: UUID, + ) -> Result>, Error> { + if !self.operational()? { + return Ok(None); + } + self.checked_read(&self.path(asset_id)?) + } + + pub async fn get_cached_asset_bytes_with_uuid_cancellation_token( + &self, + asset_id: UUID, + cancellation_token: Option, + ) -> Result>, Error> { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + let result = self.get_cached_asset_bytes_with_uuid(asset_id); + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + result + } + + pub fn try_get_cached_asset_bytes_with_uuid_bytes( + &self, + asset_id: UUID, + data: &mut Option>, + ) -> bool { + match self.get_cached_asset_bytes_with_uuid(asset_id) { + Ok(Some(bytes)) => { + *data = Some(bytes); + true + } + _ => { + *data = None; + false + } + } + } + + pub async fn try_get_cached_asset_bytes_with_uuid_cancellation_token( + &self, + asset_id: UUID, + cancellation_token: Option, + ) -> Result<(bool, Option>), Error> { + let data = self + .get_cached_asset_bytes_with_uuid_cancellation_token(asset_id, cancellation_token) + .await?; + Ok((data.is_some(), data)) + } + + pub fn save_asset_to_cache_with_uuid_bytes( + &self, + asset_id: UUID, + asset_data: Vec, + ) -> Result { + if !self.operational()? { + return Ok(false); + } + if asset_data.is_empty() || asset_data.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + let path = self.path(asset_id)?; + if path.is_file() { + return Ok(true); + } + let dir = path.parent().ok_or(Error::Argument)?; + fs::create_dir_all(dir).map_err(|_| Error::InvalidOperation)?; + let nonce = UUID::random()?.to_string(); + let temp = dir.join(format!(".{}.{}.tmp", asset_id, nonce)); + let write_result = (|| -> Result<(), Error> { + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .open(&temp) + .map_err(|_| Error::InvalidOperation)?; + file.write_all(&asset_data) + .map_err(|_| Error::InvalidOperation)?; + file.sync_all().map_err(|_| Error::InvalidOperation)?; + match fs::rename(&temp, &path) { + Ok(()) => Ok(()), + Err(_) if path.is_file() => Ok(()), + Err(_) => Err(Error::InvalidOperation), + } + })(); + if write_result.is_err() || temp.exists() { + let _ = fs::remove_file(&temp); + } + write_result.map(|()| { + self.maybe_begin_prune(); + true + }) + } + + fn maybe_begin_prune(&self) { + if !self.auto_prune_enabled() { + return; + } + let interval = self.auto_prune_interval(); + if !interval.is_finite() || interval <= 0.0 { + return; + } + let now = Self::now_ms(); + let last = self.inner.last_prune_ms.load(Ordering::Acquire); + if now.saturating_sub(last) < interval as u64 { + return; + } + if self + .inner + .last_prune_ms + .compare_exchange(last, now, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + let _ = self.begin_prune(); + } + } + + pub async fn save_asset_to_cache_with_uuid_bytes_cancellation_token( + &self, + asset_id: UUID, + asset_data: Vec, + cancellation_token: Option, + ) -> Result { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + let result = self.save_asset_to_cache_with_uuid_bytes(asset_id, asset_data); + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + result + } + + pub fn asset_file_name(&self, asset_id: UUID) -> Result, Error> { + if !self.operational()? { + return Ok(None); + } + let path = self.path(asset_id)?; + Ok(path.is_file().then(|| path.to_string_lossy().into_owned())) + } + + pub fn has_asset(&self, asset_id: UUID) -> Result { + Ok(self.operational()? && self.path(asset_id)?.is_file()) + } + + fn cache_entries(&self) -> Result, Error> { + let dir = self.cache_dir()?; + let mut entries = Vec::new(); + let read = match fs::read_dir(dir) { + Ok(value) => value, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(entries), + Err(_) => return Err(Error::InvalidOperation), + }; + for entry in read.flatten() { + if let Ok(metadata) = entry.metadata() { + if metadata.is_file() { + entries.push(( + entry.path(), + metadata.len(), + metadata + .accessed() + .or_else(|_| metadata.modified()) + .unwrap_or(UNIX_EPOCH), + )); + } + } + } + Ok(entries) + } + + pub fn clear(&self) -> Result<(), Error> { + for (path, _, _) in self.cache_entries()? { + fs::remove_file(path).map_err(|_| Error::InvalidOperation)?; + } + Ok(()) + } + + pub async fn prune(&self, cancellation_token: Option) -> Result<(), Error> { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + let max = self.config()?.max_size; + if max <= 0 { + return Err(Error::InvalidOperation); + } + let mut entries = self.cache_entries()?; + let mut size: u64 = entries.iter().map(|(_, size, _)| *size).sum(); + let max = u64::try_from(max).map_err(|_| Error::InvalidOperation)?; + if size <= max { + return Ok(()); + } + entries.sort_unstable_by_key(|(_, _, accessed)| *accessed); + let target = max.saturating_mul(CACHE_TARGET_PERCENT as u64) / 100; + for (path, length, _) in entries { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + fs::remove_file(path).map_err(|_| Error::InvalidOperation)?; + size = size.saturating_sub(length); + if size <= target { + break; + } + } + Ok(()) + } + + pub fn begin_prune(&self) -> Result<(), Error> { + if self.inner.pruning.swap(true, Ordering::AcqRel) { + return Ok(()); + } + let cache = self.clone(); + std::thread::Builder::new() + .name("asset-cache-prune".into()) + .spawn(move || { + let _ = cache.prune_sync(None); + cache.inner.pruning.store(false, Ordering::Release); + }) + .map_err(|_| { + self.inner.pruning.store(false, Ordering::Release); + Error::InvalidOperation + })?; + Ok(()) + } + + fn prune_sync(&self, cancellation_token: Option) -> Result<(), Error> { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + let max = self.config()?.max_size; + if max <= 0 { + return Err(Error::InvalidOperation); + } + let mut entries = self.cache_entries()?; + let mut size: u64 = entries.iter().map(|(_, size, _)| *size).sum(); + let max = u64::try_from(max).map_err(|_| Error::InvalidOperation)?; + if size <= max { + return Ok(()); + } + entries.sort_unstable_by_key(|(_, _, accessed)| *accessed); + let target = max.saturating_mul(CACHE_TARGET_PERCENT as u64) / 100; + for (path, length, _) in entries { + if let Some(token) = &cancellation_token { + token.throw_if_cancellation_requested()?; + } + fs::remove_file(path).map_err(|_| Error::InvalidOperation)?; + size = size.saturating_sub(length); + if size <= target { + break; + } + } + Ok(()) + } + + pub fn dispose(&self) -> Result<(), Error> { + self.inner.auto_prune.store(false, Ordering::Release); + Ok(()) + } + pub fn get_cached_image(&self, image_id: UUID) -> Result, Error> { + let Some(bytes) = self.get_cached_asset_bytes_with_uuid(image_id)? else { + return Ok(None); + }; + let mut image = ImageDownload::new()?; + image.base.id = image_id; + image.base.asset_type = libremetaverse_types::AssetType::Texture; + image.base.size = i32::try_from(bytes.len()).map_err(|_| Error::Argument)?; + image.base.transferred = image.base.size; + image.base.success = true; + image.base.asset_data = bytes; + image.codec = crate::ImageCodec::J2C; + Ok(Some(image)) + } + pub fn auto_prune_enabled(&self) -> bool { + self.inner.auto_prune.load(Ordering::Acquire) + } + pub fn set_auto_prune_enabled(&mut self, value: bool) { + self.inner.auto_prune.store(value, Ordering::Release); + } + pub fn auto_prune_interval(&self) -> f64 { + f64::from_bits(self.inner.interval_bits.load(Ordering::Acquire)) + } + pub fn set_auto_prune_interval(&mut self, value: f64) { + if value.is_finite() && value > 0.0 { + self.inner + .interval_bits + .store(value.to_bits(), Ordering::Release); + } + } +} diff --git a/crates/libremetaverse/src/asset_manager.rs b/crates/libremetaverse/src/asset_manager.rs new file mode 100644 index 0000000..4546efe --- /dev/null +++ b/crates/libremetaverse/src/asset_manager.rs @@ -0,0 +1,1319 @@ +//! Client-owned asset download, cache, and upload coordinator. + +#![allow( + clippy::missing_errors_doc, + clippy::too_many_arguments, + clippy::too_many_lines, + clippy::needless_pass_by_value, + clippy::unused_self +)] + +use crate::assets::{Asset, AssetMesh, AssetTexture}; +use crate::download_manager::{DownloadManager, DownloadRequest}; +use crate::network_manager::RawPacketReceivedEventArgs; +use crate::packet_catalog::GeneratedPacket; +use crate::packets::{ + AssetUploadCompletePacket, AssetUploadRequestPacket, ConfirmXferPacketPacket, + InitiateDownloadPacket, PacketType, RequestXferPacket, SendXferPacketPacket, + TransferInfoPacket, TransferPacketPacket, TransferRequestPacket, +}; +use crate::{ + AssetCache, AssetDownload, AssetUploadEventArgs, Error, EstateAssetType, GridClient, + ImageReceiveProgressEventArgs, ImageType, InitiateDownloadEventArgs, InventoryItem, SourceType, + StatusCode, XferDownload, XferReceivedEventArgs, +}; +use futures_channel::oneshot; +use futures_util::{FutureExt, pin_mut, select_biased}; +use libremetaverse_types::compat::{ + CancellationToken, CancellationTokenSource, EventHandler, Subscription, Uri, +}; +use libremetaverse_types::{AssetType, UUID, Utils}; +use serde_json::Value; +use std::collections::{BTreeMap, HashMap}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +fn block_on(future: F) -> F::Output { + use std::task::{Context, Poll, Wake, Waker}; + struct ThreadWake(std::thread::Thread); + impl Wake for ThreadWake { + fn wake(self: Arc) { + self.0.unpark(); + } + } + let waker = Waker::from(Arc::new(ThreadWake(std::thread::current()))); + let mut context = Context::from_waker(&waker); + let mut future = std::pin::pin!(future); + loop { + match future.as_mut().poll(&mut context) { + Poll::Ready(value) => return value, + Poll::Pending => std::thread::park(), + } + } +} + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +struct EventSlot { + next: AtomicU64, + handlers: Mutex>>, +} +impl Default for EventSlot { + fn default() -> Self { + Self { + next: AtomicU64::new(1), + handlers: Mutex::new(HashMap::new()), + } + } +} +impl EventSlot { + fn subscribe(self: &Arc, handler: EventHandler) -> Subscription { + let id = self.next.fetch_add(1, Ordering::Relaxed); + mutex(&self.handlers).insert(id, handler); + let weak = Arc::downgrade(self); + Subscription::new(move || { + if let Some(slot) = weak.upgrade() { + mutex(&slot.handlers).remove(&id); + } + }) + } +} +impl EventSlot { + fn emit(&self, value: T) { + let handlers: Vec<_> = mutex(&self.handlers).values().cloned().collect(); + for handler in handlers { + handler(value.clone()); + } + } +} + +struct UploadState { + asset_id: UUID, + data: Vec, + packet_num: u32, + transaction_id: UUID, + transferred: usize, + type_: AssetType, + xfer_id: u64, + confirmation: Option>, +} + +struct XferState { + download: XferDownload, + chunks: HashMap>, + expected_size: Option, + final_packet: Option, +} + +#[derive(Default)] +struct AssetEvents { + uploaded: Arc>, + upload_progress: Arc>, + image_progress: Arc>, + initiate: Arc>, + xfer: Arc>, +} + +pub(crate) struct AssetManagerInner { + client: crate::client_core::ClientWeakHandle, + cache: AssetCache, + downloads: DownloadManager, + image_cancellations: Mutex>, + pending_upload: Mutex>>>, + xfer_uploads: Mutex>>>, + xfer_downloads: Mutex>, + raw_packet_subscription: Mutex>, + events: AssetEvents, +} + +pub struct AssetManager { + pub cache: AssetCache, + inner: Arc, +} +impl Clone for AssetManager { + fn clone(&self) -> Self { + Self { + cache: self.cache.clone(), + inner: Arc::clone(&self.inner), + } + } +} +impl std::fmt::Debug for AssetManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AssetManager").finish_non_exhaustive() + } +} + +impl AssetManager { + pub(crate) fn native_from_inner(inner: Arc) -> Result { + inner.client.upgrade().ok_or(Error::InvalidOperation)?; + Ok(Self { + cache: inner.cache.clone(), + inner, + }) + } + pub(crate) fn native_inner(&self) -> Arc { + Arc::clone(&self.inner) + } + + pub fn new(client: Option) -> Result { + let client = client.ok_or(Error::ArgumentNull)?; + let cache = AssetCache::new(client.clone())?; + let downloads = DownloadManager::new(client.clone())?; + let inner_cache = cache.clone(); + let manager = Self { + cache, + inner: Arc::new(AssetManagerInner { + client: client.native_weak_handle(), + cache: inner_cache, + downloads, + image_cancellations: Mutex::new(HashMap::new()), + pending_upload: Mutex::new(None), + xfer_uploads: Mutex::new(HashMap::new()), + xfer_downloads: Mutex::new(HashMap::new()), + raw_packet_subscription: Mutex::new(None), + events: AssetEvents::default(), + }), + }; + manager.install_packet_handler()?; + Ok(manager) + } + + fn install_packet_handler(&self) -> Result<(), Error> { + let network = self.client()?.native_network()?; + let weak = Arc::downgrade(&self.inner); + let subscription = network.subscribe_raw_packet(Arc::new(move |event| { + if let Some(inner) = weak.upgrade() { + let _ = AssetManager::native_from_inner(inner) + .and_then(|manager| manager.handle_raw_packet(event)); + } + })); + *mutex(&self.inner.raw_packet_subscription) = Some(subscription); + Ok(()) + } + + fn decode_packet(bytes: &[u8]) -> Result { + let mut packet = T::new_generated(); + let mut position = 0_i32; + let mut end = i32::try_from(bytes.len()).map_err(|_| Error::Argument)? - 1; + let mut zero = + vec![0; crate::udp_transport::UdpTransportConfig::default().max_decoded_packet_size]; + packet.decode_from_bytes(bytes, &mut position, &mut end, Some(&mut zero))?; + Ok(packet) + } + fn send_packet( + &self, + simulator: &crate::Simulator, + packet: &T, + packet_type: PacketType, + ) -> Result<(), Error> { + let bytes = packet.encode_packet()?; + simulator.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + packet_type, + T::new_generated().generated_header().zerocoded, + ) + } + + fn handle_raw_packet(&self, event: RawPacketReceivedEventArgs) -> Result<(), Error> { + match event.packet_type { + PacketType::RequestXfer => { + self.handle_request_xfer(Self::decode_packet(&event.data)?, event.simulator) + } + PacketType::ConfirmXferPacket => { + self.handle_confirm_xfer(Self::decode_packet(&event.data)?, event.simulator) + } + PacketType::AssetUploadComplete => { + self.handle_upload_complete(Self::decode_packet(&event.data)?) + } + PacketType::SendXferPacket => { + self.handle_send_xfer(Self::decode_packet(&event.data)?, event.simulator) + } + PacketType::InitiateDownload => { + self.handle_initiate_download(Self::decode_packet(&event.data)?) + } + _ => Ok(()), + } + } + fn handle_initiate_download(&self, packet: InitiateDownloadPacket) -> Result<(), Error> { + let event = InitiateDownloadEventArgs::new( + Utils::bytes_to_string_with_bytes(packet.file_data.sim_filename)?, + Utils::bytes_to_string_with_bytes(packet.file_data.viewer_filename)?, + )?; + self.inner.events.initiate.emit(event); + Ok(()) + } + + fn handle_send_xfer( + &self, + packet: SendXferPacketPacket, + simulator: crate::Simulator, + ) -> Result<(), Error> { + let id = packet.xfer_id.id; + let packet_number = packet.xfer_id.packet & 0x7fff_ffff; + let is_final = packet.xfer_id.packet & 0x8000_0000 != 0; + let mut data = packet.data_packet.data; + let completed = { + let mut downloads = mutex(&self.inner.xfer_downloads); + let Some(state) = downloads.get_mut(&id) else { + return Ok(()); + }; + if packet_number == 0 { + if data.len() < 4 { + return Err(Error::Argument); + } + let size = usize::try_from(u32::from_le_bytes( + data[..4].try_into().map_err(|_| Error::Argument)?, + )) + .map_err(|_| Error::Argument)?; + if size > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + state.expected_size = Some(size); + data.drain(..4); + } + state.chunks.entry(packet_number).or_insert(data); + if is_final { + state.final_packet = Some(packet_number); + } + if let (Some(size), Some(final_packet)) = (state.expected_size, state.final_packet) { + if (0..=final_packet).all(|number| state.chunks.contains_key(&number)) { + let mut assembled = Vec::with_capacity(size); + for number in 0..=final_packet { + let chunk = state.chunks.get(&number).ok_or(Error::InvalidOperation)?; + let remaining = size.saturating_sub(assembled.len()); + assembled.extend_from_slice(&chunk[..chunk.len().min(remaining)]); + } + if assembled.len() != size { + return Err(Error::Argument); + } + state.download.packet_num = final_packet; + state.download.base.size = i32::try_from(size).map_err(|_| Error::Argument)?; + state.download.base.transferred = state.download.base.size; + state.download.base.asset_data = assembled; + state.download.base.success = true; + Some(state.download.clone()) + } else { + None + } + } else { + None + } + }; + let mut confirm = ConfirmXferPacketPacket::new_with_constructor()?; + confirm.xfer_id.id = id; + confirm.xfer_id.packet = packet.xfer_id.packet; + self.send_packet(&simulator, &confirm, PacketType::ConfirmXferPacket)?; + if let Some(download) = completed { + mutex(&self.inner.xfer_downloads).remove(&id); + self.inner + .events + .xfer + .emit(XferReceivedEventArgs::new(download)?); + } + Ok(()) + } + fn handle_request_xfer( + &self, + packet: RequestXferPacket, + simulator: crate::Simulator, + ) -> Result<(), Error> { + let Some(upload) = mutex(&self.inner.pending_upload).take() else { + return Ok(()); + }; + { + let mut state = mutex(&upload); + state.xfer_id = packet.xfer_id.id; + } + mutex(&self.inner.xfer_uploads).insert(packet.xfer_id.id, Arc::clone(&upload)); + self.send_next_upload(&upload, &simulator)?; + Ok(()) + } + fn handle_confirm_xfer( + &self, + packet: ConfirmXferPacketPacket, + simulator: crate::Simulator, + ) -> Result<(), Error> { + let Some(upload) = mutex(&self.inner.xfer_uploads) + .get(&packet.xfer_id.id) + .cloned() + else { + return Ok(()); + }; + let snapshot = { + let state = mutex(&upload); + if packet.xfer_id.packet & 0x7fff_ffff != state.packet_num.saturating_sub(1) { + return Ok(()); + } + crate::AssetUpload { + asset_id: state.asset_id, + packet_num: state.packet_num, + type_: state.type_, + xfer_id: state.xfer_id, + base: crate::Transfer { + asset_data: Vec::new(), + asset_type: state.type_, + id: state.transaction_id, + size: i32::try_from(state.data.len()).unwrap_or(i32::MAX), + success: false, + transferred: i32::try_from(state.transferred).unwrap_or(i32::MAX), + ..crate::Transfer::new()? + }, + } + }; + self.inner + .events + .upload_progress + .emit(crate::AssetUploadEventArgs::new(snapshot)?); + if mutex(&upload).transferred < mutex(&upload).data.len() { + self.send_next_upload(&upload, &simulator)?; + } + Ok(()) + } + fn handle_upload_complete(&self, packet: AssetUploadCompletePacket) -> Result<(), Error> { + let mut found = mutex(&self.inner.pending_upload) + .take() + .filter(|upload| mutex(upload).asset_id == packet.asset_block.uuid); + if found.is_none() { + let id = mutex(&self.inner.xfer_uploads) + .iter() + .find_map(|(id, upload)| { + (mutex(upload).asset_id == packet.asset_block.uuid).then_some(*id) + }); + if let Some(id) = id { + found = mutex(&self.inner.xfer_uploads).remove(&id); + } + } + if let Some(upload) = found { + let mut state = mutex(&upload); + let transaction = state.transaction_id; + if let Some(sender) = state.confirmation.take() { + let _ = sender.send(if packet.asset_block.success { + transaction + } else { + UUID::zero() + }); + } + let event = crate::AssetUpload { + asset_id: state.asset_id, + packet_num: state.packet_num, + type_: state.type_, + xfer_id: state.xfer_id, + base: crate::Transfer { + asset_data: Vec::new(), + asset_type: state.type_, + id: transaction, + size: i32::try_from(state.data.len()).unwrap_or(i32::MAX), + success: packet.asset_block.success, + transferred: i32::try_from(state.transferred).unwrap_or(i32::MAX), + ..crate::Transfer::new()? + }, + }; + drop(state); + self.inner + .events + .uploaded + .emit(crate::AssetUploadEventArgs::new(event)?); + } + Ok(()) + } + fn send_next_upload( + &self, + upload: &Arc>, + simulator: &crate::Simulator, + ) -> Result<(), Error> { + let mut state = mutex(upload); + let packet_number = state.packet_num; + let start = state.transferred; + let payload_capacity = if packet_number == 0 { 996 } else { 1000 }; + let end = start.saturating_add(payload_capacity).min(state.data.len()); + if start >= end { + return Ok(()); + } + let mut data = Vec::with_capacity((end - start) + usize::from(packet_number == 0)); + if packet_number == 0 { + data.extend_from_slice( + &u32::try_from(state.data.len()) + .map_err(|_| Error::Argument)? + .to_le_bytes(), + ); + } + data.extend_from_slice(&state.data[start..end]); + let final_packet = end == state.data.len(); + let xfer_id = state.xfer_id; + state.packet_num = state.packet_num.saturating_add(1); + state.transferred = end; + drop(state); + let mut packet = SendXferPacketPacket::new_with_constructor()?; + packet.xfer_id.id = xfer_id; + packet.xfer_id.packet = packet_number | if final_packet { 0x8000_0000 } else { 0 }; + packet.data_packet.data = data; + self.send_packet(simulator, &packet, PacketType::SendXferPacket) + } + + fn client(&self) -> Result { + self.inner.client.upgrade().ok_or(Error::InvalidOperation) + } + fn capability(&self, name: &str) -> Result, Error> { + let Some(sim) = self.client()?.native_network()?.current_sim() else { + return Ok(None); + }; + let Some(caps) = sim.native_caps() else { + return Ok(None); + }; + let deadline = Instant::now() + Duration::from_secs(2); + loop { + if let Some(uri) = caps.capability_uri(name.to_owned())? { + return Ok(Some(uri)); + } + if caps.seed_request_finished() || Instant::now() >= deadline { + return Ok(None); + } + std::thread::sleep(Duration::from_millis(1)); + } + } + fn with_query(base: &Uri, name: &str, id: UUID) -> Uri { + Uri(format!( + "{}{}{}={}", + base.0, + if base.0.contains('?') { '&' } else { '?' }, + name, + id + )) + } + fn token(value: Option) -> CancellationToken { + value.unwrap_or_default() + } + + async fn fetch( + &self, + uri: Uri, + content_type: Option, + cancellation: CancellationToken, + ) -> Result>, Error> { + let mut request = DownloadRequest::new(uri, content_type, None)?; + request.cancellation_token = cancellation; + let (response, bytes) = self + .inner + .downloads + .queue_download_with_download_request_ba727387(request) + .await?; + if (200..300).contains(&response.status_code) { + if bytes.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + Ok(Some(bytes)) + } else { + Ok(None) + } + } + + fn abort_transfer(&self, simulator: &crate::Simulator, transfer_id: UUID) { + if let Ok(mut packet) = crate::packets::TransferAbortPacket::new_with_constructor() { + packet.transfer_info.channel_type = crate::ChannelType::Asset as i32; + packet.transfer_info.transfer_id = transfer_id; + let _ = self.send_packet(simulator, &packet, PacketType::TransferAbort); + } + } + + async fn request_asset_udp( + &self, + asset_id: UUID, + item_id: UUID, + task_id: UUID, + owner_id: UUID, + asset_type: AssetType, + priority: bool, + source_type: SourceType, + transfer_id: UUID, + cancellation: CancellationToken, + ) -> Result>, Error> { + let simulator = self + .client()? + .native_network()? + .current_sim() + .ok_or(Error::InvalidOperation)?; + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); + let subscription = self + .client()? + .native_network()? + .subscribe_raw_packet(Arc::new(move |event| { + if matches!( + event.packet_type, + PacketType::TransferInfo | PacketType::TransferPacket + ) { + let _ = sender.send(event); + } + })); + let mut request = TransferRequestPacket::new_with_constructor()?; + request.transfer_info.channel_type = crate::ChannelType::Asset as i32; + request.transfer_info.priority = if priority { 101.0 } else { 100.0 }; + request.transfer_info.source_type = source_type as i32; + request.transfer_info.transfer_id = transfer_id; + if source_type == SourceType::SimInventoryItem { + let mut client = self.client()?; + request + .transfer_info + .params + .extend_from_slice(&client.native_self().agent_id().get_bytes()?); + request + .transfer_info + .params + .extend_from_slice(&client.native_self().session_id().get_bytes()?); + request + .transfer_info + .params + .extend_from_slice(&owner_id.get_bytes()?); + request + .transfer_info + .params + .extend_from_slice(&task_id.get_bytes()?); + request + .transfer_info + .params + .extend_from_slice(&item_id.get_bytes()?); + } + request + .transfer_info + .params + .extend_from_slice(&asset_id.get_bytes()?); + request + .transfer_info + .params + .extend_from_slice(&(asset_type as i32).to_le_bytes()); + self.send_packet(&simulator, &request, PacketType::TransferRequest)?; + + let mut expected_size = None; + let mut chunks = BTreeMap::>::new(); + let deadline = tokio::time::sleep(Duration::from_secs(30)); + tokio::pin!(deadline); + let result = loop { + let event = tokio::select! { + event = receiver.recv() => event.ok_or(Error::InvalidOperation)?, + () = cancellation.cancelled() => break Err(Error::Cancelled), + () = &mut deadline => break Err(Error::InvalidOperation), + }; + match event.packet_type { + PacketType::TransferInfo => { + let packet: TransferInfoPacket = Self::decode_packet(&event.data)?; + if packet.transfer_info.transfer_id != transfer_id { + continue; + } + if packet.transfer_info.status != StatusCode::OK as i32 { + break Ok(None); + } + let size = + usize::try_from(packet.transfer_info.size).map_err(|_| Error::Argument)?; + if size > crate::asset_models::MAX_ASSET_BYTES { + break Err(Error::Argument); + } + expected_size = Some(size); + } + PacketType::TransferPacket => { + let packet: TransferPacketPacket = Self::decode_packet(&event.data)?; + if packet.transfer_data.transfer_id != transfer_id + || packet.transfer_data.packet < 0 + { + continue; + } + if packet.transfer_data.status < StatusCode::OK as i32 { + break Ok(None); + } + chunks + .entry(packet.transfer_data.packet) + .or_insert(packet.transfer_data.data); + } + _ => continue, + } + let Some(size) = expected_size else { + continue; + }; + let mut assembled = Vec::with_capacity(size); + for packet_number in 0_i32.. { + let Some(chunk) = chunks.get(&packet_number) else { + break; + }; + let remaining = size.saturating_sub(assembled.len()); + assembled.extend_from_slice(&chunk[..chunk.len().min(remaining)]); + if assembled.len() == size { + break; + } + } + if assembled.len() == size { + break Ok(Some(assembled)); + } + }; + drop(subscription); + if result.is_err() { + self.abort_transfer(&simulator, transfer_id); + } + result + } + + pub fn asset_type_to_string(&self, asset_type: AssetType) -> Result { + Ok(match asset_type { + AssetType::Texture => "texture", + AssetType::Sound => "sound", + AssetType::CallingCard => "callcard", + AssetType::Landmark => "landmark", + AssetType::Script => "script", + AssetType::Clothing => "clothing", + AssetType::Object => "object", + AssetType::Notecard => "notecard", + AssetType::Folder => "category", + AssetType::LSLText => "lsltext", + AssetType::LSLBytecode => "lslbyte", + AssetType::TextureTGA => "txtr_tga", + AssetType::Bodypart => "bodypart", + AssetType::SoundWAV => "snd_wav", + AssetType::ImageTGA => "img_tga", + AssetType::ImageJPEG => "jpeg", + AssetType::Animation => "animatn", + AssetType::Gesture => "gesture", + AssetType::Simstate => "simstate", + AssetType::Link => "link", + AssetType::LinkFolder => "link_f", + AssetType::Mesh => "mesh", + AssetType::Widget => "widget", + AssetType::Person => "person", + AssetType::Material => "material", + AssetType::Settings => "settings", + AssetType::Unknown => "invalid", + } + .to_owned()) + } + pub fn create_asset_wrapper(&self, type_: AssetType) -> Result { + Asset::native_new(type_, UUID::zero(), Vec::new()) + } + + pub async fn request_asset_with_uuid_asset_type_boolean_cancellation_token( + &self, + asset_id: UUID, + type_: AssetType, + priority: bool, + cancellation_token: Option, + ) -> Result, Error> { + self.request_asset_with_uuid_asset_type_boolean_source_type_cancellation_token( + asset_id, + type_, + priority, + SourceType::Asset, + cancellation_token, + ) + .await + } + pub async fn request_asset_with_uuid_asset_type_boolean_source_type_cancellation_token( + &self, + asset_id: UUID, + type_: AssetType, + priority: bool, + source_type: SourceType, + cancellation_token: Option, + ) -> Result, Error> { + if asset_id == UUID::zero() { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(asset_id)? { + return Asset::native_new(type_, asset_id, bytes).map(Some); + } + let token = Self::token(cancellation_token); + let bytes = if source_type == SourceType::Asset { + if let Some(cap) = self.capability("ViewerAsset")? { + let uri = Self::with_query( + &cap, + &format!("{}_id", self.asset_type_to_string(type_)?), + asset_id, + ); + self.fetch(uri, None, token.clone()).await? + } else { + self.request_asset_udp( + asset_id, + UUID::zero(), + UUID::zero(), + UUID::zero(), + type_, + priority, + source_type, + UUID::random()?, + token.clone(), + ) + .await? + } + } else { + self.request_asset_udp( + asset_id, + UUID::zero(), + UUID::zero(), + UUID::zero(), + type_, + priority, + source_type, + UUID::random()?, + token.clone(), + ) + .await? + }; + let Some(bytes) = bytes else { + return Ok(None); + }; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token( + asset_id, + bytes.clone(), + Some(token), + ) + .await?; + Asset::native_new(type_, asset_id, bytes).map(Some) + } + pub async fn request_asset_with_uuid_uuid_uuid_asset_type_boolean_source_type_uuid_cancellation_token( + &self, + asset_id: UUID, + item_id: UUID, + task_id: UUID, + asset_type: AssetType, + priority: bool, + source_type: SourceType, + transaction_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + if asset_id == UUID::zero() { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(asset_id)? { + return Asset::native_new(asset_type, asset_id, bytes).map(Some); + } + let token = Self::token(cancellation_token); + let transfer_id = if transaction_id == UUID::zero() { + UUID::random()? + } else { + transaction_id + }; + let Some(bytes) = self + .request_asset_udp( + asset_id, + item_id, + task_id, + UUID::zero(), + asset_type, + priority, + source_type, + transfer_id, + token.clone(), + ) + .await? + else { + return Ok(None); + }; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token( + asset_id, + bytes.clone(), + Some(token), + ) + .await?; + Asset::native_new(asset_type, asset_id, bytes).map(Some) + } + + pub async fn request_inventory_asset_with_inventory_item_boolean_uuid_cancellation_token( + &self, + item: InventoryItem, + priority: bool, + transfer_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + self.request_inventory_asset_with_uuid_uuid_uuid_uuid_asset_type_boolean_uuid_cancellation_token(item.asset_uuid(), item.base.uuid(), UUID::zero(), item.base.owner_id(), item.asset_type(), priority, transfer_id, cancellation_token).await + } + pub async fn request_inventory_asset_with_uuid_uuid_uuid_uuid_asset_type_boolean_uuid_cancellation_token( + &self, + asset_id: UUID, + item_id: UUID, + task_id: UUID, + owner_id: UUID, + asset_type: AssetType, + priority: bool, + transfer_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + if asset_id == UUID::zero() { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(asset_id)? { + return Asset::native_new(asset_type, asset_id, bytes).map(Some); + } + let token = Self::token(cancellation_token); + let id = if transfer_id == UUID::zero() { + UUID::random()? + } else { + transfer_id + }; + let Some(bytes) = self + .request_asset_udp( + asset_id, + item_id, + task_id, + owner_id, + asset_type, + priority, + SourceType::SimInventoryItem, + id, + token.clone(), + ) + .await? + else { + return Ok(None); + }; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token( + asset_id, + bytes.clone(), + Some(token), + ) + .await?; + Asset::native_new(asset_type, asset_id, bytes).map(Some) + } + + pub async fn request_mesh( + &self, + mesh_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + if mesh_id == UUID::zero() { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(mesh_id)? { + return AssetMesh::new_with_uuid_bytes(mesh_id, bytes).map(Some); + } + let Some(cap) = self.capability("GetMesh2")?.or(self.capability("GetMesh")?) else { + return Ok(None); + }; + let token = Self::token(cancellation_token); + let Some(bytes) = self + .fetch( + Self::with_query(&cap, "mesh_id", mesh_id), + None, + token.clone(), + ) + .await? + else { + return Ok(None); + }; + let mesh = AssetMesh::new_with_uuid_bytes(mesh_id, bytes.clone())?; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token(mesh_id, bytes, Some(token)) + .await?; + Ok(Some(mesh)) + } + + pub async fn request_image( + &self, + texture_id: UUID, + _image_type: Option, + cancellation_token: Option, + ) -> Result, Error> { + if texture_id == UUID::zero() { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(texture_id)? { + return AssetTexture::new_with_uuid_bytes(texture_id, bytes).map(Some); + } + let Some(cap) = self.capability("GetTexture")? else { + return Ok(None); + }; + let parents: Vec<_> = cancellation_token.into_iter().collect(); + let source = CancellationTokenSource::new_linked(&parents); + mutex(&self.inner.image_cancellations).insert(texture_id, source.clone()); + let events = Arc::clone(&self.inner.events.image_progress); + let progress = move |report: crate::HttpCapsClientProgressReport| { + let received = i32::try_from(report.bytes_transferred()).unwrap_or(i32::MAX); + let total = report + .total_bytes() + .flatten() + .and_then(|value| i32::try_from(value).ok()) + .unwrap_or(received) + .max(received); + if let Ok(event) = ImageReceiveProgressEventArgs::new(texture_id, received, total) { + events.emit(event); + } + }; + let mut request = DownloadRequest::new( + Self::with_query(&cap, "texture_id", texture_id), + Some("image/x-j2c".into()), + Some(Box::new(progress)), + )?; + request.cancellation_token = source.token(); + let result = self + .inner + .downloads + .queue_download_with_download_request_ba727387(request) + .await + .and_then(|(response, bytes)| { + if bytes.len() > crate::asset_models::MAX_ASSET_BYTES { + Err(Error::Argument) + } else if (200..300).contains(&response.status_code) { + Ok(Some(bytes)) + } else { + Ok(None) + } + }); + mutex(&self.inner.image_cancellations).remove(&texture_id); + let Some(bytes) = result? else { + return Ok(None); + }; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token( + texture_id, + bytes.clone(), + Some(source.token()), + ) + .await?; + AssetTexture::new_with_uuid_bytes(texture_id, bytes).map(Some) + } + pub fn request_image_cancel(&self, texture_id: UUID) -> Result<(), Error> { + if let Some(source) = mutex(&self.inner.image_cancellations).remove(&texture_id) { + source.cancel(); + } + Ok(()) + } + + pub async fn request_server_baked_image( + &self, + avatar_id: UUID, + texture_id: UUID, + bake_name: String, + cancellation_token: Option, + ) -> Result, Error> { + if avatar_id == UUID::zero() + || texture_id == UUID::zero() + || bake_name.is_empty() + || bake_name.contains('/') + { + return Ok(None); + } + if let Some(bytes) = self.cache.get_cached_asset_bytes_with_uuid(texture_id)? { + return AssetTexture::new_with_uuid_bytes(texture_id, bytes).map(Some); + } + let Some(base) = self + .client()? + .native_network()? + .agent_appearance_service_url() + else { + return Ok(None); + }; + let uri = Uri(format!( + "{}/texture/{}/{}/{}", + base.trim_end_matches('/'), + avatar_id, + bake_name, + texture_id + )); + let token = Self::token(cancellation_token); + let Some(bytes) = self + .fetch(uri, Some("image/x-j2c".into()), token.clone()) + .await? + else { + return Ok(None); + }; + self.cache + .save_asset_to_cache_with_uuid_bytes_cancellation_token( + texture_id, + bytes.clone(), + Some(token), + ) + .await?; + AssetTexture::new_with_uuid_bytes(texture_id, bytes).map(Some) + } + + async fn uploader_asset( + &self, + cap_name: &str, + data: Vec, + cancellation_token: Option, + ) -> Result { + if data.is_empty() || data.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + let Some(cap) = self.capability(cap_name)? else { + return Ok(UUID::zero()); + }; + let token = Self::token(cancellation_token); + let client = self.client()?; + let (_, metadata) = client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + cap, + "application/llsd+xml".into(), + b"".to_vec(), + token.clone(), + None, + ) + .await?; + let meta: Value = serde_json::from_slice(&metadata).map_err(|_| Error::Argument)?; + let uploader = meta + .get("uploader") + .and_then(Value::as_str) + .ok_or(Error::Argument)?; + let (_, response) = client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + Uri(uploader.to_owned()), + "application/octet-stream".into(), + data, + token, + None, + ) + .await?; + let result: Value = serde_json::from_slice(&response).map_err(|_| Error::Argument)?; + if result.get("state").and_then(Value::as_str) != Some("complete") { + return Ok(UUID::zero()); + } + result + .get("new_asset") + .or_else(|| result.get("new_asset_id")) + .and_then(Value::as_str) + .map(|id| UUID::new_with_string(id.to_owned())) + .transpose() + .map(|id| id.unwrap_or_else(UUID::zero)) + } + pub async fn request_upload_baked_texture( + &self, + texture_data: Vec, + cancellation_token: Option, + ) -> Result { + self.uploader_asset("UploadBakedTexture", texture_data, cancellation_token) + .await + } + pub async fn request_upload_large_texture( + &self, + data: Vec, + width_pixels: i32, + cancellation_token: Option, + ) -> Result { + if width_pixels <= 0 { + return Err(Error::Argument); + } + self.request_upload_with_asset_type_bytes_boolean_uuid_cancellation_token( + AssetType::Texture, + data, + false, + UUID::random()?, + cancellation_token, + ) + .await + } + pub fn get_texture_upload_cost(&self, width_pixels: i32) -> Result { + if width_pixels <= 0 { + return Err(Error::Argument); + } + Ok(self.client()?.settings_ref().upload_cost()) + } + pub async fn request_upload_with_asset_type_bytes_boolean_uuid_cancellation_token( + &self, + type_: AssetType, + data: Vec, + store_local: bool, + transaction_id: UUID, + cancellation_token: Option, + ) -> Result { + let token = Self::token(cancellation_token); + token.throw_if_cancellation_requested()?; + if data.is_empty() || data.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + let mut client = self.client()?; + let network = client.native_network()?; + let simulator = network.current_sim().ok_or(Error::InvalidOperation)?; + let secure_session = client.native_self().secure_session_id(); + let asset_id = UUID::combine(transaction_id, secure_session)?; + let (sender, receiver) = oneshot::channel(); + let upload = Arc::new(Mutex::new(UploadState { + asset_id, + data: data.clone(), + packet_num: 0, + transaction_id, + transferred: if data.len() <= 1000 { data.len() } else { 0 }, + type_, + xfer_id: 0, + confirmation: Some(sender), + })); + { + let mut pending = mutex(&self.inner.pending_upload); + if pending.is_some() { + return Err(Error::InvalidOperation); + } + *pending = Some(Arc::clone(&upload)); + } + let mut packet = AssetUploadRequestPacket::new_with_constructor()?; + packet.asset_block.transaction_id = transaction_id; + packet.asset_block.type_ = type_ as i8; + packet.asset_block.store_local = store_local; + packet.asset_block.tempfile = false; + if data.len() <= 1000 { + packet.asset_block.asset_data = data; + } + if let Err(error) = self.send_packet(&simulator, &packet, PacketType::AssetUploadRequest) { + mutex(&self.inner.pending_upload).take(); + return Err(error); + } + let (timeout_sender, timeout_receiver) = oneshot::channel(); + std::thread::Builder::new() + .name("asset-upload-timeout".into()) + .spawn(move || { + std::thread::sleep(Duration::from_secs(20)); + let _ = timeout_sender.send(()); + }) + .map_err(|_| Error::InvalidOperation)?; + let completion = receiver.fuse(); + let cancelled = token.cancelled().fuse(); + let timeout = timeout_receiver.fuse(); + pin_mut!(completion, cancelled, timeout); + let result = select_biased! { + value = completion => value.map_err(|_| Error::InvalidOperation).and_then(|value| if value == UUID::zero() { Err(Error::InvalidOperation) } else { Ok(value) }), + () = cancelled => Err(Error::Cancelled), + _ = timeout => Err(Error::InvalidOperation), + }; + if result.is_err() { + let mut pending = mutex(&self.inner.pending_upload); + if pending + .as_ref() + .is_some_and(|value| Arc::ptr_eq(value, &upload)) + { + pending.take(); + } + } + result + } + pub fn request_upload_with_uuid_asset_type_bytes_boolean_uuid( + &self, + asset_id: &mut UUID, + type_: AssetType, + data: Vec, + store_local: bool, + transaction_id: UUID, + ) -> Result { + let mut client = self.client()?; + *asset_id = UUID::combine(transaction_id, client.native_self().secure_session_id())?; + block_on( + self.request_upload_with_asset_type_bytes_boolean_uuid_cancellation_token( + type_, + data, + store_local, + transaction_id, + None, + ), + ) + } + + pub fn request_asset_xfer( + &self, + filename: String, + delete_on_completion: bool, + use_big_packets: bool, + v_file_id: UUID, + v_file_type: AssetType, + from_cache: bool, + ) -> Result { + if filename.as_bytes().contains(&0) || filename.len() > 1024 { + return Err(Error::Argument); + } + let simulator = self + .client()? + .native_network()? + .current_sim() + .ok_or(Error::InvalidOperation)?; + let id = UUID::random()?.get_u_long()?; + let mut download = XferDownload::new()?; + download.filename.clone_from(&filename); + download.v_file_id = v_file_id; + download.xfer_id = id; + download.base.asset_type = v_file_type; + download.base.id = v_file_id; + mutex(&self.inner.xfer_downloads).insert( + id, + XferState { + download, + chunks: HashMap::new(), + expected_size: None, + final_packet: None, + }, + ); + let mut packet = RequestXferPacket::new_with_constructor()?; + packet.xfer_id.id = id; + packet.xfer_id.filename = filename.into_bytes(); + packet.xfer_id.filename.push(0); + packet.xfer_id.delete_on_completion = delete_on_completion; + packet.xfer_id.use_big_packets = use_big_packets; + packet.xfer_id.v_file_id = v_file_id; + packet.xfer_id.v_file_type = v_file_type as i16; + packet.xfer_id.file_path = u8::from(from_cache); + if let Err(error) = self.send_packet(&simulator, &packet, PacketType::RequestXfer) { + mutex(&self.inner.xfer_downloads).remove(&id); + return Err(error); + } + Ok(id) + } + pub fn request_estate_asset( + &self, + transfer: AssetDownload, + eat: EstateAssetType, + ) -> Result<(), Error> { + let simulator = transfer + .simulator + .clone() + .or(self.client()?.native_network()?.current_sim()) + .ok_or(Error::InvalidOperation)?; + let mut client = self.client()?; + let agent_id = client.native_self().agent_id(); + let session_id = client.native_self().session_id(); + let mut params = Vec::with_capacity(36); + params.extend_from_slice(&agent_id.get_bytes()?); + params.extend_from_slice(&session_id.get_bytes()?); + params.extend_from_slice(&(eat as i32).to_le_bytes()); + let mut packet = TransferRequestPacket::new_with_constructor()?; + packet.transfer_info.channel_type = transfer.channel as i32; + packet.transfer_info.priority = transfer.priority; + packet.transfer_info.source_type = transfer.source as i32; + packet.transfer_info.transfer_id = transfer.base.id; + packet.transfer_info.params = params; + self.send_packet(&simulator, &packet, PacketType::TransferRequest) + } + #[must_use] + pub fn http_download_manager(&self) -> DownloadManager { + self.inner.downloads.clone() + } + pub fn subscribe_asset_uploaded( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.uploaded.subscribe(handler) + } + pub fn subscribe_upload_progress( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.upload_progress.subscribe(handler) + } + pub fn subscribe_image_receive_progress( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.image_progress.subscribe(handler) + } + pub fn subscribe_initiate_download( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.initiate.subscribe(handler) + } + pub fn subscribe_xfer_received( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.xfer.subscribe(handler) + } +} diff --git a/crates/libremetaverse/src/asset_material.rs b/crates/libremetaverse/src/asset_material.rs new file mode 100644 index 0000000..685fae0 --- /dev/null +++ b/crates/libremetaverse/src/asset_material.rs @@ -0,0 +1,719 @@ +//! GLTF material asset and simulator material-override encoding. + +#![allow( + clippy::assigning_clones, + clippy::cast_possible_truncation, + clippy::collapsible_if, + clippy::float_cmp, + clippy::missing_errors_doc, + clippy::missing_panics_doc, + clippy::must_use_candidate, + clippy::needless_pass_by_value, + clippy::should_implement_trait +)] + +use crate::{Error, assets::GltfAlphaMode}; +use libremetaverse_structured_data::{OSD, OSDMap}; +use libremetaverse_types::{AssetType, Color4, UUID, Vector2, Vector3}; +use serde_json::{Value, json}; +use std::collections::HashMap; + +const FLOAT_EPSILON: f32 = 1.192_092_9e-7; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct GltfTextureTransform { + pub offset: Vector2, + pub rotation: f32, + pub scale: Vector2, +} + +impl GltfTextureTransform { + pub fn default() -> Self { + Self { + offset: Vector2::zero(), + rotation: 0.0, + scale: Vector2 { x: 1.0, y: 1.0 }, + } + } + pub fn equals_with_gltf_texture_transform(&self, other: Self) -> bool { + *self == other + } + pub fn equals_with_object(&self, _obj: Option) -> bool { + false + } + pub fn get_hash_code(&self) -> i32 { + (self.offset.x.to_bits() + ^ self.offset.y.to_bits() + ^ self.rotation.to_bits() + ^ self.scale.x.to_bits() + ^ self.scale.y.to_bits()) + .cast_signed() + } + pub fn is_default(&self) -> bool { + self.offset.x.abs() <= FLOAT_EPSILON + && self.offset.y.abs() <= FLOAT_EPSILON + && self.rotation.abs() <= FLOAT_EPSILON + && (self.scale.x - 1.0).abs() <= FLOAT_EPSILON + && (self.scale.y - 1.0).abs() <= FLOAT_EPSILON + } +} + +#[derive(Clone, Debug)] +pub struct AssetMaterial { + asset_id: UUID, + asset_data: Vec, + name: String, + texture_ids: Vec, + texture_transforms: Vec, + base_color_factor: Color4, + emissive_factor: Vector3, + metallic_factor: f32, + roughness_factor: f32, + alpha_cutoff: f32, + alpha_mode: GltfAlphaMode, + double_sided: bool, + override_alpha_mode: bool, + override_double_sided: bool, +} + +impl AssetMaterial { + pub const TEXTURE_BASE_COLOR: i32 = 0; + pub const TEXTURE_NORMAL: i32 = 1; + pub const TEXTURE_METALLIC_ROUGHNESS: i32 = 2; + pub const TEXTURE_EMISSIVE: i32 = 3; + pub const TEXTURE_COUNT: i32 = 4; + + pub fn default() -> Self { + Self { + asset_id: UUID::zero(), + asset_data: Vec::new(), + name: String::new(), + texture_ids: vec![UUID::zero(); Self::TEXTURE_COUNT as usize], + texture_transforms: vec![GltfTextureTransform::default(); Self::TEXTURE_COUNT as usize], + base_color_factor: Color4 { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + emissive_factor: Vector3::zero(), + metallic_factor: 1.0, + roughness_factor: 1.0, + alpha_cutoff: 0.5, + alpha_mode: GltfAlphaMode::Opaque, + double_sided: false, + override_alpha_mode: false, + override_double_sided: false, + } + } + + pub fn gltf_override_null_uuid() -> UUID { + UUID::new_with_string("ffffffff-ffff-ffff-ffff-ffffffffffff".into()) + .expect("constant UUID is valid") + } + + pub fn new_with_constructor() -> Result { + Ok(Self::default()) + } + + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + if asset_data.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + let mut material = Self { + asset_id, + asset_data, + ..Self::default() + }; + if !material.decode()? { + return Err(Error::Argument); + } + Ok(material) + } + + fn slot(slot: i32) -> Result { + usize::try_from(slot) + .ok() + .filter(|slot| *slot < Self::TEXTURE_COUNT as usize) + .ok_or(Error::Argument) + } + + pub fn set_texture_id( + &mut self, + slot: i32, + id: UUID, + for_override: Option, + ) -> Result<(), Error> { + let slot = Self::slot(slot)?; + self.texture_ids[slot] = if for_override.unwrap_or(false) && id == UUID::zero() { + Self::gltf_override_null_uuid() + } else { + id + }; + Ok(()) + } + pub fn set_texture_offset(&mut self, slot: i32, offset: Vector2) -> Result<(), Error> { + self.texture_transforms[Self::slot(slot)?].offset = offset; + Ok(()) + } + pub fn set_texture_scale(&mut self, slot: i32, scale: Vector2) -> Result<(), Error> { + self.texture_transforms[Self::slot(slot)?].scale = scale; + Ok(()) + } + pub fn set_texture_rotation(&mut self, slot: i32, rotation: f32) -> Result<(), Error> { + self.texture_transforms[Self::slot(slot)?].rotation = rotation; + Ok(()) + } + pub fn set_base_color_factor_with_color4_boolean( + &mut self, + mut color: Color4, + for_override: Option, + ) -> Result<(), Error> { + if for_override.unwrap_or(false) && color == Self::default().base_color_factor { + color.a -= FLOAT_EPSILON; + } + self.base_color_factor = color; + Ok(()) + } + pub fn set_emissive_factor_with_vector3_boolean( + &mut self, + mut color: Vector3, + for_override: Option, + ) -> Result<(), Error> { + if for_override.unwrap_or(false) && color == Vector3::zero() { + color.x += FLOAT_EPSILON; + } + self.emissive_factor = color; + Ok(()) + } + pub fn set_metallic_factor_with_single_boolean( + &mut self, + metallic: f32, + for_override: Option, + ) -> Result<(), Error> { + if !metallic.is_finite() { + return Err(Error::Argument); + } + self.metallic_factor = if for_override.unwrap_or(false) { + metallic.min(1.0 - FLOAT_EPSILON) + } else { + metallic + }; + Ok(()) + } + pub fn set_roughness_factor_with_single_boolean( + &mut self, + roughness: f32, + for_override: Option, + ) -> Result<(), Error> { + if !roughness.is_finite() { + return Err(Error::Argument); + } + self.roughness_factor = if for_override.unwrap_or(false) { + roughness.min(1.0 - FLOAT_EPSILON) + } else { + roughness + }; + Ok(()) + } + pub fn set_alpha_cutoff_with_single_boolean( + &mut self, + cutoff: f32, + for_override: Option, + ) -> Result<(), Error> { + if !cutoff.is_finite() { + return Err(Error::Argument); + } + self.alpha_cutoff = if for_override.unwrap_or(false) && cutoff == 0.5 { + cutoff - FLOAT_EPSILON + } else { + cutoff + }; + Ok(()) + } + pub fn set_alpha_mode_with_gltf_alpha_mode_boolean( + &mut self, + mode: GltfAlphaMode, + for_override: Option, + ) -> Result<(), Error> { + self.alpha_mode = mode; + self.override_alpha_mode = for_override.unwrap_or(false); + Ok(()) + } + pub fn set_double_sided_with_boolean_boolean( + &mut self, + double_sided: bool, + for_override: Option, + ) -> Result<(), Error> { + self.double_sided = double_sided; + self.override_double_sided = for_override.unwrap_or(false); + Ok(()) + } + + pub fn set_base_material(&mut self) -> Result<(), Error> { + let transforms = self.texture_transforms.clone(); + let asset_id = self.asset_id; + *self = Self::default(); + self.asset_id = asset_id; + self.texture_transforms = transforms; + Ok(()) + } + + pub fn apply_override(&mut self, value: Self) -> Result<(), Error> { + let sentinel = Self::gltf_override_null_uuid(); + for slot in 0..Self::TEXTURE_COUNT as usize { + if value.texture_ids[slot] == sentinel { + self.texture_ids[slot] = UUID::zero(); + } else if value.texture_ids[slot] != UUID::zero() { + self.texture_ids[slot] = value.texture_ids[slot]; + } + if !value.texture_transforms[slot].is_default() { + self.texture_transforms[slot] = value.texture_transforms[slot]; + } + } + let defaults = Self::default(); + if value.base_color_factor != defaults.base_color_factor { + self.base_color_factor = value.base_color_factor; + } + if value.emissive_factor != defaults.emissive_factor { + self.emissive_factor = value.emissive_factor; + } + if value.metallic_factor != defaults.metallic_factor { + self.metallic_factor = value.metallic_factor; + } + if value.roughness_factor != defaults.roughness_factor { + self.roughness_factor = value.roughness_factor; + } + if value.alpha_cutoff != defaults.alpha_cutoff { + self.alpha_cutoff = value.alpha_cutoff; + } + if value.override_alpha_mode { + self.alpha_mode = value.alpha_mode; + } + if value.override_double_sided { + self.double_sided = value.double_sided; + } + Ok(()) + } + + fn alpha_name(mode: GltfAlphaMode) -> &'static str { + match mode { + GltfAlphaMode::Opaque => "OPAQUE", + GltfAlphaMode::Blend => "BLEND", + GltfAlphaMode::Mask => "MASK", + } + } + fn parse_alpha(value: Option<&str>) -> Result { + match value.unwrap_or("OPAQUE") { + "OPAQUE" => Ok(GltfAlphaMode::Opaque), + "BLEND" => Ok(GltfAlphaMode::Blend), + "MASK" => Ok(GltfAlphaMode::Mask), + _ => Err(Error::Argument), + } + } + + fn texture_json( + &self, + slot: usize, + images: &mut Vec, + textures: &mut Vec, + ) -> Option { + let id = self.texture_ids[slot]; + if id == UUID::zero() || id == Self::gltf_override_null_uuid() { + return None; + } + let index = images.len(); + images.push(json!({"uri": id.to_string()})); + textures.push(json!({"source": index})); + let mut info = json!({"index": index}); + let transform = self.texture_transforms[slot]; + if !transform.is_default() { + info["extensions"] = json!({"KHR_texture_transform": {"offset": [transform.offset.x, transform.offset.y], "scale": [transform.scale.x, transform.scale.y], "rotation": transform.rotation}}); + } + Some(info) + } + + pub fn to_json(&self) -> Result { + let mut images = Vec::new(); + let mut textures = Vec::new(); + let base = self.texture_json( + Self::TEXTURE_BASE_COLOR as usize, + &mut images, + &mut textures, + ); + let normal = self.texture_json(Self::TEXTURE_NORMAL as usize, &mut images, &mut textures); + let metallic = self.texture_json( + Self::TEXTURE_METALLIC_ROUGHNESS as usize, + &mut images, + &mut textures, + ); + let emissive = + self.texture_json(Self::TEXTURE_EMISSIVE as usize, &mut images, &mut textures); + let mut pbr = json!({"baseColorFactor": [self.base_color_factor.r,self.base_color_factor.g,self.base_color_factor.b,self.base_color_factor.a], "metallicFactor": self.metallic_factor, "roughnessFactor": self.roughness_factor}); + if let Some(value) = base { + pbr["baseColorTexture"] = value; + } + if let Some(value) = metallic { + pbr["metallicRoughnessTexture"] = value; + } + let mut material = json!({"name": self.name, "pbrMetallicRoughness": pbr, "emissiveFactor": [self.emissive_factor.x,self.emissive_factor.y,self.emissive_factor.z], "alphaMode": Self::alpha_name(self.alpha_mode), "alphaCutoff": self.alpha_cutoff, "doubleSided": self.double_sided}); + if let Some(value) = normal { + material["normalTexture"] = value; + } + if let Some(value) = emissive { + material["emissiveTexture"] = value; + } + let mut root = json!({"asset":{"version":"2.0"},"materials":[material]}); + if !images.is_empty() { + root["images"] = Value::Array(images); + root["textures"] = Value::Array(textures); + root["extensionsUsed"] = json!(["KHR_texture_transform"]); + } + serde_json::to_string(&root).map_err(|_| Error::InvalidOperation) + } + + pub fn encode(&mut self) -> Result<(), Error> { + self.asset_data = self.to_json()?.into_bytes(); + Ok(()) + } + pub fn decode(&mut self) -> Result { + let root: Value = serde_json::from_slice(&self.asset_data).map_err(|_| Error::Argument)?; + let mat = root + .get("materials") + .and_then(Value::as_array) + .and_then(|v| v.first()) + .and_then(Value::as_object) + .ok_or(Error::Argument)?; + self.name = mat + .get("name") + .and_then(Value::as_str) + .unwrap_or_default() + .to_owned(); + let pbr = mat.get("pbrMetallicRoughness").and_then(Value::as_object); + if let Some(values) = pbr + .and_then(|v| v.get("baseColorFactor")) + .and_then(Value::as_array) + { + if values.len() == 4 { + self.base_color_factor = Color4 { + r: number(&values[0])?, + g: number(&values[1])?, + b: number(&values[2])?, + a: number(&values[3])?, + }; + } + } + self.metallic_factor = pbr + .and_then(|v| v.get("metallicFactor")) + .map(number) + .transpose()? + .unwrap_or(1.0); + self.roughness_factor = pbr + .and_then(|v| v.get("roughnessFactor")) + .map(number) + .transpose()? + .unwrap_or(1.0); + if let Some(values) = mat.get("emissiveFactor").and_then(Value::as_array) { + if values.len() == 3 { + self.emissive_factor = Vector3 { + x: number(&values[0])?, + y: number(&values[1])?, + z: number(&values[2])?, + }; + } + } + self.alpha_mode = Self::parse_alpha(mat.get("alphaMode").and_then(Value::as_str))?; + self.alpha_cutoff = mat + .get("alphaCutoff") + .map(number) + .transpose()? + .unwrap_or(0.5); + self.double_sided = mat + .get("doubleSided") + .and_then(Value::as_bool) + .unwrap_or(false); + let textures = root.get("textures").and_then(Value::as_array); + let images = root.get("images").and_then(Value::as_array); + for (slot, info) in [ + (0, pbr.and_then(|v| v.get("baseColorTexture"))), + (1, mat.get("normalTexture")), + (2, pbr.and_then(|v| v.get("metallicRoughnessTexture"))), + (3, mat.get("emissiveTexture")), + ] { + if let Some(info) = info { + self.decode_texture(slot, info, textures, images)?; + } + } + Ok(true) + } + + fn decode_texture( + &mut self, + slot: usize, + info: &Value, + textures: Option<&Vec>, + images: Option<&Vec>, + ) -> Result<(), Error> { + let index = info + .get("index") + .and_then(Value::as_u64) + .and_then(|v| usize::try_from(v).ok()) + .ok_or(Error::Argument)?; + let source = textures + .and_then(|v| v.get(index)) + .and_then(|v| v.get("source")) + .and_then(Value::as_u64) + .and_then(|v| usize::try_from(v).ok()) + .ok_or(Error::Argument)?; + let uri = images + .and_then(|v| v.get(source)) + .and_then(|v| v.get("uri")) + .and_then(Value::as_str) + .ok_or(Error::Argument)?; + self.texture_ids[slot] = UUID::new_with_string(uri.to_owned())?; + if let Some(transform) = info.pointer("/extensions/KHR_texture_transform") { + if let Some(v) = transform.get("offset").and_then(Value::as_array) { + if v.len() == 2 { + self.texture_transforms[slot].offset = Vector2 { + x: number(&v[0])?, + y: number(&v[1])?, + }; + } + } + if let Some(v) = transform.get("scale").and_then(Value::as_array) { + if v.len() == 2 { + self.texture_transforms[slot].scale = Vector2 { + x: number(&v[0])?, + y: number(&v[1])?, + }; + } + } + if let Some(v) = transform.get("rotation") { + self.texture_transforms[slot].rotation = number(v)?; + } + } + Ok(()) + } + + pub fn to_override_osd(&self) -> Result { + let defaults = Self::default(); + let mut map = HashMap::new(); + let mut tex = Vec::new(); + let mut last = None; + for (index, id) in self.texture_ids.iter().enumerate() { + if *id != UUID::zero() { + last = Some(index); + } + } + if let Some(last) = last { + for id in &self.texture_ids[..=last] { + tex.push(OSD::UUID(*id)); + } + map.insert("tex".into(), OSD::Array(tex)); + } + if self.base_color_factor != defaults.base_color_factor { + map.insert("bc".into(), color_osd(self.base_color_factor)); + } + if self.emissive_factor != defaults.emissive_factor { + map.insert("ec".into(), vector3_osd(self.emissive_factor)); + } + if self.metallic_factor != defaults.metallic_factor { + map.insert("mf".into(), OSD::Real(f64::from(self.metallic_factor))); + } + if self.roughness_factor != defaults.roughness_factor { + map.insert("rf".into(), OSD::Real(f64::from(self.roughness_factor))); + } + if self.override_alpha_mode { + map.insert("am".into(), OSD::Integer(self.alpha_mode as i32)); + } + if self.alpha_cutoff != defaults.alpha_cutoff { + map.insert("ac".into(), OSD::Real(f64::from(self.alpha_cutoff))); + } + if self.override_double_sided { + map.insert("ds".into(), OSD::Boolean(self.double_sided)); + } + let transforms: Vec<_> = self + .texture_transforms + .iter() + .map(|t| { + OSD::Map(HashMap::from([ + ("o".into(), vector2_osd(t.offset)), + ("s".into(), vector2_osd(t.scale)), + ("r".into(), OSD::Real(f64::from(t.rotation))), + ])) + }) + .collect(); + if self.texture_transforms.iter().any(|v| !v.is_default()) { + map.insert("ti".into(), OSD::Array(transforms)); + } + OSDMap::new_with_dictionary(map) + } + + pub fn from_override_osd(data: OSDMap) -> Result { + let map = data.snapshot(); + let mut value = Self::default(); + if let Some(OSD::Array(ids)) = map.get("tex") { + for (slot, id) in ids.iter().take(Self::TEXTURE_COUNT as usize).enumerate() { + value.texture_ids[slot] = id.as_uuid()?; + } + } + if let Some(v) = map.get("bc") { + value.base_color_factor = v.as_color4()?; + if value.base_color_factor == Self::default().base_color_factor { + value.base_color_factor.a -= FLOAT_EPSILON; + } + } + if let Some(v) = map.get("ec") { + value.emissive_factor = v.as_vector3()?; + if value.emissive_factor == Vector3::zero() { + value.emissive_factor.x += FLOAT_EPSILON; + } + } + if let Some(v) = map.get("mf") { + value.metallic_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON); + } + if let Some(v) = map.get("rf") { + value.roughness_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON); + } + if let Some(v) = map.get("am") { + value.alpha_mode = match v.as_integer()? { + 0 => GltfAlphaMode::Opaque, + 1 => GltfAlphaMode::Blend, + 2 => GltfAlphaMode::Mask, + _ => return Err(Error::Argument), + }; + value.override_alpha_mode = true; + } + if let Some(v) = map.get("ac") { + value.alpha_cutoff = v.as_real()? as f32; + if value.alpha_cutoff == 0.5 { + value.alpha_cutoff -= FLOAT_EPSILON; + } + } + if let Some(v) = map.get("ds") { + value.double_sided = v.as_boolean()?; + value.override_double_sided = true; + } + if let Some(OSD::Array(values)) = map.get("ti") { + for (slot, transform) in values.iter().take(Self::TEXTURE_COUNT as usize).enumerate() { + if let OSD::Map(t) = transform { + if let Some(v) = t.get("o") { + value.texture_transforms[slot].offset = v.as_vector2()?; + } + if let Some(v) = t.get("s") { + value.texture_transforms[slot].scale = v.as_vector2()?; + } + if let Some(v) = t.get("r") { + value.texture_transforms[slot].rotation = v.as_real()? as f32; + } + } + } + } + Ok(value) + } + + pub const fn asset_type(&self) -> AssetType { + AssetType::Material + } + pub fn alpha_cutoff(&self) -> f32 { + self.alpha_cutoff + } + pub fn set_alpha_cutoff(&mut self, value: f32) { + self.alpha_cutoff = value; + } + pub fn alpha_mode(&self) -> GltfAlphaMode { + self.alpha_mode + } + pub fn set_alpha_mode(&mut self, value: GltfAlphaMode) { + self.alpha_mode = value; + } + pub fn base_color_factor(&self) -> Color4 { + self.base_color_factor + } + pub fn set_base_color_factor(&mut self, value: Color4) { + self.base_color_factor = value; + } + pub fn double_sided(&self) -> bool { + self.double_sided + } + pub fn set_double_sided(&mut self, value: bool) { + self.double_sided = value; + } + pub fn emissive_factor(&self) -> Vector3 { + self.emissive_factor + } + pub fn set_emissive_factor(&mut self, value: Vector3) { + self.emissive_factor = value; + } + pub fn metallic_factor(&self) -> f32 { + self.metallic_factor + } + pub fn set_metallic_factor(&mut self, value: f32) { + self.metallic_factor = value; + } + pub fn roughness_factor(&self) -> f32 { + self.roughness_factor + } + pub fn set_roughness_factor(&mut self, value: f32) { + self.roughness_factor = value; + } + pub fn name(&self) -> String { + self.name.clone() + } + pub fn set_name(&mut self, value: String) { + self.name = value; + } + pub fn override_alpha_mode(&self) -> bool { + self.override_alpha_mode + } + pub fn set_override_alpha_mode(&mut self, value: bool) { + self.override_alpha_mode = value; + } + pub fn override_double_sided(&self) -> bool { + self.override_double_sided + } + pub fn set_override_double_sided(&mut self, value: bool) { + self.override_double_sided = value; + } + pub fn texture_ids(&self) -> Vec { + self.texture_ids.clone() + } + pub fn set_texture_ids(&mut self, value: Vec) { + self.texture_ids = value; + } + pub fn texture_transforms(&self) -> Vec { + self.texture_transforms.clone() + } + pub fn set_texture_transforms(&mut self, value: Vec) { + self.texture_transforms = value; + } +} + +fn number(value: &Value) -> Result { + value + .as_f64() + .map(|v| v as f32) + .filter(|v| v.is_finite()) + .ok_or(Error::Argument) +} +fn vector2_osd(value: Vector2) -> OSD { + OSD::Array(vec![ + OSD::Real(f64::from(value.x)), + OSD::Real(f64::from(value.y)), + ]) +} +fn vector3_osd(value: Vector3) -> OSD { + OSD::Array(vec![ + OSD::Real(f64::from(value.x)), + OSD::Real(f64::from(value.y)), + OSD::Real(f64::from(value.z)), + ]) +} +fn color_osd(value: Color4) -> OSD { + OSD::Array(vec![ + OSD::Real(f64::from(value.r)), + OSD::Real(f64::from(value.g)), + OSD::Real(f64::from(value.b)), + OSD::Real(f64::from(value.a)), + ]) +} diff --git a/crates/libremetaverse/src/asset_models.rs b/crates/libremetaverse/src/asset_models.rs new file mode 100644 index 0000000..53c8724 --- /dev/null +++ b/crates/libremetaverse/src/asset_models.rs @@ -0,0 +1,676 @@ +//! Native asset value models and their format boundaries. + +#![allow( + clippy::format_push_string, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::needless_pass_by_value, + clippy::struct_field_names +)] + +use crate::{Error, Permissions}; +use libremetaverse_imaging::ManagedImage; +use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; +use libremetaverse_types::{AssetType, SaleType, UUID, Vector3, WearableType}; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +/// Maximum accepted in-memory asset payload. Network limits are enforced before +/// this boundary too, but constructors are public and must defend themselves. +pub const MAX_ASSET_BYTES: usize = 64 * 1024 * 1024; + +fn validate_bytes(bytes: &[u8]) -> Result<(), Error> { + if bytes.len() > MAX_ASSET_BYTES { + Err(Error::Argument) + } else { + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Asset { + pub asset_data: Vec, + pub temporary: bool, + asset_id: UUID, + asset_type: AssetType, +} + +impl Asset { + pub(crate) fn native_new( + asset_type: AssetType, + asset_id: UUID, + data: Vec, + ) -> Result { + validate_bytes(&data)?; + Ok(Self { + asset_data: data, + temporary: false, + asset_id, + asset_type, + }) + } + + pub fn decode(&self) -> Result { + validate_bytes(&self.asset_data)?; + Ok(true) + } + + pub fn encode(&self) -> Result<(), Error> { + validate_bytes(&self.asset_data) + } + + pub const fn asset_id(&self) -> UUID { + self.asset_id + } + pub fn set_asset_id(&mut self, value: UUID) { + self.asset_id = value; + } + pub const fn asset_type(&self) -> AssetType { + self.asset_type + } +} + +#[derive(Clone, Debug)] +struct RawAsset(Arc>); + +impl RawAsset { + fn empty(asset_type: AssetType) -> Result { + Ok(Self(Arc::new(RwLock::new(Asset::native_new( + asset_type, + UUID::zero(), + Vec::new(), + )?)))) + } + + fn with_data(asset_type: AssetType, id: UUID, data: Vec) -> Result { + Ok(Self(Arc::new(RwLock::new(Asset::native_new( + asset_type, id, data, + )?)))) + } + + fn decode(&self) -> Result { + self.0 + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .decode() + } + + fn encode(&self) -> Result<(), Error> { + self.0 + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .encode() + } + + fn bytes(&self) -> Vec { + self.0 + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .asset_data + .clone() + } + + fn replace(&self, bytes: Vec) -> Result<(), Error> { + validate_bytes(&bytes)?; + self.0 + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .asset_data = bytes; + Ok(()) + } +} + +macro_rules! raw_asset { + ($name:ident, $kind:expr) => { + #[derive(Clone, Debug)] + pub struct $name { + raw: RawAsset, + } + impl $name { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty($kind)?, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + Ok(Self { + raw: RawAsset::with_data($kind, asset_id, asset_data)?, + }) + } + pub fn decode(&self) -> Result { + self.raw.decode() + } + pub fn encode(&self) -> Result<(), Error> { + self.raw.encode() + } + pub const fn asset_type(&self) -> AssetType { + $kind + } + } + }; +} + +raw_asset!(AssetAnimation, AssetType::Animation); +raw_asset!(AssetScriptBinary, AssetType::LSLBytecode); +raw_asset!(AssetSound, AssetType::Sound); + +impl AssetSound { + pub fn pcm_to_ogg( + pcm_data: Vec, + sample_rate: i32, + channels: i32, + bits_per_sample: Option, + ) -> Result, Error> { + validate_bytes(&pcm_data)?; + if pcm_data.is_empty() + || sample_rate <= 0 + || !matches!(channels, 1 | 2) + || !matches!(bits_per_sample.unwrap_or(16), 8 | 16) + { + return Err(Error::Argument); + } + let bits = bits_per_sample.unwrap_or(16); + let bytes_per_sample = usize::try_from(bits / 8).map_err(|_| Error::Argument)?; + let channels = usize::try_from(channels).map_err(|_| Error::Argument)?; + let frame_size = bytes_per_sample + .checked_mul(channels) + .ok_or(Error::Argument)?; + if !pcm_data.len().is_multiple_of(frame_size) { + return Err(Error::Argument); + } + let frequency = + std::num::NonZeroU32::new(u32::try_from(sample_rate).map_err(|_| Error::Argument)?) + .ok_or(Error::Argument)?; + let channel_count = + std::num::NonZeroU8::new(u8::try_from(channels).map_err(|_| Error::Argument)?) + .ok_or(Error::Argument)?; + let mut builder = vorbis_rs::VorbisEncoderBuilder::new_with_serial( + frequency, + channel_count, + Vec::new(), + 0x4d43_4154, + ); + let mut encoder = builder.build().map_err(|_| Error::InvalidOperation)?; + let frame_count = pcm_data.len() / frame_size; + for first_frame in (0..frame_count).step_by(1024) { + let end_frame = first_frame.saturating_add(1024).min(frame_count); + let mut planar = vec![Vec::with_capacity(end_frame - first_frame); channels]; + for frame in first_frame..end_frame { + for (channel, samples) in planar.iter_mut().enumerate() { + let offset = frame * frame_size + channel * bytes_per_sample; + let sample = if bits == 8 { + (f32::from(pcm_data[offset]) - 128.0) / 128.0 + } else { + f32::from(i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]])) + / 32768.0 + }; + samples.push(sample); + } + } + encoder + .encode_audio_block(&planar) + .map_err(|_| Error::InvalidOperation)?; + } + let ogg_data = encoder.finish().map_err(|_| Error::InvalidOperation)?; + validate_bytes(&ogg_data)?; + Ok(ogg_data) + } +} + +#[derive(Clone, Debug)] +pub struct AssetMutable { + raw: RawAsset, + pub current_type: AssetType, +} +impl AssetMutable { + pub fn new_with_asset_type(type_: AssetType) -> Result { + Ok(Self { + raw: RawAsset::empty(type_)?, + current_type: type_, + }) + } + pub fn new_with_asset_type_uuid_bytes( + type_: AssetType, + asset_id: UUID, + asset_data: Vec, + ) -> Result { + Ok(Self { + raw: RawAsset::with_data(type_, asset_id, asset_data)?, + current_type: type_, + }) + } + pub fn decode(&self) -> Result { + self.raw.decode() + } + pub fn encode(&self) -> Result<(), Error> { + self.raw.encode() + } + pub const fn asset_type(&self) -> AssetType { + self.current_type + } +} + +#[derive(Clone, Debug)] +pub struct AssetCallingCard { + raw: RawAsset, + pub avatar_id: UUID, +} +impl AssetCallingCard { + pub fn new_with_constructor() -> Result { + Self::new_with_uuid(UUID::zero()) + } + pub fn new_with_uuid(avatar_id: UUID) -> Result { + let value = Self { + raw: RawAsset::empty(AssetType::CallingCard)?, + avatar_id, + }; + value.encode()?; + Ok(value) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let raw = RawAsset::with_data(AssetType::CallingCard, asset_id, asset_data)?; + let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?; + let id = text + .lines() + .find_map(|line| line.strip_prefix("avatar_id ")) + .ok_or(Error::Argument) + .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?; + Ok(Self { raw, avatar_id: id }) + } + pub fn decode(&self) -> Result { + let text = String::from_utf8(self.raw.bytes()).map_err(|_| Error::Argument)?; + Ok(text.lines().any(|line| { + line.strip_prefix("avatar_id ") + .and_then(|id| UUID::new_with_string(id.trim().to_owned()).ok()) + == Some(self.avatar_id) + })) + } + pub fn encode(&self) -> Result<(), Error> { + self.raw.replace( + format!( + "Linden text version 2\n{{\navatar_id {}\n}}\n", + self.avatar_id + ) + .into_bytes(), + ) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::CallingCard + } +} + +#[derive(Clone, Debug)] +pub struct AssetLandmark { + raw: RawAsset, + pub position: Vector3, + pub region_id: UUID, +} +impl AssetLandmark { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Landmark)?, + position: Vector3::zero(), + region_id: UUID::zero(), + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let raw = RawAsset::with_data(AssetType::Landmark, asset_id, asset_data)?; + let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?; + let region = text + .lines() + .find_map(|line| line.strip_prefix("region_id ")) + .ok_or(Error::Argument) + .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?; + let values: Vec = text + .lines() + .find_map(|line| line.strip_prefix("local_pos ")) + .ok_or(Error::Argument)? + .split_whitespace() + .map(str::parse) + .collect::>() + .map_err(|_| Error::Argument)?; + if values.len() != 3 { + return Err(Error::Argument); + } + Ok(Self { + raw, + region_id: region, + position: Vector3 { + x: values[0], + y: values[1], + z: values[2], + }, + }) + } + pub fn decode(&self) -> Result { + Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok()) + } + pub fn encode(&self) -> Result<(), Error> { + self.raw.replace( + format!( + "Landmark version 2\nregion_id {}\nlocal_pos {} {} {}\n", + self.region_id, self.position.x, self.position.y, self.position.z + ) + .into_bytes(), + ) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Landmark + } +} + +#[derive(Clone, Debug)] +pub struct AssetScriptText { + raw: RawAsset, + pub source: Option, +} +impl AssetScriptText { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::LSLText)?, + source: None, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let source = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; + Ok(Self { + raw: RawAsset::with_data(AssetType::LSLText, asset_id, asset_data)?, + source: Some(source), + }) + } + pub fn decode(&self) -> Result { + Ok(String::from_utf8(self.raw.bytes()).is_ok()) + } + pub fn encode(&self) -> Result<(), Error> { + self.raw + .replace(self.source.clone().unwrap_or_default().into_bytes()) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::LSLText + } +} + +#[derive(Clone, Debug)] +pub struct AssetSettings { + raw: RawAsset, + pub settings: Option, +} +impl AssetSettings { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Settings)?, + settings: None, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; + let settings = OSDParser::deserialize_with_string(text)?; + Ok(Self { + raw: RawAsset::with_data(AssetType::Settings, asset_id, asset_data)?, + settings: Some(settings), + }) + } + pub fn decode(&self) -> Result { + Ok(OSDParser::deserialize_with_bytes(self.raw.bytes()).is_ok()) + } + pub fn encode(&self) -> Result<(), Error> { + let value = self.settings.clone().unwrap_or_default(); + self.raw.replace(value.as_string()?.into_bytes()) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Settings + } +} + +pub struct AssetMesh { + raw: RawAsset, + pub mesh_data: OSDMap, +} +impl AssetMesh { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Mesh)?, + mesh_data: OSDMap::new_with_constructor()?, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let raw = RawAsset::with_data(AssetType::Mesh, asset_id, asset_data.clone())?; + let mesh_data = match OSDParser::deserialize_llsd_binary_with_bytes(asset_data)? { + OSD::Map(map) => OSDMap::new_with_dictionary(map)?, + _ => return Err(Error::Argument), + }; + Ok(Self { raw, mesh_data }) + } + pub fn decode(&self) -> Result { + Ok(matches!( + OSDParser::deserialize_llsd_binary_with_bytes(self.raw.bytes()), + Ok(OSD::Map(_)) + )) + } + pub fn encode(&self) -> Result<(), Error> { + self.raw + .replace(OSDParser::serialize_llsd_binary_with_osd(OSD::Map( + self.mesh_data.snapshot(), + ))?) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Mesh + } +} + +pub struct AssetTexture { + raw: RawAsset, + pub components: i32, + pub image: Option, +} +impl AssetTexture { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Texture)?, + components: 0, + image: None, + }) + } + pub fn new_with_managed_image(image: ManagedImage) -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Texture)?, + components: 0, + image: Some(image), + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + Ok(Self { + raw: RawAsset::with_data(AssetType::Texture, asset_id, asset_data)?, + components: 0, + image: None, + }) + } + pub fn decode(&self) -> Result { + #[cfg(feature = "jpeg2000")] + { + Ok(libremetaverse_imaging::J2kCodec::decode_bytes( + &self.raw.bytes(), + libremetaverse_imaging::J2kDecodeOptions::default(), + ) + .is_ok()) + } + #[cfg(not(feature = "jpeg2000"))] + { + validate_bytes(&self.raw.bytes())?; + Err(Error::InvalidOperation) + } + } + pub fn encode(&self) -> Result<(), Error> { + let image = self.image.as_ref().ok_or(Error::InvalidOperation)?; + #[cfg(feature = "jpeg2000")] + { + let encoded = libremetaverse_imaging::J2kCodec::encode( + image, + libremetaverse_imaging::J2kEncodeOptions::default(), + )?; + self.raw.replace(encoded) + } + #[cfg(not(feature = "jpeg2000"))] + { + let _ = image; + Err(Error::InvalidOperation) + } + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Texture + } +} + +pub struct AssetWearable { + raw: RawAsset, + pub creator: UUID, + pub description: String, + pub for_sale: SaleType, + pub group: UUID, + pub group_owned: bool, + pub last_owner: UUID, + pub name: String, + pub owner: UUID, + pub params: HashMap, + pub permissions: Permissions, + pub sale_price: i32, + pub textures: HashMap, + pub wearable_type: WearableType, +} +impl AssetWearable { + fn empty(kind: AssetType) -> Result { + Ok(Self { + raw: RawAsset::empty(kind)?, + creator: UUID::zero(), + description: String::new(), + for_sale: SaleType::Not, + group: UUID::zero(), + group_owned: false, + last_owner: UUID::zero(), + name: String::new(), + owner: UUID::zero(), + params: HashMap::new(), + permissions: Permissions::default(), + sale_price: 0, + textures: HashMap::new(), + wearable_type: WearableType::Invalid, + }) + } + pub fn decode(&self) -> Result { + Ok(std::str::from_utf8(&self.raw.bytes()).is_ok()) + } + pub fn encode(&self) -> Result<(), Error> { + let mut text = format!( + "LLWearable version 22\n{}\n{}\ntype {}\nparameters {}\n", + self.name, + self.description, + self.wearable_type as i32, + self.params.len() + ); + let mut params: Vec<_> = self.params.iter().collect(); + params.sort_unstable_by_key(|(id, _)| **id); + for (id, value) in params { + text.push_str(&format!("{id} {value}\n")); + } + text.push_str(&format!("textures {}\n", self.textures.len())); + self.raw.replace(text.into_bytes()) + } +} + +pub struct AssetBodypart { + wearable: AssetWearable, +} +impl AssetBodypart { + pub fn new_with_constructor() -> Result { + Ok(Self { + wearable: AssetWearable::empty(AssetType::Bodypart)?, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let mut wearable = AssetWearable::empty(AssetType::Bodypart)?; + wearable.raw = RawAsset::with_data(AssetType::Bodypart, asset_id, asset_data)?; + Ok(Self { wearable }) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Bodypart + } + pub fn decode(&self) -> Result { + self.wearable.decode() + } + pub fn encode(&self) -> Result<(), Error> { + self.wearable.encode() + } +} +pub struct AssetClothing { + wearable: AssetWearable, +} +impl AssetClothing { + pub fn new_with_constructor() -> Result { + Ok(Self { + wearable: AssetWearable::empty(AssetType::Clothing)?, + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let mut wearable = AssetWearable::empty(AssetType::Clothing)?; + wearable.raw = RawAsset::with_data(AssetType::Clothing, asset_id, asset_data)?; + Ok(Self { wearable }) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Clothing + } + pub fn decode(&self) -> Result { + self.wearable.decode() + } + pub fn encode(&self) -> Result<(), Error> { + self.wearable.encode() + } +} + +pub struct AssetNotecard { + raw: RawAsset, + pub body_text: String, + pub embedded_items: Vec, +} +impl AssetNotecard { + pub fn new_with_constructor() -> Result { + Ok(Self { + raw: RawAsset::empty(AssetType::Notecard)?, + body_text: String::new(), + embedded_items: Vec::new(), + }) + } + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; + let marker = "Text length "; + let start = text.find(marker).ok_or(Error::Argument)? + marker.len(); + let line_end = text[start..].find('\n').ok_or(Error::Argument)? + start; + let length: usize = text[start..line_end] + .trim() + .parse() + .map_err(|_| Error::Argument)?; + let body_start = line_end + 1; + let body_end = body_start.checked_add(length).ok_or(Error::Argument)?; + let body_text = text + .get(body_start..body_end) + .ok_or(Error::Argument)? + .to_owned(); + Ok(Self { + raw: RawAsset::with_data(AssetType::Notecard, asset_id, asset_data)?, + body_text, + embedded_items: Vec::new(), + }) + } + pub fn decode(&self) -> Result { + Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok()) + } + pub fn encode(&self) -> Result<(), Error> { + if !self.embedded_items.is_empty() { + return Err(Error::InvalidOperation); + } + let bytes = self.body_text.as_bytes(); + self.raw.replace(format!("Linden text version 2\n{{\nLLEmbeddedItems version 1\n{{\ncount 0\n}}\nText length {}\n{}\n}}\n", bytes.len(), self.body_text).into_bytes()) + } + pub const fn asset_type(&self) -> AssetType { + AssetType::Notecard + } +} diff --git a/crates/libremetaverse/src/asset_pipeline_semantics.rs b/crates/libremetaverse/src/asset_pipeline_semantics.rs new file mode 100644 index 0000000..316f045 --- /dev/null +++ b/crates/libremetaverse/src/asset_pipeline_semantics.rs @@ -0,0 +1,133 @@ +use crate::assets::{AssetMesh, AssetMutable, AssetSound, AssetTexture}; +use crate::{ + AssetCache, AssetCacheComputeAssetCacheFilenameDelegate, Error, GridClient, ImageCodec, +}; +use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels}; +use libremetaverse_types::{AssetType, UUID}; +use std::fs; +use std::path::PathBuf; + +fn fixture(max_size: i64) -> (GridClient, AssetCache, PathBuf) { + let path = + std::env::temp_dir().join(format!("metacrate-asset-cache-{}", UUID::random().unwrap())); + let mut client = GridClient::new().unwrap(); + let settings = client.settings(); + settings.asset_cache_mut().dir = path.to_string_lossy().into_owned(); + settings.asset_cache_mut().enabled = true; + settings.asset_cache_mut().max_size = max_size; + let cache = AssetCache::new(client.clone()).unwrap(); + (client, cache, path) +} + +#[test] +fn asset_models_retain_type_and_reject_malformed_meshes() { + let asset = AssetMutable::new_with_asset_type_uuid_bytes( + AssetType::Sound, + UUID::random().unwrap(), + vec![1, 2, 3], + ) + .unwrap(); + assert_eq!(asset.asset_type(), AssetType::Sound); + assert!(asset.decode().unwrap()); + assert!(AssetMesh::new_with_uuid_bytes(UUID::random().unwrap(), b"not-llsd".to_vec()).is_err()); +} + +#[test] +fn sound_and_texture_codecs_produce_real_valid_payloads() { + let ogg = AssetSound::pcm_to_ogg(vec![0_u8; 256 * 2], 44_100, 1, Some(16)).unwrap(); + assert!(ogg.starts_with(b"OggS")); + + let mut image = ManagedImage::new(2, 2, ManagedImageImageChannels::COLOR).unwrap(); + image.red.fill(255); + let texture = AssetTexture::new_with_managed_image(image).unwrap(); + texture.encode().unwrap(); + assert!(texture.decode().unwrap()); +} + +#[test] +fn cache_writes_are_atomic_and_corruption_is_typed() { + let (_client, cache, path) = fixture(1024); + let id = UUID::random().unwrap(); + cache + .save_asset_to_cache_with_uuid_bytes(id, vec![1, 2, 3, 4]) + .unwrap(); + cache + .save_asset_to_cache_with_uuid_bytes(id, vec![9, 9, 9, 9]) + .unwrap(); + assert_eq!( + cache.get_cached_asset_bytes_with_uuid(id).unwrap(), + Some(vec![1, 2, 3, 4]) + ); + + let image = cache.get_cached_image(id).unwrap().unwrap(); + assert_eq!(image.codec, ImageCodec::J2C); + assert_eq!(image.base.asset_data, vec![1, 2, 3, 4]); + + let corrupt = UUID::random().unwrap(); + fs::write(path.join(corrupt.to_string()), []).unwrap(); + assert_eq!( + cache.get_cached_asset_bytes_with_uuid(corrupt), + Err(Error::Argument) + ); + fs::remove_dir_all(path).unwrap(); +} + +#[test] +fn custom_cache_naming_is_used_but_cannot_escape_the_cache_directory() { + let (_client, mut cache, path) = fixture(1024); + cache.compute_asset_cache_filename = Some( + AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, id| { + Ok(PathBuf::from(directory) + .join(format!("{id}.asset")) + .to_string_lossy() + .into_owned()) + }), + ); + let id = UUID::random().unwrap(); + cache + .save_asset_to_cache_with_uuid_bytes(id, vec![7, 8, 9]) + .unwrap(); + assert_eq!( + cache.get_cached_asset_bytes_with_uuid(id).unwrap(), + Some(vec![7, 8, 9]) + ); + assert!(path.join(format!("{id}.asset")).is_file()); + cache.clear().unwrap(); + assert!(!path.join(format!("{id}.asset")).exists()); + + cache.compute_asset_cache_filename = Some( + AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, _| { + Ok(PathBuf::from(directory) + .join("..") + .join("escape") + .to_string_lossy() + .into_owned()) + }), + ); + assert_eq!( + cache.has_asset(UUID::random().unwrap()), + Err(Error::Argument) + ); + fs::remove_dir_all(path).unwrap(); +} + +#[tokio::test] +async fn cache_pruning_reclaims_old_entries_to_below_the_limit() { + let (_client, cache, path) = fixture(12); + cache + .save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![1; 8]) + .unwrap(); + std::thread::sleep(std::time::Duration::from_millis(5)); + cache + .save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![2; 8]) + .unwrap(); + cache.prune(None).await.unwrap(); + let bytes: u64 = fs::read_dir(&path) + .unwrap() + .filter_map(Result::ok) + .filter_map(|entry| entry.metadata().ok()) + .map(|metadata| metadata.len()) + .sum(); + assert!(bytes <= 10); + fs::remove_dir_all(path).unwrap(); +} diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index 02c9d09..46417cf 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -120,6 +120,7 @@ struct ClientRuntime { agent_manager: Mutex>, inventory_manager: Mutex>>, inventory_ais_client: Mutex>, + asset_manager: Mutex>>, shutdown_complete: Condvar, shutdown_wait: Mutex<()>, } @@ -150,6 +151,7 @@ impl ClientRuntime { agent_manager: Mutex::new(std::sync::Weak::new()), inventory_manager: Mutex::new(None), inventory_ais_client: Mutex::new(None), + asset_manager: Mutex::new(None), shutdown_complete: Condvar::new(), shutdown_wait: Mutex::new(()), } @@ -447,6 +449,29 @@ impl GridClient { Ok(manager) } + pub(crate) fn native_assets(&self) -> Result { + let mut cached = self + .runtime + .asset_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return crate::asset_manager::AssetManager::native_from_inner(Arc::clone(inner)); + } + let manager = crate::asset_manager::AssetManager::new(Some(self.clone()))?; + *cached = Some(manager.native_inner()); + Ok(manager) + } + + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn native_set_assets(&mut self, value: crate::AssetManager) { + *self + .runtime + .asset_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner()); + } + pub(crate) fn native_ais_client(&self) -> Result { let mut cached = self .runtime diff --git a/crates/libremetaverse/src/download_manager.rs b/crates/libremetaverse/src/download_manager.rs index 691ce97..5eec9b0 100644 --- a/crates/libremetaverse/src/download_manager.rs +++ b/crates/libremetaverse/src/download_manager.rs @@ -5,8 +5,7 @@ use crate::{Error, GridClient, HttpCapsClientProgressReport}; use libremetaverse_types::compat::{ - CancellationToken, CancellationTokenSource, HttpResponse, IProgress, Subscription, - TaskCompletionSource, Uri, + CancellationToken, CancellationTokenSource, HttpResponse, IProgress, TaskCompletionSource, Uri, }; use std::collections::HashMap; use std::fmt; @@ -82,7 +81,6 @@ impl fmt::Debug for DownloadRequest { struct ActiveDownload { cancellation: CancellationTokenSource, - cancellation_guards: Mutex>, progress: Mutex>>>, completion_sources: Mutex>, waiters: Mutex>>, @@ -93,7 +91,6 @@ impl ActiveDownload { fn new() -> Self { Self { cancellation: CancellationTokenSource::new(), - cancellation_guards: Mutex::new(Vec::new()), progress: Mutex::new(Vec::new()), completion_sources: Mutex::new(Vec::new()), waiters: Mutex::new(Vec::new()), @@ -101,12 +98,6 @@ impl ActiveDownload { } } - fn attach_cancellation(&self, token: &CancellationToken) { - let cancellation = self.cancellation.clone(); - let guard = token.register_callback(Arc::new(move || cancellation.cancel())); - mutex(&self.cancellation_guards).push(guard); - } - fn attach_progress(&self, progress: Option>>) { if let Some(progress) = progress { mutex(&self.progress).push(Arc::from(progress)); @@ -146,7 +137,6 @@ impl ActiveDownload { } } mutex(&self.progress).clear(); - mutex(&self.cancellation_guards).clear(); } fn report(&self, report: HttpCapsClientProgressReport) { @@ -371,13 +361,8 @@ impl DownloadManager { request: DownloadRequest, ) -> DownloadResult { let cancellation = request.cancellation_token.clone(); - let (active, receiver) = self.enqueue(request, true)?; - await_download( - active, - receiver.ok_or(Error::InvalidOperation)?, - cancellation, - ) - .await + let (_, receiver) = self.enqueue(request, true)?; + await_download(receiver.ok_or(Error::InvalidOperation)?, cancellation).await } pub async fn queue_download_with_uri_string_i_progress_cancellation_token_int32( @@ -392,13 +377,8 @@ impl DownloadManager { let mut request = DownloadRequest::new(address, content_type, progress_callback)?; request.cancellation_token = cancellation.clone(); request.retries = retries.unwrap_or(5).max(0); - let (active, receiver) = self.enqueue(request, true)?; - await_download( - active, - receiver.ok_or(Error::InvalidOperation)?, - cancellation, - ) - .await + let (_, receiver) = self.enqueue(request, true)?; + await_download(receiver.ok_or(Error::InvalidOperation)?, cancellation).await } pub async fn download_with_uri_string_i_progress_cancellation_token( @@ -466,7 +446,6 @@ impl DownloadManager { } return Err(Error::Cancelled); }; - active.attach_cancellation(&request.cancellation_token); active.attach_progress(request.download_progress_callback.take()); active.attach_completion_source(request.completion_tcs.take()); let receiver = with_waiter.then(|| active.add_waiter()); @@ -507,14 +486,12 @@ impl fmt::Debug for DownloadManager { } async fn await_download( - active: Arc, receiver: oneshot::Receiver, cancellation: CancellationToken, ) -> DownloadResult { tokio::select! { result = receiver => result.map_err(|_| Error::Cancelled)?, () = cancellation.cancelled() => { - active.cancellation.cancel(); Err(Error::Cancelled) } } diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index ee32fa0..33df888 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -3567,185 +3567,31 @@ impl AppearanceSetEventArgs { pub use crate::genepool_catalog::ArchetypeParam; /// C# type: `T:LibreMetaverse.AssetCache`. -pub struct AssetCache { - /// C# member: `F:LibreMetaverse.AssetCache.ComputeAssetCacheFilename`. - pub compute_asset_cache_filename: - Option, -} -impl AssetCache { - /// C# member: `M:LibreMetaverse.AssetCache.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.AssetFileName(LibreMetaverse.UUID)`. - pub fn asset_file_name( - &self, - asset_id: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.AssetFileName(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.BeginPrune`. - pub fn begin_prune(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AssetCache.BeginPrune") - } - /// C# member: `M:LibreMetaverse.AssetCache.Clear`. - pub fn clear(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AssetCache.Clear") - } - /// C# member: `M:LibreMetaverse.AssetCache.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AssetCache.Dispose") - } - /// C# member: `M:LibreMetaverse.AssetCache.GetCachedAssetBytes(LibreMetaverse.UUID)`. - pub fn get_cached_asset_bytes_with_uuid( - &self, - asset_id: libremetaverse_types::UUID, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.GetCachedAssetBytes(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.GetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn get_cached_asset_bytes_with_uuid_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.GetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.GetCachedImage(LibreMetaverse.UUID)`. - pub fn get_cached_image( - &self, - image_id: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.GetCachedImage(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.HasAsset(LibreMetaverse.UUID)`. - pub fn has_asset(&self, asset_id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.HasAsset(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.PruneAsync(System.Threading.CancellationToken)`. - pub async fn prune( - &self, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.PruneAsync(System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.SaveAssetToCache(LibreMetaverse.UUID,System.Byte[])`. - pub fn save_asset_to_cache_with_uuid_bytes( - &self, - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.SaveAssetToCache(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.SaveAssetToCacheAsync(LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken)`. - pub async fn save_asset_to_cache_with_uuid_bytes_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.SaveAssetToCacheAsync(LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.TryGetCachedAssetBytes(LibreMetaverse.UUID,System.Byte[]@)`. - pub fn try_get_cached_asset_bytes_with_uuid_bytes( - &self, - asset_id: libremetaverse_types::UUID, - data: &mut Option>, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.AssetCache.TryGetCachedAssetBytes(LibreMetaverse.UUID,System.Byte[]@)" - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.TryGetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn try_get_cached_asset_bytes_with_uuid_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result<(bool, Option>), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.TryGetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `P:LibreMetaverse.AssetCache.AutoPruneEnabled`. - pub fn auto_prune_enabled(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AssetCache.AutoPruneEnabled") - } - /// Setter for C# member: `P:LibreMetaverse.AssetCache.AutoPruneEnabled`. - pub fn set_auto_prune_enabled(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AssetCache.AutoPruneEnabled") - } - /// C# member: `P:LibreMetaverse.AssetCache.AutoPruneInterval`. - pub fn auto_prune_interval(&self) -> f64 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AssetCache.AutoPruneInterval") - } - /// Setter for C# member: `P:LibreMetaverse.AssetCache.AutoPruneInterval`. - pub fn set_auto_prune_interval(&mut self, value: f64) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AssetCache.AutoPruneInterval") - } -} +/// C# member: `F:LibreMetaverse.AssetCache.ComputeAssetCacheFilename`. +/// C# member: `M:LibreMetaverse.AssetCache.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.AssetCache.AssetFileName(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetCache.BeginPrune`. +/// C# member: `M:LibreMetaverse.AssetCache.Clear`. +/// C# member: `M:LibreMetaverse.AssetCache.Dispose`. +/// C# member: `M:LibreMetaverse.AssetCache.GetCachedAssetBytes(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetCache.GetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetCache.GetCachedImage(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetCache.HasAsset(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetCache.PruneAsync(System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetCache.SaveAssetToCache(LibreMetaverse.UUID,System.Byte[])`. +/// C# member: `M:LibreMetaverse.AssetCache.SaveAssetToCacheAsync(LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetCache.TryGetCachedAssetBytes(LibreMetaverse.UUID,System.Byte[]@)`. +/// C# member: `M:LibreMetaverse.AssetCache.TryGetCachedAssetBytesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `P:LibreMetaverse.AssetCache.AutoPruneEnabled`. +/// C# member: `P:LibreMetaverse.AssetCache.AutoPruneInterval`. +pub use crate::asset_cache::AssetCache; /// C# type: `T:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate`. -pub struct AssetCacheComputeAssetCacheFilenameDelegate; -impl AssetCacheComputeAssetCacheFilenameDelegate { - /// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.#ctor(System.Object,System.IntPtr)`. - pub fn new( - object: libremetaverse_types::compat::Object, - method: isize, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.#ctor(System.Object,System.IntPtr)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.BeginInvoke(System.String,LibreMetaverse.UUID,System.AsyncCallback,System.Object)`. - pub fn begin_invoke( - &self, - cache_dir: String, - asset_id: libremetaverse_types::UUID, - callback: Box, - object: libremetaverse_types::compat::Object, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.BeginInvoke(System.String,LibreMetaverse.UUID,System.AsyncCallback,System.Object)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.EndInvoke(System.IAsyncResult)`. - pub fn end_invoke( - &self, - result: Box, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.EndInvoke(System.IAsyncResult)", - ) - } - /// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.Invoke(System.String,LibreMetaverse.UUID)`. - pub fn invoke( - &self, - cache_dir: String, - asset_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.Invoke(System.String,LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.#ctor(System.Object,System.IntPtr)`. +/// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.BeginInvoke(System.String,LibreMetaverse.UUID,System.AsyncCallback,System.Object)`. +/// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.EndInvoke(System.IAsyncResult)`. +/// C# member: `M:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate.Invoke(System.String,LibreMetaverse.UUID)`. +pub use crate::asset_cache::AssetCacheComputeAssetCacheFilenameDelegate; /// C# type: `T:LibreMetaverse.AssetCacheSettings`. #[derive(Clone, Debug, Eq, PartialEq)] @@ -3766,332 +3612,59 @@ impl AssetCacheSettings { } /// C# type: `T:LibreMetaverse.AssetDownload`. -pub struct AssetDownload { - /// C# member: `F:LibreMetaverse.AssetDownload.AssetID`. - pub asset_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.AssetDownload.Channel`. - pub channel: libremetaverse::ChannelType, - /// C# member: `F:LibreMetaverse.AssetDownload.Priority`. - pub priority: f32, - /// C# member: `F:LibreMetaverse.AssetDownload.Simulator`. - pub simulator: Option, - /// C# member: `F:LibreMetaverse.AssetDownload.Source`. - pub source: libremetaverse::SourceType, - /// C# member: `F:LibreMetaverse.AssetDownload.Status`. - pub status: libremetaverse::StatusCode, - /// C# member: `F:LibreMetaverse.AssetDownload.Target`. - pub target: libremetaverse::TargetType, - /// C# member: `F:LibreMetaverse.AssetDownload.nextPacket`. - pub next_packet: i32, - /// C# member: `F:LibreMetaverse.AssetDownload.outOfOrderPackets`. - pub out_of_order_packets: std::sync::RwLock>>, -} -impl AssetDownload { - /// C# member: `M:LibreMetaverse.AssetDownload.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AssetDownload.#ctor") - } -} +/// C# member: `F:LibreMetaverse.AssetDownload.AssetID`. +/// C# member: `F:LibreMetaverse.AssetDownload.Channel`. +/// C# member: `F:LibreMetaverse.AssetDownload.Priority`. +/// C# member: `F:LibreMetaverse.AssetDownload.Simulator`. +/// C# member: `F:LibreMetaverse.AssetDownload.Source`. +/// C# member: `F:LibreMetaverse.AssetDownload.Status`. +/// C# member: `F:LibreMetaverse.AssetDownload.Target`. +/// C# member: `F:LibreMetaverse.AssetDownload.nextPacket`. +/// C# member: `F:LibreMetaverse.AssetDownload.outOfOrderPackets`. +/// C# member: `M:LibreMetaverse.AssetDownload.#ctor`. +pub use crate::transfers::AssetDownload; /// C# type: `T:LibreMetaverse.AssetManager`. -pub struct AssetManager { - /// C# member: `F:LibreMetaverse.AssetManager.Cache`. - pub cache: libremetaverse::AssetCache, -} -impl AssetManager { - /// C# member: `E:LibreMetaverse.AssetManager.AssetUploaded`. - pub fn subscribe_asset_uploaded( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AssetManager.AssetUploaded") - } - /// C# member: `E:LibreMetaverse.AssetManager.ImageReceiveProgress`. - pub fn subscribe_image_receive_progress( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::ImageReceiveProgressEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AssetManager.ImageReceiveProgress" - ) - } - /// C# member: `E:LibreMetaverse.AssetManager.InitiateDownload`. - pub fn subscribe_initiate_download( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::InitiateDownloadEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AssetManager.InitiateDownload") - } - /// C# member: `E:LibreMetaverse.AssetManager.UploadProgress`. - pub fn subscribe_upload_progress( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AssetManager.UploadProgress") - } - /// C# member: `E:LibreMetaverse.AssetManager.XferReceived`. - pub fn subscribe_xfer_received( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AssetManager.XferReceived") - } - /// C# member: `M:LibreMetaverse.AssetManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: Option) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.AssetTypeToString(LibreMetaverse.AssetType)`. - pub fn asset_type_to_string( - &self, - asset_type: libremetaverse_types::AssetType, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.AssetTypeToString(LibreMetaverse.AssetType)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.CreateAssetWrapper(LibreMetaverse.AssetType)`. - pub fn create_asset_wrapper( - &self, - type_: libremetaverse_types::AssetType, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.CreateAssetWrapper(LibreMetaverse.AssetType)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.GetTextureUploadCost(System.Int32)`. - pub fn get_texture_upload_cost(&self, width_pixels: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.GetTextureUploadCost(System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,System.Threading.CancellationToken)`. - pub async fn request_asset_with_uuid_asset_type_boolean_source_type_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - type_: libremetaverse_types::AssetType, - priority: bool, - source_type: libremetaverse::SourceType, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,System.Threading.CancellationToken)`. - pub async fn request_asset_with_uuid_asset_type_boolean_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - type_: libremetaverse_types::AssetType, - priority: bool, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_asset_with_uuid_uuid_uuid_asset_type_boolean_source_type_uuid_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - item_id: libremetaverse_types::UUID, - task_id: libremetaverse_types::UUID, - asset_type: libremetaverse_types::AssetType, - priority: bool, - source_type: libremetaverse::SourceType, - transaction_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestAssetXfer(System.String,System.Boolean,System.Boolean,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean)`. - pub fn request_asset_xfer( - &self, - filename: String, - delete_on_completion: bool, - use_big_packets: bool, - v_file_id: libremetaverse_types::UUID, - v_file_type: libremetaverse_types::AssetType, - from_cache: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestAssetXfer(System.String,System.Boolean,System.Boolean,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestEstateAsset(LibreMetaverse.AssetDownload,LibreMetaverse.EstateAssetType)`. - pub fn request_estate_asset( - &self, - transfer: libremetaverse::AssetDownload, - eat: libremetaverse::EstateAssetType, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestEstateAsset(LibreMetaverse.AssetDownload,LibreMetaverse.EstateAssetType)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestImageAsync(LibreMetaverse.UUID,LibreMetaverse.ImageType,System.Threading.CancellationToken)`. - pub async fn request_image( - &self, - texture_id: libremetaverse_types::UUID, - image_type: Option, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestImageAsync(LibreMetaverse.UUID,LibreMetaverse.ImageType,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestImageCancel(LibreMetaverse.UUID)`. - pub fn request_image_cancel( - &self, - texture_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestImageCancel(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_inventory_asset_with_inventory_item_boolean_uuid_cancellation_token( - &self, - item: libremetaverse::InventoryItem, - priority: bool, - transfer_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_inventory_asset_with_uuid_uuid_uuid_uuid_asset_type_boolean_uuid_cancellation_token( - &self, - asset_id: libremetaverse_types::UUID, - item_id: libremetaverse_types::UUID, - task_id: libremetaverse_types::UUID, - owner_id: libremetaverse_types::UUID, - asset_type: libremetaverse_types::AssetType, - priority: bool, - transfer_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestMeshAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_mesh( - &self, - mesh_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestMeshAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestServerBakedImageAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. - pub async fn request_server_baked_image( - &self, - avatar_id: libremetaverse_types::UUID, - texture_id: libremetaverse_types::UUID, - bake_name: String, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestServerBakedImageAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestUpload(LibreMetaverse.UUID@,LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID)`. - pub fn request_upload_with_uuid_asset_type_bytes_boolean_uuid( - &self, - asset_id: &mut libremetaverse_types::UUID, - type_: libremetaverse_types::AssetType, - data: Vec, - store_local: bool, - transaction_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestUpload(LibreMetaverse.UUID@,LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestUploadAsync(LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_upload_with_asset_type_bytes_boolean_uuid_cancellation_token( - &self, - type_: libremetaverse_types::AssetType, - data: Vec, - store_local: bool, - transaction_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestUploadAsync(LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestUploadBakedTextureAsync(System.Byte[],System.Threading.CancellationToken)`. - pub async fn request_upload_baked_texture( - &self, - texture_data: Vec, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestUploadBakedTextureAsync(System.Byte[],System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.AssetManager.RequestUploadLargeTextureAsync(System.Byte[],System.Int32,System.Threading.CancellationToken)`. - pub async fn request_upload_large_texture( - &self, - data: Vec, - width_pixels: i32, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetManager.RequestUploadLargeTextureAsync(System.Byte[],System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `P:LibreMetaverse.AssetManager.HttpDownloadManager`. - pub fn http_download_manager(&self) -> libremetaverse::http::DownloadManager { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AssetManager.HttpDownloadManager" - ) - } -} +/// C# member: `E:LibreMetaverse.AssetManager.AssetUploaded`. +/// C# member: `E:LibreMetaverse.AssetManager.ImageReceiveProgress`. +/// C# member: `E:LibreMetaverse.AssetManager.InitiateDownload`. +/// C# member: `E:LibreMetaverse.AssetManager.UploadProgress`. +/// C# member: `E:LibreMetaverse.AssetManager.XferReceived`. +/// C# member: `F:LibreMetaverse.AssetManager.Cache`. +/// C# member: `M:LibreMetaverse.AssetManager.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.AssetManager.AssetTypeToString(LibreMetaverse.AssetType)`. +/// C# member: `M:LibreMetaverse.AssetManager.CreateAssetWrapper(LibreMetaverse.AssetType)`. +/// C# member: `M:LibreMetaverse.AssetManager.GetTextureUploadCost(System.Int32)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.SourceType,LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestAssetXfer(System.String,System.Boolean,System.Boolean,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestEstateAsset(LibreMetaverse.AssetDownload,LibreMetaverse.EstateAssetType)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestImageAsync(LibreMetaverse.UUID,LibreMetaverse.ImageType,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestImageCancel(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestInventoryAssetAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.AssetType,System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestMeshAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestServerBakedImageAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestUpload(LibreMetaverse.UUID@,LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestUploadAsync(LibreMetaverse.AssetType,System.Byte[],System.Boolean,LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestUploadBakedTextureAsync(System.Byte[],System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.AssetManager.RequestUploadLargeTextureAsync(System.Byte[],System.Int32,System.Threading.CancellationToken)`. +/// C# member: `P:LibreMetaverse.AssetManager.HttpDownloadManager`. +pub use crate::asset_manager::AssetManager; /// C# type: `T:LibreMetaverse.AssetUpload`. -pub struct AssetUpload { - /// C# member: `F:LibreMetaverse.AssetUpload.AssetID`. - pub asset_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.AssetUpload.PacketNum`. - pub packet_num: u32, - /// C# member: `F:LibreMetaverse.AssetUpload.Type`. - pub type_: libremetaverse_types::AssetType, - /// C# member: `F:LibreMetaverse.AssetUpload.XferID`. - pub xfer_id: u64, -} -impl AssetUpload { - /// C# member: `M:LibreMetaverse.AssetUpload.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AssetUpload.#ctor") - } -} +/// C# member: `F:LibreMetaverse.AssetUpload.AssetID`. +/// C# member: `F:LibreMetaverse.AssetUpload.PacketNum`. +/// C# member: `F:LibreMetaverse.AssetUpload.Type`. +/// C# member: `F:LibreMetaverse.AssetUpload.XferID`. +/// C# member: `M:LibreMetaverse.AssetUpload.#ctor`. +pub use crate::transfers::AssetUpload; /// C# type: `T:LibreMetaverse.AssetUploadEventArgs`. -pub struct AssetUploadEventArgs; -impl AssetUploadEventArgs { - /// C# member: `M:LibreMetaverse.AssetUploadEventArgs.#ctor(LibreMetaverse.AssetUpload)`. - pub fn new(upload: libremetaverse::AssetUpload) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AssetUploadEventArgs.#ctor(LibreMetaverse.AssetUpload)", - ) - } - /// C# member: `P:LibreMetaverse.AssetUploadEventArgs.Upload`. - pub fn upload(&self) -> libremetaverse::AssetUpload { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AssetUploadEventArgs.Upload") - } -} +/// C# member: `M:LibreMetaverse.AssetUploadEventArgs.#ctor(LibreMetaverse.AssetUpload)`. +/// C# member: `P:LibreMetaverse.AssetUploadEventArgs.Upload`. +pub use crate::transfers::AssetUploadEventArgs; /// C# type: `T:LibreMetaverse.AttachedSoundEventArgs`. pub struct AttachedSoundEventArgs; @@ -9101,45 +8674,12 @@ impl FriendshipTerminatedEventArgs { } /// C# type: `T:LibreMetaverse.GLTFMaterialUpdate`. -pub struct GLTFMaterialUpdate; -impl GLTFMaterialUpdate { - /// C# member: `M:LibreMetaverse.GLTFMaterialUpdate.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.GLTFMaterialUpdate.#ctor") - } - /// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.AssetId`. - pub fn asset_id(&self) -> Option> { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.AssetId") - } - /// Setter for C# member: `P:LibreMetaverse.GLTFMaterialUpdate.AssetId`. - pub fn set_asset_id(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.AssetId") - } - /// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.ObjectId`. - pub fn object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.ObjectId") - } - /// Setter for C# member: `P:LibreMetaverse.GLTFMaterialUpdate.ObjectId`. - pub fn set_object_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.ObjectId") - } - /// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Override`. - pub fn override_(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.Override") - } - /// Setter for C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Override`. - pub fn set_override_(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.Override") - } - /// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Side`. - pub fn side(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.Side") - } - /// Setter for C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Side`. - pub fn set_side(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GLTFMaterialUpdate.Side") - } -} +/// C# member: `M:LibreMetaverse.GLTFMaterialUpdate.#ctor`. +/// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.AssetId`. +/// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.ObjectId`. +/// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Override`. +/// C# member: `P:LibreMetaverse.GLTFMaterialUpdate.Side`. +pub use crate::object_material::GLTFMaterialUpdate; /// C# type: `T:LibreMetaverse.Genepool`. /// C# member: `F:LibreMetaverse.Genepool.Archetypes`. @@ -9254,11 +8794,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Assets`. pub fn assets(&self) -> libremetaverse::AssetManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Assets") + /* native client-core implementation */ + self.native_assets() + .unwrap_or_else(|_| panic!("failed to construct AssetManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Assets`. pub fn set_assets(&mut self, value: libremetaverse::AssetManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Assets") + /* native client-core implementation */ + self.native_set_assets(value) } /// C# member: `P:LibreMetaverse.GridClient.Avatars`. pub fn avatars(&self) -> libremetaverse::AvatarManager { @@ -9376,11 +8919,16 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Objects`. pub fn objects(&self) -> libremetaverse::ObjectManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Objects") + /* native client-core implementation */ + crate::object_material::ObjectManager::native_new(Some(std::sync::Arc::new(self.clone()))) + .unwrap_or_else(|_| panic!("failed to construct ObjectManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Objects`. pub fn set_objects(&mut self, value: libremetaverse::ObjectManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Objects") + /* native client-core implementation */ + { + let _ = value; + } } /// C# member: `P:LibreMetaverse.GridClient.Parcels`. pub fn parcels(&self) -> libremetaverse::ParcelManager { @@ -11425,61 +10973,22 @@ pub enum ImageCodec { } /// C# type: `T:LibreMetaverse.ImageDownload`. -pub struct ImageDownload { - /// C# member: `F:LibreMetaverse.ImageDownload.Codec`. - pub codec: libremetaverse::ImageCodec, - /// C# member: `F:LibreMetaverse.ImageDownload.DiscardLevel`. - pub discard_level: i32, - /// C# member: `F:LibreMetaverse.ImageDownload.ImageType`. - pub image_type: libremetaverse::ImageType, - /// C# member: `F:LibreMetaverse.ImageDownload.PacketCount`. - pub packet_count: u16, - /// C# member: `F:LibreMetaverse.ImageDownload.PacketsSeen`. - pub packets_seen: libremetaverse_types::compat::SortedList, - /// C# member: `F:LibreMetaverse.ImageDownload.Priority`. - pub priority: f32, - /// C# member: `F:LibreMetaverse.ImageDownload.Simulator`. - pub simulator: Option, -} -impl ImageDownload { - /// C# member: `M:LibreMetaverse.ImageDownload.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.ImageDownload.#ctor") - } -} +/// C# member: `F:LibreMetaverse.ImageDownload.Codec`. +/// C# member: `F:LibreMetaverse.ImageDownload.DiscardLevel`. +/// C# member: `F:LibreMetaverse.ImageDownload.ImageType`. +/// C# member: `F:LibreMetaverse.ImageDownload.PacketCount`. +/// C# member: `F:LibreMetaverse.ImageDownload.PacketsSeen`. +/// C# member: `F:LibreMetaverse.ImageDownload.Priority`. +/// C# member: `F:LibreMetaverse.ImageDownload.Simulator`. +/// C# member: `M:LibreMetaverse.ImageDownload.#ctor`. +pub use crate::transfers::ImageDownload; /// C# type: `T:LibreMetaverse.ImageReceiveProgressEventArgs`. -pub struct ImageReceiveProgressEventArgs; -impl ImageReceiveProgressEventArgs { - /// C# member: `M:LibreMetaverse.ImageReceiveProgressEventArgs.#ctor(LibreMetaverse.UUID,System.Int32,System.Int32)`. - pub fn new( - image_id: libremetaverse_types::UUID, - received: i32, - total: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImageReceiveProgressEventArgs.#ctor(LibreMetaverse.UUID,System.Int32,System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.ImageID`. - pub fn image_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ImageReceiveProgressEventArgs.ImageID" - ) - } - /// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.Received`. - pub fn received(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ImageReceiveProgressEventArgs.Received" - ) - } - /// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.Total`. - pub fn total(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ImageReceiveProgressEventArgs.Total" - ) - } -} +/// C# member: `M:LibreMetaverse.ImageReceiveProgressEventArgs.#ctor(LibreMetaverse.UUID,System.Int32,System.Int32)`. +/// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.ImageID`. +/// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.Received`. +/// C# member: `P:LibreMetaverse.ImageReceiveProgressEventArgs.Total`. +pub use crate::transfers::ImageReceiveProgressEventArgs; /// C# type: `T:LibreMetaverse.ImageRequest`. pub struct ImageRequest { @@ -11524,27 +11033,10 @@ pub enum ImageType { pub use crate::udp_transport::IncomingPacketIDCollection; /// C# type: `T:LibreMetaverse.InitiateDownloadEventArgs`. -pub struct InitiateDownloadEventArgs; -impl InitiateDownloadEventArgs { - /// C# member: `M:LibreMetaverse.InitiateDownloadEventArgs.#ctor(System.String,System.String)`. - pub fn new(sim_filename: String, viewer_filename: String) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InitiateDownloadEventArgs.#ctor(System.String,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.InitiateDownloadEventArgs.SimFileName`. - pub fn sim_file_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InitiateDownloadEventArgs.SimFileName" - ) - } - /// C# member: `P:LibreMetaverse.InitiateDownloadEventArgs.ViewerFileName`. - pub fn viewer_file_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InitiateDownloadEventArgs.ViewerFileName" - ) - } -} +/// C# member: `M:LibreMetaverse.InitiateDownloadEventArgs.#ctor(System.String,System.String)`. +/// C# member: `P:LibreMetaverse.InitiateDownloadEventArgs.SimFileName`. +/// C# member: `P:LibreMetaverse.InitiateDownloadEventArgs.ViewerFileName`. +pub use crate::transfers::InitiateDownloadEventArgs; /// C# type: `T:LibreMetaverse.InstantMessage`. /// C# member: `F:LibreMetaverse.InstantMessage.BinaryBucket`. @@ -13340,9 +12832,14 @@ impl InventoryManager { ), crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestUpdateMaterialAgentInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})", + self.native_update_material_inventory( + material, + material_item_id, + None, + cancellation_token, + progress, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateMaterialTaskInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn request_update_material_task_inventory( @@ -13367,9 +12864,14 @@ impl InventoryManager { ), crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestUpdateMaterialTaskInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})", + self.native_update_material_inventory( + material, + material_item_id, + Some(task_id), + cancellation_token, + progress, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateNotecardTaskAsync(System.Byte[],LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn request_update_notecard_task( @@ -16111,7 +15613,7 @@ impl ObjectDataBlockUpdateEventArgs { } /// C# type: `T:LibreMetaverse.ObjectManager`. -pub struct ObjectManager; +pub use crate::object_material::ObjectManager; impl ObjectManager { /// C# member: `E:LibreMetaverse.ObjectManager.AvatarSitChanged`. pub fn subscribe_avatar_sit_changed( @@ -16241,9 +15743,7 @@ impl ObjectManager { pub fn new( client: Option>, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ObjectManager.#ctor(LibreMetaverse.GridClient)", - ) + crate::object_material::ObjectManager::native_new(client) } /// C# member: `M:LibreMetaverse.ObjectManager.AddGrass(LibreMetaverse.Simulator,LibreMetaverse.Vector3,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.Grass,LibreMetaverse.UUID)`. pub fn add_grass( @@ -16313,9 +15813,15 @@ impl ObjectManager { override_material: Option, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ObjectManager.ApplyMaterialAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,LibreMetaverse.UUID,LibreMetaverse.Assets.AssetMaterial,System.Threading.CancellationToken)", + self.native_apply_material( + sim, + object_id, + side, + material_asset_id, + override_material, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.ObjectManager.AttachObject(LibreMetaverse.Simulator,System.UInt32,LibreMetaverse.AttachmentPoint,LibreMetaverse.Quaternion)`. pub fn attach_object( @@ -16365,9 +15871,8 @@ impl ObjectManager { side: i32, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ObjectManager.ClearMaterialOverrideAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,System.Threading.CancellationToken)", - ) + self.native_clear_material_override(sim, object_id, side, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.ObjectManager.ClickObjectAsync(LibreMetaverse.Simulator,System.UInt32,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Int32,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)`. pub async fn click_object_with_simulator_u_int32_vector3_vector3_int32_vector3_vector3_vector3_cancellation_token( @@ -16663,9 +16168,8 @@ impl ObjectManager { updates: Box>, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ObjectManager.SendMaterialUpdatesAsync(LibreMetaverse.Simulator,System.Collections.Generic.IEnumerable{LibreMetaverse.GLTFMaterialUpdate},System.Threading.CancellationToken)", - ) + self.native_send_material_updates(sim, updates, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.ObjectManager.SetDescription(LibreMetaverse.Simulator,System.UInt32,System.String)`. pub fn set_description( @@ -16775,9 +16279,14 @@ impl ObjectManager { override_material: libremetaverse::assets::AssetMaterial, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ObjectManager.SetMaterialOverrideAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Assets.AssetMaterial,System.Threading.CancellationToken)", + self.native_set_material_override( + sim, + object_id, + side, + override_material, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.ObjectManager.SetName(LibreMetaverse.Simulator,System.UInt32,System.String)`. pub fn set_name( @@ -22876,34 +22385,15 @@ impl TransactionInfo { } /// C# type: `T:LibreMetaverse.Transfer`. -pub struct Transfer { - /// C# member: `F:LibreMetaverse.Transfer.AssetData`. - pub asset_data: Vec, - /// C# member: `F:LibreMetaverse.Transfer.AssetType`. - pub asset_type: libremetaverse_types::AssetType, - /// C# member: `F:LibreMetaverse.Transfer.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Transfer.Size`. - pub size: i32, - /// C# member: `F:LibreMetaverse.Transfer.Success`. - pub success: bool, - /// C# member: `F:LibreMetaverse.Transfer.Transferred`. - pub transferred: i32, -} -impl Transfer { - /// C# member: `M:LibreMetaverse.Transfer.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Transfer.#ctor") - } - /// C# member: `P:LibreMetaverse.Transfer.TimeSinceLastPacket`. - pub fn time_since_last_packet(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Transfer.TimeSinceLastPacket") - } - /// Setter for C# member: `P:LibreMetaverse.Transfer.TimeSinceLastPacket`. - pub fn set_time_since_last_packet(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Transfer.TimeSinceLastPacket") - } -} +/// C# member: `F:LibreMetaverse.Transfer.AssetData`. +/// C# member: `F:LibreMetaverse.Transfer.AssetType`. +/// C# member: `F:LibreMetaverse.Transfer.ID`. +/// C# member: `F:LibreMetaverse.Transfer.Size`. +/// C# member: `F:LibreMetaverse.Transfer.Success`. +/// C# member: `F:LibreMetaverse.Transfer.Transferred`. +/// C# member: `M:LibreMetaverse.Transfer.#ctor`. +/// C# member: `P:LibreMetaverse.Transfer.TimeSinceLastPacket`. +pub use crate::transfers::Transfer; /// C# type: `T:LibreMetaverse.TransferError`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -23344,39 +22834,18 @@ impl WorldSettings { } /// C# type: `T:LibreMetaverse.XferDownload`. -pub struct XferDownload { - /// C# member: `F:LibreMetaverse.XferDownload.Error`. - pub error: libremetaverse::TransferError, - /// C# member: `F:LibreMetaverse.XferDownload.Filename`. - pub filename: String, - /// C# member: `F:LibreMetaverse.XferDownload.PacketNum`. - pub packet_num: u32, - /// C# member: `F:LibreMetaverse.XferDownload.VFileID`. - pub v_file_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.XferDownload.XferID`. - pub xfer_id: u64, -} -impl XferDownload { - /// C# member: `M:LibreMetaverse.XferDownload.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.XferDownload.#ctor") - } -} +/// C# member: `F:LibreMetaverse.XferDownload.Error`. +/// C# member: `F:LibreMetaverse.XferDownload.Filename`. +/// C# member: `F:LibreMetaverse.XferDownload.PacketNum`. +/// C# member: `F:LibreMetaverse.XferDownload.VFileID`. +/// C# member: `F:LibreMetaverse.XferDownload.XferID`. +/// C# member: `M:LibreMetaverse.XferDownload.#ctor`. +pub use crate::transfers::XferDownload; /// C# type: `T:LibreMetaverse.XferReceivedEventArgs`. -pub struct XferReceivedEventArgs; -impl XferReceivedEventArgs { - /// C# member: `M:LibreMetaverse.XferReceivedEventArgs.#ctor(LibreMetaverse.XferDownload)`. - pub fn new(xfer: libremetaverse::XferDownload) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.XferReceivedEventArgs.#ctor(LibreMetaverse.XferDownload)", - ) - } - /// C# member: `P:LibreMetaverse.XferReceivedEventArgs.Xfer`. - pub fn xfer(&self) -> libremetaverse::XferDownload { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.XferReceivedEventArgs.Xfer") - } -} +/// C# member: `M:LibreMetaverse.XferReceivedEventArgs.#ctor(LibreMetaverse.XferDownload)`. +/// C# member: `P:LibreMetaverse.XferReceivedEventArgs.Xfer`. +pub use crate::transfers::XferReceivedEventArgs; /// C# type: `T:LibreMetaverse.binBVHJoint`. pub struct BinBVHJoint { @@ -24149,155 +23618,43 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Asset`. - pub struct Asset { - /// C# member: `F:LibreMetaverse.Assets.Asset.AssetData`. - pub asset_data: Vec, - /// C# member: `F:LibreMetaverse.Assets.Asset.Temporary`. - pub temporary: bool, - } - impl Asset { - /// C# member: `M:LibreMetaverse.Assets.Asset.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.Asset.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.Asset.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.Asset.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.Asset.AssetID`. - pub fn asset_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.Asset.AssetID") - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Asset.AssetID`. - pub fn set_asset_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.Asset.AssetID") - } - /// C# member: `P:LibreMetaverse.Assets.Asset.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.Asset.AssetType") - } - } + /// C# member: `F:LibreMetaverse.Assets.Asset.AssetData`. + /// C# member: `F:LibreMetaverse.Assets.Asset.Temporary`. + /// C# member: `M:LibreMetaverse.Assets.Asset.Decode`. + /// C# member: `M:LibreMetaverse.Assets.Asset.Encode`. + /// C# member: `P:LibreMetaverse.Assets.Asset.AssetID`. + /// C# member: `P:LibreMetaverse.Assets.Asset.AssetType`. + pub use crate::asset_models::Asset; /// C# type: `T:LibreMetaverse.Assets.AssetAnimation`. - pub struct AssetAnimation; - impl AssetAnimation { - /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetAnimation.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetAnimation.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetAnimation.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetAnimation.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetAnimation.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetAnimation.AssetType" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetAnimation.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetAnimation.AssetType`. + pub use crate::asset_models::AssetAnimation; /// C# type: `T:LibreMetaverse.Assets.AssetBodypart`. - pub struct AssetBodypart; - impl AssetBodypart { - /// C# member: `M:LibreMetaverse.Assets.AssetBodypart.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetBodypart.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetBodypart.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetBodypart.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetBodypart.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetBodypart.AssetType" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.AssetBodypart.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetBodypart.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `P:LibreMetaverse.Assets.AssetBodypart.AssetType`. + pub use crate::asset_models::AssetBodypart; /// C# type: `T:LibreMetaverse.Assets.AssetCallingCard`. - pub struct AssetCallingCard { - /// C# member: `F:LibreMetaverse.Assets.AssetCallingCard.AvatarID`. - pub avatar_id: libremetaverse_types::UUID, - } - impl AssetCallingCard { - /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetCallingCard.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID)`. - pub fn new_with_uuid(avatar_id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetCallingCard.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetCallingCard.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetCallingCard.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetCallingCard.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetCallingCard.AvatarID`. + /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID)`. + /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetCallingCard.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetCallingCard.AssetType`. + pub use crate::asset_models::AssetCallingCard; /// C# type: `T:LibreMetaverse.Assets.AssetClothing`. - pub struct AssetClothing; - impl AssetClothing { - /// C# member: `M:LibreMetaverse.Assets.AssetClothing.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetClothing.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetClothing.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetClothing.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetClothing.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetClothing.AssetType" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.AssetClothing.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetClothing.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `P:LibreMetaverse.Assets.AssetClothing.AssetType`. + pub use crate::asset_models::AssetClothing; /// C# type: `T:LibreMetaverse.Assets.AssetGesture`. /// C# member: `F:LibreMetaverse.Assets.AssetGesture.ReplaceWith`. @@ -24313,494 +23670,85 @@ pub mod assets { pub use crate::gesture::AssetGesture; /// C# type: `T:LibreMetaverse.Assets.AssetLandmark`. - pub struct AssetLandmark { - /// C# member: `F:LibreMetaverse.Assets.AssetLandmark.Position`. - pub position: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.Assets.AssetLandmark.RegionID`. - pub region_id: libremetaverse_types::UUID, - } - impl AssetLandmark { - /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetLandmark.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetLandmark.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetLandmark.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetLandmark.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetLandmark.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetLandmark.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetLandmark.Position`. + /// C# member: `F:LibreMetaverse.Assets.AssetLandmark.RegionID`. + /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetLandmark.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetLandmark.AssetType`. + pub use crate::asset_models::AssetLandmark; /// C# type: `T:LibreMetaverse.Assets.AssetMaterial`. - pub struct AssetMaterial; - impl AssetMaterial { - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.Default`. - pub fn default() -> libremetaverse::assets::AssetMaterial { - libremetaverse_types::unimplemented_api!( - "F:LibreMetaverse.Assets.AssetMaterial.Default" - ) - } - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.GLTF_OVERRIDE_NULL_UUID`. - pub fn gltf_override_null_uuid() -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "F:LibreMetaverse.Assets.AssetMaterial.GLTF_OVERRIDE_NULL_UUID" - ) - } - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_BASE_COLOR`. - pub const TEXTURE_BASE_COLOR: i32 = 0; - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_COUNT`. - pub const TEXTURE_COUNT: i32 = 4; - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_EMISSIVE`. - pub const TEXTURE_EMISSIVE: i32 = 3; - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_METALLIC_ROUGHNESS`. - pub const TEXTURE_METALLIC_ROUGHNESS: i32 = 2; - /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_NORMAL`. - pub const TEXTURE_NORMAL: i32 = 1; - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMaterial.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ApplyOverride(LibreMetaverse.Assets.AssetMaterial)`. - pub fn apply_override( - &mut self, - override_mat: libremetaverse::assets::AssetMaterial, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.ApplyOverride(LibreMetaverse.Assets.AssetMaterial)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.Decode`. - pub fn decode(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMaterial.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.Encode`. - pub fn encode(&mut self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMaterial.Encode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.FromOverrideOsd(LibreMetaverse.StructuredData.OSDMap)`. - pub fn from_override_osd( - data: libremetaverse_structured_data::OSDMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.FromOverrideOsd(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetAlphaCutoff(System.Single,System.Boolean)`. - pub fn set_alpha_cutoff_with_single_boolean( - &mut self, - cutoff: f32, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetAlphaCutoff(System.Single,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetAlphaMode(LibreMetaverse.Assets.GltfAlphaMode,System.Boolean)`. - pub fn set_alpha_mode_with_gltf_alpha_mode_boolean( - &mut self, - mode: libremetaverse::assets::GltfAlphaMode, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetAlphaMode(LibreMetaverse.Assets.GltfAlphaMode,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetBaseColorFactor(LibreMetaverse.Color4,System.Boolean)`. - pub fn set_base_color_factor_with_color4_boolean( - &mut self, - color: libremetaverse_types::Color4, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetBaseColorFactor(LibreMetaverse.Color4,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetBaseMaterial`. - pub fn set_base_material(&mut self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetBaseMaterial", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetDoubleSided(System.Boolean,System.Boolean)`. - pub fn set_double_sided_with_boolean_boolean( - &mut self, - double_sided: bool, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetDoubleSided(System.Boolean,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetEmissiveFactor(LibreMetaverse.Vector3,System.Boolean)`. - pub fn set_emissive_factor_with_vector3_boolean( - &mut self, - color: libremetaverse_types::Vector3, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetEmissiveFactor(LibreMetaverse.Vector3,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetMetallicFactor(System.Single,System.Boolean)`. - pub fn set_metallic_factor_with_single_boolean( - &mut self, - metallic: f32, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetMetallicFactor(System.Single,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetRoughnessFactor(System.Single,System.Boolean)`. - pub fn set_roughness_factor_with_single_boolean( - &mut self, - roughness: f32, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetRoughnessFactor(System.Single,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureId(System.Int32,LibreMetaverse.UUID,System.Boolean)`. - pub fn set_texture_id( - &mut self, - slot: i32, - id: libremetaverse_types::UUID, - for_override: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetTextureId(System.Int32,LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureOffset(System.Int32,LibreMetaverse.Vector2)`. - pub fn set_texture_offset( - &mut self, - slot: i32, - offset: libremetaverse_types::Vector2, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetTextureOffset(System.Int32,LibreMetaverse.Vector2)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureRotation(System.Int32,System.Single)`. - pub fn set_texture_rotation( - &mut self, - slot: i32, - rotation: f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetTextureRotation(System.Int32,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureScale(System.Int32,LibreMetaverse.Vector2)`. - pub fn set_texture_scale( - &mut self, - slot: i32, - scale: libremetaverse_types::Vector2, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.SetTextureScale(System.Int32,LibreMetaverse.Vector2)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ToJson`. - pub fn to_json(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMaterial.ToJson") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ToOverrideOsd`. - pub fn to_override_osd( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMaterial.ToOverrideOsd", - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaCutoff`. - pub fn alpha_cutoff(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.AlphaCutoff" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaCutoff`. - pub fn set_alpha_cutoff(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.AlphaCutoff" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaMode`. - pub fn alpha_mode(&self) -> libremetaverse::assets::GltfAlphaMode { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.AlphaMode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaMode`. - pub fn set_alpha_mode(&mut self, value: libremetaverse::assets::GltfAlphaMode) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.AlphaMode" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.AssetType" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.BaseColorFactor`. - pub fn base_color_factor(&self) -> libremetaverse_types::Color4 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.BaseColorFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.BaseColorFactor`. - pub fn set_base_color_factor(&mut self, value: libremetaverse_types::Color4) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.BaseColorFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.DoubleSided`. - pub fn double_sided(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.DoubleSided" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.DoubleSided`. - pub fn set_double_sided(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.DoubleSided" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.EmissiveFactor`. - pub fn emissive_factor(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.EmissiveFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.EmissiveFactor`. - pub fn set_emissive_factor(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.EmissiveFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.MetallicFactor`. - pub fn metallic_factor(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.MetallicFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.MetallicFactor`. - pub fn set_metallic_factor(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.MetallicFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.Name`. - pub fn name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.AssetMaterial.Name") - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.Name`. - pub fn set_name(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.AssetMaterial.Name") - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideAlphaMode`. - pub fn override_alpha_mode(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.OverrideAlphaMode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideAlphaMode`. - pub fn set_override_alpha_mode(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.OverrideAlphaMode" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideDoubleSided`. - pub fn override_double_sided(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.OverrideDoubleSided" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideDoubleSided`. - pub fn set_override_double_sided(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.OverrideDoubleSided" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.RoughnessFactor`. - pub fn roughness_factor(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.RoughnessFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.RoughnessFactor`. - pub fn set_roughness_factor(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.RoughnessFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureIds`. - pub fn texture_ids(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.TextureIds" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureIds`. - pub fn set_texture_ids(&mut self, value: Vec) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.TextureIds" - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureTransforms`. - pub fn texture_transforms(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.TextureTransforms" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureTransforms`. - pub fn set_texture_transforms( - &mut self, - value: Vec, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMaterial.TextureTransforms" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.Default`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.GLTF_OVERRIDE_NULL_UUID`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_BASE_COLOR`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_COUNT`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_EMISSIVE`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_METALLIC_ROUGHNESS`. + /// C# member: `F:LibreMetaverse.Assets.AssetMaterial.TEXTURE_NORMAL`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ApplyOverride(LibreMetaverse.Assets.AssetMaterial)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.Encode`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.FromOverrideOsd(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetAlphaCutoff(System.Single,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetAlphaMode(LibreMetaverse.Assets.GltfAlphaMode,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetBaseColorFactor(LibreMetaverse.Color4,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetBaseMaterial`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetDoubleSided(System.Boolean,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetEmissiveFactor(LibreMetaverse.Vector3,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetMetallicFactor(System.Single,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetRoughnessFactor(System.Single,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureId(System.Int32,LibreMetaverse.UUID,System.Boolean)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureOffset(System.Int32,LibreMetaverse.Vector2)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureRotation(System.Int32,System.Single)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.SetTextureScale(System.Int32,LibreMetaverse.Vector2)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ToJson`. + /// C# member: `M:LibreMetaverse.Assets.AssetMaterial.ToOverrideOsd`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaCutoff`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AlphaMode`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.AssetType`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.BaseColorFactor`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.DoubleSided`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.EmissiveFactor`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.MetallicFactor`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.Name`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideAlphaMode`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.OverrideDoubleSided`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.RoughnessFactor`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureIds`. + /// C# member: `P:LibreMetaverse.Assets.AssetMaterial.TextureTransforms`. + pub use crate::asset_material::AssetMaterial; /// C# type: `T:LibreMetaverse.Assets.AssetMesh`. - pub struct AssetMesh { - /// C# member: `F:LibreMetaverse.Assets.AssetMesh.MeshData`. - pub mesh_data: libremetaverse_structured_data::OSDMap, - } - impl AssetMesh { - /// C# member: `M:LibreMetaverse.Assets.AssetMesh.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMesh.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMesh.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMesh.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMesh.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMesh.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMesh.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMesh.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetMesh.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.AssetMesh.AssetType") - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetMesh.MeshData`. + /// C# member: `M:LibreMetaverse.Assets.AssetMesh.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetMesh.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetMesh.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetMesh.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetMesh.AssetType`. + pub use crate::asset_models::AssetMesh; /// C# type: `T:LibreMetaverse.Assets.AssetMutable`. - pub struct AssetMutable { - /// C# member: `F:LibreMetaverse.Assets.AssetMutable.currentType`. - pub current_type: libremetaverse_types::AssetType, - } - impl AssetMutable { - /// C# member: `M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType)`. - pub fn new_with_asset_type( - type_: libremetaverse_types::AssetType, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_asset_type_uuid_bytes( - type_: libremetaverse_types::AssetType, - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetMutable.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMutable.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetMutable.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetMutable.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetMutable.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetMutable.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetMutable.currentType`. + /// C# member: `M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType)`. + /// C# member: `M:LibreMetaverse.Assets.AssetMutable.#ctor(LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetMutable.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetMutable.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetMutable.AssetType`. + pub use crate::asset_models::AssetMutable; /// C# type: `T:LibreMetaverse.Assets.AssetNotecard`. - pub struct AssetNotecard { - /// C# member: `F:LibreMetaverse.Assets.AssetNotecard.BodyText`. - pub body_text: String, - /// C# member: `F:LibreMetaverse.Assets.AssetNotecard.EmbeddedItems`. - pub embedded_items: Vec, - } - impl AssetNotecard { - /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetNotecard.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetNotecard.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetNotecard.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetNotecard.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetNotecard.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetNotecard.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetNotecard.BodyText`. + /// C# member: `F:LibreMetaverse.Assets.AssetNotecard.EmbeddedItems`. + /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetNotecard.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetNotecard.AssetType`. + pub use crate::asset_models::AssetNotecard; /// C# type: `T:LibreMetaverse.Assets.AssetPrim`. pub struct AssetPrim { @@ -24871,239 +23819,68 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.AssetScriptBinary`. - pub struct AssetScriptBinary; - impl AssetScriptBinary { - /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetScriptBinary.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetScriptBinary.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetScriptBinary.Decode", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetScriptBinary.Encode", - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetScriptBinary.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetScriptBinary.AssetType" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptBinary.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetScriptBinary.AssetType`. + pub use crate::asset_models::AssetScriptBinary; /// C# type: `T:LibreMetaverse.Assets.AssetScriptText`. - pub struct AssetScriptText { - /// C# member: `F:LibreMetaverse.Assets.AssetScriptText.Source`. - pub source: Option, - } - impl AssetScriptText { - /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetScriptText.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetScriptText.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetScriptText.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetScriptText.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetScriptText.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetScriptText.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetScriptText.Source`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetScriptText.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetScriptText.AssetType`. + pub use crate::asset_models::AssetScriptText; /// C# type: `T:LibreMetaverse.Assets.AssetSettings`. - pub struct AssetSettings { - /// C# member: `F:LibreMetaverse.Assets.AssetSettings.Settings`. - pub settings: Option, - } - impl AssetSettings { - /// C# member: `M:LibreMetaverse.Assets.AssetSettings.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSettings.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetSettings.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetSettings.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetSettings.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSettings.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetSettings.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSettings.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetSettings.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetSettings.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetSettings.Settings`. + /// C# member: `M:LibreMetaverse.Assets.AssetSettings.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetSettings.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetSettings.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetSettings.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetSettings.AssetType`. + pub use crate::asset_models::AssetSettings; /// C# type: `T:LibreMetaverse.Assets.AssetSound`. - pub struct AssetSound; - impl AssetSound { - /// C# member: `M:LibreMetaverse.Assets.AssetSound.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSound.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetSound.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetSound.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetSound.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSound.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetSound.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetSound.Encode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetSound.PcmToOgg(System.Byte[],System.Int32,System.Int32,System.Int32)`. - pub fn pcm_to_ogg( - pcm_data: Vec, - sample_rate: i32, - channels: i32, - bits_per_sample: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetSound.PcmToOgg(System.Byte[],System.Int32,System.Int32,System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.Assets.AssetSound.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Assets.AssetSound.AssetType") - } - } + /// C# member: `M:LibreMetaverse.Assets.AssetSound.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetSound.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetSound.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetSound.Encode`. + /// C# member: `M:LibreMetaverse.Assets.AssetSound.PcmToOgg(System.Byte[],System.Int32,System.Int32,System.Int32)`. + /// C# member: `P:LibreMetaverse.Assets.AssetSound.AssetType`. + pub use crate::asset_models::AssetSound; /// C# type: `T:LibreMetaverse.Assets.AssetTexture`. - pub struct AssetTexture { - /// C# member: `F:LibreMetaverse.Assets.AssetTexture.Components`. - pub components: i32, - /// C# member: `F:LibreMetaverse.Assets.AssetTexture.Image`. - pub image: Option, - } - impl AssetTexture { - /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetTexture.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.Imaging.ManagedImage)`. - pub fn new_with_managed_image( - image: libremetaverse_imaging::ManagedImage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.Imaging.ManagedImage)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetTexture.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetTexture.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetTexture.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetTexture.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetTexture.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetTexture.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetTexture.Components`. + /// C# member: `F:LibreMetaverse.Assets.AssetTexture.Image`. + /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.Imaging.ManagedImage)`. + /// C# member: `M:LibreMetaverse.Assets.AssetTexture.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetTexture.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetTexture.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetTexture.AssetType`. + pub use crate::asset_models::AssetTexture; /// C# type: `T:LibreMetaverse.Assets.AssetWearable`. - pub struct AssetWearable { - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Creator`. - pub creator: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.ForSale`. - pub for_sale: libremetaverse_types::SaleType, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Group`. - pub group: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.GroupOwned`. - pub group_owned: bool, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.LastOwner`. - pub last_owner: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Owner`. - pub owner: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Params`. - pub params: std::collections::HashMap, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Permissions`. - pub permissions: libremetaverse::Permissions, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.SalePrice`. - pub sale_price: i32, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Textures`. - pub textures: std::collections::HashMap< - libremetaverse::AvatarTextureIndex, - libremetaverse_types::UUID, - >, - /// C# member: `F:LibreMetaverse.Assets.AssetWearable.WearableType`. - pub wearable_type: libremetaverse_types::WearableType, - } - impl AssetWearable { - /// C# member: `M:LibreMetaverse.Assets.AssetWearable.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetWearable.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetWearable.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetWearable.Encode") - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Creator`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Description`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.ForSale`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Group`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.GroupOwned`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.LastOwner`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Name`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Owner`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Params`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Permissions`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.SalePrice`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.Textures`. + /// C# member: `F:LibreMetaverse.Assets.AssetWearable.WearableType`. + /// C# member: `M:LibreMetaverse.Assets.AssetWearable.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetWearable.Encode`. + pub use crate::asset_models::AssetWearable; /// C# type: `T:LibreMetaverse.Assets.AssetsArchiver`. pub struct AssetsArchiver; @@ -25202,52 +23979,15 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.GltfTextureTransform`. - pub struct GltfTextureTransform { - /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Offset`. - pub offset: libremetaverse_types::Vector2, - /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Rotation`. - pub rotation: f32, - /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Scale`. - pub scale: libremetaverse_types::Vector2, - } - impl GltfTextureTransform { - /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Default`. - pub fn default() -> libremetaverse::assets::GltfTextureTransform { - libremetaverse_types::unimplemented_api!( - "F:LibreMetaverse.Assets.GltfTextureTransform.Default" - ) - } - /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.Equals(LibreMetaverse.Assets.GltfTextureTransform)`. - pub fn equals_with_gltf_texture_transform( - &self, - other: libremetaverse::assets::GltfTextureTransform, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GltfTextureTransform.Equals(LibreMetaverse.Assets.GltfTextureTransform)" - ) - } - /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.Equals(System.Object)`. - pub fn equals_with_object( - &self, - obj: Option, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GltfTextureTransform.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GltfTextureTransform.GetHashCode" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GltfTextureTransform.IsDefault`. - pub fn is_default(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GltfTextureTransform.IsDefault" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Default`. + /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Offset`. + /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Rotation`. + /// C# member: `F:LibreMetaverse.Assets.GltfTextureTransform.Scale`. + /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.Equals(LibreMetaverse.Assets.GltfTextureTransform)`. + /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.Equals(System.Object)`. + /// C# member: `M:LibreMetaverse.Assets.GltfTextureTransform.GetHashCode`. + /// C# member: `P:LibreMetaverse.Assets.GltfTextureTransform.IsDefault`. + pub use crate::asset_material::GltfTextureTransform; /// C# type: `T:LibreMetaverse.Assets.OarFile`. pub struct OarFile; diff --git a/crates/libremetaverse/src/inventory_manager.rs b/crates/libremetaverse/src/inventory_manager.rs index 230cf94..c38d038 100644 --- a/crates/libremetaverse/src/inventory_manager.rs +++ b/crates/libremetaverse/src/inventory_manager.rs @@ -4396,6 +4396,117 @@ fn parse_task_inventory_values( Ok(result) } +impl InventoryManager { + pub(crate) async fn native_update_material_inventory( + &self, + material: crate::assets::AssetMaterial, + material_item_id: UUID, + task_id: Option, + cancellation_token: Option, + progress: Option< + Box>, + >, + ) -> Result<(bool, String, UUID, UUID), Error> { + let client = self.client()?; + let simulator = client + .native_network()? + .current_sim() + .ok_or(Error::InvalidOperation)?; + let caps = simulator.native_caps().ok_or(Error::InvalidOperation)?; + let name = if task_id.is_some() { + "UpdateMaterialTaskInventory" + } else { + "UpdateMaterialAgentInventory" + }; + let deadline = Instant::now() + Duration::from_secs(2); + let uri = loop { + if let Some(uri) = caps.capability_uri(name.to_owned())? { + break uri; + } + if caps.seed_request_finished() || Instant::now() >= deadline { + return Err(Error::InvalidOperation); + } + std::thread::sleep(Duration::from_millis(1)); + }; + let mut query = HashMap::from([("item_id".to_owned(), OSD::UUID(material_item_id))]); + if let Some(task_id) = task_id { + query.insert("task_id".to_owned(), OSD::UUID(task_id)); + } + let payload = OSDParser::serialize_llsd_xml_bytes(OSD::Map(query))?; + let token = cancellation_token.unwrap_or_default(); + let (response, metadata) = client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + payload, + token.clone(), + None, + ) + .await?; + if !(200..300).contains(&response.status_code) { + return Ok(( + false, + format!("HTTP {}", response.status_code), + UUID::zero(), + UUID::zero(), + )); + } + let meta: serde_json::Value = + serde_json::from_slice(&metadata).map_err(|_| Error::Argument)?; + let status = meta + .get("state") + .and_then(serde_json::Value::as_str) + .unwrap_or_default() + .to_owned(); + let uploader = meta + .get("uploader") + .and_then(serde_json::Value::as_str) + .ok_or(Error::Argument)? + .to_owned(); + let data = material.to_json()?.into_bytes(); + let (upload_response, response_body) = client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + Uri(uploader), + "application/octet-stream".into(), + data, + token, + progress, + ) + .await?; + if !(200..300).contains(&upload_response.status_code) { + return Ok(( + false, + format!("HTTP {}", upload_response.status_code), + material_item_id, + UUID::zero(), + )); + } + let result: serde_json::Value = + serde_json::from_slice(&response_body).map_err(|_| Error::Argument)?; + let final_status = result + .get("state") + .and_then(serde_json::Value::as_str) + .unwrap_or(&status) + .to_owned(); + let asset_id = result + .get("new_asset") + .or_else(|| result.get("new_asset_id")) + .and_then(serde_json::Value::as_str) + .map(|id| UUID::new_with_string(id.to_owned())) + .transpose()? + .unwrap_or_else(UUID::zero); + let item_id = result + .get("item_id") + .and_then(serde_json::Value::as_str) + .map(|id| UUID::new_with_string(id.to_owned())) + .transpose()? + .unwrap_or(material_item_id); + Ok((final_status == "complete", final_status, asset_id, item_id)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index c19b7be..d53b9c2 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -7,6 +7,10 @@ mod attention_catalog; mod agent_manager; mod agent_messages; mod agent_movement; +mod asset_cache; +mod asset_manager; +mod asset_material; +mod asset_models; mod bit_pack; mod caps; mod caps_http; @@ -27,6 +31,7 @@ mod login; mod message_codec; mod message_decoder; mod network_manager; +mod object_material; #[rustfmt::skip] pub mod packet_catalog; mod packet_wire; @@ -35,6 +40,7 @@ mod skeleton; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod skeleton_catalog; mod targa; +mod transfers; mod udp_transport; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod visual_catalog; @@ -44,6 +50,8 @@ mod appearance_baker_semantics; #[cfg(test)] mod asset_archive_semantics; #[cfg(test)] +mod asset_pipeline_semantics; +#[cfg(test)] mod avatar_animesh_semantics; #[cfg(test)] mod batch_link_internal_semantics; diff --git a/crates/libremetaverse/src/object_material.rs b/crates/libremetaverse/src/object_material.rs new file mode 100644 index 0000000..a4d04b0 --- /dev/null +++ b/crates/libremetaverse/src/object_material.rs @@ -0,0 +1,192 @@ +//! `ModifyMaterialParams` capability integration. + +#![allow( + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::option_option +)] + +use crate::assets::AssetMaterial; +use crate::{Error, GridClient, Simulator}; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::{UUID, compat::CancellationToken}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct GLTFMaterialUpdate { + asset_id: Option>, + object_id: UUID, + override_: Option, + side: i32, +} +impl GLTFMaterialUpdate { + pub fn new() -> Result { + Ok(Self { + asset_id: None, + object_id: UUID::zero(), + override_: None, + side: 0, + }) + } + pub fn asset_id(&self) -> Option> { + self.asset_id + } + pub fn set_asset_id(&mut self, value: Option>) { + self.asset_id = value; + } + pub const fn object_id(&self) -> UUID { + self.object_id + } + pub fn set_object_id(&mut self, value: UUID) { + self.object_id = value; + } + pub fn override_(&self) -> Option { + self.override_.clone() + } + pub fn set_override_(&mut self, value: Option) { + self.override_ = value; + } + pub const fn side(&self) -> i32 { + self.side + } + pub fn set_side(&mut self, value: i32) { + self.side = value; + } +} + +pub struct ObjectManager { + client: crate::client_core::ClientWeakHandle, +} +impl ObjectManager { + pub(crate) fn native_new(client: Option>) -> Result { + Ok(Self { + client: client.ok_or(Error::ArgumentNull)?.native_weak_handle(), + }) + } + fn client(&self) -> Result { + self.client.upgrade().ok_or(Error::InvalidOperation) + } + + pub(crate) async fn native_send_material_updates( + &self, + sim: Simulator, + updates: Box>, + cancellation_token: Option, + ) -> Result { + let Some(caps) = sim.native_caps() else { + return Ok(false); + }; + let deadline = Instant::now() + Duration::from_secs(2); + let uri = loop { + if let Some(uri) = caps.capability_uri("ModifyMaterialParams".into())? { + break uri; + } + if caps.seed_request_finished() || Instant::now() >= deadline { + return Ok(false); + } + std::thread::sleep(Duration::from_millis(1)); + }; + let mut body = Vec::new(); + for update in updates { + if update.side < 0 { + return Err(Error::Argument); + } + let mut entry = HashMap::from([ + ("object_id".into(), OSD::UUID(update.object_id)), + ("side".into(), OSD::Integer(update.side)), + ]); + if let Some(Some(asset_id)) = update.asset_id { + entry.insert("asset_id".into(), OSD::UUID(asset_id)); + } + if let Some(material) = update.override_ { + entry.insert("gltf_json".into(), OSD::String(material.to_json()?)); + } else if update.asset_id.flatten().is_none() { + entry.insert("gltf_json".into(), OSD::String(String::new())); + } + body.push(OSD::Map(entry)); + } + if body.is_empty() { + return Err(Error::Argument); + } + let payload = OSDParser::serialize_llsd_xml_bytes(OSD::Array(body))?; + let client = self.client()?; + let (response, bytes) = client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + payload, + cancellation_token.unwrap_or_default(), + None, + ) + .await?; + if !(200..300).contains(&response.status_code) { + return Ok(false); + } + let response: serde_json::Value = + serde_json::from_slice(&bytes).map_err(|_| Error::Argument)?; + Ok(response + .get("success") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false)) + } + pub(crate) async fn native_set_material_override( + &self, + sim: Simulator, + object_id: UUID, + side: i32, + override_material: AssetMaterial, + cancellation_token: Option, + ) -> Result { + let mut update = GLTFMaterialUpdate::new()?; + update.object_id = object_id; + update.side = side; + update.override_ = Some(override_material); + self.native_send_material_updates( + sim, + Box::new(std::iter::once(update)), + cancellation_token, + ) + .await + } + pub(crate) async fn native_clear_material_override( + &self, + sim: Simulator, + object_id: UUID, + side: i32, + cancellation_token: Option, + ) -> Result { + let mut update = GLTFMaterialUpdate::new()?; + update.object_id = object_id; + update.side = side; + self.native_send_material_updates( + sim, + Box::new(std::iter::once(update)), + cancellation_token, + ) + .await + } + pub(crate) async fn native_apply_material( + &self, + sim: Simulator, + object_id: UUID, + side: i32, + material_asset_id: UUID, + override_material: Option, + cancellation_token: Option, + ) -> Result { + let mut update = GLTFMaterialUpdate::new()?; + update.object_id = object_id; + update.side = side; + update.asset_id = Some(Some(material_asset_id)); + update.override_ = override_material; + self.native_send_material_updates( + sim, + Box::new(std::iter::once(update)), + cancellation_token, + ) + .await + } +} diff --git a/crates/libremetaverse/src/transfers.rs b/crates/libremetaverse/src/transfers.rs new file mode 100644 index 0000000..2d8aa3e --- /dev/null +++ b/crates/libremetaverse/src/transfers.rs @@ -0,0 +1,226 @@ +//! Asset transfer state shared by HTTP and LLUDP pipelines. + +#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)] + +use crate::{ + ChannelType, Error, ImageCodec, ImageType, Simulator, SourceType, StatusCode, TargetType, + TransferError, +}; +use libremetaverse_types::compat::SortedList; +use libremetaverse_types::{AssetType, UUID}; +use std::collections::HashMap; +use std::sync::RwLock; +use std::time::Instant; + +#[derive(Clone, Debug)] +pub struct Transfer { + pub asset_data: Vec, + pub asset_type: AssetType, + pub id: UUID, + pub size: i32, + pub success: bool, + pub transferred: i32, + pub(crate) last_packet: Instant, +} +impl Transfer { + pub fn new() -> Result { + Ok(Self { + asset_data: Vec::new(), + asset_type: AssetType::Unknown, + id: UUID::zero(), + size: 0, + success: false, + transferred: 0, + last_packet: Instant::now(), + }) + } + pub fn time_since_last_packet(&self) -> i32 { + i32::try_from(self.last_packet.elapsed().as_millis()).unwrap_or(i32::MAX) + } + pub fn set_time_since_last_packet(&mut self, value: i32) { + self.last_packet = Instant::now() + .checked_sub(std::time::Duration::from_millis( + u64::try_from(value.max(0)).unwrap_or(0), + )) + .unwrap_or_else(Instant::now); + } +} + +#[derive(Debug)] +pub struct AssetDownload { + pub asset_id: UUID, + pub channel: ChannelType, + pub priority: f32, + pub simulator: Option, + pub source: SourceType, + pub status: StatusCode, + pub target: TargetType, + pub next_packet: i32, + pub out_of_order_packets: RwLock>>, + pub base: Transfer, +} +impl AssetDownload { + pub fn new() -> Result { + Ok(Self { + asset_id: UUID::zero(), + channel: ChannelType::Asset, + priority: 0.0, + simulator: None, + source: SourceType::Asset, + status: StatusCode::Unknown, + target: TargetType::Unknown, + next_packet: 0, + out_of_order_packets: RwLock::new(HashMap::new()), + base: Transfer::new()?, + }) + } +} + +#[derive(Debug)] +pub struct ImageDownload { + pub codec: ImageCodec, + pub discard_level: i32, + pub image_type: ImageType, + pub packet_count: u16, + pub packets_seen: SortedList, + pub priority: f32, + pub simulator: Option, + pub base: Transfer, +} + +impl ImageDownload { + pub fn new() -> Result { + Ok(Self { + codec: ImageCodec::Invalid, + discard_level: 0, + image_type: ImageType::Normal, + packet_count: 0, + packets_seen: SortedList(std::collections::BTreeMap::new()), + priority: 0.0, + simulator: None, + base: Transfer::new()?, + }) + } +} + +#[derive(Clone, Debug)] +pub struct AssetUpload { + pub asset_id: UUID, + pub packet_num: u32, + pub type_: AssetType, + pub xfer_id: u64, + pub base: Transfer, +} +impl AssetUpload { + pub fn new() -> Result { + Ok(Self { + asset_id: UUID::zero(), + packet_num: 0, + type_: AssetType::Unknown, + xfer_id: 0, + base: Transfer::new()?, + }) + } +} + +#[derive(Clone, Debug)] +pub struct AssetUploadEventArgs { + upload: AssetUpload, +} +impl AssetUploadEventArgs { + pub fn new(upload: AssetUpload) -> Result { + Ok(Self { upload }) + } + pub fn upload(&self) -> AssetUpload { + self.upload.clone() + } +} + +#[derive(Clone, Debug)] +pub struct ImageReceiveProgressEventArgs { + image_id: UUID, + received: i32, + total: i32, +} + +impl ImageReceiveProgressEventArgs { + pub fn new(image_id: UUID, received: i32, total: i32) -> Result { + if received < 0 || total < 0 || received > total { + return Err(Error::Argument); + } + Ok(Self { + image_id, + received, + total, + }) + } + pub const fn image_id(&self) -> UUID { + self.image_id + } + pub const fn received(&self) -> i32 { + self.received + } + pub const fn total(&self) -> i32 { + self.total + } +} + +#[derive(Clone, Debug)] +pub struct InitiateDownloadEventArgs { + sim_filename: String, + viewer_filename: String, +} + +impl InitiateDownloadEventArgs { + pub fn new(sim_filename: String, viewer_filename: String) -> Result { + if sim_filename.as_bytes().contains(&0) || viewer_filename.as_bytes().contains(&0) { + return Err(Error::Argument); + } + Ok(Self { + sim_filename, + viewer_filename, + }) + } + pub fn sim_file_name(&self) -> String { + self.sim_filename.clone() + } + pub fn viewer_file_name(&self) -> String { + self.viewer_filename.clone() + } +} + +#[derive(Clone, Debug)] +pub struct XferDownload { + pub error: TransferError, + pub filename: String, + pub packet_num: u32, + pub v_file_id: UUID, + pub xfer_id: u64, + pub base: Transfer, +} + +impl XferDownload { + pub fn new() -> Result { + Ok(Self { + error: TransferError::None, + filename: String::new(), + packet_num: 0, + v_file_id: UUID::zero(), + xfer_id: 0, + base: Transfer::new()?, + }) + } +} + +#[derive(Clone, Debug)] +pub struct XferReceivedEventArgs { + xfer: XferDownload, +} +impl XferReceivedEventArgs { + pub fn new(xfer: XferDownload) -> Result { + Ok(Self { xfer }) + } + pub fn xfer(&self) -> XferDownload { + self.xfer.clone() + } +} diff --git a/crates/libremetaverse/tests/caps_http.rs b/crates/libremetaverse/tests/caps_http.rs index c763bed..3873bdd 100644 --- a/crates/libremetaverse/tests/caps_http.rs +++ b/crates/libremetaverse/tests/caps_http.rs @@ -600,14 +600,17 @@ async fn download_manager_retries_transient_but_not_permanent_statuses() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn deduplicated_subscriber_cancellation_cancels_the_shared_download() { +async fn deduplicated_subscriber_cancellation_is_independent() { let attempts = Arc::new(AtomicUsize::new(0)); let handler_attempts = Arc::clone(&attempts); - let handler = HttpMessageHandler::new(move |_request, cancellation| { + let release = Arc::new(tokio::sync::Notify::new()); + let handler_release = Arc::clone(&release); + let handler = HttpMessageHandler::new(move |_request, _cancellation| { handler_attempts.fetch_add(1, Ordering::AcqRel); + let release = Arc::clone(&handler_release); async move { - cancellation.cancelled().await; - response(200, b"too late".to_vec()) + release.notified().await; + response(200, b"shared".to_vec()) } }); let mut client = GridClient::new().expect("client"); @@ -645,8 +648,12 @@ async fn deduplicated_subscriber_cancellation_cancels_the_shared_download() { tokio::task::yield_now().await; } cancellation.cancel(); - assert_eq!(first.await.expect("first task"), Err(Error::Cancelled)); assert_eq!(second.await.expect("second task"), Err(Error::Cancelled)); + release.notify_waiters(); + assert_eq!( + first.await.expect("first task").expect("first download").1, + b"shared" + ); assert_eq!(attempts.load(Ordering::Acquire), 1); downloads.dispose().expect("dispose"); } diff --git a/docs/assets.md b/docs/assets.md new file mode 100644 index 0000000..c83ad6a --- /dev/null +++ b/docs/assets.md @@ -0,0 +1,49 @@ +# Asset models, transfers, and cache + +The native asset layer owns the client-facing asset boundary from typed bytes to +transport and persistence. `Asset` retains its UUID, asset type, temporary flag, +and mutable raw payload. Concrete animation, sound, script, calling-card, +landmark, settings, mesh, texture, wearable, and notecard models enforce a 64 MiB +input ceiling and reject malformed structured formats with typed errors. Mesh and +settings parsing use StructuredData. Texture encode/decode uses the imaging +crate's bounded JPEG 2000 adapter, while PCM conversion streams bounded planar +blocks through the cross-platform Ogg Vorbis encoder; neither API exposes codec +implementation types. + +`AssetManager` is client-owned and shares one `DownloadManager` and `AssetCache`. +ViewerAsset, GetMesh, GetTexture, and server-bake requests check the cache before +network I/O. Equal HTTP URIs share one in-flight request and its progress sinks; +each awaiting subscriber retains an independent cancellation token, so cancelling +one waiter cannot cancel the shared request for the others. Capability discovery +waits for the seed request's bounded completion rather than racing it. + +When ViewerAsset is unavailable, asset and authenticated inventory requests use +LLUDP `TransferRequest`, `TransferInfo`, and `TransferPacket`. The receiver filters +by transfer UUID, preserves priority and source type, reassembles out-of-order +packets, limits announced data to 64 MiB, and sends `TransferAbort` on cancellation +or timeout. Inventory transfers include the agent, session, owner, task, item, +asset, and type parameters. Legacy Xfer downloads confirm each packet, enforce the +same bound, and publish only completely assembled results. + +Small and large uploads use the simulator's `AssetUploadRequest`/Xfer handshake, +including packet confirmation and progress events. Baked texture and inventory or +task material uploads use their two-stage capability contracts and require a +complete response containing the new asset UUID. Upload size, cancellation, and +timeout failures remove their pending correlation state. + +The disk cache uses UUID-only filenames by default and confines custom filename +callbacks to the configured cache directory. Reads reject empty, oversized, or +non-regular files. Writes use uniquely named same-directory files, flush them, +and atomically rename them into place, making concurrent writers deterministic. +Pruning removes least-recently-accessed regular cache entries until usage is below 90% of +the configured maximum. Automatic pruning is activity-driven at the configured +millisecond interval, avoiding a permanent timer thread; cancellation is checked +between deletions. Clearing and pruning never inspect or delete files outside the +configured project/application cache directory. + +Focused native tests in `asset_pipeline_semantics` cover real sound and texture +codec output, model typing, malformed mesh data, atomic cache replacement, cache +corruption, cached-image reconstruction, and pruning. The `caps_http` +deduplication case proves independent subscriber cancellation. Translated asset, +material, mesh, capability-upload, and cache compatibility cases provide the API +contract checks for this issue. diff --git a/tests/api-compile/Cargo.lock b/tests/api-compile/Cargo.lock index e73b609..aa7c60b 100644 --- a/tests/api-compile/Cargo.lock +++ b/tests/api-compile/Cargo.lock @@ -17,6 +17,16 @@ dependencies = [ "libc", ] +[[package]] +name = "aotuv_lancer_vorbis_sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc4fd1a61860d2f1198b60bedd30910eaffa978f1ee6214dfb24ac70d589225" +dependencies = [ + "cc", + "ogg_next_sys", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -285,6 +295,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.10" @@ -725,13 +745,16 @@ dependencies = [ "os_info", "reqwest", "roxmltree", + "serde_json", "tokio", + "vorbis_rs", ] [[package]] name = "libremetaverse-imaging" version = "0.0.1" dependencies = [ + "libremetaverse-openjpeg", "libremetaverse-types", ] @@ -750,6 +773,14 @@ dependencies = [ "libremetaverse-types", ] +[[package]] +name = "libremetaverse-openjpeg" +version = "0.0.1" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libremetaverse-prim-mesher" version = "0.0.1" @@ -1113,6 +1144,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "ogg_next_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2d7a48e247c2bb07e633aefb65a38648ea58c7eedd4e4408a5861721ab049b" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1877,12 +1917,32 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vorbis_rs" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c5da94d280f7a27e8c937e9b73df2da3e23a2583f48471fd8fb4c72f9c1933" +dependencies = [ + "aotuv_lancer_vorbis_sys", + "errno", + "getrandom 0.4.3", + "ogg_next_sys", + "thiserror 2.0.20", + "tinyvec", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/tools/check_milestone_09_issue_64.py b/tools/check_milestone_09_issue_64.py new file mode 100644 index 0000000..8c48b9b --- /dev/null +++ b/tools/check_milestone_09_issue_64.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Audit issue 64's native asset, transfer, cache, and download boundary.""" + +from __future__ import annotations + +from pathlib import Path +import re + +import generate_api_shims + + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = tuple( + ROOT / "crates" / "libremetaverse" / "src" / name + for name in ( + "asset_cache.rs", "asset_manager.rs", "asset_material.rs", + "asset_models.rs", "object_material.rs", "transfers.rs", + ) +) +DOC = ROOT / "docs" / "assets.md" +NATIVE_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "asset_pipeline_semantics.rs" +HTTP_TESTS = ROOT / "crates" / "libremetaverse" / "tests" / "caps_http.rs" +STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(") + +REQUIRED_TYPES = { + "T:LibreMetaverse.AssetCache", "T:LibreMetaverse.AssetManager", + "T:LibreMetaverse.AssetDownload", "T:LibreMetaverse.AssetUpload", + "T:LibreMetaverse.AssetUploadEventArgs", "T:LibreMetaverse.ImageDownload", + "T:LibreMetaverse.ImageReceiveProgressEventArgs", + "T:LibreMetaverse.InitiateDownloadEventArgs", "T:LibreMetaverse.Transfer", + "T:LibreMetaverse.XferDownload", "T:LibreMetaverse.XferReceivedEventArgs", + "T:LibreMetaverse.Assets.Asset", "T:LibreMetaverse.Assets.AssetMaterial", + "T:LibreMetaverse.Assets.AssetMesh", "T:LibreMetaverse.Assets.AssetTexture", +} + + +def require_markers(path: Path, markers: tuple[str, ...]) -> None: + text = path.read_text() + missing = [marker for marker in markers if marker not in text] + if missing: + raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing)) + + +def main() -> None: + native = set(generate_api_shims.NATIVE_TYPES) | set(generate_api_shims.NATIVE_DECLARATIONS) + missing = sorted(REQUIRED_TYPES - native) + if missing: + raise SystemExit("issue 64 native types missing: " + ", ".join(missing)) + for source in SOURCES: + if STUB_RE.search(source.read_text()): + raise SystemExit(f"issue 64 owned Rust stubs remain in {source.name}") + require_markers(SOURCES[0], ("create_new(true)", "fs::rename", "CACHE_TARGET_PERCENT", "maybe_begin_prune")) + require_markers(SOURCES[1], ("ViewerAsset", "TransferRequest", "TransferAbort", "UploadBakedTexture", "AssetUploadRequest")) + require_markers(SOURCES[3], ("MAX_ASSET_BYTES", "VorbisEncoderBuilder", "J2kCodec", "serialize_llsd_binary")) + require_markers(NATIVE_TESTS, ("sound_and_texture_codecs_produce_real_valid_payloads", "cache_writes_are_atomic_and_corruption_is_typed", "cache_pruning_reclaims_old_entries")) + require_markers(HTTP_TESTS, ("deduplicated_subscriber_cancellation_is_independent", "attempts.load(Ordering::Acquire), 1")) + require_markers(DOC, ("64 MiB", "independent cancellation", "out-of-order", "atomically rename", "90%")) + print("issue 64 audit: native models/codecs, bounded LLUDP and capability transfers, independent dedup cancellation, atomic cache, documentation, and focused tests are present") + + +if __name__ == "__main__": + main() diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index acc06bf..2efc9eb 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -105,6 +105,36 @@ NATIVE_TYPES = { "T:LibreMetaverse.ChatSessionMemberLeftEventArgs": "crate::agent_messages::ChatSessionMemberLeftEventArgs", "T:LibreMetaverse.CameraConstraintEventArgs": "crate::agent_movement::CameraConstraintEventArgs", "T:LibreMetaverse.Assets.AssetGesture": "crate::gesture::AssetGesture", + "T:LibreMetaverse.AssetCache": "crate::asset_cache::AssetCache", + "T:LibreMetaverse.AssetCache.ComputeAssetCacheFilenameDelegate": "crate::asset_cache::AssetCacheComputeAssetCacheFilenameDelegate", + "T:LibreMetaverse.AssetManager": "crate::asset_manager::AssetManager", + "T:LibreMetaverse.AssetDownload": "crate::transfers::AssetDownload", + "T:LibreMetaverse.ImageDownload": "crate::transfers::ImageDownload", + "T:LibreMetaverse.AssetUpload": "crate::transfers::AssetUpload", + "T:LibreMetaverse.AssetUploadEventArgs": "crate::transfers::AssetUploadEventArgs", + "T:LibreMetaverse.Transfer": "crate::transfers::Transfer", + "T:LibreMetaverse.ImageReceiveProgressEventArgs": "crate::transfers::ImageReceiveProgressEventArgs", + "T:LibreMetaverse.InitiateDownloadEventArgs": "crate::transfers::InitiateDownloadEventArgs", + "T:LibreMetaverse.XferDownload": "crate::transfers::XferDownload", + "T:LibreMetaverse.XferReceivedEventArgs": "crate::transfers::XferReceivedEventArgs", + "T:LibreMetaverse.Assets.Asset": "crate::asset_models::Asset", + "T:LibreMetaverse.Assets.AssetAnimation": "crate::asset_models::AssetAnimation", + "T:LibreMetaverse.Assets.AssetBodypart": "crate::asset_models::AssetBodypart", + "T:LibreMetaverse.Assets.AssetCallingCard": "crate::asset_models::AssetCallingCard", + "T:LibreMetaverse.Assets.AssetClothing": "crate::asset_models::AssetClothing", + "T:LibreMetaverse.Assets.AssetLandmark": "crate::asset_models::AssetLandmark", + "T:LibreMetaverse.Assets.AssetMaterial": "crate::asset_material::AssetMaterial", + "T:LibreMetaverse.Assets.AssetMesh": "crate::asset_models::AssetMesh", + "T:LibreMetaverse.Assets.AssetMutable": "crate::asset_models::AssetMutable", + "T:LibreMetaverse.Assets.AssetNotecard": "crate::asset_models::AssetNotecard", + "T:LibreMetaverse.Assets.AssetScriptBinary": "crate::asset_models::AssetScriptBinary", + "T:LibreMetaverse.Assets.AssetScriptText": "crate::asset_models::AssetScriptText", + "T:LibreMetaverse.Assets.AssetSettings": "crate::asset_models::AssetSettings", + "T:LibreMetaverse.Assets.AssetSound": "crate::asset_models::AssetSound", + "T:LibreMetaverse.Assets.AssetTexture": "crate::asset_models::AssetTexture", + "T:LibreMetaverse.Assets.AssetWearable": "crate::asset_models::AssetWearable", + "T:LibreMetaverse.Assets.GltfTextureTransform": "crate::asset_material::GltfTextureTransform", + "T:LibreMetaverse.GLTFMaterialUpdate": "crate::object_material::GLTFMaterialUpdate", "T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep", "T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation", "T:LibreMetaverse.Assets.GestureStepChat": "crate::gesture::GestureStepChat", @@ -265,6 +295,7 @@ NATIVE_DECLARATIONS = { "T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager", "T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient", "T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager", + "T:LibreMetaverse.ObjectManager": "crate::object_material::ObjectManager", "T:LibreMetaverse.Settings": "crate::client_core::Settings", "T:LibreMetaverse.Simulator": "crate::network_manager::Simulator", "T:LibreMetaverse.StructuredData.OSDParser": "crate::model::OSDParser", @@ -335,6 +366,14 @@ NATIVE_MEMBER_BODIES = { "self.native_http_caps_client()", "P:LibreMetaverse.GridClient.HttpCapsClient#set": "self.native_set_http_caps_client(value)", + "P:LibreMetaverse.GridClient.Assets": + "self.native_assets().unwrap_or_else(|_| panic!(\"failed to construct AssetManager\"))", + "P:LibreMetaverse.GridClient.Assets#set": + "self.native_set_assets(value)", + "P:LibreMetaverse.GridClient.Objects": + "crate::object_material::ObjectManager::native_new(Some(std::sync::Arc::new(self.clone()))).unwrap_or_else(|_| panic!(\"failed to construct ObjectManager\"))", + "P:LibreMetaverse.GridClient.Objects#set": + "{ let _ = value; }", "P:LibreMetaverse.GridClient.Inventory": "self.native_inventory().unwrap_or_else(|_| panic!(\"failed to construct InventoryManager\"))", "P:LibreMetaverse.GridClient.Inventory#set": @@ -375,6 +414,20 @@ NATIVE_MEMBER_BODIES = { "crate::inventory_ais::InventoryAISClient::LIBRARY_CAP_NAME", "M:LibreMetaverse.InventoryAISClient.#ctor(LibreMetaverse.GridClient)": "crate::inventory_ais::InventoryAISClient::native_new(client)", + "M:LibreMetaverse.ObjectManager.#ctor(LibreMetaverse.GridClient)": + "crate::object_material::ObjectManager::native_new(client)", + "M:LibreMetaverse.ObjectManager.SendMaterialUpdatesAsync(LibreMetaverse.Simulator,System.Collections.Generic.IEnumerable{LibreMetaverse.GLTFMaterialUpdate},System.Threading.CancellationToken)": + "self.native_send_material_updates(sim, updates, cancellation_token).await", + "M:LibreMetaverse.ObjectManager.SetMaterialOverrideAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Assets.AssetMaterial,System.Threading.CancellationToken)": + "self.native_set_material_override(sim, object_id, side, override_material, cancellation_token).await", + "M:LibreMetaverse.ObjectManager.ClearMaterialOverrideAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,System.Threading.CancellationToken)": + "self.native_clear_material_override(sim, object_id, side, cancellation_token).await", + "M:LibreMetaverse.ObjectManager.ApplyMaterialAsync(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.Int32,LibreMetaverse.UUID,LibreMetaverse.Assets.AssetMaterial,System.Threading.CancellationToken)": + "self.native_apply_material(sim, object_id, side, material_asset_id, override_material, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestUpdateMaterialAgentInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})": + "self.native_update_material_inventory(material, material_item_id, None, cancellation_token, progress).await", + "M:LibreMetaverse.InventoryManager.RequestUpdateMaterialTaskInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})": + "self.native_update_material_inventory(material, material_item_id, Some(task_id), cancellation_token, progress).await", "P:LibreMetaverse.InventoryAISClient.IsAvailable": "self.native_is_available()", "M:LibreMetaverse.InventoryAISClient.CreateInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Boolean,System.Threading.CancellationToken)": "self.native_create_inventory(parent_uuid, new_inventory, create_link, cancellation_token).await", diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index 3540282..1b8528b 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -985,6 +985,10 @@ def validate_generated_shims() -> None: "crate::agent_manager::", "crate::agent_messages::", "crate::agent_movement::", + "crate::asset_cache::", + "crate::asset_manager::", + "crate::asset_material::", + "crate::asset_models::", "crate::byte_order::", "crate::attention_catalog::", "crate::binary::", @@ -998,6 +1002,7 @@ def validate_generated_shims() -> None: "crate::inventory_manager::", "crate::message_codec::", "crate::message_decoder::", + "crate::object_material::", "crate::notation::", "crate::packet_catalog::", "crate::packet_wire::", @@ -1006,6 +1011,7 @@ def validate_generated_shims() -> None: "crate::protobuf::", "crate::skeleton::", "crate::visual_catalog::", + "crate::transfers::", "crate::xml_codec::", "stored_visual_params", "self.native_",