Implement appearance and current outfit services (#65)
Some checks failed
Native code generation / deterministic (push) Failing after 2m19s
Imaging and meshing gate / native (push) Failing after 1m26s
Native Rust workspace compile / compile (push) Failing after 2m11s

This commit is contained in:
2026-08-10 08:58:29 +00:00
parent 52f62d8c39
commit a9927fcfcb
13 changed files with 2900 additions and 242 deletions

View File

@@ -382,3 +382,13 @@ side-effect removals, and folder versions are validated and reconciled through
one atomic store transaction; cancellation and malformed responses cannot
partially mutate the cache. Focused compatibility and injected-HTTP tests cover
link correction, endpoint recording, error isolation, and metadata updates.
Native appearance state now tracks layered wearables and attachment points,
sends the inventory-backed wearable and attachment LLUDP messages, and exposes
worn queries by slot, item, and point. The client-owned Current Outfit Folder
service resolves inventory links, preserves layer descriptions, serializes
concurrent mutations through a cancellation-aware gate, and compensates failed
link or attachment replacement without publishing partial local state.
Composite policies make pure decisions from snapshots and receive committed
changes after synchronization is released. The behavior and transaction
boundaries are documented in [`docs/appearance.md`](docs/appearance.md).

View File

@@ -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: 197 types / 15,177 members; remaining surface is callable failure-only shims |
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 200 types / 15,251 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 |

View File

@@ -0,0 +1,964 @@
//! Native wearable and attachment state for `AppearanceManager`.
use crate::client_core::ClientWeakHandle;
use crate::packet_catalog::PacketType;
use crate::packets::{
AgentIsNowWearingPacket, AgentIsNowWearingPacketWearableDataBlock,
DetachAttachmentIntoInvPacket, RezSingleAttachmentFromInvPacket,
};
use crate::{
AgentWearablesReplyEventArgs, AppearanceManagerWearableData, AppearanceSetEventArgs,
GridClient, InventoryBase, InventoryFolder, InventoryItem, InventoryObject,
InventoryObjectClass, InventoryWearable, Permissions, PrimitiveTextureEntry,
appearance::CurrentOutfitFolder,
};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
use libremetaverse_types::{
AssetType, AttachmentPoint, Error, FolderType, InventoryType, MultiValueDictionary, UUID,
WearableType,
};
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
const ATTACHMENT_ADD: u8 = 0x80;
const MAX_WEARABLE_LAYERS: usize = 60;
const MAX_FOLDER_TRAVERSAL: usize = 256;
fn mutex<T>(lock: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
lock.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn read<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn check_cancelled(token: Option<&CancellationToken>) -> Result<(), Error> {
token.map_or(Ok(()), CancellationToken::throw_if_cancellation_requested)
}
struct EventRegistry<T> {
next_id: AtomicU64,
handlers: Arc<Mutex<HashMap<u64, EventHandler<T>>>>,
}
impl<T> Default for EventRegistry<T> {
fn default() -> Self {
Self {
next_id: AtomicU64::new(1),
handlers: Arc::new(Mutex::new(HashMap::new())),
}
}
}
impl<T: 'static> EventRegistry<T> {
fn subscribe(&self, handler: EventHandler<T>) -> Subscription {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
mutex(&self.handlers).insert(id, handler);
let handlers = Arc::downgrade(&self.handlers);
Subscription::new(move || {
if let Some(handlers) = handlers.upgrade() {
mutex(&handlers).remove(&id);
}
})
}
fn emit_with(&self, mut event: impl FnMut() -> T) {
let handlers: Vec<_> = mutex(&self.handlers).values().cloned().collect();
for handler in handlers {
handler(event());
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct WearableRecord {
item_id: UUID,
asset_id: UUID,
asset_type: AssetType,
wearable_type: WearableType,
}
impl WearableRecord {
fn from_item(item: &InventoryItem) -> Option<Self> {
if !matches!(item.asset_type(), AssetType::Bodypart | AssetType::Clothing) {
return None;
}
let wearable = InventoryWearable { base: item.clone() };
Some(Self {
item_id: item.base.uuid(),
asset_id: item.asset_uuid(),
asset_type: item.asset_type(),
wearable_type: wearable.wearable_type(),
})
}
fn public(&self) -> AppearanceManagerWearableData {
AppearanceManagerWearableData {
asset: None,
asset_id: self.asset_id,
asset_type: self.asset_type,
item_id: self.item_id,
wearable_type: self.wearable_type,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct AttachmentRecord {
item: InventoryItem,
point: AttachmentPoint,
}
#[derive(Default)]
struct AppearanceEvents {
agent_wearables_reply: EventRegistry<AgentWearablesReplyEventArgs>,
appearance_set: EventRegistry<AppearanceSetEventArgs>,
}
pub(crate) struct AppearanceManagerInner {
client: ClientWeakHandle,
wearables: RwLock<Vec<WearableRecord>>,
attachments: RwLock<Vec<AttachmentRecord>>,
last_cof_version: AtomicI32,
busy: AtomicBool,
disposed: AtomicBool,
events: AppearanceEvents,
}
/// Shared native manager. Public texture fields remain on the compatibility
/// wrapper; wearable and attachment state is shared by every client accessor.
pub struct AppearanceManager {
pub my_textures: PrimitiveTextureEntry,
pub my_visual_parameters: Vec<u8>,
inner: Arc<AppearanceManagerInner>,
}
impl Clone for AppearanceManager {
fn clone(&self) -> Self {
Self::native_from_inner(Arc::clone(&self.inner))
}
}
impl fmt::Debug for AppearanceManager {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AppearanceManager")
.field("wearables", &read(&self.inner.wearables).len())
.field("attachments", &read(&self.inner.attachments).len())
.field("busy", &self.native_manager_busy())
.finish_non_exhaustive()
}
}
struct BusyGuard<'a>(&'a AtomicBool);
impl Drop for BusyGuard<'_> {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}
impl AppearanceManager {
pub(crate) fn native_new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
let client = client.ok_or(Error::ArgumentNull)?;
Ok(Self::native_from_inner(Arc::new(AppearanceManagerInner {
client: client.native_weak_handle(),
wearables: RwLock::new(Vec::new()),
attachments: RwLock::new(Vec::new()),
last_cof_version: AtomicI32::new(-1),
busy: AtomicBool::new(false),
disposed: AtomicBool::new(false),
events: AppearanceEvents::default(),
})))
}
pub(crate) fn native_from_inner(inner: Arc<AppearanceManagerInner>) -> Self {
Self {
my_textures: PrimitiveTextureEntry {
default_texture: None,
face_textures: Vec::new(),
},
my_visual_parameters: Vec::new(),
inner,
}
}
pub(crate) fn native_inner(&self) -> Arc<AppearanceManagerInner> {
Arc::clone(&self.inner)
}
fn client(&self) -> Result<GridClient, Error> {
if self.inner.disposed.load(Ordering::Acquire) {
return Err(Error::InvalidOperation);
}
self.inner.client.upgrade().ok_or(Error::InvalidOperation)
}
fn begin(&self) -> Result<BusyGuard<'_>, Error> {
self.client()?;
if self
.inner
.busy
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return Err(Error::InvalidOperation);
}
Ok(BusyGuard(&self.inner.busy))
}
pub(crate) fn native_subscribe_agent_wearables_reply(
&self,
handler: EventHandler<AgentWearablesReplyEventArgs>,
) -> Subscription {
self.inner.events.agent_wearables_reply.subscribe(handler)
}
pub(crate) fn native_subscribe_appearance_set(
&self,
handler: EventHandler<AppearanceSetEventArgs>,
) -> Subscription {
self.inner.events.appearance_set.subscribe(handler)
}
#[allow(clippy::unnecessary_wraps)] // The mapped Dispose API is fallible.
pub(crate) fn native_dispose(&self) -> Result<(), Error> {
self.inner.disposed.store(true, Ordering::Release);
write(&self.inner.wearables).clear();
write(&self.inner.attachments).clear();
Ok(())
}
pub(crate) fn native_manager_busy(&self) -> bool {
self.inner.busy.load(Ordering::Acquire)
}
pub(crate) fn native_last_cof_version(&self) -> i32 {
self.inner.last_cof_version.load(Ordering::Acquire)
}
#[allow(clippy::unnecessary_wraps)] // The mapped method returns Result<(), Error>.
pub(crate) fn native_update_cof_version(&self, version: i32) -> Result<(), Error> {
self.inner
.last_cof_version
.fetch_max(version, Ordering::AcqRel);
Ok(())
}
pub(crate) fn native_add_to_outfit(
&self,
items: Vec<InventoryItem>,
replace: bool,
) -> Result<(), Error> {
let busy = self.begin()?;
let mut next = read(&self.inner.wearables).clone();
apply_wearables(&mut next, items, replace)?;
self.send_wearables(&next)?;
*write(&self.inner.wearables) = next;
drop(busy);
self.inner
.events
.appearance_set
.emit_with(|| AppearanceSetEventArgs);
self.inner
.events
.agent_wearables_reply
.emit_with(|| AgentWearablesReplyEventArgs);
Ok(())
}
pub(crate) fn native_remove_from_outfit(&self, items: Vec<InventoryItem>) -> Result<(), Error> {
let busy = self.begin()?;
let ids: HashSet<_> = items.into_iter().map(|item| item.base.uuid()).collect();
let mut next = read(&self.inner.wearables).clone();
next.retain(|wearable| !ids.contains(&wearable.item_id));
self.send_wearables(&next)?;
*write(&self.inner.wearables) = next;
drop(busy);
self.inner
.events
.appearance_set
.emit_with(|| AppearanceSetEventArgs);
self.inner
.events
.agent_wearables_reply
.emit_with(|| AgentWearablesReplyEventArgs);
Ok(())
}
#[allow(clippy::unused_async)] // Preserves the mapped asynchronous API boundary.
pub(crate) async fn native_replace_outfit(
&self,
items: Vec<InventoryItem>,
safe: bool,
) -> Result<(), Error> {
let busy = self.begin()?;
let mut next = if safe {
read(&self.inner.wearables)
.iter()
.filter(|wearable| wearable.asset_type == AssetType::Bodypart)
.cloned()
.collect()
} else {
Vec::new()
};
apply_wearables(&mut next, items, false)?;
self.send_wearables(&next)?;
*write(&self.inner.wearables) = next;
drop(busy);
self.inner
.events
.appearance_set
.emit_with(|| AppearanceSetEventArgs);
self.inner
.events
.agent_wearables_reply
.emit_with(|| AgentWearablesReplyEventArgs);
Ok(())
}
pub(crate) async fn native_wear_outfit(
&self,
values: Vec<InventoryBase>,
replace: bool,
) -> Result<(), Error> {
let client = self.client()?;
let store = client
.native_inventory()?
.native_store()
.ok_or(Error::InvalidOperation)?;
let items: Vec<_> = values
.into_iter()
.filter_map(|value| store.native_item_value(value.uuid()))
.collect();
if replace {
self.native_replace_outfit(items, false).await
} else {
self.native_add_to_outfit(items, false)
}
}
pub(crate) fn native_get_wearables(
&self,
) -> Result<Box<dyn Iterator<Item = AppearanceManagerWearableData>>, Error> {
self.client()?;
let values: Vec<_> = read(&self.inner.wearables)
.iter()
.map(WearableRecord::public)
.collect();
Ok(Box::new(values.into_iter()))
}
pub(crate) fn native_get_wearable_assets(
&self,
wearable_type: WearableType,
) -> Result<Box<dyn Iterator<Item = UUID>>, Error> {
self.client()?;
let values: Vec<_> = read(&self.inner.wearables)
.iter()
.filter(|wearable| wearable.wearable_type == wearable_type)
.map(|wearable| wearable.asset_id)
.collect();
Ok(Box::new(values.into_iter()))
}
pub(crate) fn native_get_wearables_by_type(
&self,
) -> Result<MultiValueDictionary<WearableType, AppearanceManagerWearableData>, Error> {
self.client()?;
let result = MultiValueDictionary::new_with_constructor()?;
for wearable in read(&self.inner.wearables).iter() {
result.add(wearable.wearable_type, Some(wearable.public()))?;
}
Ok(result)
}
pub(crate) fn native_is_item_worn(&self, item_id: UUID) -> Result<WearableType, Error> {
self.client()?;
Ok(read(&self.inner.wearables)
.iter()
.find(|wearable| wearable.item_id == item_id)
.map_or(WearableType::Invalid, |wearable| wearable.wearable_type))
}
#[allow(clippy::unused_async)] // Preserves the mapped asynchronous API boundary.
pub(crate) async fn native_send_outfit(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<(), Error> {
check_cancelled(cancellation_token.as_ref())?;
let busy = self.begin()?;
let snapshot = read(&self.inner.wearables).clone();
check_cancelled(cancellation_token.as_ref())?;
self.send_wearables(&snapshot)?;
drop(busy);
self.inner
.events
.appearance_set
.emit_with(|| AppearanceSetEventArgs);
Ok(())
}
fn send_wearables(&self, wearables: &[WearableRecord]) -> Result<(), Error> {
let client = self.client()?;
let network = client.native_network()?;
let mut packet = AgentIsNowWearingPacket::new_with_constructor()?;
packet.agent_data.agent_id = network.native_agent_id();
packet.agent_data.session_id = network.native_session_id();
packet.wearable_data = wearables
.iter()
.map(|wearable| {
let mut block = AgentIsNowWearingPacketWearableDataBlock::new_with_constructor()?;
block.item_id = wearable.item_id;
block.wearable_type = wearable.wearable_type as u8;
Ok(block)
})
.collect::<Result<Vec<_>, Error>>()?;
send_generated(
&network,
PacketType::AgentIsNowWearing,
packet.to_bytes_with_method()?,
)?;
Ok(())
}
pub(crate) fn native_attach(
&self,
item: InventoryItem,
point: AttachmentPoint,
replace: bool,
) -> Result<(), Error> {
let busy = self.begin()?;
let name = item.base.name();
let description = item.description();
let permissions = item.permissions();
self.send_attach_packet(
item.base.uuid(),
item.base.owner_id(),
&name,
&description,
permissions,
item.flags(),
point,
replace,
)?;
self.record_attachment(item, point, replace);
drop(busy);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn native_attach_fields(
&self,
item_id: UUID,
owner_id: UUID,
name: String,
description: String,
permissions: Permissions,
item_flags: u32,
point: AttachmentPoint,
replace: bool,
) -> Result<(), Error> {
let busy = self.begin()?;
self.send_attach_packet(
item_id,
owner_id,
&name,
&description,
permissions,
item_flags,
point,
replace,
)?;
let mut item = InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, item_id)?;
item.base.set_owner_id(owner_id);
item.base.set_name(name);
item.set_description(description);
item.set_permissions(permissions);
item.set_flags(item_flags);
item.set_asset_type(AssetType::Object);
self.record_attachment(item, point, replace);
drop(busy);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn send_attach_packet(
&self,
item_id: UUID,
owner_id: UUID,
name: &str,
description: &str,
permissions: Permissions,
item_flags: u32,
point: AttachmentPoint,
replace: bool,
) -> Result<(), Error> {
let client = self.client()?;
let network = client.native_network()?;
send_generated(
&network,
PacketType::RezSingleAttachmentFromInv,
attachment_packet_bytes(
network.native_agent_id(),
network.native_session_id(),
item_id,
owner_id,
name,
description,
permissions,
item_flags,
point,
replace,
)?,
)
}
fn record_attachment(&self, item: InventoryItem, point: AttachmentPoint, replace: bool) {
let mut attachments = write(&self.inner.attachments);
if replace && point != AttachmentPoint::Default {
attachments.retain(|existing| existing.point != point);
}
attachments.retain(|existing| existing.item.base.uuid() != item.base.uuid());
attachments.push(AttachmentRecord { item, point });
}
pub(crate) fn native_add_attachments(
&self,
items: Vec<InventoryItem>,
remove_existing_first: bool,
replace: bool,
) -> Result<(), Error> {
let busy = self.begin()?;
let existing = read(&self.inner.attachments).clone();
if remove_existing_first {
for attachment in &existing {
self.send_detach_packet(attachment.item.base.uuid())?;
write(&self.inner.attachments)
.retain(|candidate| candidate.item.base.uuid() != attachment.item.base.uuid());
}
}
for item in items {
let object = InventoryObject { base: item.clone() };
let point = object.attach_point();
let name = item.base.name();
let description = item.description();
self.send_attach_packet(
item.base.uuid(),
item.base.owner_id(),
&name,
&description,
item.permissions(),
item.flags(),
point,
replace,
)?;
self.record_attachment(item, point, replace);
}
drop(busy);
Ok(())
}
pub(crate) fn native_detach(&self, item_id: UUID) -> Result<(), Error> {
let busy = self.begin()?;
self.send_detach_packet(item_id)?;
write(&self.inner.attachments).retain(|attachment| attachment.item.base.uuid() != item_id);
drop(busy);
Ok(())
}
fn send_detach_packet(&self, item_id: UUID) -> Result<(), Error> {
let client = self.client()?;
let network = client.native_network()?;
let mut packet = DetachAttachmentIntoInvPacket::new_with_constructor()?;
packet.object_data.agent_id = network.native_agent_id();
packet.object_data.item_id = item_id;
send_generated(
&network,
PacketType::DetachAttachmentIntoInv,
packet.to_bytes_with_method()?,
)
}
pub(crate) fn native_get_attachments(
&self,
) -> Result<Box<dyn Iterator<Item = InventoryItem>>, Error> {
self.client()?;
let values: Vec<_> = read(&self.inner.attachments)
.iter()
.map(|attachment| attachment.item.clone())
.collect();
Ok(Box::new(values.into_iter()))
}
#[allow(clippy::unused_async)] // Preserves the mapped asynchronous API boundary.
pub(crate) async fn native_get_attachments_by_point(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<MultiValueDictionary<AttachmentPoint, InventoryItem>, Error> {
check_cancelled(cancellation_token.as_ref())?;
self.client()?;
let result = MultiValueDictionary::new_with_constructor()?;
for attachment in read(&self.inner.attachments).iter() {
result.add(attachment.point, Some(attachment.item.clone()))?;
}
Ok(result)
}
pub(crate) fn native_get_attachments_by_item(
&self,
) -> Result<HashMap<InventoryItem, AttachmentPoint>, Error> {
self.client()?;
Ok(read(&self.inner.attachments)
.iter()
.map(|attachment| (attachment.item.clone(), attachment.point))
.collect())
}
pub(crate) fn native_get_attachments_by_id(
&self,
) -> Result<HashMap<UUID, AttachmentPoint>, Error> {
self.client()?;
Ok(read(&self.inner.attachments)
.iter()
.map(|attachment| (attachment.item.base.uuid(), attachment.point))
.collect())
}
pub(crate) fn native_is_item_attached(&self, item_id: UUID) -> bool {
read(&self.inner.attachments)
.iter()
.any(|attachment| attachment.item.base.uuid() == item_id)
}
pub(crate) fn native_attachments_at(&self, point: AttachmentPoint) -> Vec<InventoryItem> {
read(&self.inner.attachments)
.iter()
.filter(|attachment| attachment.point == point)
.map(|attachment| attachment.item.clone())
.collect()
}
pub(crate) fn native_attachment_point(&self, item_id: UUID) -> Option<AttachmentPoint> {
read(&self.inner.attachments)
.iter()
.find(|attachment| attachment.item.base.uuid() == item_id)
.map(|attachment| attachment.point)
}
#[allow(clippy::unused_async)] // Preserves the mapped asynchronous API boundary.
pub(crate) async fn native_get_current_outfit_folder(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<InventoryFolder>, Error> {
check_cancelled(cancellation_token.as_ref())?;
let client = self.client()?;
let Some(store) = client.native_inventory()?.native_store() else {
return Ok(None);
};
let Some(id) = store.find_folder_for_type(FolderType::CurrentOutfit) else {
return Ok(None);
};
store.get_value_or_default_with_uuid_a7c63fbe(id)
}
#[allow(clippy::unused_async)] // Preserves the mapped asynchronous API boundary.
pub(crate) async fn native_get_folder_wearables(
&self,
folder: UUID,
cancellation_token: Option<CancellationToken>,
) -> Result<(bool, Vec<InventoryWearable>, Vec<InventoryItem>), Error> {
check_cancelled(cancellation_token.as_ref())?;
let client = self.client()?;
let Some(store) = client.native_inventory()?.native_store() else {
return Ok((false, Vec::new(), Vec::new()));
};
let mut queue = VecDeque::from([folder]);
let mut visited = HashSet::new();
let mut scheduled = HashSet::from([folder]);
let mut wearables = Vec::new();
let mut attachments = Vec::new();
while let Some(folder_id) = queue.pop_front() {
check_cancelled(cancellation_token.as_ref())?;
if !visited.insert(folder_id) {
continue;
}
let Ok(contents) = store.get_contents_with_uuid(folder_id) else {
return Ok((false, wearables, attachments));
};
for value in contents {
if let Some(child) = value.as_any().downcast_ref::<InventoryFolder>() {
let child_id = child.base.uuid();
if scheduled.insert(child_id) {
if scheduled.len() > MAX_FOLDER_TRAVERSAL {
return Ok((false, wearables, attachments));
}
queue.push_back(child_id);
}
continue;
}
let Some(item) = inventory_item(value.as_ref()) else {
continue;
};
if matches!(item.asset_type(), AssetType::Bodypart | AssetType::Clothing) {
wearables.push(InventoryWearable { base: item });
} else if matches!(item.asset_type(), AssetType::Object | AssetType::Mesh) {
attachments.push(item);
}
}
}
Ok((true, wearables, attachments))
}
pub(crate) async fn native_request_agent_worn(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<Vec<InventoryBase>, Error> {
let client = self.client()?;
let cof = CurrentOutfitFolder::new(Some(Arc::new(client)))?;
let links = cof
.get_current_outfit_links(cancellation_token.clone())
.await?;
check_cancelled(cancellation_token.as_ref())?;
Ok(links.into_iter().map(|item| item.base).collect())
}
}
impl PartialEq for AppearanceManagerWearableData {
fn eq(&self, other: &Self) -> bool {
self.asset_id == other.asset_id
&& self.asset_type == other.asset_type
&& self.item_id == other.item_id
&& self.wearable_type == other.wearable_type
}
}
impl Eq for AppearanceManagerWearableData {}
pub(crate) const fn wearable_type_to_asset_type(wearable_type: WearableType) -> AssetType {
match wearable_type {
WearableType::Shape | WearableType::Skin | WearableType::Hair | WearableType::Eyes => {
AssetType::Bodypart
}
WearableType::Invalid => AssetType::Unknown,
_ => AssetType::Clothing,
}
}
fn apply_wearables(
current: &mut Vec<WearableRecord>,
items: Vec<InventoryItem>,
replace: bool,
) -> Result<(), Error> {
let mut seen = HashSet::new();
for item in items {
let wearable = WearableRecord::from_item(&item).ok_or(Error::Argument)?;
if !seen.insert(wearable.item_id)
|| current
.iter()
.any(|existing| existing.item_id == wearable.item_id)
{
continue;
}
if replace || wearable.asset_type == AssetType::Bodypart {
current.retain(|existing| existing.wearable_type != wearable.wearable_type);
} else if current
.iter()
.filter(|existing| existing.wearable_type == wearable.wearable_type)
.count()
>= MAX_WEARABLE_LAYERS
{
return Err(Error::InvalidOperation);
}
current.push(wearable);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn attachment_packet_bytes(
agent_id: UUID,
session_id: UUID,
item_id: UUID,
owner_id: UUID,
name: &str,
description: &str,
permissions: Permissions,
item_flags: u32,
point: AttachmentPoint,
replace: bool,
) -> Result<Vec<u8>, Error> {
let mut packet = RezSingleAttachmentFromInvPacket::new_with_constructor()?;
packet.agent_data.agent_id = agent_id;
packet.agent_data.session_id = session_id;
packet.object_data.item_id = item_id;
packet.object_data.owner_id = owner_id;
packet.object_data.name = name.as_bytes().to_vec();
packet.object_data.description = description.as_bytes().to_vec();
packet.object_data.everyone_mask = permissions.everyone_mask.0;
packet.object_data.group_mask = permissions.group_mask.0;
packet.object_data.next_owner_mask = permissions.next_owner_mask.0;
packet.object_data.item_flags = item_flags;
packet.object_data.attachment_pt = (point as u8) | if replace { 0 } else { ATTACHMENT_ADD };
packet.to_bytes_with_method()
}
fn send_generated(
network: &crate::NetworkManager,
packet_type: PacketType,
bytes: Vec<u8>,
) -> Result<(), Error> {
let simulator = network
.native_current_sim()
.ok_or(Error::InvalidOperation)?;
let length = i32::try_from(bytes.len()).map_err(|_| Error::Argument)?;
simulator.native_send_packet_data(bytes, length, packet_type, false)
}
fn inventory_item(value: &dyn InventoryObjectClass) -> Option<InventoryItem> {
if let Some(item) = value.as_any().downcast_ref::<InventoryItem>() {
return Some(item.clone());
}
macro_rules! item {
($kind:ty) => {
if let Some(item) = value.as_any().downcast_ref::<$kind>() {
return Some(item.base.clone());
}
};
}
item!(crate::InventoryAnimation);
item!(crate::InventoryAttachment);
item!(crate::InventoryCallingCard);
item!(crate::InventoryCategory);
item!(crate::InventoryGesture);
item!(crate::InventoryLSL);
item!(crate::InventoryLandmark);
item!(crate::InventoryMaterial);
item!(crate::InventoryNotecard);
item!(crate::InventoryObject);
item!(crate::InventorySettings);
item!(crate::InventorySnapshot);
item!(crate::InventorySound);
item!(crate::InventoryTexture);
item!(crate::InventoryWearable);
None
}
#[cfg(test)]
mod tests {
use super::*;
fn wearable(id: u64, wearable_type: WearableType, asset_type: AssetType) -> InventoryItem {
let mut wearable = InventoryWearable::new(UUID::new_with_u_int64(id).unwrap()).unwrap();
wearable.base.set_asset_type(asset_type);
wearable.base.set_inventory_type(InventoryType::WEARABLE);
wearable
.base
.set_asset_uuid(UUID::new_with_u_int64(id + 1_000).unwrap());
wearable.set_wearable_type(wearable_type);
wearable.base
}
#[test]
fn wearable_layers_are_bounded_and_bodyparts_replace_by_slot() {
let mut clothing = Vec::new();
apply_wearables(
&mut clothing,
(1..=60)
.map(|id| wearable(id, WearableType::Shirt, AssetType::Clothing))
.collect(),
false,
)
.unwrap();
assert_eq!(clothing.len(), MAX_WEARABLE_LAYERS);
assert_eq!(
apply_wearables(
&mut clothing,
vec![wearable(61, WearableType::Shirt, AssetType::Clothing)],
false,
),
Err(Error::InvalidOperation)
);
let mut bodyparts = Vec::new();
apply_wearables(
&mut bodyparts,
vec![wearable(100, WearableType::Shape, AssetType::Bodypart)],
false,
)
.unwrap();
apply_wearables(
&mut bodyparts,
vec![wearable(101, WearableType::Shape, AssetType::Bodypart)],
false,
)
.unwrap();
assert_eq!(bodyparts.len(), 1);
assert_eq!(bodyparts[0].item_id, UUID::new_with_u_int64(101).unwrap());
}
#[test]
fn attachment_packet_and_replacement_state_preserve_inventory_fields() {
let client = GridClient::new().unwrap();
let item_id = UUID::new_with_u_int64(500).unwrap();
let owner_id = UUID::new_with_u_int64(501).unwrap();
let permissions = Permissions::new(1, 2, 3, 4, 5).unwrap();
let mut item =
InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, item_id).unwrap();
item.base.set_owner_id(owner_id);
item.base.set_name("recording hat".into());
item.set_description("packet fields".into());
item.set_permissions(permissions);
item.set_flags(0x1234_5678);
item.set_asset_type(AssetType::Object);
let bytes = attachment_packet_bytes(
UUID::zero(),
UUID::zero(),
item_id,
owner_id,
"recording hat",
"packet fields",
permissions,
0x1234_5678,
AttachmentPoint::Skull,
false,
)
.unwrap();
let mut position = 0;
let packet =
RezSingleAttachmentFromInvPacket::new_with_bytes_int32(bytes, &mut position).unwrap();
assert_eq!(packet.object_data.item_id, item_id);
assert_eq!(packet.object_data.owner_id, owner_id);
assert_eq!(packet.object_data.name, b"recording hat");
assert_eq!(packet.object_data.description, b"packet fields");
assert_eq!(packet.object_data.everyone_mask, 2);
assert_eq!(packet.object_data.group_mask, 3);
assert_eq!(packet.object_data.next_owner_mask, 4);
assert_eq!(packet.object_data.item_flags, 0x1234_5678);
assert_eq!(
packet.object_data.attachment_pt,
(AttachmentPoint::Skull as u8) | ATTACHMENT_ADD
);
let manager = client.appearance();
manager.record_attachment(item, AttachmentPoint::Skull, false);
let replacement_id = UUID::new_with_u_int64(502).unwrap();
let mut replacement =
InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, replacement_id)
.unwrap();
replacement.set_permissions(permissions);
manager.record_attachment(replacement, AttachmentPoint::Skull, true);
let state = manager.native_get_attachments_by_id().unwrap();
assert_eq!(state.len(), 1);
assert_eq!(state.get(&replacement_id), Some(&AttachmentPoint::Skull));
}
}

View File

@@ -521,6 +521,7 @@ impl AssetTexture {
}
}
#[derive(Clone, Debug)]
pub struct AssetWearable {
raw: RawAsset,
pub creator: UUID,

View File

@@ -118,6 +118,7 @@ struct ClientRuntime {
caps_rate_limiter: Mutex<crate::caps_http::CapsRateLimiter>,
network_manager: Mutex<std::sync::Weak<crate::network_manager::NetworkManagerInner>>,
agent_manager: Mutex<std::sync::Weak<crate::agent_manager::AgentManagerInner>>,
appearance_manager: Mutex<Option<Arc<crate::appearance_manager::AppearanceManagerInner>>>,
inventory_manager: Mutex<Option<Arc<crate::inventory_manager::InventoryManagerInner>>>,
inventory_ais_client: Mutex<Option<crate::inventory_ais::InventoryAISClient>>,
asset_manager: Mutex<Option<Arc<crate::asset_manager::AssetManagerInner>>>,
@@ -149,6 +150,7 @@ impl ClientRuntime {
caps_rate_limiter: Mutex::new(caps_rate_limiter),
network_manager: Mutex::new(std::sync::Weak::new()),
agent_manager: Mutex::new(std::sync::Weak::new()),
appearance_manager: Mutex::new(None),
inventory_manager: Mutex::new(None),
inventory_ais_client: Mutex::new(None),
asset_manager: Mutex::new(None),
@@ -449,6 +451,32 @@ impl GridClient {
Ok(manager)
}
pub(crate) fn native_appearance(&self) -> Result<crate::AppearanceManager, crate::Error> {
let mut cached = self
.runtime
.appearance_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Ok(
crate::appearance_manager::AppearanceManager::native_from_inner(Arc::clone(inner)),
);
}
let manager =
crate::appearance_manager::AppearanceManager::native_new(Some(Arc::new(self.clone())))?;
*cached = Some(manager.native_inner());
Ok(manager)
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn native_set_appearance(&mut self, value: crate::AppearanceManager) {
*self
.runtime
.appearance_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner());
}
pub(crate) fn native_assets(&self) -> Result<crate::AssetManager, crate::Error> {
let mut cached = self
.runtime

File diff suppressed because it is too large Load Diff

View File

@@ -2861,12 +2861,9 @@ impl AppearanceFlags {
}
/// C# type: `T:LibreMetaverse.AppearanceManager`.
pub struct AppearanceManager {
/// C# member: `F:LibreMetaverse.AppearanceManager.MyTextures`.
pub my_textures: libremetaverse::PrimitiveTextureEntry,
/// C# member: `F:LibreMetaverse.AppearanceManager.MyVisualParameters`.
pub my_visual_parameters: Vec<u8>,
}
pub use crate::appearance_manager::AppearanceManager;
/// C# member: `F:LibreMetaverse.AppearanceManager.MyTextures`.
/// C# member: `F:LibreMetaverse.AppearanceManager.MyVisualParameters`.
impl AppearanceManager {
/// C# member: `E:LibreMetaverse.AppearanceManager.AgentWearablesReply`.
pub fn subscribe_agent_wearables_reply(
@@ -2875,16 +2872,14 @@ impl AppearanceManager {
libremetaverse::AgentWearablesReplyEventArgs,
>,
) -> libremetaverse_types::compat::Subscription {
libremetaverse_types::unimplemented_api!(
"E:LibreMetaverse.AppearanceManager.AgentWearablesReply"
)
self.native_subscribe_agent_wearables_reply(handler)
}
/// C# member: `E:LibreMetaverse.AppearanceManager.AppearanceSet`.
pub fn subscribe_appearance_set(
&self,
handler: libremetaverse_types::compat::EventHandler<libremetaverse::AppearanceSetEventArgs>,
) -> libremetaverse_types::compat::Subscription {
libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AppearanceManager.AppearanceSet")
self.native_subscribe_appearance_set(handler)
}
/// C# member: `E:LibreMetaverse.AppearanceManager.CachedBakesReply`.
pub fn subscribe_cached_bakes_reply(
@@ -3025,9 +3020,7 @@ impl AppearanceManager {
pub fn new(
client: Option<std::sync::Arc<libremetaverse::GridClient>>,
) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.#ctor(LibreMetaverse.GridClient)",
)
crate::appearance_manager::AppearanceManager::native_new(client)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.AddAttachments(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Boolean)`.
pub fn add_attachments(
@@ -3036,9 +3029,7 @@ impl AppearanceManager {
remove_existing_first: bool,
replace: Option<bool>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.AddAttachments(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Boolean)",
)
self.native_add_attachments(attachments, remove_existing_first, replace.unwrap_or(false))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.AddToOutfit(LibreMetaverse.InventoryItem,System.Boolean)`.
pub fn add_to_outfit_with_inventory_item_boolean(
@@ -3046,9 +3037,7 @@ impl AppearanceManager {
wearable_item: libremetaverse::InventoryItem,
replace: Option<bool>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.AddToOutfit(LibreMetaverse.InventoryItem,System.Boolean)",
)
self.native_add_to_outfit(vec![wearable_item], replace.unwrap_or(false))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.AddToOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)`.
pub fn add_to_outfit_with_list_boolean(
@@ -3056,9 +3045,7 @@ impl AppearanceManager {
wearable_items: Vec<libremetaverse::InventoryItem>,
replace: Option<bool>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.AddToOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)",
)
self.native_add_to_outfit(wearable_items, replace.unwrap_or(false))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.ApplyArchetype(LibreMetaverse.GenepoolArchetype)`.
pub async fn apply_archetype(
@@ -3076,9 +3063,7 @@ impl AppearanceManager {
attach_point: libremetaverse_types::AttachmentPoint,
replace: Option<bool>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean)",
)
self.native_attach(item, attach_point, replace.unwrap_or(false))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.Permissions,System.UInt32,LibreMetaverse.AttachmentPoint,System.Boolean)`.
pub fn attach_with_uuid_uuid_string_string_permissions_u_int32_attachment_point_boolean(
@@ -3092,8 +3077,15 @@ impl AppearanceManager {
attach_point: libremetaverse_types::AttachmentPoint,
replace: Option<bool>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.Permissions,System.UInt32,LibreMetaverse.AttachmentPoint,System.Boolean)",
self.native_attach_fields(
item_id,
owner_id,
name,
description,
perms,
item_flags,
attach_point,
replace.unwrap_or(false),
)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.BakeTypeToAgentTextureIndex(LibreMetaverse.BakeType)`.
@@ -3136,28 +3128,24 @@ impl AppearanceManager {
&self,
item: libremetaverse::InventoryItem,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.InventoryItem)",
)
self.native_detach(item.base.uuid())
}
/// C# member: `M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.UUID)`.
pub fn detach_with_uuid(
&self,
item_id: libremetaverse_types::UUID,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.UUID)",
)
self.native_detach(item_id)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.Dispose`.
pub fn dispose(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.AppearanceManager.Dispose")
self.native_dispose()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetAttachments`.
pub fn get_attachments(
&self,
) -> Result<Box<dyn Iterator<Item = libremetaverse::InventoryItem>>, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.AppearanceManager.GetAttachments")
self.native_get_attachments()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetAttachmentsByAttachmentPointAsync(System.Threading.CancellationToken)`.
pub async fn get_attachments_by_attachment_point(
@@ -3170,9 +3158,8 @@ impl AppearanceManager {
>,
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByAttachmentPointAsync(System.Threading.CancellationToken)",
)
self.native_get_attachments_by_point(cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetAttachmentsByInventoryItem`.
pub fn get_attachments_by_inventory_item(
@@ -3184,9 +3171,7 @@ impl AppearanceManager {
>,
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByInventoryItem",
)
self.native_get_attachments_by_item()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetAttachmentsByItemId`.
pub fn get_attachments_by_item_id(
@@ -3198,9 +3183,7 @@ impl AppearanceManager {
>,
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByItemId",
)
self.native_get_attachments_by_id()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetColorFromParams(System.Collections.Generic.List{LibreMetaverse.AppearanceManager.ColorParamInfo})`.
pub fn get_color_from_params(
@@ -3215,9 +3198,8 @@ impl AppearanceManager {
&self,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Option<libremetaverse::InventoryFolder>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)",
)
self.native_get_current_outfit_folder(cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetCurrentParamValues`.
pub fn get_current_param_values(
@@ -3240,25 +3222,22 @@ impl AppearanceManager {
),
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
)
self.native_get_folder_wearables(folder, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetWearableAssets(LibreMetaverse.WearableType)`.
pub fn get_wearable_assets(
&self,
type_: libremetaverse_types::WearableType,
) -> Result<Box<dyn Iterator<Item = libremetaverse_types::UUID>>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetWearableAssets(LibreMetaverse.WearableType)",
)
self.native_get_wearable_assets(type_)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetWearables`.
pub fn get_wearables(
&self,
) -> Result<Box<dyn Iterator<Item = libremetaverse::AppearanceManagerWearableData>>, crate::Error>
{
libremetaverse_types::not_implemented("M:LibreMetaverse.AppearanceManager.GetWearables")
self.native_get_wearables()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.GetWearablesByType`.
pub fn get_wearables_by_type(
@@ -3270,27 +3249,21 @@ impl AppearanceManager {
>,
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.GetWearablesByType",
)
self.native_get_wearables_by_type()
}
/// C# member: `M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.InventoryItem)`.
pub fn is_item_worn_with_inventory_item(
&self,
item: libremetaverse::InventoryItem,
) -> Result<libremetaverse_types::WearableType, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.InventoryItem)",
)
self.native_is_item_worn(item.base.uuid())
}
/// C# member: `M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.UUID)`.
pub fn is_item_worn_with_uuid(
&self,
item_id: libremetaverse_types::UUID,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.UUID)",
)
Ok(self.native_is_item_worn(item_id)? != libremetaverse_types::WearableType::Invalid)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.MakeAppearancePacket`.
pub fn make_appearance_packet(
@@ -3322,27 +3295,21 @@ impl AppearanceManager {
&self,
wearable_item: libremetaverse::InventoryItem,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.RemoveFromOutfit(LibreMetaverse.InventoryItem)",
)
self.native_remove_from_outfit(vec![wearable_item])
}
/// C# member: `M:LibreMetaverse.AppearanceManager.RemoveFromOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem})`.
pub fn remove_from_outfit_with_list(
&self,
wearable_items: Vec<libremetaverse::InventoryItem>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.RemoveFromOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem})",
)
self.native_remove_from_outfit(wearable_items)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem})`.
pub async fn replace_outfit_with_list(
&self,
wearable_items: Vec<libremetaverse::InventoryItem>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem})",
)
self.native_replace_outfit(wearable_items, false).await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)`.
pub async fn replace_outfit_with_list_boolean(
@@ -3350,18 +3317,14 @@ impl AppearanceManager {
wearable_items: Vec<libremetaverse::InventoryItem>,
safe: bool,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)",
)
self.native_replace_outfit(wearable_items, safe).await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.RequestAgentWornAsync(System.Threading.CancellationToken)`.
pub async fn request_agent_worn(
&self,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Vec<libremetaverse::InventoryBase>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.RequestAgentWornAsync(System.Threading.CancellationToken)",
)
self.native_request_agent_worn(cancellation_token).await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.RequestCachedBakes`.
pub fn request_cached_bakes(&self) -> Result<(), crate::Error> {
@@ -3383,9 +3346,7 @@ impl AppearanceManager {
&self,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)",
)
self.native_send_outfit(cancellation_token).await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.ServerBakingRegion`.
pub fn server_baking_region(&self) -> Result<bool, crate::Error> {
@@ -3398,9 +3359,7 @@ impl AppearanceManager {
&mut self,
cof_version: i32,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)",
)
self.native_update_cof_version(cof_version)
}
/// C# member: `M:LibreMetaverse.AppearanceManager.WearOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryBase},System.Boolean)`.
pub async fn wear_outfit(
@@ -3408,45 +3367,37 @@ impl AppearanceManager {
wearables: Vec<libremetaverse::InventoryBase>,
replace_items: bool,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.WearOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryBase},System.Boolean)",
)
self.native_wear_outfit(wearables, replace_items).await
}
/// C# member: `M:LibreMetaverse.AppearanceManager.WearableTypeToAssetType(LibreMetaverse.WearableType)`.
pub fn wearable_type_to_asset_type(
type_: libremetaverse_types::WearableType,
) -> Result<libremetaverse_types::AssetType, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.WearableTypeToAssetType(LibreMetaverse.WearableType)",
)
Ok(crate::appearance_manager::wearable_type_to_asset_type(
type_,
))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.InventoryItem)`.
pub fn is_item_attached_with_inventory_item(
&self,
item: libremetaverse::InventoryItem,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.InventoryItem)",
)
Ok(self.native_is_item_attached(item.base.uuid()))
}
/// C# member: `M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.UUID)`.
pub fn is_item_attached_with_uuid(
&self,
key: libremetaverse_types::UUID,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.UUID)",
)
Ok(self.native_is_item_attached(key))
}
/// C# member: `P:LibreMetaverse.AppearanceManager.LastUpdateReceivedCOFVersion`.
pub fn last_update_received_cof_version(&self) -> i32 {
libremetaverse_types::unimplemented_api!(
"P:LibreMetaverse.AppearanceManager.LastUpdateReceivedCOFVersion"
)
self.native_last_cof_version()
}
/// C# member: `P:LibreMetaverse.AppearanceManager.ManagerBusy`.
pub fn manager_busy(&self) -> bool {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AppearanceManager.ManagerBusy")
self.native_manager_busy()
}
/// C# member: `P:LibreMetaverse.AppearanceManager.TextureProvider`.
pub fn texture_provider(&self) -> std::sync::Arc<dyn libremetaverse::IBakingTextureProvider> {
@@ -3507,6 +3458,7 @@ impl AppearanceManagerTextureData {
}
/// C# type: `T:LibreMetaverse.AppearanceManager.WearableData`.
#[derive(Clone, Debug)]
pub struct AppearanceManagerWearableData {
/// C# member: `F:LibreMetaverse.AppearanceManager.WearableData.Asset`.
pub asset: Option<libremetaverse::assets::AssetWearable>,
@@ -8786,11 +8738,14 @@ impl GridClient {
}
/// C# member: `P:LibreMetaverse.GridClient.Appearance`.
pub fn appearance(&self) -> libremetaverse::AppearanceManager {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Appearance")
/* native client-core implementation */
self.native_appearance()
.unwrap_or_else(|_| panic!("failed to construct AppearanceManager"))
}
/// Setter for C# member: `P:LibreMetaverse.GridClient.Appearance`.
pub fn set_appearance(&mut self, value: libremetaverse::AppearanceManager) {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Appearance")
/* native client-core implementation */
self.native_set_appearance(value)
}
/// C# member: `P:LibreMetaverse.GridClient.Assets`.
pub fn assets(&self) -> libremetaverse::AssetManager {
@@ -14722,9 +14677,13 @@ impl NameValue {
sendto_type: libremetaverse::NameValueSendtoType,
value: libremetaverse_types::compat::Object,
) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.NameValue.#ctor(System.String,LibreMetaverse.NameValue.ValueType,LibreMetaverse.NameValue.ClassType,LibreMetaverse.NameValue.SendtoType,System.Object)",
)
Ok(crate::NameValue {
class_: class_type,
name,
sendto: sendto_type,
type_: value_type,
value: Some(value),
})
}
/// C# member: `M:LibreMetaverse.NameValue.Equals(LibreMetaverse.NameValue)`.
pub fn equals_with_name_value(&self, other: libremetaverse::NameValue) -> bool {
@@ -18572,7 +18531,7 @@ pub struct Primitive {
impl Primitive {
/// C# member: `M:LibreMetaverse.Primitive.#ctor`.
pub fn new_with_constructor() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.Primitive.#ctor")
Ok(crate::current_outfit::primitive_defaults())
}
/// C# member: `M:LibreMetaverse.Primitive.#ctor(LibreMetaverse.Primitive)`.
pub fn new_with_primitive(prim: libremetaverse::Primitive) -> Result<Self, crate::Error> {
@@ -23074,13 +23033,11 @@ pub mod animesh {
pub mod appearance {
/// C# type: `T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy`.
pub struct CompositeCurrentOutfitPolicy;
pub use crate::current_outfit::CompositeCurrentOutfitPolicy;
impl CompositeCurrentOutfitPolicy {
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.#ctor",
)
crate::current_outfit::CompositeCurrentOutfitPolicy::native_new()
}
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)`.
pub fn add_policy(
@@ -23089,27 +23046,21 @@ pub mod appearance {
std::sync::Arc<dyn libremetaverse::appearance::ICurrentOutfitPolicy>,
>,
) -> Result<&Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)",
)
self.native_add_policy(policy_to_add)
}
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanAttach(LibreMetaverse.InventoryItem)`.
pub fn can_attach(
&self,
item: Option<std::sync::Arc<libremetaverse::InventoryItem>>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanAttach(LibreMetaverse.InventoryItem)",
)
self.native_can_attach(item)
}
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)`.
pub fn can_detach(
&self,
item: Option<std::sync::Arc<libremetaverse::InventoryItem>>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)",
)
self.native_can_detach(item)
}
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)`.
pub fn remove_policy(
@@ -23118,9 +23069,7 @@ pub mod appearance {
std::sync::Arc<dyn libremetaverse::appearance::ICurrentOutfitPolicy>,
>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)",
)
self.native_remove_policy(policy_to_remove)
}
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`.
pub async fn report_item_change(
@@ -23129,23 +23078,19 @@ pub mod appearance {
removed_items: Option<std::sync::Arc<Vec<libremetaverse::InventoryItem>>>,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)",
)
self.native_report_item_change(added_items, removed_items, cancellation_token)
.await
}
}
impl libremetaverse::appearance::ICurrentOutfitPolicy for CompositeCurrentOutfitPolicy {}
/// C# type: `T:LibreMetaverse.Appearance.CurrentOutfitFolder`.
pub struct CurrentOutfitFolder;
pub use crate::current_outfit::CurrentOutfitFolder;
impl CurrentOutfitFolder {
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.#ctor(LibreMetaverse.GridClient)`.
pub fn new(
client: Option<std::sync::Arc<libremetaverse::GridClient>>,
) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.#ctor(LibreMetaverse.GridClient)",
)
crate::current_outfit::CurrentOutfitFolder::native_new(client)
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)`.
pub fn add_policy(
@@ -23155,9 +23100,7 @@ pub mod appearance {
std::sync::Arc<dyn libremetaverse::appearance::ICurrentOutfitPolicy>,
crate::Error,
> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)",
)
self.native_add_policy(policy_to_add)
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(LibreMetaverse.InventoryItem,System.Boolean,System.Threading.CancellationToken)`.
pub async fn add_to_outfit_with_inventory_item_boolean_cancellation_token(
@@ -23166,9 +23109,8 @@ pub mod appearance {
replace: bool,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(LibreMetaverse.InventoryItem,System.Boolean,System.Threading.CancellationToken)",
)
self.native_add_to_outfit(vec![item], replace, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Threading.CancellationToken)`.
pub async fn add_to_outfit_with_list_boolean_cancellation_token(
@@ -23177,9 +23119,8 @@ pub mod appearance {
replace: bool,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Threading.CancellationToken)",
)
self.native_add_to_outfit(requested_items_to_add, replace, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.AttachAsync(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean,System.Threading.CancellationToken)`.
pub async fn attach(
@@ -23189,9 +23130,8 @@ pub mod appearance {
replace: bool,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AttachAsync(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean,System.Threading.CancellationToken)",
)
self.native_attach(item, point, replace, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanAttachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)`.
pub async fn can_attach_item(
@@ -23199,9 +23139,7 @@ pub mod appearance {
item: libremetaverse::InventoryItem,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanAttachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)",
)
self.native_can_attach_item(item, cancellation_token).await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanDetachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)`.
pub async fn can_detach_item(
@@ -23209,9 +23147,7 @@ pub mod appearance {
item: libremetaverse::InventoryItem,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanDetachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)",
)
self.native_can_detach_item(item, cancellation_token).await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.DetachAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)`.
pub async fn detach(
@@ -23219,15 +23155,11 @@ pub mod appearance {
item: libremetaverse::InventoryItem,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.DetachAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)",
)
self.native_detach(item, cancellation_token).await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.Dispose`.
pub fn dispose(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.Dispose",
)
self.native_dispose()
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.FetchParentAsync(LibreMetaverse.InventoryBase,System.Threading.CancellationToken)`.
pub async fn fetch_parent(
@@ -23235,26 +23167,21 @@ pub mod appearance {
item: libremetaverse::InventoryBase,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Option<libremetaverse::InventoryBase>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.FetchParentAsync(LibreMetaverse.InventoryBase,System.Threading.CancellationToken)",
)
self.native_fetch_parent(item, cancellation_token).await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetAttachmentItemID(LibreMetaverse.Primitive)`.
pub fn get_attachment_item_id(
prim: libremetaverse::Primitive,
) -> Result<libremetaverse_types::UUID, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetAttachmentItemID(LibreMetaverse.Primitive)",
)
crate::current_outfit::CurrentOutfitFolder::native_get_attachment_item_id(prim)
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetCurrentOutfitLinksAsync(System.Threading.CancellationToken)`.
pub async fn get_current_outfit_links(
&self,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Vec<libremetaverse::InventoryItem>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetCurrentOutfitLinksAsync(System.Threading.CancellationToken)",
)
self.native_get_current_outfit_links(cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetWornAtAsync(LibreMetaverse.WearableType,System.Threading.CancellationToken)`.
pub async fn get_worn_at(
@@ -23262,9 +23189,7 @@ pub mod appearance {
type_: libremetaverse_types::WearableType,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Vec<libremetaverse::InventoryItem>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetWornAtAsync(LibreMetaverse.WearableType,System.Threading.CancellationToken)",
)
self.native_get_worn_at(type_, cancellation_token).await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.IsObjectDescendentOfAsync(LibreMetaverse.InventoryBase,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
pub async fn is_object_descendent_of(
@@ -23273,9 +23198,8 @@ pub mod appearance {
parent_id: libremetaverse_types::UUID,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.IsObjectDescendentOfAsync(LibreMetaverse.InventoryBase,LibreMetaverse.UUID,System.Threading.CancellationToken)",
)
self.native_is_object_descendent_of(item, parent_id, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)`.
pub async fn remove_from_outfit_with_inventory_item_cancellation_token(
@@ -23283,9 +23207,8 @@ pub mod appearance {
item: libremetaverse::InventoryItem,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)",
)
self.native_remove_from_outfit(vec![item], cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`.
pub async fn remove_from_outfit_with_list_cancellation_token(
@@ -23293,18 +23216,15 @@ pub mod appearance {
requested_items_to_remove: Vec<libremetaverse::InventoryItem>,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)",
)
self.native_remove_from_outfit(requested_items_to_remove, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)`.
pub fn remove_policy(
&self,
policy_to_remove: std::sync::Arc<dyn libremetaverse::appearance::ICurrentOutfitPolicy>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)",
)
self.native_remove_policy(policy_to_remove)
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.ReplaceOutfitAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
pub async fn replace_outfit(
@@ -23312,36 +23232,27 @@ pub mod appearance {
new_outfit_folder_id: libremetaverse_types::UUID,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.ReplaceOutfitAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
)
self.native_replace_outfit(new_outfit_folder_id, cancellation_token)
.await
}
/// C# member: `M:LibreMetaverse.Appearance.CurrentOutfitFolder.ResolveInventoryLink(LibreMetaverse.InventoryItem)`.
pub fn resolve_inventory_link(
&self,
item_link: std::sync::Arc<libremetaverse::InventoryItem>,
) -> Result<Option<std::sync::Arc<libremetaverse::InventoryItem>>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.ResolveInventoryLink(LibreMetaverse.InventoryItem)",
)
self.native_resolve_inventory_link(item_link)
}
/// C# member: `P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF`.
pub fn cof(&self) -> Option<libremetaverse::InventoryFolder> {
libremetaverse_types::unimplemented_api!(
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF"
)
self.native_cof()
}
/// Setter for C# member: `P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF`.
pub fn set_cof(&mut self, value: Option<libremetaverse::InventoryFolder>) {
libremetaverse_types::unimplemented_api!(
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF"
)
self.native_set_cof(value)
}
/// C# member: `P:LibreMetaverse.Appearance.CurrentOutfitFolder.MaxClothingLayers`.
pub fn max_clothing_layers(&self) -> i32 {
libremetaverse_types::unimplemented_api!(
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.MaxClothingLayers"
)
crate::current_outfit::MAX_CLOTHING_LAYERS
}
}
@@ -23352,18 +23263,14 @@ pub mod appearance {
&self,
item: Option<std::sync::Arc<libremetaverse::InventoryItem>>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.CanAttach(LibreMetaverse.InventoryItem)",
)
crate::current_outfit::default_policy_decision(item)
}
/// C# member: `M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)`.
fn can_detach(
&self,
item: Option<std::sync::Arc<libremetaverse::InventoryItem>>,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)",
)
crate::current_outfit::default_policy_decision(item)
}
/// C# member: `M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`.
fn report_item_change(
@@ -23374,11 +23281,11 @@ pub mod appearance {
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)",
crate::current_outfit::default_policy_report(
added_items,
removed_items,
cancellation_token,
)
})
}
}

View File

@@ -18,6 +18,7 @@ use libremetaverse_types::{
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fs::{self, File, OpenOptions};
use std::hash::{Hash, Hasher};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock, Weak};
@@ -306,6 +307,24 @@ impl PartialEq for InventoryItem {
impl Eq for InventoryItem {}
impl Hash for InventoryItem {
fn hash<H: Hasher>(&self, state: &mut H) {
self.base.hash(state);
self.asset_uuid.hash(state);
self.asset_type.hash(state);
self.inventory_type.hash(state);
self.description.hash(state);
self.group_id.hash(state);
self.group_owned.hash(state);
self.sale_price.hash(state);
self.sale_type.hash(state);
self.flags.hash(state);
self.creation_date.hash(state);
self.last_owner_id.hash(state);
self.permissions.hash(state);
}
}
impl InventoryItem {
fn with_type(inventory_type: InventoryType, uuid: UUID) -> Self {
Self {

View File

@@ -7,6 +7,7 @@ mod attention_catalog;
mod agent_manager;
mod agent_messages;
mod agent_movement;
mod appearance_manager;
mod asset_cache;
mod asset_manager;
mod asset_material;
@@ -15,6 +16,7 @@ mod bit_pack;
mod caps;
mod caps_http;
mod client_core;
mod current_outfit;
mod download_manager;
mod event_queue;
mod gesture;
@@ -122,41 +124,6 @@ impl animesh::AnimeshPlayer {
}
}
#[allow(dead_code)]
trait CofLinkItem {
fn inventory_item(&self) -> &InventoryItem;
fn wearable_type(&self) -> Option<libremetaverse_types::WearableType>;
}
impl CofLinkItem for InventoryWearable {
fn inventory_item(&self) -> &InventoryItem {
&self.base
}
fn wearable_type(&self) -> Option<libremetaverse_types::WearableType> {
Some(self.wearable_type())
}
}
impl CofLinkItem for InventoryObject {
fn inventory_item(&self) -> &InventoryItem {
&self.base
}
fn wearable_type(&self) -> Option<libremetaverse_types::WearableType> {
None
}
}
#[allow(clippy::unused_self, dead_code)]
impl appearance::CurrentOutfitFolder {
fn get_cof_link_description(&self, _item: &dyn CofLinkItem) -> Result<String, Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetCofLinkDescription(LibreMetaverse.InventoryItem)",
)
}
}
#[allow(dead_code)] // Internal C# surface consumed by InventoryManager workflows.
impl InventoryAISClient {
async fn create_inventory_links(

61
docs/appearance.md Normal file
View File

@@ -0,0 +1,61 @@
# Appearance, wearables, and the Current Outfit Folder
`AppearanceManager` owns the client-visible wearable and attachment snapshots.
Wearables retain their inventory item, asset, asset type, and wearable slot. A
clothing slot accepts at most 60 ordered layers; body parts are single-value
slots and replace an existing value of the same type. Sending the outfit emits
the native `AgentIsNowWearing` packet from a snapshot, then publishes the
appearance event after the send. Wearable reply and appearance subscribers are
also snapshotted before invocation, so no state or subscriber lock is held
during a callback.
Attachment calls preserve the inventory owner, name, description, permission
masks, flags, and requested attachment point in
`RezSingleAttachmentFromInv`. Add versus replace is encoded in the attachment
point field, and replace removes only objects at the selected non-default
point. `DetachAttachmentIntoInv` is keyed by inventory item ID. Queries expose
the same state by item, item ID, and attachment point.
## Current Outfit Folder ownership
`CurrentOutfitFolder` discovers the `CurrentOutfit` system folder from the
inventory store unless an explicit folder has been supplied. Its contents are
links: link UUIDs identify COF entries and link asset UUIDs identify the worn
inventory objects. Resolution first consults committed local link targets and
then the inventory store. This keeps successful mutations immediately
queryable while an asynchronous inventory reply is still being reconciled.
Clothing link descriptions encode the wearable type followed by a two-digit
layer number. Add chooses the first free layer, preserves existing layers, and
rejects a 61st layer. Replace removes the selected wearable type before
allocating its new layers. Body-part and attachment descriptions remain empty,
matching the reference link format. Folder ancestry and recursive outfit
discovery are cycle-safe and bounded to 256 nodes.
Mutations pass through a one-permit asynchronous operation gate. Waiting for
the gate is cancellation-aware, but the gate is not an inventory/state lock;
network awaits never retain a synchronous lock. Cancellation is checked while
planning and before committing. Once a commit begins, it completes or runs its
compensation before reporting the result, preventing a cancellation race from
leaving a half-written outfit.
Outfit changes create replacement links before removing old links. If removal
fails, new links are removed and old link descriptions are recreated. Attach
creates its COF link before executing the rez request; replacement removes the
displaced links only after the new attachment succeeds. A later failure
detaches the new item and restores displaced attachments. Detach similarly
restores the attachment if its COF link cannot be removed. The local shadow is
changed only after those network steps succeed.
`ICurrentOutfitPolicy` remains a pure decision/reporting boundary. Composite
policies use a stable subscriber snapshot, consult every policy for attach and
detach decisions, and invoke reports in registration order. Policy locks are
released before callbacks and committed changes are reported exactly once.
Focused compatibility coverage lives in `current_outfit_folder_semantics`,
`current_outfit_policy_semantics`, the COF link-description cases, and the
issue-relevant appearance cases. Native recording fakes additionally prove
create/remove compensation, create-before-attach ordering, replacement state,
layer preservation, and cancellation before backend interaction. Texture
baking, visual-parameter synthesis, and texture upload are a separate
appearance pipeline and are not owned by this service slice.

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Audit issue 65's wearable, attachment, COF, and policy boundary."""
from __future__ import annotations
from pathlib import Path
import re
import generate_api_shims
ROOT = Path(__file__).resolve().parents[1]
APPEARANCE = ROOT / "crates" / "libremetaverse" / "src" / "appearance_manager.rs"
COF = ROOT / "crates" / "libremetaverse" / "src" / "current_outfit.rs"
CLIENT = ROOT / "crates" / "libremetaverse" / "src" / "client_core.rs"
COMPAT = ROOT / "tests" / "compat" / "tests"
LINK_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "batch_link_internal_semantics.rs"
DOC = ROOT / "docs" / "appearance.md"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
REQUIRED_TYPES = {
"T:LibreMetaverse.AppearanceManager",
"T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy",
"T:LibreMetaverse.Appearance.CurrentOutfitFolder",
}
REQUIRED_MEMBERS = {
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Threading.CancellationToken)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AttachAsync(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean,System.Threading.CancellationToken)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.DetachAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetCurrentOutfitLinksAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetWornAtAsync(LibreMetaverse.WearableType,System.Threading.CancellationToken)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.ReplaceOutfitAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean)",
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByAttachmentPointAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
"M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)",
}
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_types = sorted(REQUIRED_TYPES - native)
if missing_types:
raise SystemExit("issue 65 native types missing: " + ", ".join(missing_types))
missing_members = sorted(REQUIRED_MEMBERS - set(generate_api_shims.NATIVE_MEMBER_BODIES))
if missing_members:
raise SystemExit("issue 65 native members missing: " + ", ".join(missing_members))
for source in (APPEARANCE, COF):
if STUB_RE.search(source.read_text()):
raise SystemExit(f"issue 65 owned Rust stubs remain in {source.name}")
require_markers(APPEARANCE, (
"MAX_WEARABLE_LAYERS", "MAX_FOLDER_TRAVERSAL",
"AgentIsNowWearing", "RezSingleAttachmentFromInv",
"DetachAttachmentIntoInv", "permissions.everyone_mask",
"native_attachments_at", "emit_with",
"attachment_packet_and_replacement_state_preserve_inventory_fields",
"wearable_layers_are_bounded_and_bodyparts_replace_by_slot",
))
require_markers(COF, (
"MAX_CLOTHING_LAYERS", "MAX_ANCESTRY_DEPTH", "shadow_links",
"tokio::sync::Semaphore", "token.cancelled()", "snapshot()",
"create_links", "remove_links", "native_resolve_inventory_link",
"failed_replace_keeps_old_shadow_state_and_records_compensation",
"attach_creates_cof_link_before_network_execution",
"attachment_replace_removes_displaced_link_after_new_attachment",
"cancelled_operation_does_not_reach_backend",
))
require_markers(CLIENT, ("native_appearance", "native_set_appearance"))
require_markers(COMPAT / "current_outfit_folder_semantics.rs", ("parity-case", "get_current_outfit_links"))
require_markers(COMPAT / "current_outfit_policy_semantics.rs", ("parity-case", "report_item_change"))
require_markers(COMPAT / "appearance_semantics.rs", ("wearable_type_to_asset_type", "last_update_received_cof_version"))
require_markers(LINK_TESTS, ("get_cof_link_description", "clothing_wearable"))
require_markers(DOC, ("60", "256", "create replacement links before removing old links", "no state or subscriber lock", "compensation"))
print("issue 65 audit: native wearable/attachment state, transactional COF links, policy snapshots, cancellation, documentation, and focused fake/compatibility evidence are present")
if __name__ == "__main__":
main()

View File

@@ -291,6 +291,9 @@ NATIVE_TYPES = {
# hand-written, while its fixed public methods remain generator-audited.
NATIVE_DECLARATIONS = {
"T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager",
"T:LibreMetaverse.AppearanceManager": "crate::appearance_manager::AppearanceManager",
"T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy": "crate::current_outfit::CompositeCurrentOutfitPolicy",
"T:LibreMetaverse.Appearance.CurrentOutfitFolder": "crate::current_outfit::CurrentOutfitFolder",
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
"T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager",
"T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient",
@@ -302,6 +305,146 @@ NATIVE_DECLARATIONS = {
}
NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.NameValue.#ctor(System.String,LibreMetaverse.NameValue.ValueType,LibreMetaverse.NameValue.ClassType,LibreMetaverse.NameValue.SendtoType,System.Object)":
"Ok(crate::NameValue { class_: class_type, name, sendto: sendto_type, type_: value_type, value: Some(value) })",
"M:LibreMetaverse.Primitive.#ctor":
"Ok(crate::current_outfit::primitive_defaults())",
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.CanAttach(LibreMetaverse.InventoryItem)":
"crate::current_outfit::default_policy_decision(item)",
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)":
"crate::current_outfit::default_policy_decision(item)",
"M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)":
"crate::current_outfit::default_policy_report(added_items, removed_items, cancellation_token)",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.#ctor":
"crate::current_outfit::CompositeCurrentOutfitPolicy::native_new()",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)":
"self.native_add_policy(policy_to_add)",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanAttach(LibreMetaverse.InventoryItem)":
"self.native_can_attach(item)",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.CanDetach(LibreMetaverse.InventoryItem)":
"self.native_can_detach(item)",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)":
"self.native_remove_policy(policy_to_remove)",
"M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)":
"self.native_report_item_change(added_items, removed_items, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.#ctor(LibreMetaverse.GridClient)":
"crate::current_outfit::CurrentOutfitFolder::native_new(client)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddPolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)":
"self.native_add_policy(policy_to_add)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(LibreMetaverse.InventoryItem,System.Boolean,System.Threading.CancellationToken)":
"self.native_add_to_outfit(vec![item], replace, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AddToOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Threading.CancellationToken)":
"self.native_add_to_outfit(requested_items_to_add, replace, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.AttachAsync(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean,System.Threading.CancellationToken)":
"self.native_attach(item, point, replace, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanAttachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)":
"self.native_can_attach_item(item, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.CanDetachItemAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)":
"self.native_can_detach_item(item, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.DetachAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)":
"self.native_detach(item, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.Dispose":
"self.native_dispose()",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.FetchParentAsync(LibreMetaverse.InventoryBase,System.Threading.CancellationToken)":
"self.native_fetch_parent(item, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetAttachmentItemID(LibreMetaverse.Primitive)":
"crate::current_outfit::CurrentOutfitFolder::native_get_attachment_item_id(prim)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetCurrentOutfitLinksAsync(System.Threading.CancellationToken)":
"self.native_get_current_outfit_links(cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.GetWornAtAsync(LibreMetaverse.WearableType,System.Threading.CancellationToken)":
"self.native_get_worn_at(type_, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.IsObjectDescendentOfAsync(LibreMetaverse.InventoryBase,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_is_object_descendent_of(item, parent_id, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(LibreMetaverse.InventoryItem,System.Threading.CancellationToken)":
"self.native_remove_from_outfit(vec![item], cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemoveFromOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)":
"self.native_remove_from_outfit(requested_items_to_remove, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.RemovePolicy(LibreMetaverse.Appearance.ICurrentOutfitPolicy)":
"self.native_remove_policy(policy_to_remove)",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.ReplaceOutfitAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_replace_outfit(new_outfit_folder_id, cancellation_token).await",
"M:LibreMetaverse.Appearance.CurrentOutfitFolder.ResolveInventoryLink(LibreMetaverse.InventoryItem)":
"self.native_resolve_inventory_link(item_link)",
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF":
"self.native_cof()",
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.COF#set":
"self.native_set_cof(value)",
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.MaxClothingLayers":
"crate::current_outfit::MAX_CLOTHING_LAYERS",
"M:LibreMetaverse.AppearanceManager.#ctor(LibreMetaverse.GridClient)":
"crate::appearance_manager::AppearanceManager::native_new(client)",
"E:LibreMetaverse.AppearanceManager.AgentWearablesReply":
"self.native_subscribe_agent_wearables_reply(handler)",
"E:LibreMetaverse.AppearanceManager.AppearanceSet":
"self.native_subscribe_appearance_set(handler)",
"M:LibreMetaverse.AppearanceManager.AddAttachments(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Boolean)":
"self.native_add_attachments(attachments, remove_existing_first, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.AddToOutfit(LibreMetaverse.InventoryItem,System.Boolean)":
"self.native_add_to_outfit(vec![wearable_item], replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.AddToOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)":
"self.native_add_to_outfit(wearable_items, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean)":
"self.native_attach(item, attach_point, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.Permissions,System.UInt32,LibreMetaverse.AttachmentPoint,System.Boolean)":
"self.native_attach_fields(item_id, owner_id, name, description, perms, item_flags, attach_point, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.InventoryItem)":
"self.native_detach(item.base.uuid())",
"M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.UUID)":
"self.native_detach(item_id)",
"M:LibreMetaverse.AppearanceManager.Dispose":
"self.native_dispose()",
"M:LibreMetaverse.AppearanceManager.GetAttachments":
"self.native_get_attachments()",
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByAttachmentPointAsync(System.Threading.CancellationToken)":
"self.native_get_attachments_by_point(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByInventoryItem":
"self.native_get_attachments_by_item()",
"M:LibreMetaverse.AppearanceManager.GetAttachmentsByItemId":
"self.native_get_attachments_by_id()",
"M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)":
"self.native_get_current_outfit_folder(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_get_folder_wearables(folder, cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.GetWearableAssets(LibreMetaverse.WearableType)":
"self.native_get_wearable_assets(type_)",
"M:LibreMetaverse.AppearanceManager.GetWearables":
"self.native_get_wearables()",
"M:LibreMetaverse.AppearanceManager.GetWearablesByType":
"self.native_get_wearables_by_type()",
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.InventoryItem)":
"self.native_is_item_worn(item.base.uuid())",
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.UUID)":
"Ok(self.native_is_item_worn(item_id)? != libremetaverse_types::WearableType::Invalid)",
"M:LibreMetaverse.AppearanceManager.RemoveFromOutfit(LibreMetaverse.InventoryItem)":
"self.native_remove_from_outfit(vec![wearable_item])",
"M:LibreMetaverse.AppearanceManager.RemoveFromOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem})":
"self.native_remove_from_outfit(wearable_items)",
"M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem})":
"self.native_replace_outfit(wearable_items, false).await",
"M:LibreMetaverse.AppearanceManager.ReplaceOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)":
"self.native_replace_outfit(wearable_items, safe).await",
"M:LibreMetaverse.AppearanceManager.RequestAgentWornAsync(System.Threading.CancellationToken)":
"self.native_request_agent_worn(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)":
"self.native_send_outfit(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)":
"self.native_update_cof_version(cof_version)",
"M:LibreMetaverse.AppearanceManager.WearOutfitAsync(System.Collections.Generic.List{LibreMetaverse.InventoryBase},System.Boolean)":
"self.native_wear_outfit(wearables, replace_items).await",
"M:LibreMetaverse.AppearanceManager.WearableTypeToAssetType(LibreMetaverse.WearableType)":
"Ok(crate::appearance_manager::wearable_type_to_asset_type(type_))",
"M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.InventoryItem)":
"Ok(self.native_is_item_attached(item.base.uuid()))",
"M:LibreMetaverse.AppearanceManager.isItemAttached(LibreMetaverse.UUID)":
"Ok(self.native_is_item_attached(key))",
"P:LibreMetaverse.AppearanceManager.LastUpdateReceivedCOFVersion":
"self.native_last_cof_version()",
"P:LibreMetaverse.AppearanceManager.ManagerBusy":
"self.native_manager_busy()",
"P:LibreMetaverse.GridClient.Appearance":
"self.native_appearance().unwrap_or_else(|_| panic!(\"failed to construct AppearanceManager\"))",
"P:LibreMetaverse.GridClient.Appearance#set":
"self.native_set_appearance(value)",
"M:LibreMetaverse.Avatar.AvatarProperties.FromOSD(LibreMetaverse.StructuredData.OSD)":
"crate::agent_messages::avatar_properties_from_osd(o)",
"M:LibreMetaverse.Avatar.AvatarProperties.GetOSD":
@@ -1892,6 +2035,7 @@ PRIVATE_LAYOUTS = {
}
VALUE_DERIVES = {
"T:LibreMetaverse.AgentSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.AppearanceManager.WearableData": "Clone, Debug",
"T:LibreMetaverse.AssetCacheSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.BuddyListEntry": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.ConnectionSettings": "Clone, Eq, PartialEq",
@@ -2390,6 +2534,8 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
# of receiving an invalid empty generated impl.
if f"T:{base}" in NATIVE_TYPES:
continue
if item["doc_id"] in NATIVE_DECLARATIONS:
continue
if item["doc_id"] == "T:LibreMetaverse.GridClient" and base == "LibreMetaverse.IGridClient":
continue
mapped_arguments = [mapper.type(argument, None, set(names)) for argument in arguments]

View File

@@ -989,6 +989,7 @@ def validate_generated_shims() -> None:
"crate::asset_manager::",
"crate::asset_material::",
"crate::asset_models::",
"crate::appearance_manager::",
"crate::byte_order::",
"crate::attention_catalog::",
"crate::binary::",
@@ -1000,8 +1001,10 @@ def validate_generated_shims() -> None:
"crate::inventory::",
"crate::inventory_ais::",
"crate::inventory_manager::",
"crate::current_outfit::",
"crate::message_codec::",
"crate::message_decoder::",
"crate::NameValue {",
"crate::object_material::",
"crate::notation::",
"crate::packet_catalog::",
@@ -1020,8 +1023,16 @@ def validate_generated_shims() -> None:
if uses_standardized_or_native_path:
continue
if FAKE_BODY.search(body):
raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}")
raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}")
excerpt = " ".join(body.strip().split())[:160]
raise ValueError(
"generated shim returns a plausible fallback: "
f"{path.relative_to(ROOT)}: {excerpt}"
)
excerpt = " ".join(body.strip().split())[:160]
raise ValueError(
"generated shim function does not use the standardized failure: "
f"{path.relative_to(ROOT)}: {excerpt}"
)
def build_type_rows(catalog: dict) -> tuple[list[dict[str, str]], dict[str, str]]: