Implement appearance baking pipeline (#66)
Some checks failed
Native code generation / deterministic (push) Failing after 2m18s
Imaging and meshing gate / native (push) Failing after 1m28s
Native Rust workspace compile / compile (push) Failing after 58s

This commit is contained in:
2026-08-10 10:33:29 +00:00
parent a9927fcfcb
commit b1f60db3d6
17 changed files with 3487 additions and 249 deletions

View File

@@ -108,6 +108,7 @@ jobs:
python3 tools/check_milestone_09_issue_61.py python3 tools/check_milestone_09_issue_61.py
python3 tools/check_milestone_09_issue_62.py python3 tools/check_milestone_09_issue_62.py
python3 tools/check_milestone_09_issue_63.py python3 tools/check_milestone_09_issue_63.py
python3 tools/check_milestone_09_issue_66.py
cargo check --manifest-path tests/api-compile/Cargo.toml --locked cargo check --manifest-path tests/api-compile/Cargo.toml --locked
- name: Test generated wire codecs and translated parity cases - name: Test generated wire codecs and translated parity cases
run: | run: |
@@ -125,6 +126,9 @@ jobs:
run: | run: |
cargo test -p libremetaverse --test generated_catalogs cargo test -p libremetaverse --test generated_catalogs
cargo test -p libremetaverse-compat-tests --test appearance_visual_semantics cargo test -p libremetaverse-compat-tests --test appearance_visual_semantics
cargo test -p libremetaverse-compat-tests --test appearance_semantics
cargo test -p libremetaverse --lib appearance_
cargo test -p libremetaverse --lib initial_outfit::tests
- name: Test generated skeleton, attention, and genepool catalogs - name: Test generated skeleton, attention, and genepool catalogs
run: | run: |
cargo test -p libremetaverse --test generated_avatar_catalogs cargo test -p libremetaverse --test generated_avatar_catalogs

View File

@@ -392,3 +392,14 @@ link or attachment replacement without publishing partial local state.
Composite policies make pure decisions from snapshots and receive committed Composite policies make pure decisions from snapshots and receive committed
changes after synchronization is released. The behavior and transaction changes after synchronization is released. The behavior and transaction
boundaries are documented in [`docs/appearance.md`](docs/appearance.md). boundaries are documented in [`docs/appearance.md`](docs/appearance.md).
The appearance service now completes cached, local, and server-side baking for
all 11 bake targets. It parses wearable parameters and texture indices,
downloads and decodes texture inputs through a bounded provider abstraction,
composites color/alpha/bump layers, uploads JPEG2000 bakes, builds the complete
appearance packet, and handles cache and forced-rebake messages without locks
crossing awaits or callbacks. First-login outfit setup performs a bounded
library copy and transactional COF application with monotonic progress,
cancellation, and compensation. Exact mappings, hashes, generated visual data,
pixels, packet/event payloads, and recovery behavior are covered by focused
tests and 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 | | Assembly | Types | Members | Status |
|---|---:|---:|---| |---|---:|---:|---|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 200 types / 15,251 members; remaining surface is callable failure-only shims | | `LibreMetaverse` | 2,711 | 27,281 | native implementation: 207 types / 15,315 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.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.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |

View File

@@ -0,0 +1,605 @@
//! Bounded, platform-neutral avatar texture compositing.
use crate::targa::Targa;
use crate::{AppearanceManagerTextureData, AvatarTextureIndex, BakeType, Error};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels as Channels};
use libremetaverse_types::Color4;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
const CLASSIC_BAKE_SIZE: i32 = 1024;
const EYE_BAKE_SIZE: i32 = 128;
fn cache() -> &'static Mutex<HashMap<String, ManagedImage>> {
static CACHE: OnceLock<Mutex<HashMap<String, ManagedImage>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
/// A compositor owns its input images so callers can freely reuse or mutate
/// their originals after adding a layer.
pub struct Baker {
bake_type: BakeType,
bake_width: i32,
bake_height: i32,
textures: Vec<AppearanceManagerTextureData>,
baked_texture: Option<crate::assets::AssetTexture>,
}
impl Baker {
#[allow(clippy::unnecessary_wraps)] // The generated constructor uses the crate Result model.
pub(crate) fn native_new(bake_type: BakeType) -> Result<Self, Error> {
let size = if bake_type == BakeType::Eyes {
EYE_BAKE_SIZE
} else {
CLASSIC_BAKE_SIZE
};
Ok(Self {
bake_type,
bake_width: size,
bake_height: size,
textures: Vec::new(),
baked_texture: None,
})
}
pub(crate) fn native_add_texture(
&mut self,
texture: AppearanceManagerTextureData,
) -> Result<(), Error> {
if self.textures.len() >= 60 {
return Err(Error::Argument);
}
self.textures.push(texture);
Ok(())
}
pub(crate) fn native_bake(&mut self) -> Result<(), Error> {
let channels = Channels::COLOR | Channels::ALPHA | Channels::BUMP;
let mut output = ManagedImage::new(self.bake_width, self.bake_height, channels)?;
if self.bake_type == BakeType::Eyes {
fill_dithered(&mut output, Color4::white());
} else if let Some(first) = self.textures.first() {
fill_dithered(&mut output, first.color);
}
let has_skin = self.textures.iter().any(|layer| {
layer.texture.is_some()
&& matches!(
layer.texture_index,
AvatarTextureIndex::HeadBodypaint
| AvatarTextureIndex::UpperBodypaint
| AvatarTextureIndex::LowerBodypaint
)
});
if self.bake_type == BakeType::Head {
if let Some(layer) = Self::native_load_resource_layer("head_color.tga".to_owned())? {
draw_layer(&mut output, layer, false)?;
}
if let Some(mask) = Self::native_load_resource_layer("head_alpha.tga".to_owned())? {
apply_min_alpha_resized(&mut output, mask)?;
}
if let Some(mask) = Self::native_load_resource_layer("head_skingrain.tga".to_owned())? {
multiply_color_mask(&mut output, mask)?;
}
} else if !has_skin && matches!(self.bake_type, BakeType::UpperBody | BakeType::LowerBody) {
let name = if self.bake_type == BakeType::UpperBody {
"upperbody_color.tga"
} else {
"lowerbody_color.tga"
};
if let Some(layer) = Self::native_load_resource_layer(name.to_owned())? {
draw_layer(&mut output, layer, false)?;
}
}
let mut alpha_wearables = Vec::new();
let mut deferred_head_skin = None;
let mut deferred_head_tattoos = Vec::new();
for (position, layer) in self.textures.iter().enumerate() {
let Some(asset) = &layer.texture else {
continue;
};
let Some(source) = &asset.image else { continue };
let index = layer.texture_index as i32;
if (AvatarTextureIndex::LowerAlpha as i32..=AvatarTextureIndex::HairAlpha as i32)
.contains(&index)
{
if source.channels.contains(Channels::ALPHA) {
alpha_wearables.push(source.clone()?);
}
continue;
}
if self.bake_type == BakeType::Head
&& matches!(
layer.texture_index,
AvatarTextureIndex::HeadBodypaint
| AvatarTextureIndex::HeadTattoo
| AvatarTextureIndex::HeadUniversalTattoo
)
{
if layer.texture_index == AvatarTextureIndex::HeadBodypaint {
deferred_head_skin = Some(layer);
} else {
deferred_head_tattoos.push(layer);
}
continue;
}
composite_texture(
&mut output,
layer,
self.bake_width,
self.bake_height,
self.bake_type,
has_skin,
position == 0 && matches!(self.bake_type, BakeType::Skirt | BakeType::Hair),
)?;
}
if let Some(layer) = deferred_head_skin {
draw_head_layer(&mut output, layer, self.bake_width, self.bake_height)?;
}
for layer in deferred_head_tattoos {
draw_head_layer(&mut output, layer, self.bake_width, self.bake_height)?;
}
for mut mask in alpha_wearables {
mask.resize_nearest_neighbor(self.bake_width, self.bake_height)?;
apply_min_alpha(&mut output, &mask);
}
let texture = crate::assets::AssetTexture::new_with_managed_image(output)?;
// JPEG2000 is the protocol representation. Keep the decoded image even
// when a build intentionally excludes that optional codec.
#[cfg(feature = "jpeg2000")]
texture.encode()?;
self.baked_texture = Some(texture);
Ok(())
}
pub(crate) fn native_bake_type_for(index: AvatarTextureIndex) -> BakeType {
match index {
AvatarTextureIndex::HeadBodypaint => BakeType::Head,
AvatarTextureIndex::UpperBodypaint
| AvatarTextureIndex::UpperGloves
| AvatarTextureIndex::UpperUndershirt
| AvatarTextureIndex::UpperShirt
| AvatarTextureIndex::UpperJacket => BakeType::UpperBody,
AvatarTextureIndex::LowerBodypaint
| AvatarTextureIndex::LowerUnderpants
| AvatarTextureIndex::LowerSocks
| AvatarTextureIndex::LowerShoes
| AvatarTextureIndex::LowerPants
| AvatarTextureIndex::LowerJacket => BakeType::LowerBody,
AvatarTextureIndex::EyesIris => BakeType::Eyes,
AvatarTextureIndex::Skirt => BakeType::Skirt,
AvatarTextureIndex::Hair => BakeType::Hair,
_ => BakeType::Unknown,
}
}
pub(crate) fn native_load_resource_layer(
file_name: String,
) -> Result<Option<ManagedImage>, Error> {
let path = Path::new(&file_name);
let supported = path.extension().is_some_and(|extension| {
extension.eq_ignore_ascii_case("tga") || extension.eq_ignore_ascii_case("dds")
});
if file_name.is_empty() || path.components().count() != 1 || !supported {
return Ok(None);
}
if let Some(image) = cache()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(&file_name)
{
return image.clone().map(Some);
}
let image = resource_candidates(&file_name)
.into_iter()
.find_map(|path| {
Targa::decode_to_managed_image_with_string(path.to_string_lossy().into_owned()).ok()
})
.or_else(|| bundled_fallback(&file_name).ok().flatten());
if let Some(image) = image {
let result = image.clone()?;
cache()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(file_name, image);
Ok(Some(result))
} else {
Ok(None)
}
}
pub(crate) fn native_resize_to_bake_dimensions(
texture: ManagedImage,
width: i32,
height: i32,
) -> Result<Option<ManagedImage>, Error> {
if texture.width == width && texture.height == height {
return Ok(Some(texture));
}
if texture.width < width && texture.height < height {
return tile(&texture, width, height).map(Some);
}
let mut texture = texture;
texture.resize_nearest_neighbor(width, height)?;
Ok(Some(texture))
}
pub(crate) const fn native_bake_height(&self) -> i32 {
self.bake_height
}
pub(crate) const fn native_bake_width(&self) -> i32 {
self.bake_width
}
pub(crate) const fn native_bake_type(&self) -> BakeType {
self.bake_type
}
pub(crate) fn native_baked_texture(&self) -> Option<crate::assets::AssetTexture> {
self.baked_texture
.as_ref()
.and_then(|texture| texture.native_deep_clone().ok())
}
pub(crate) fn native_textures(&self) -> Vec<AppearanceManagerTextureData> {
self.textures
.iter()
.filter_map(clone_texture_data)
.collect()
}
}
fn clone_texture_data(
layer: &AppearanceManagerTextureData,
) -> Option<AppearanceManagerTextureData> {
Some(AppearanceManagerTextureData {
alpha_masks: layer.alpha_masks.clone(),
color: layer.color,
texture: layer
.texture
.as_ref()
.map(crate::assets::AssetTexture::native_deep_clone)
.transpose()
.ok()?,
texture_id: layer.texture_id,
texture_index: layer.texture_index,
})
}
fn resource_candidates(name: &str) -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Some(root) = std::env::var_os("LIBREMETAVERSE_RESOURCE_DIR") {
roots.push(PathBuf::from(root));
}
roots.push(PathBuf::from(crate::Settings::resource_dir()));
roots.push(PathBuf::from("crates/libremetaverse/linden"));
roots
.into_iter()
.map(|root| root.join("character").join(name))
.collect()
}
fn bundled_fallback(name: &str) -> Result<Option<ManagedImage>, Error> {
// The canonical character XML is embedded in generated Rust. These neutral
// base masks keep head/upper/lower baking functional in minimal deployments
// that deliberately omit the optional viewer resource bundle.
let (channels, value) = match name {
"head_color.tga" | "upperbody_color.tga" | "lowerbody_color.tga" => (Channels::COLOR, 255),
"head_alpha.tga" | "head_skingrain.tga" | "head_hair.tga" => (Channels::GRAY, 255),
_ => return Ok(None),
};
let mut image = ManagedImage::new(512, 512, channels)?;
image.red.fill(value);
image.green.fill(value);
image.blue.fill(value);
Ok(Some(image))
}
fn tile(source: &ManagedImage, width: i32, height: i32) -> Result<ManagedImage, Error> {
source.validate()?;
let mut output = ManagedImage::new(width, height, source.channels)?;
let sw = usize::try_from(source.width).map_err(|_| Error::Argument)?;
let sh = usize::try_from(source.height).map_err(|_| Error::Argument)?;
let dw = usize::try_from(width).map_err(|_| Error::Argument)?;
let dh = usize::try_from(height).map_err(|_| Error::Argument)?;
for y in 0..dh {
for x in 0..dw {
let src = (y % sh) * sw + x % sw;
let dst = y * dw + x;
for (target, input) in [
(&mut output.red, &source.red),
(&mut output.green, &source.green),
(&mut output.blue, &source.blue),
(&mut output.alpha, &source.alpha),
(&mut output.bump, &source.bump),
] {
if !input.is_empty() {
target[dst] = input[src];
}
}
}
}
Ok(output)
}
fn composite_texture(
output: &mut ManagedImage,
layer: &AppearanceManagerTextureData,
width: i32,
height: i32,
bake_type: BakeType,
has_skin: bool,
preserve_alpha: bool,
) -> Result<(), Error> {
let source = layer
.texture
.as_ref()
.and_then(|asset| asset.image.as_ref())
.ok_or(Error::InvalidOperation)?
.clone()?;
let mut source = Baker::native_resize_to_bake_dimensions(source, width, height)?
.ok_or(Error::InvalidOperation)?;
let skin = matches!(
layer.texture_index,
AvatarTextureIndex::HeadBodypaint
| AvatarTextureIndex::UpperBodypaint
| AvatarTextureIndex::LowerBodypaint
);
if !has_skin && bake_type == BakeType::Head && layer.texture_index == AvatarTextureIndex::Hair {
if source.channels.contains(Channels::ALPHA) {
source.alpha.fill(255);
}
if let Some(mask) = Baker::native_load_resource_layer("head_hair.tga".to_owned())? {
multiply_color_mask(&mut source, mask)?;
}
}
if !skin {
tint(&mut source, layer.color);
}
if bake_type == BakeType::Hair {
if source.channels.contains(Channels::ALPHA) {
output.bump.clone_from(&source.alpha);
} else {
output.bump.fill(255);
}
}
if !skin && bake_type != BakeType::Hair && !layer.alpha_masks.is_empty() {
let mut combined = ManagedImage::new(width, height, Channels::ALPHA)?;
let mut normal_count = 0;
for (param, value) in &layer.alpha_masks {
if !param.multiply_blend
&& mask_belongs(bake_type, &param.tga_file)
&& (*value > 0.0 || !param.skip_if_zero)
{
apply_parameter_mask(&mut combined, param, *value)?;
normal_count += 1;
}
}
if normal_count == 0 {
combined.alpha.fill(255);
}
for (param, value) in &layer.alpha_masks {
if param.multiply_blend
&& mask_belongs(bake_type, &param.tga_file)
&& (*value > 0.0 || !param.skip_if_zero)
{
apply_parameter_mask(&mut combined, param, *value)?;
}
}
if !source.channels.contains(Channels::ALPHA) {
source.convert_channels(source.channels | Channels::ALPHA)?;
}
apply_min_alpha(&mut source, &combined);
if layer.texture_index == crate::appearance_manager::morph_layer_for_bake_type(bake_type) {
output.bump.clone_from(&source.alpha);
}
}
draw_layer(output, source, preserve_alpha)
}
fn draw_head_layer(
output: &mut ManagedImage,
layer: &AppearanceManagerTextureData,
width: i32,
height: i32,
) -> Result<(), Error> {
let source = layer
.texture
.as_ref()
.and_then(|asset| asset.image.as_ref())
.ok_or(Error::InvalidOperation)?
.clone()?;
let source = Baker::native_resize_to_bake_dimensions(source, width, height)?
.ok_or(Error::InvalidOperation)?;
draw_layer(output, source, false)
}
fn mask_belongs(bake: BakeType, name: &str) -> bool {
(bake != BakeType::LowerBody || (!name.contains("upper") && !name.contains("shirt")))
&& (bake != BakeType::UpperBody || !name.contains("lower"))
}
fn effective_mask(image: &ManagedImage) -> Vec<u8> {
if image.channels.contains(Channels::ALPHA) {
return image.alpha.clone();
}
if image.channels.contains(Channels::GRAY) {
return image.red.clone();
}
if image.channels.contains(Channels::COLOR) {
return image
.red
.iter()
.zip(&image.green)
.zip(&image.blue)
.map(|((&red, &green), &blue)| {
u8::try_from((u16::from(red) + u16::from(green) + u16::from(blue)) / 3)
.unwrap_or(255)
})
.collect();
}
let length = image
.width
.checked_mul(image.height)
.and_then(|value| usize::try_from(value).ok())
.unwrap_or(0);
vec![255; length]
}
fn apply_parameter_mask(
destination: &mut ManagedImage,
param: &crate::VisualAlphaParam,
value: f32,
) -> Result<(), Error> {
let Some(mut source) = Baker::native_load_resource_layer(param.tga_file.clone())? else {
return Ok(());
};
source.resize_nearest_neighbor(destination.width, destination.height)?;
let mask = effective_mask(&source);
let threshold = quantize_u8(1.0 - value.clamp(0.0, 1.0));
for (target, sample) in destination.alpha.iter_mut().zip(mask) {
let alpha = if sample <= threshold { 0 } else { 255 };
if param.multiply_blend {
*target = ((u16::from(*target) * u16::from(alpha)) >> 8) as u8;
} else {
*target = (*target).max(alpha);
}
}
Ok(())
}
fn apply_min_alpha_resized(
destination: &mut ManagedImage,
mut source: ManagedImage,
) -> Result<(), Error> {
source.resize_nearest_neighbor(destination.width, destination.height)?;
let mask = effective_mask(&source);
for (target, sample) in destination.alpha.iter_mut().zip(mask) {
*target = (*target).min(sample);
}
Ok(())
}
fn multiply_color_mask(
destination: &mut ManagedImage,
mut source: ManagedImage,
) -> Result<(), Error> {
source.resize_nearest_neighbor(destination.width, destination.height)?;
let mask = effective_mask(&source);
if !destination.channels.contains(Channels::COLOR) {
return Ok(());
}
for (index, sample) in mask.into_iter().enumerate() {
destination.red[index] =
((u16::from(destination.red[index]) * u16::from(sample)) >> 8) as u8;
destination.green[index] =
((u16::from(destination.green[index]) * u16::from(sample)) >> 8) as u8;
destination.blue[index] =
((u16::from(destination.blue[index]) * u16::from(sample)) >> 8) as u8;
}
Ok(())
}
fn fill_dithered(image: &mut ManagedImage, color: Color4) {
let (red, green, blue) = (
quantize_u8(color.r),
quantize_u8(color.g),
quantize_u8(color.b),
);
let alt = |value: u8| if value == 255 { 254 } else { value + 1 };
let width = usize::try_from(image.width).unwrap_or(1);
for (index, target_red) in image.red.iter_mut().enumerate() {
let column = index % width;
let row = index / width;
let alternate = ((column ^ row) & 0x10) == 0;
*target_red = if alternate { alt(red) } else { red };
image.green[index] = if alternate { green } else { alt(green) };
image.blue[index] = if alternate { blue } else { alt(blue) };
}
image.alpha.fill(255);
image.bump.fill(0);
}
fn tint(image: &mut ManagedImage, color: Color4) {
if image.channels.contains(Channels::GRAY) && !image.channels.contains(Channels::COLOR) {
let gray = image.red.clone();
image.channels = Channels(image.channels.0 & !Channels::GRAY.0) | Channels::COLOR;
image.red.clone_from(&gray);
image.green.clone_from(&gray);
image.blue = gray;
}
if !image.channels.contains(Channels::COLOR) {
return;
}
let (red, green, blue) = (
u16::from(quantize_u8(color.r)),
u16::from(quantize_u8(color.g)),
u16::from(quantize_u8(color.b)),
);
for index in 0..image.red.len() {
image.red[index] = ((u16::from(image.red[index]) * red) >> 8) as u8;
image.green[index] = ((u16::from(image.green[index]) * green) >> 8) as u8;
image.blue[index] = ((u16::from(image.blue[index]) * blue) >> 8) as u8;
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn quantize_u8(value: f32) -> u8 {
(value.clamp(0.0, 1.0) * 255.0).round() as u8
}
fn draw_layer(
output: &mut ManagedImage,
mut source: ManagedImage,
add_source_alpha: bool,
) -> Result<(), Error> {
if source.width != output.width || source.height != output.height {
source.resize_nearest_neighbor(output.width, output.height)?;
}
let color = source.channels.contains(Channels::COLOR);
let gray = source.channels.contains(Channels::GRAY);
let alpha = source.channels.contains(Channels::ALPHA);
for index in 0..output.red.len() {
let source_alpha = if alpha { source.alpha[index] } else { 255 };
let inverse_alpha = 255_u16 - u16::from(source_alpha);
if color || gray {
let sr = source.red[index];
let sg = if color { source.green[index] } else { sr };
let sb = if color { source.blue[index] } else { sr };
if alpha {
output.red[index] = ((u16::from(output.red[index]) * inverse_alpha
+ u16::from(sr) * u16::from(source_alpha))
>> 8) as u8;
output.green[index] = ((u16::from(output.green[index]) * inverse_alpha
+ u16::from(sg) * u16::from(source_alpha))
>> 8) as u8;
output.blue[index] = ((u16::from(output.blue[index]) * inverse_alpha
+ u16::from(sb) * u16::from(source_alpha))
>> 8) as u8;
} else {
output.red[index] = sr;
output.green[index] = sg;
output.blue[index] = sb;
}
}
if add_source_alpha && alpha {
output.alpha[index] = output.alpha[index].min(source_alpha);
}
if source.channels.contains(Channels::BUMP) {
output.bump[index] = source.bump[index];
}
}
Ok(())
}
fn apply_min_alpha(output: &mut ManagedImage, mask: &ManagedImage) {
let effective = if mask.channels.contains(Channels::ALPHA) {
&mask.alpha
} else {
&mask.red
};
for (target, source) in output.alpha.iter_mut().zip(effective) {
*target = (*target).min(*source);
}
}

View File

@@ -35,7 +35,12 @@ fn bake_grayscale_layer_texture_does_not_throw() {
let mut baker = imaging::Baker::new(BakeType::UpperBody).unwrap(); let mut baker = imaging::Baker::new(BakeType::UpperBody).unwrap();
baker.add_texture(data).unwrap(); baker.add_texture(data).unwrap();
baker.bake().unwrap(); baker.bake().unwrap();
assert!(baker.baked_texture().is_some()); let baked = baker.baked_texture().unwrap();
let image = baked.image.unwrap();
assert_eq!((image.width, image.height), (1024, 1024));
assert_eq!((image.red[0], image.green[0], image.blue[0]), (63, 31, 94));
assert_eq!(image.alpha[0], 255);
assert_eq!(image.bump[0], 0);
} }
// parity-case: LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow::test e48b737d9138e63cda98b5e80bf561a80d1dab5de78901ac9e1bb320ca281eb6 translated // parity-case: LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow::test e48b737d9138e63cda98b5e80bf561a80d1dab5de78901ac9e1bb320ca281eb6 translated
@@ -97,3 +102,19 @@ fn resize_to_bake_dimensions_source_smaller_in_one_axis_only_scales_rather_than_
assert_eq!(result.height, 4); assert_eq!(result.height, 4);
assert!(result.red[3 * 4] > 50); assert!(result.red[3 * 4] > 50);
} }
#[test]
fn bake_type_for_preserves_the_reference_base_layer_mapping() {
assert_eq!(
imaging::Baker::bake_type_for(AvatarTextureIndex::UpperShirt).unwrap(),
BakeType::UpperBody
);
assert_eq!(
imaging::Baker::bake_type_for(AvatarTextureIndex::UpperTattoo).unwrap(),
BakeType::Unknown
);
assert_eq!(
imaging::Baker::bake_type_for(AvatarTextureIndex::Aux1Tattoo).unwrap(),
BakeType::Unknown
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -464,6 +464,52 @@ pub struct AssetTexture {
pub image: Option<ManagedImage>, pub image: Option<ManagedImage>,
} }
impl AssetTexture { impl AssetTexture {
pub(crate) fn native_bytes(&self) -> Vec<u8> {
self.raw.bytes()
}
pub(crate) fn native_deep_clone(&self) -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::with_data(
AssetType::Texture,
self.raw
.0
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.asset_id(),
self.raw.bytes(),
)?,
components: self.components,
image: self.image.as_ref().map(ManagedImage::clone).transpose()?,
})
}
pub(crate) fn native_decode_image(&mut self) -> Result<bool, Error> {
if self.image.is_some() {
return Ok(true);
}
#[cfg(feature = "jpeg2000")]
{
self.image = Some(libremetaverse_imaging::J2kCodec::decode_bytes(
&self.raw.bytes(),
libremetaverse_imaging::J2kDecodeOptions::default(),
)?);
self.components = if self.image.as_ref().is_some_and(|image| {
image
.channels
.contains(libremetaverse_imaging::ManagedImageImageChannels::ALPHA)
}) {
4
} else {
3
};
Ok(true)
}
#[cfg(not(feature = "jpeg2000"))]
{
Err(Error::InvalidOperation)
}
}
pub fn new_with_constructor() -> Result<Self, Error> { pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self { Ok(Self {
raw: RawAsset::empty(AssetType::Texture)?, raw: RawAsset::empty(AssetType::Texture)?,
@@ -557,6 +603,63 @@ impl AssetWearable {
wearable_type: WearableType::Invalid, wearable_type: WearableType::Invalid,
}) })
} }
pub(crate) fn native_from_asset(
asset: &Asset,
wearable_type: WearableType,
) -> Result<Self, Error> {
if !matches!(
asset.asset_type(),
AssetType::Bodypart | AssetType::Clothing
) {
return Err(Error::Argument);
}
let text = std::str::from_utf8(&asset.asset_data).map_err(|_| Error::Argument)?;
let mut result = Self::empty(asset.asset_type())?;
result.raw = RawAsset::with_data(
asset.asset_type(),
asset.asset_id(),
asset.asset_data.clone(),
)?;
result.wearable_type = wearable_type;
let lines: Vec<_> = text.lines().collect();
let mut index = 0;
while index < lines.len() {
let line = lines[index].trim();
if let Some(count) = line
.strip_prefix("parameters ")
.and_then(|value| value.parse::<usize>().ok())
{
for parameter in lines.iter().skip(index + 1).take(count) {
let mut values = parameter.split_whitespace();
if let (Some(id), Some(value)) = (values.next(), values.next())
&& let (Ok(id), Ok(value)) = (id.parse::<i32>(), value.parse::<f32>())
{
result.params.insert(id, value);
}
}
index = index.saturating_add(count);
} else if let Some(count) = line
.strip_prefix("textures ")
.and_then(|value| value.parse::<usize>().ok())
{
for texture in lines.iter().skip(index + 1).take(count) {
let mut values = texture.split_whitespace();
if let (Some(slot), Some(id)) = (values.next(), values.next())
&& let (Ok(slot), Ok(id)) =
(slot.parse::<i32>(), UUID::new_with_string(id.to_owned()))
&& let Some(slot) =
crate::appearance_manager::avatar_texture_index_from_i32(slot)
{
result.textures.insert(slot, id);
}
}
index = index.saturating_add(count);
}
index += 1;
}
Ok(result)
}
pub fn decode(&self) -> Result<bool, Error> { pub fn decode(&self) -> Result<bool, Error> {
Ok(std::str::from_utf8(&self.raw.bytes()).is_ok()) Ok(std::str::from_utf8(&self.raw.bytes()).is_ok())
} }
@@ -675,3 +778,45 @@ impl AssetNotecard {
AssetType::Notecard AssetType::Notecard
} }
} }
#[cfg(test)]
mod appearance_asset_tests {
use super::*;
#[test]
fn wearable_parser_preserves_parameters_and_texture_indices() {
let texture_id = UUID::new_with_u_int64(42).unwrap();
let bytes = format!(
"LLWearable version 22\nshirt\ndescription\ntype 4\nparameters 2\n33 0.75\n108 -0.25\ntextures 2\n1 {texture_id}\n13 {texture_id}\n"
)
.into_bytes();
let asset_id = UUID::new_with_u_int64(41).unwrap();
let asset = Asset::native_new(AssetType::Clothing, asset_id, bytes).unwrap();
let wearable = AssetWearable::native_from_asset(&asset, WearableType::Shirt).unwrap();
assert_eq!(wearable.params.get(&33), Some(&0.75));
assert_eq!(wearable.params.get(&108), Some(&-0.25));
assert_eq!(
wearable
.textures
.get(&crate::AvatarTextureIndex::UpperShirt),
Some(&texture_id)
);
assert_eq!(
wearable
.textures
.get(&crate::AvatarTextureIndex::UpperJacket),
Some(&texture_id)
);
assert_eq!(wearable.wearable_type, WearableType::Shirt);
assert_eq!(wearable.raw.0.read().unwrap().asset_id(), asset_id);
}
#[test]
fn wearable_parser_rejects_non_wearable_assets() {
let asset = Asset::native_new(AssetType::Texture, UUID::zero(), Vec::new()).unwrap();
assert!(matches!(
AssetWearable::native_from_asset(&asset, WearableType::Shirt),
Err(Error::Argument)
));
}
}

View File

@@ -0,0 +1,56 @@
//! Default decoded-texture provider for the avatar baking pipeline.
use crate::client_core::ClientWeakHandle;
use crate::{Error, GridClient, IBakingTextureProvider};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::CancellationToken;
use std::future::Future;
use std::pin::Pin;
pub struct GridClientBakingTextureProvider {
client: ClientWeakHandle,
}
impl GridClientBakingTextureProvider {
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
// The generated constructor owns its GridClient argument and uses the shared Result model.
pub(crate) fn native_new(client: GridClient) -> Result<Self, Error> {
Ok(Self {
client: client.native_weak_handle(),
})
}
pub(crate) async fn native_request_texture(
&self,
texture_id: UUID,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<crate::assets::AssetTexture>, Error> {
let token = cancellation_token.unwrap_or_default();
token.throw_if_cancellation_requested()?;
let client = self.client.upgrade().ok_or(Error::InvalidOperation)?;
let Some(mut texture) = client
.native_assets()?
.request_image(texture_id, None, Some(token.clone()))
.await?
else {
return Ok(None);
};
token.throw_if_cancellation_requested()?;
if texture.native_decode_image()? {
Ok(Some(texture))
} else {
Ok(None)
}
}
}
impl IBakingTextureProvider for GridClientBakingTextureProvider {
fn request_texture(
&self,
texture_id: UUID,
cancellation_token: Option<CancellationToken>,
) -> Pin<Box<dyn Future<Output = Result<Option<crate::assets::AssetTexture>, Error>> + Send + '_>>
{
Box::pin(self.native_request_texture(texture_id, cancellation_token))
}
}

View File

@@ -2888,9 +2888,7 @@ impl AppearanceManager {
libremetaverse::AgentCachedBakesReplyEventArgs, libremetaverse::AgentCachedBakesReplyEventArgs,
>, >,
) -> libremetaverse_types::compat::Subscription { ) -> libremetaverse_types::compat::Subscription {
libremetaverse_types::unimplemented_api!( self.native_subscribe_cached_bakes_reply(handler)
"E:LibreMetaverse.AppearanceManager.CachedBakesReply"
)
} }
/// C# member: `E:LibreMetaverse.AppearanceManager.RebakeAvatarRequested`. /// C# member: `E:LibreMetaverse.AppearanceManager.RebakeAvatarRequested`.
pub fn subscribe_rebake_avatar_requested( pub fn subscribe_rebake_avatar_requested(
@@ -2899,118 +2897,82 @@ impl AppearanceManager {
libremetaverse::RebakeAvatarTexturesEventArgs, libremetaverse::RebakeAvatarTexturesEventArgs,
>, >,
) -> libremetaverse_types::compat::Subscription { ) -> libremetaverse_types::compat::Subscription {
libremetaverse_types::unimplemented_api!( self.native_subscribe_rebake_avatar_requested(handler)
"E:LibreMetaverse.AppearanceManager.RebakeAvatarRequested"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.ATTACHMENT_ADD`. /// C# member: `F:LibreMetaverse.AppearanceManager.ATTACHMENT_ADD`.
pub fn attachment_add() -> u8 { pub fn attachment_add() -> u8 {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::ATTACHMENT_ADD
"F:LibreMetaverse.AppearanceManager.ATTACHMENT_ADD"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_COUNT`. /// C# member: `F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_COUNT`.
pub const BAKED_TEXTURE_COUNT: i32 = 11; pub const BAKED_TEXTURE_COUNT: i32 = 11;
/// C# member: `F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_HASH`. /// C# member: `F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_HASH`.
pub fn baked_texture_hash() -> Vec<libremetaverse_types::UUID> { pub fn baked_texture_hash() -> Vec<libremetaverse_types::UUID> {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::baked_texture_hash()
"F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_HASH"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.BakeIndexToTextureIndex`. /// C# member: `F:LibreMetaverse.AppearanceManager.BakeIndexToTextureIndex`.
pub fn bake_index_to_texture_index() -> Vec<u8> { pub fn bake_index_to_texture_index() -> Vec<u8> {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::bake_index_to_texture_index()
"F:LibreMetaverse.AppearanceManager.BakeIndexToTextureIndex"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.DEFAULT_AVATAR_TEXTURE`. /// C# member: `F:LibreMetaverse.AppearanceManager.DEFAULT_AVATAR_TEXTURE`.
pub fn default_avatar_texture() -> libremetaverse_types::UUID { pub fn default_avatar_texture() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::default_avatar_texture()
"F:LibreMetaverse.AppearanceManager.DEFAULT_AVATAR_TEXTURE"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX1`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX1`.
pub fn img_use_baked_aux1() -> libremetaverse_types::UUID { pub fn img_use_baked_aux1() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux1)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX1"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX2`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX2`.
pub fn img_use_baked_aux2() -> libremetaverse_types::UUID { pub fn img_use_baked_aux2() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux2)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX2"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX3`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX3`.
pub fn img_use_baked_aux3() -> libremetaverse_types::UUID { pub fn img_use_baked_aux3() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux3)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX3"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_EYES`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_EYES`.
pub fn img_use_baked_eyes() -> libremetaverse_types::UUID { pub fn img_use_baked_eyes() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::Eyes)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_EYES"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HAIR`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HAIR`.
pub fn img_use_baked_hair() -> libremetaverse_types::UUID { pub fn img_use_baked_hair() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::Hair)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HAIR"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HEAD`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HEAD`.
pub fn img_use_baked_head() -> libremetaverse_types::UUID { pub fn img_use_baked_head() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::Head)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HEAD"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_INDICES`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_INDICES`.
pub fn img_use_baked_indices() -> libremetaverse_types::compat::FrozenDictionary< pub fn img_use_baked_indices() -> libremetaverse_types::compat::FrozenDictionary<
libremetaverse_types::UUID, libremetaverse_types::UUID,
libremetaverse::AvatarTextureIndex, libremetaverse::AvatarTextureIndex,
> { > {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked_indices()
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_INDICES"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTARM`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTARM`.
pub fn img_use_baked_leftarm() -> libremetaverse_types::UUID { pub fn img_use_baked_leftarm() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::BakedLeftArm)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTARM"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTLEG`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTLEG`.
pub fn img_use_baked_leftleg() -> libremetaverse_types::UUID { pub fn img_use_baked_leftleg() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::BakedLeftLeg)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTLEG"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LOWER`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LOWER`.
pub fn img_use_baked_lower() -> libremetaverse_types::UUID { pub fn img_use_baked_lower() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::LowerBody)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LOWER"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_SKIRT`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_SKIRT`.
pub fn img_use_baked_skirt() -> libremetaverse_types::UUID { pub fn img_use_baked_skirt() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::Skirt)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_SKIRT"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_UPPER`. /// C# member: `F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_UPPER`.
pub fn img_use_baked_upper() -> libremetaverse_types::UUID { pub fn img_use_baked_upper() -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::img_use_baked(crate::BakeType::UpperBody)
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_UPPER"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLES_PER_LAYER`. /// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLES_PER_LAYER`.
pub const WEARABLES_PER_LAYER: i32 = 9; pub const WEARABLES_PER_LAYER: i32 = 9;
/// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLE_BAKE_MAP`. /// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLE_BAKE_MAP`.
pub fn wearable_bake_map() -> Vec<Vec<libremetaverse_types::WearableType>> { pub fn wearable_bake_map() -> Vec<Vec<libremetaverse_types::WearableType>> {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::wearable_bake_map()
"F:LibreMetaverse.AppearanceManager.WEARABLE_BAKE_MAP"
)
} }
/// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLE_COUNT`. /// C# member: `F:LibreMetaverse.AppearanceManager.WEARABLE_COUNT`.
pub const WEARABLE_COUNT: i32 = 16; pub const WEARABLE_COUNT: i32 = 16;
@@ -3092,17 +3054,15 @@ impl AppearanceManager {
pub fn bake_type_to_agent_texture_index( pub fn bake_type_to_agent_texture_index(
index: libremetaverse::BakeType, index: libremetaverse::BakeType,
) -> Result<libremetaverse::AvatarTextureIndex, crate::Error> { ) -> Result<libremetaverse::AvatarTextureIndex, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_manager::bake_type_to_agent_texture_index(
"M:LibreMetaverse.AppearanceManager.BakeTypeToAgentTextureIndex(LibreMetaverse.BakeType)", index,
) ))
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.BakeTypeToTextures(LibreMetaverse.BakeType)`. /// C# member: `M:LibreMetaverse.AppearanceManager.BakeTypeToTextures(LibreMetaverse.BakeType)`.
pub fn bake_type_to_textures( pub fn bake_type_to_textures(
bake_type: libremetaverse::BakeType, bake_type: libremetaverse::BakeType,
) -> Result<Vec<libremetaverse::AvatarTextureIndex>, crate::Error> { ) -> Result<Vec<libremetaverse::AvatarTextureIndex>, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_manager::bake_type_to_textures(bake_type))
"M:LibreMetaverse.AppearanceManager.BakeTypeToTextures(LibreMetaverse.BakeType)",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.BlendToArchetype(LibreMetaverse.GenepoolArchetype,System.Single)`. /// C# member: `M:LibreMetaverse.AppearanceManager.BlendToArchetype(LibreMetaverse.GenepoolArchetype,System.Single)`.
pub async fn blend_to_archetype( pub async fn blend_to_archetype(
@@ -3119,9 +3079,7 @@ impl AppearanceManager {
wearable: libremetaverse::AppearanceManagerWearableData, wearable: libremetaverse::AppearanceManagerWearableData,
textures: &mut Vec<libremetaverse::AppearanceManagerTextureData>, textures: &mut Vec<libremetaverse::AppearanceManagerTextureData>,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_manager::decode_wearable_params(wearable, textures)
"M:LibreMetaverse.AppearanceManager.DecodeWearableParams(LibreMetaverse.AppearanceManager.WearableData,LibreMetaverse.AppearanceManager.TextureData[]@)",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.InventoryItem)`. /// C# member: `M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.InventoryItem)`.
pub fn detach_with_inventory_item( pub fn detach_with_inventory_item(
@@ -3189,9 +3147,7 @@ impl AppearanceManager {
pub fn get_color_from_params( pub fn get_color_from_params(
param: Vec<libremetaverse::AppearanceManagerColorParamInfo>, param: Vec<libremetaverse::AppearanceManagerColorParamInfo>,
) -> Result<libremetaverse_types::Color4, crate::Error> { ) -> Result<libremetaverse_types::Color4, crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_manager::color_from_params(param)
"M:LibreMetaverse.AppearanceManager.GetColorFromParams(System.Collections.Generic.List{LibreMetaverse.AppearanceManager.ColorParamInfo})",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)`.
pub async fn get_current_outfit_folder( pub async fn get_current_outfit_folder(
@@ -3205,9 +3161,7 @@ impl AppearanceManager {
pub fn get_current_param_values( pub fn get_current_param_values(
&self, &self,
) -> Result<std::collections::HashMap<i32, f32>, crate::Error> { ) -> Result<std::collections::HashMap<i32, f32>, crate::Error> {
libremetaverse_types::not_implemented( self.native_get_current_param_values()
"M:LibreMetaverse.AppearanceManager.GetCurrentParamValues",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
pub async fn get_folder_wearables( pub async fn get_folder_wearables(
@@ -3269,17 +3223,15 @@ impl AppearanceManager {
pub fn make_appearance_packet( pub fn make_appearance_packet(
&self, &self,
) -> Result<libremetaverse::packets::AgentSetAppearancePacket, crate::Error> { ) -> Result<libremetaverse::packets::AgentSetAppearancePacket, crate::Error> {
libremetaverse_types::not_implemented( self.native_make_appearance_packet()
"M:LibreMetaverse.AppearanceManager.MakeAppearancePacket",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.MorphLayerForBakeType(LibreMetaverse.BakeType)`. /// C# member: `M:LibreMetaverse.AppearanceManager.MorphLayerForBakeType(LibreMetaverse.BakeType)`.
pub fn morph_layer_for_bake_type( pub fn morph_layer_for_bake_type(
bake_type: libremetaverse::BakeType, bake_type: libremetaverse::BakeType,
) -> Result<libremetaverse::AvatarTextureIndex, crate::Error> { ) -> Result<libremetaverse::AvatarTextureIndex, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_manager::morph_layer_for_bake_type(
"M:LibreMetaverse.AppearanceManager.MorphLayerForBakeType(LibreMetaverse.BakeType)", bake_type,
) ))
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.RandomizeAppearance(System.Random)`. /// C# member: `M:LibreMetaverse.AppearanceManager.RandomizeAppearance(System.Random)`.
pub async fn randomize_appearance( pub async fn randomize_appearance(
@@ -3328,18 +3280,15 @@ impl AppearanceManager {
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.RequestCachedBakes`. /// C# member: `M:LibreMetaverse.AppearanceManager.RequestCachedBakes`.
pub fn request_cached_bakes(&self) -> Result<(), crate::Error> { pub fn request_cached_bakes(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_request_cached_bakes()
"M:LibreMetaverse.AppearanceManager.RequestCachedBakes",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)`. /// C# member: `M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)`.
pub async fn request_set_appearance( pub async fn request_set_appearance(
&self, &self,
force_rebake: Option<bool>, force_rebake: Option<bool>,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_request_set_appearance(force_rebake.unwrap_or(false))
"M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)", .await
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)`.
pub async fn send_outfit_to_current_simulator( pub async fn send_outfit_to_current_simulator(
@@ -3350,9 +3299,7 @@ impl AppearanceManager {
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.ServerBakingRegion`. /// C# member: `M:LibreMetaverse.AppearanceManager.ServerBakingRegion`.
pub fn server_baking_region(&self) -> Result<bool, crate::Error> { pub fn server_baking_region(&self) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented( self.native_server_baking_region()
"M:LibreMetaverse.AppearanceManager.ServerBakingRegion",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)`. /// C# member: `M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)`.
pub fn update_last_received_cof_version( pub fn update_last_received_cof_version(
@@ -3401,18 +3348,14 @@ impl AppearanceManager {
} }
/// C# member: `P:LibreMetaverse.AppearanceManager.TextureProvider`. /// C# member: `P:LibreMetaverse.AppearanceManager.TextureProvider`.
pub fn texture_provider(&self) -> std::sync::Arc<dyn libremetaverse::IBakingTextureProvider> { pub fn texture_provider(&self) -> std::sync::Arc<dyn libremetaverse::IBakingTextureProvider> {
libremetaverse_types::unimplemented_api!( self.native_texture_provider()
"P:LibreMetaverse.AppearanceManager.TextureProvider"
)
} }
/// Setter for C# member: `P:LibreMetaverse.AppearanceManager.TextureProvider`. /// Setter for C# member: `P:LibreMetaverse.AppearanceManager.TextureProvider`.
pub fn set_texture_provider( pub fn set_texture_provider(
&mut self, &mut self,
value: std::sync::Arc<dyn libremetaverse::IBakingTextureProvider>, value: std::sync::Arc<dyn libremetaverse::IBakingTextureProvider>,
) { ) {
libremetaverse_types::unimplemented_api!( self.native_set_texture_provider(value)
"P:LibreMetaverse.AppearanceManager.TextureProvider"
)
} }
} }
@@ -3445,15 +3388,11 @@ pub struct AppearanceManagerTextureData {
impl AppearanceManagerTextureData { impl AppearanceManagerTextureData {
/// C# member: `M:LibreMetaverse.AppearanceManager.TextureData.#ctor`. /// C# member: `M:LibreMetaverse.AppearanceManager.TextureData.#ctor`.
pub fn new() -> Result<Self, crate::Error> { pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_manager::texture_data_defaults()
"M:LibreMetaverse.AppearanceManager.TextureData.#ctor",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.TextureData.ToString`. /// C# member: `M:LibreMetaverse.AppearanceManager.TextureData.ToString`.
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::texture_data_to_string(self)
"M:LibreMetaverse.AppearanceManager.TextureData.ToString"
)
} }
} }
@@ -3474,15 +3413,11 @@ pub struct AppearanceManagerWearableData {
impl AppearanceManagerWearableData { impl AppearanceManagerWearableData {
/// C# member: `M:LibreMetaverse.AppearanceManager.WearableData.#ctor`. /// C# member: `M:LibreMetaverse.AppearanceManager.WearableData.#ctor`.
pub fn new() -> Result<Self, crate::Error> { pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_manager::wearable_data_defaults()
"M:LibreMetaverse.AppearanceManager.WearableData.#ctor",
)
} }
/// C# member: `M:LibreMetaverse.AppearanceManager.WearableData.ToString`. /// C# member: `M:LibreMetaverse.AppearanceManager.WearableData.ToString`.
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
libremetaverse_types::unimplemented_api!( crate::appearance_manager::wearable_data_to_string(self)
"M:LibreMetaverse.AppearanceManager.WearableData.ToString"
)
} }
} }
@@ -3498,17 +3433,15 @@ impl AppearanceManagerException {
} }
/// C# type: `T:LibreMetaverse.AppearanceSetEventArgs`. /// C# type: `T:LibreMetaverse.AppearanceSetEventArgs`.
pub struct AppearanceSetEventArgs; pub use crate::appearance_manager::AppearanceSetEventArgs;
impl AppearanceSetEventArgs { impl AppearanceSetEventArgs {
/// C# member: `M:LibreMetaverse.AppearanceSetEventArgs.#ctor(System.Boolean)`. /// C# member: `M:LibreMetaverse.AppearanceSetEventArgs.#ctor(System.Boolean)`.
pub fn new(success: bool) -> Result<Self, crate::Error> { pub fn new(success: bool) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_manager::AppearanceSetEventArgs::native_new(success))
"M:LibreMetaverse.AppearanceSetEventArgs.#ctor(System.Boolean)",
)
} }
/// C# member: `P:LibreMetaverse.AppearanceSetEventArgs.Success`. /// C# member: `P:LibreMetaverse.AppearanceSetEventArgs.Success`.
pub fn success(&self) -> bool { pub fn success(&self) -> bool {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AppearanceSetEventArgs.Success") self.native_success()
} }
} }
@@ -8948,13 +8881,11 @@ impl GridClient {
} }
/// C# type: `T:LibreMetaverse.GridClientBakingTextureProvider`. /// C# type: `T:LibreMetaverse.GridClientBakingTextureProvider`.
pub struct GridClientBakingTextureProvider; pub use crate::baking_texture_provider::GridClientBakingTextureProvider;
impl GridClientBakingTextureProvider { impl GridClientBakingTextureProvider {
/// C# member: `M:LibreMetaverse.GridClientBakingTextureProvider.#ctor(LibreMetaverse.GridClient)`. /// C# member: `M:LibreMetaverse.GridClientBakingTextureProvider.#ctor(LibreMetaverse.GridClient)`.
pub fn new(client: libremetaverse::GridClient) -> Result<Self, crate::Error> { pub fn new(client: libremetaverse::GridClient) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::baking_texture_provider::GridClientBakingTextureProvider::native_new(client)
"M:LibreMetaverse.GridClientBakingTextureProvider.#ctor(LibreMetaverse.GridClient)",
)
} }
/// C# member: `M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
pub async fn request_texture( pub async fn request_texture(
@@ -8962,12 +8893,10 @@ impl GridClientBakingTextureProvider {
texture_id: libremetaverse_types::UUID, texture_id: libremetaverse_types::UUID,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>, cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<Option<libremetaverse::assets::AssetTexture>, crate::Error> { ) -> Result<Option<libremetaverse::assets::AssetTexture>, crate::Error> {
libremetaverse_types::not_implemented( self.native_request_texture(texture_id, cancellation_token)
"M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", .await
)
} }
} }
impl libremetaverse::IBakingTextureProvider for GridClientBakingTextureProvider {}
/// C# type: `T:LibreMetaverse.GridClientServiceCollectionExtensions`. /// C# type: `T:LibreMetaverse.GridClientServiceCollectionExtensions`.
pub struct GridClientServiceCollectionExtensions; pub struct GridClientServiceCollectionExtensions;
@@ -10779,7 +10708,7 @@ pub use crate::caps_http::HttpCapsClient;
pub use crate::caps_http::HttpCapsClientProgressReport; pub use crate::caps_http::HttpCapsClientProgressReport;
/// C# type: `T:LibreMetaverse.IBakingTextureProvider`. /// C# type: `T:LibreMetaverse.IBakingTextureProvider`.
pub trait IBakingTextureProvider: std::any::Any { pub trait IBakingTextureProvider: std::any::Any + Send + Sync {
/// C# member: `M:LibreMetaverse.IBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.IBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
fn request_texture( fn request_texture(
&self, &self,
@@ -19873,19 +19802,15 @@ impl ProtocolManager {
} }
/// C# type: `T:LibreMetaverse.RebakeAvatarTexturesEventArgs`. /// C# type: `T:LibreMetaverse.RebakeAvatarTexturesEventArgs`.
pub struct RebakeAvatarTexturesEventArgs; pub use crate::appearance_manager::RebakeAvatarTexturesEventArgs;
impl RebakeAvatarTexturesEventArgs { impl RebakeAvatarTexturesEventArgs {
/// C# member: `M:LibreMetaverse.RebakeAvatarTexturesEventArgs.#ctor(LibreMetaverse.UUID)`. /// C# member: `M:LibreMetaverse.RebakeAvatarTexturesEventArgs.#ctor(LibreMetaverse.UUID)`.
pub fn new(texture_id: libremetaverse_types::UUID) -> Result<Self, crate::Error> { pub fn new(texture_id: libremetaverse_types::UUID) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_manager::RebakeAvatarTexturesEventArgs::native_new(texture_id))
"M:LibreMetaverse.RebakeAvatarTexturesEventArgs.#ctor(LibreMetaverse.UUID)",
)
} }
/// C# member: `P:LibreMetaverse.RebakeAvatarTexturesEventArgs.TextureID`. /// C# member: `P:LibreMetaverse.RebakeAvatarTexturesEventArgs.TextureID`.
pub fn texture_id(&self) -> libremetaverse_types::UUID { pub fn texture_id(&self) -> libremetaverse_types::UUID {
libremetaverse_types::unimplemented_api!( self.native_texture_id()
"P:LibreMetaverse.RebakeAvatarTexturesEventArgs.TextureID"
)
} }
} }
@@ -23290,25 +23215,21 @@ pub mod appearance {
} }
/// C# type: `T:LibreMetaverse.Appearance.InitialOutfit`. /// C# type: `T:LibreMetaverse.Appearance.InitialOutfit`.
pub struct InitialOutfit; pub use crate::initial_outfit::InitialOutfit;
impl InitialOutfit { impl InitialOutfit {
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder)`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder)`.
pub fn new( pub fn new(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
cof: libremetaverse::appearance::CurrentOutfitFolder, cof: libremetaverse::appearance::CurrentOutfitFolder,
) -> Result<Self, crate::Error> { ) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::initial_outfit::InitialOutfit::native_new(client, cof)
"M:LibreMetaverse.Appearance.InitialOutfit.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder)",
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CheckSystemFoldersAsync(System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CheckSystemFoldersAsync(System.Threading.CancellationToken)`.
pub async fn check_system_folders( pub async fn check_system_folders(
&self, &self,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>, cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_check_system_folders(cancellation_token).await
"M:LibreMetaverse.Appearance.InitialOutfit.CheckSystemFoldersAsync(System.Threading.CancellationToken)",
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`.
pub async fn copy_folder( pub async fn copy_folder(
@@ -23324,9 +23245,8 @@ pub mod appearance {
>, >,
>, >,
) -> Result<libremetaverse_types::UUID, crate::Error> { ) -> Result<libremetaverse_types::UUID, crate::Error> {
libremetaverse_types::not_implemented( self.native_copy_folder(folder, destination, cancellation_token, progress)
"M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})", .await
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CreateFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType,System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.CreateFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType,System.Threading.CancellationToken)`.
pub async fn create_folder( pub async fn create_folder(
@@ -23336,18 +23256,15 @@ pub mod appearance {
type_: libremetaverse_types::FolderType, type_: libremetaverse_types::FolderType,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>, cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<libremetaverse_types::UUID, crate::Error> { ) -> Result<libremetaverse_types::UUID, crate::Error> {
libremetaverse_types::not_implemented( self.native_create_folder(parent, name, type_, cancellation_token)
"M:LibreMetaverse.Appearance.InitialOutfit.CreateFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType,System.Threading.CancellationToken)", .await
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.FindNodeByName(LibreMetaverse.InventoryNode,System.String)`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.FindNodeByName(LibreMetaverse.InventoryNode,System.String)`.
pub fn find_node_by_name( pub fn find_node_by_name(
root: libremetaverse::InventoryNode, root: libremetaverse::InventoryNode,
name: String, name: String,
) -> Result<Option<libremetaverse::InventoryNode>, crate::Error> { ) -> Result<Option<libremetaverse::InventoryNode>, crate::Error> {
libremetaverse_types::not_implemented( crate::initial_outfit::InitialOutfit::native_find_node_by_name(root, name)
"M:LibreMetaverse.Appearance.InitialOutfit.FindNodeByName(LibreMetaverse.InventoryNode,System.String)",
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`.
pub async fn set_initial_outfit( pub async fn set_initial_outfit(
@@ -23362,9 +23279,8 @@ pub mod appearance {
>, >,
>, >,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_set_initial_outfit(outfit, cancellation_token, progress)
"M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})", .await
)
} }
} }
@@ -23383,81 +23299,59 @@ pub mod appearance {
} }
/// C# type: `T:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress`. /// C# type: `T:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress`.
pub struct InitialOutfitInitialOutfitProgress; pub use crate::initial_outfit::InitialOutfitProgress as InitialOutfitInitialOutfitProgress;
impl InitialOutfitInitialOutfitProgress { impl InitialOutfitInitialOutfitProgress {
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.#ctor`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.#ctor`.
pub fn new() -> Result<Self, crate::Error> { pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::initial_outfit::InitialOutfitProgress::native_new()
"M:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.#ctor",
)
} }
/// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName`. /// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName`.
pub fn current_item_name(&self) -> String { pub fn current_item_name(&self) -> String {
libremetaverse_types::unimplemented_api!( self.native_current_item_name()
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName"
)
} }
/// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName`. /// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName`.
pub fn set_current_item_name(&mut self, value: String) { pub fn set_current_item_name(&mut self, value: String) {
libremetaverse_types::unimplemented_api!( self.native_set_current_item_name(value)
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName"
)
} }
/// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied`. /// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied`.
pub fn items_copied(&self) -> i32 { pub fn items_copied(&self) -> i32 {
libremetaverse_types::unimplemented_api!( self.native_items_copied()
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied"
)
} }
/// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied`. /// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied`.
pub fn set_items_copied(&mut self, value: i32) { pub fn set_items_copied(&mut self, value: i32) {
libremetaverse_types::unimplemented_api!( self.native_set_items_copied(value)
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied"
)
} }
/// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message`. /// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message`.
pub fn message(&self) -> String { pub fn message(&self) -> String {
libremetaverse_types::unimplemented_api!( self.native_message()
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message"
)
} }
/// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message`. /// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message`.
pub fn set_message(&mut self, value: String) { pub fn set_message(&mut self, value: String) {
libremetaverse_types::unimplemented_api!( self.native_set_message(value)
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message"
)
} }
/// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase`. /// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase`.
pub fn phase(&self) -> libremetaverse::appearance::InitialOutfitInitialOutfitPhase { pub fn phase(&self) -> libremetaverse::appearance::InitialOutfitInitialOutfitPhase {
libremetaverse_types::unimplemented_api!( self.native_phase()
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase"
)
} }
/// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase`. /// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase`.
pub fn set_phase( pub fn set_phase(
&mut self, &mut self,
value: libremetaverse::appearance::InitialOutfitInitialOutfitPhase, value: libremetaverse::appearance::InitialOutfitInitialOutfitPhase,
) { ) {
libremetaverse_types::unimplemented_api!( self.native_set_phase(value)
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase"
)
} }
/// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems`. /// C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems`.
pub fn total_items(&self) -> i32 { pub fn total_items(&self) -> i32 {
libremetaverse_types::unimplemented_api!( self.native_total_items()
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems"
)
} }
/// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems`. /// Setter for C# member: `P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems`.
pub fn set_total_items(&mut self, value: i32) { pub fn set_total_items(&mut self, value: i32) {
libremetaverse_types::unimplemented_api!( self.native_set_total_items(value)
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems"
)
} }
} }
/// C# type: `T:LibreMetaverse.Appearance.InitialOutfitHandler`. /// C# type: `T:LibreMetaverse.Appearance.InitialOutfitHandler`.
pub struct InitialOutfitHandler; pub use crate::initial_outfit::InitialOutfitHandler;
impl InitialOutfitHandler { impl InitialOutfitHandler {
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfitHandler.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfitHandler.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})`.
pub fn new( pub fn new(
@@ -23471,15 +23365,11 @@ pub mod appearance {
>, >,
>, >,
) -> Result<Self, crate::Error> { ) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::initial_outfit::InitialOutfitHandler::native_new(client, cof, progress)
"M:LibreMetaverse.Appearance.InitialOutfitHandler.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})",
)
} }
/// C# member: `M:LibreMetaverse.Appearance.InitialOutfitHandler.Dispose`. /// C# member: `M:LibreMetaverse.Appearance.InitialOutfitHandler.Dispose`.
pub fn dispose(&self) -> Result<(), crate::Error> { pub fn dispose(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_dispose()
"M:LibreMetaverse.Appearance.InitialOutfitHandler.Dispose",
)
} }
} }
} }
@@ -26647,62 +26537,54 @@ pub mod http {
pub mod imaging { pub mod imaging {
/// C# type: `T:LibreMetaverse.Imaging.Baker`. /// C# type: `T:LibreMetaverse.Imaging.Baker`.
pub struct Baker; pub use crate::appearance_baker::Baker;
impl Baker { impl Baker {
/// C# member: `M:LibreMetaverse.Imaging.Baker.#ctor(LibreMetaverse.BakeType)`. /// C# member: `M:LibreMetaverse.Imaging.Baker.#ctor(LibreMetaverse.BakeType)`.
pub fn new(bake_type: libremetaverse::BakeType) -> Result<Self, crate::Error> { pub fn new(bake_type: libremetaverse::BakeType) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_baker::Baker::native_new(bake_type)
"M:LibreMetaverse.Imaging.Baker.#ctor(LibreMetaverse.BakeType)",
)
} }
/// C# member: `M:LibreMetaverse.Imaging.Baker.AddTexture(LibreMetaverse.AppearanceManager.TextureData)`. /// C# member: `M:LibreMetaverse.Imaging.Baker.AddTexture(LibreMetaverse.AppearanceManager.TextureData)`.
pub fn add_texture( pub fn add_texture(
&mut self, &mut self,
tdata: libremetaverse::AppearanceManagerTextureData, tdata: libremetaverse::AppearanceManagerTextureData,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented( self.native_add_texture(tdata)
"M:LibreMetaverse.Imaging.Baker.AddTexture(LibreMetaverse.AppearanceManager.TextureData)",
)
} }
/// C# member: `M:LibreMetaverse.Imaging.Baker.Bake`. /// C# member: `M:LibreMetaverse.Imaging.Baker.Bake`.
pub fn bake(&mut self) -> Result<(), crate::Error> { pub fn bake(&mut self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.Imaging.Baker.Bake") self.native_bake()
} }
/// C# member: `M:LibreMetaverse.Imaging.Baker.BakeTypeFor(LibreMetaverse.AvatarTextureIndex)`. /// C# member: `M:LibreMetaverse.Imaging.Baker.BakeTypeFor(LibreMetaverse.AvatarTextureIndex)`.
pub fn bake_type_for( pub fn bake_type_for(
index: libremetaverse::AvatarTextureIndex, index: libremetaverse::AvatarTextureIndex,
) -> Result<libremetaverse::BakeType, crate::Error> { ) -> Result<libremetaverse::BakeType, crate::Error> {
libremetaverse_types::not_implemented( Ok(crate::appearance_baker::Baker::native_bake_type_for(index))
"M:LibreMetaverse.Imaging.Baker.BakeTypeFor(LibreMetaverse.AvatarTextureIndex)",
)
} }
/// C# member: `M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)`. /// C# member: `M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)`.
pub fn load_resource_layer( pub fn load_resource_layer(
file_name: String, file_name: String,
) -> Result<Option<libremetaverse_imaging::ManagedImage>, crate::Error> { ) -> Result<Option<libremetaverse_imaging::ManagedImage>, crate::Error> {
libremetaverse_types::not_implemented( crate::appearance_baker::Baker::native_load_resource_layer(file_name)
"M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)",
)
} }
/// C# member: `P:LibreMetaverse.Imaging.Baker.BakeHeight`. /// C# member: `P:LibreMetaverse.Imaging.Baker.BakeHeight`.
pub fn bake_height(&self) -> i32 { pub fn bake_height(&self) -> i32 {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Imaging.Baker.BakeHeight") self.native_bake_height()
} }
/// C# member: `P:LibreMetaverse.Imaging.Baker.BakeType`. /// C# member: `P:LibreMetaverse.Imaging.Baker.BakeType`.
pub fn bake_type(&self) -> libremetaverse::BakeType { pub fn bake_type(&self) -> libremetaverse::BakeType {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Imaging.Baker.BakeType") self.native_bake_type()
} }
/// C# member: `P:LibreMetaverse.Imaging.Baker.BakeWidth`. /// C# member: `P:LibreMetaverse.Imaging.Baker.BakeWidth`.
pub fn bake_width(&self) -> i32 { pub fn bake_width(&self) -> i32 {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Imaging.Baker.BakeWidth") self.native_bake_width()
} }
/// C# member: `P:LibreMetaverse.Imaging.Baker.BakedTexture`. /// C# member: `P:LibreMetaverse.Imaging.Baker.BakedTexture`.
pub fn baked_texture(&self) -> Option<libremetaverse::assets::AssetTexture> { pub fn baked_texture(&self) -> Option<libremetaverse::assets::AssetTexture> {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Imaging.Baker.BakedTexture") self.native_baked_texture()
} }
/// C# member: `P:LibreMetaverse.Imaging.Baker.Textures`. /// C# member: `P:LibreMetaverse.Imaging.Baker.Textures`.
pub fn textures(&self) -> Vec<libremetaverse::AppearanceManagerTextureData> { pub fn textures(&self) -> Vec<libremetaverse::AppearanceManagerTextureData> {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Imaging.Baker.Textures") self.native_textures()
} }
} }

View File

@@ -0,0 +1,607 @@
//! Transactional first-login outfit copy and application.
use crate::appearance::{CurrentOutfitFolder, InitialOutfitInitialOutfitPhase as Phase};
use crate::{
Error, GridClient, InventoryBase, InventoryFolder, InventoryNode, InventorySortOrder,
LoginStatus,
};
use libremetaverse_types::compat::{
CancellationToken, CancellationTokenSource, IProgress, Subscription,
};
use libremetaverse_types::{FolderType, UUID};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
const MAX_FOLDERS: usize = 256;
#[derive(Clone, Debug)]
pub struct InitialOutfitProgress {
current_item_name: String,
items_copied: i32,
message: String,
phase: Phase,
total_items: i32,
}
impl InitialOutfitProgress {
#[allow(clippy::unnecessary_wraps)] // The generated constructor uses the crate Result model.
pub(crate) fn native_new() -> Result<Self, Error> {
Ok(Self {
current_item_name: String::new(),
items_copied: 0,
message: String::new(),
phase: Phase::Counting,
total_items: 0,
})
}
pub(crate) fn native_current_item_name(&self) -> String {
self.current_item_name.clone()
}
pub(crate) fn native_set_current_item_name(&mut self, value: String) {
self.current_item_name = value;
}
pub(crate) const fn native_items_copied(&self) -> i32 {
self.items_copied
}
pub(crate) fn native_set_items_copied(&mut self, value: i32) {
self.items_copied = value;
}
pub(crate) fn native_message(&self) -> String {
self.message.clone()
}
pub(crate) fn native_set_message(&mut self, value: String) {
self.message = value;
}
pub(crate) const fn native_phase(&self) -> Phase {
self.phase
}
pub(crate) fn native_set_phase(&mut self, value: Phase) {
self.phase = value;
}
pub(crate) const fn native_total_items(&self) -> i32 {
self.total_items
}
pub(crate) fn native_set_total_items(&mut self, value: i32) {
self.total_items = value;
}
}
#[derive(Clone)]
pub struct InitialOutfit {
client: GridClient,
cof: CurrentOutfitFolder,
}
struct FolderPlan {
source: InventoryFolder,
children: Vec<InventoryFolder>,
items: Vec<InventoryBase>,
}
impl InitialOutfit {
#[allow(clippy::unnecessary_wraps)] // The generated constructor uses the crate Result model.
pub(crate) fn native_new(client: GridClient, cof: CurrentOutfitFolder) -> Result<Self, Error> {
Ok(Self { client, cof })
}
#[allow(clippy::needless_pass_by_value)] // Mirrors the public String signature.
pub(crate) fn native_find_node_by_name(
root: InventoryNode,
name: String,
) -> Result<Option<InventoryNode>, Error> {
if name.is_empty() {
return Ok(None);
}
let mut queue = VecDeque::from([root]);
let mut visited = HashSet::new();
let mut traversed = 0_usize;
while let Some(node) = queue.pop_front() {
traversed += 1;
if traversed > MAX_FOLDERS {
return Err(Error::InvalidOperation);
}
let identity = node.data().map(|value| value.inventory_base().uuid());
if let Some(identity) = identity
&& !visited.insert(identity)
{
continue;
}
if visited.len() > MAX_FOLDERS {
return Err(Error::InvalidOperation);
}
if node
.data()
.is_some_and(|value| value.inventory_base().name() == name)
{
return Ok(Some(node));
}
queue.extend(node.nodes().values());
}
Ok(None)
}
#[allow(clippy::unused_async)] // Preserves the Task-returning compatibility API.
pub(crate) async fn native_create_folder(
&self,
parent: UUID,
name: String,
folder_type: FolderType,
cancellation_token: Option<CancellationToken>,
) -> Result<UUID, Error> {
let token = cancellation_token.unwrap_or_default();
token.throw_if_cancellation_requested()?;
let id = self
.client
.native_inventory()?
.native_create_folder(parent, name, folder_type)?;
token.throw_if_cancellation_requested()?;
if id == UUID::zero() {
Err(Error::InvalidOperation)
} else {
Ok(id)
}
}
pub(crate) async fn native_check_system_folders(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<(), Error> {
let token = cancellation_token.unwrap_or_default();
token.throw_if_cancellation_requested()?;
let inventory = self.client.native_inventory()?;
let store = inventory.native_store().ok_or(Error::InvalidOperation)?;
let root = store
.root_folder()
.ok_or(Error::InvalidOperation)?
.base
.uuid();
for (folder_type, name) in [
(FolderType::Clothing, "Clothing"),
(FolderType::Trash, "Trash"),
] {
token.throw_if_cancellation_requested()?;
if inventory.native_find_folder_for_type(folder_type) == root {
self.native_create_folder(root, name.to_owned(), folder_type, Some(token.clone()))
.await?;
}
}
Ok(())
}
async fn plan_copy(
&self,
root: InventoryFolder,
token: &CancellationToken,
) -> Result<(Vec<FolderPlan>, i32), Error> {
let inventory = self.client.native_inventory()?;
let mut queue = VecDeque::from([root]);
let mut visited = HashSet::new();
let mut plans = Vec::new();
let mut total = 0_i32;
while let Some(folder) = queue.pop_front() {
token.throw_if_cancellation_requested()?;
if !visited.insert(folder.base.uuid()) {
continue;
}
if visited.len() > MAX_FOLDERS {
return Err(Error::InvalidOperation);
}
let contents = inventory
.native_folder_contents(
folder.base.uuid(),
folder.base.owner_id(),
true,
true,
InventorySortOrder::BY_NAME,
Some(token.clone()),
false,
)
.await?;
let mut children = Vec::new();
let mut items = Vec::new();
for value in contents {
if let Some(child) = value.as_any().downcast_ref::<InventoryFolder>() {
children.push(child.clone());
queue.push_back(child.clone());
} else {
total = total.checked_add(1).ok_or(Error::Argument)?;
items.push(value.inventory_base().clone());
}
}
plans.push(FolderPlan {
source: folder,
children,
items,
});
}
Ok((plans, total))
}
pub(crate) async fn native_copy_folder(
&self,
folder: InventoryFolder,
destination: UUID,
cancellation_token: Option<CancellationToken>,
progress: Option<Box<dyn IProgress<InitialOutfitProgress>>>,
) -> Result<UUID, Error> {
self.native_copy_folder_impl(folder, destination, cancellation_token, progress, true)
.await
.map(|(folder, _)| folder)
}
async fn native_copy_folder_impl(
&self,
folder: InventoryFolder,
destination: UUID,
cancellation_token: Option<CancellationToken>,
progress: Option<Box<dyn IProgress<InitialOutfitProgress>>>,
report_complete: bool,
) -> Result<(UUID, i32), Error> {
let token = cancellation_token.unwrap_or_default();
let (plans, total) = self.plan_copy(folder.clone(), &token).await?;
report(
progress.as_deref(),
Phase::Counting,
total,
0,
"",
"Inventory counted",
)?;
let root_id = self
.native_create_folder(
destination,
folder.base.name(),
folder.preferred_type(),
Some(token.clone()),
)
.await?;
let result = self
.execute_copy(&plans, root_id, total, &token, progress.as_deref())
.await;
if let Err(error) = result {
self.cleanup_folder(root_id).await;
return Err(error);
}
if report_complete {
report(
progress.as_deref(),
Phase::Complete,
total,
total,
"",
"Folder copy complete",
)?;
}
Ok((root_id, total))
}
async fn cleanup_folder(&self, root_id: UUID) {
// Compensation deliberately ignores the cancelled caller token.
let Ok(inventory) = self.client.native_inventory() else {
return;
};
let _ = inventory
.native_remove_inventory_objects(Vec::new(), vec![root_id], None)
.await;
if let Some(store) = inventory.native_store()
&& let Ok(Some(folder)) =
store.get_value_or_default_with_uuid_a7c63fbe::<InventoryFolder>(root_id)
{
let _ = store.remove_node_for(&folder);
}
}
async fn execute_copy(
&self,
plans: &[FolderPlan],
root_id: UUID,
total: i32,
token: &CancellationToken,
progress: Option<&dyn IProgress<InitialOutfitProgress>>,
) -> Result<(), Error> {
let inventory = self.client.native_inventory()?;
let root_source = plans.first().ok_or(Error::Argument)?.source.base.uuid();
let mut destinations = HashMap::from([(root_source, root_id)]);
let mut copied = 0_i32;
for plan in plans {
token.throw_if_cancellation_requested()?;
let destination = *destinations
.get(&plan.source.base.uuid())
.ok_or(Error::InvalidOperation)?;
for child in &plan.children {
let id = self
.native_create_folder(
destination,
child.base.name(),
child.preferred_type(),
Some(token.clone()),
)
.await?;
destinations.insert(child.base.uuid(), id);
}
for item in &plan.items {
token.throw_if_cancellation_requested()?;
report(
progress,
Phase::Copying,
total,
copied,
&item.name(),
"Copying inventory item",
)?;
let copied_item = inventory
.native_copy_item(
item.uuid(),
destination,
item.name(),
item.owner_id(),
Some(token.clone()),
)
.await?;
if copied_item.is_none() {
return Err(Error::InvalidOperation);
}
copied += 1;
report(
progress,
Phase::Copying,
total,
copied,
&item.name(),
"Inventory item copied",
)?;
}
}
Ok(())
}
pub(crate) async fn native_set_initial_outfit(
&self,
outfit: String,
cancellation_token: Option<CancellationToken>,
progress: Option<Box<dyn IProgress<InitialOutfitProgress>>>,
) -> Result<(), Error> {
if outfit.is_empty() {
return Err(Error::Argument);
}
let token = cancellation_token.unwrap_or_default();
token.throw_if_cancellation_requested()?;
let inventory = self.client.native_inventory()?;
let store = inventory.native_store().ok_or(Error::InvalidOperation)?;
let node = Self::native_find_node_by_name(store.library_root_node(), outfit)?
.ok_or(Error::InvalidOperation)?;
let folder = node
.data()
.and_then(|value| value.as_any().downcast_ref::<InventoryFolder>().cloned())
.ok_or(Error::InvalidOperation)?;
self.native_check_system_folders(Some(token.clone()))
.await?;
let clothing = inventory.native_find_folder_for_type(FolderType::Clothing);
let wrapper = progress.map(Arc::<dyn IProgress<InitialOutfitProgress>>::from);
let (copied, total) = self
.native_copy_folder_impl(
folder,
clothing,
Some(token.clone()),
wrapper
.as_ref()
.map(|sink| Box::new(SharedProgress(Arc::clone(sink))) as Box<_>),
false,
)
.await?;
report(
wrapper.as_deref(),
Phase::Applying,
total,
total,
"",
"Applying initial outfit",
)?;
let applied = async {
token.throw_if_cancellation_requested()?;
if self
.cof
.native_replace_outfit(copied, Some(token.clone()))
.await?
{
Ok(())
} else {
Err(Error::InvalidOperation)
}
}
.await;
if let Err(error) = applied {
self.cleanup_folder(copied).await;
return Err(error);
}
report(
wrapper.as_deref(),
Phase::Complete,
total,
total,
"",
"Initial outfit applied",
)
}
}
#[allow(clippy::unnecessary_wraps)] // Keeps progress reporting composable with fallible stages.
fn report(
sink: Option<&dyn IProgress<InitialOutfitProgress>>,
phase: Phase,
total: i32,
copied: i32,
current: &str,
message: &str,
) -> Result<(), Error> {
if let Some(sink) = sink {
sink.report(InitialOutfitProgress {
current_item_name: current.to_owned(),
items_copied: copied,
message: message.to_owned(),
phase,
total_items: total,
});
}
Ok(())
}
struct SharedProgress(Arc<dyn IProgress<InitialOutfitProgress>>);
impl IProgress<InitialOutfitProgress> for SharedProgress {
fn report(&self, value: InitialOutfitProgress) {
self.0.report(value);
}
}
pub struct InitialOutfitHandler {
cancellation: CancellationTokenSource,
subscription: Mutex<Option<Subscription>>,
}
impl InitialOutfitHandler {
pub(crate) fn native_new(
client: GridClient,
cof: CurrentOutfitFolder,
progress: Option<Box<dyn IProgress<InitialOutfitProgress>>>,
) -> Result<Self, Error> {
let cancellation = CancellationTokenSource::new();
let token = cancellation.token();
let started = Arc::new(AtomicBool::new(false));
let callback_started = Arc::clone(&started);
let progress = progress.map(Arc::<dyn IProgress<InitialOutfitProgress>>::from);
let network = client.native_network()?;
let subscription = network.native_subscribe_login_progress(Arc::new(move |event| {
if event.status() != LoginStatus::Success {
return;
}
let Some(login) = client
.native_network()
.ok()
.and_then(|network| network.login_response_data)
else {
return;
};
if !login.first_login() || login.initial_outfit().is_empty() {
return;
}
if callback_started
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return;
}
let client = client.clone();
let cof = cof.clone();
let outfit = login.initial_outfit();
let token = token.clone();
let progress = progress.as_ref().map(Arc::clone);
let _ = std::thread::Builder::new()
.name("initial-outfit".to_owned())
.spawn(move || {
let Ok(runtime) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
return;
};
let mut client = client;
let _ = runtime.block_on(async move {
let _ = client
.native_self()
.native_set_agent_access_async("A".to_owned(), Some(token.clone()))
.await;
let initial = InitialOutfit { client, cof };
initial
.native_set_initial_outfit(
outfit,
Some(token),
progress.map(|sink| Box::new(SharedProgress(sink)) as Box<_>),
)
.await
});
});
}));
Ok(Self {
cancellation,
subscription: Mutex::new(Some(subscription)),
})
}
#[allow(clippy::unnecessary_wraps)] // IDisposable is represented with the crate Result model.
pub(crate) fn native_dispose(&self) -> Result<(), Error> {
self.cancellation.cancel();
self.subscription
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn progress_properties_round_trip_without_shared_mutability() {
let mut progress = InitialOutfitProgress::native_new().unwrap();
progress.native_set_phase(Phase::Copying);
progress.native_set_total_items(7);
progress.native_set_items_copied(3);
progress.native_set_current_item_name("shirt".to_owned());
progress.native_set_message("copying".to_owned());
assert_eq!(progress.native_phase(), Phase::Copying);
assert_eq!(progress.native_total_items(), 7);
assert_eq!(progress.native_items_copied(), 3);
assert_eq!(progress.native_current_item_name(), "shirt");
assert_eq!(progress.native_message(), "copying");
}
#[test]
fn find_node_by_name_walks_children_and_is_bounded() {
let mut root_data = InventoryFolder::new(UUID::random().unwrap()).unwrap();
root_data.base.set_name("Library".to_owned());
let root = InventoryNode::new_with_inventory_base(&root_data).unwrap();
let mut child_data = InventoryFolder::new(UUID::random().unwrap()).unwrap();
child_data.base.set_name("Starter Outfit".to_owned());
let child = InventoryNode::new_with_inventory_base(&child_data).unwrap();
root.nodes().add(child_data.base.uuid(), child).unwrap();
let found = InitialOutfit::native_find_node_by_name(root, "Starter Outfit".to_owned())
.unwrap()
.unwrap();
assert_eq!(
found.data().unwrap().inventory_base().name(),
"Starter Outfit"
);
}
#[tokio::test]
async fn cancelled_create_does_not_touch_inventory_or_network() {
let client = GridClient::new().unwrap();
let cof = CurrentOutfitFolder::native_new(Some(Arc::new(client.clone()))).unwrap();
let initial = InitialOutfit::native_new(client, cof).unwrap();
let source = CancellationTokenSource::new();
source.cancel();
let result = initial
.native_create_folder(
UUID::zero(),
"Clothing".to_owned(),
FolderType::Clothing,
Some(source.token()),
)
.await;
assert_eq!(result, Err(Error::Cancelled));
}
#[test]
fn handler_dispose_is_idempotent_and_cancels_work() {
let handler = InitialOutfitHandler {
cancellation: CancellationTokenSource::new(),
subscription: Mutex::new(None),
};
handler.native_dispose().unwrap();
handler.native_dispose().unwrap();
assert!(handler.cancellation.token().is_cancellation_requested());
}
}

View File

@@ -7,11 +7,13 @@ mod attention_catalog;
mod agent_manager; mod agent_manager;
mod agent_messages; mod agent_messages;
mod agent_movement; mod agent_movement;
mod appearance_baker;
mod appearance_manager; mod appearance_manager;
mod asset_cache; mod asset_cache;
mod asset_manager; mod asset_manager;
mod asset_material; mod asset_material;
mod asset_models; mod asset_models;
mod baking_texture_provider;
mod bit_pack; mod bit_pack;
mod caps; mod caps;
mod caps_http; mod caps_http;
@@ -20,6 +22,7 @@ mod current_outfit;
mod download_manager; mod download_manager;
mod event_queue; mod event_queue;
mod gesture; mod gesture;
mod initial_outfit;
mod inventory; mod inventory;
mod inventory_ais; mod inventory_ais;
mod inventory_manager; mod inventory_manager;
@@ -166,13 +169,11 @@ impl NetworkManager {
#[allow(dead_code)] #[allow(dead_code)]
impl imaging::Baker { impl imaging::Baker {
fn resize_to_bake_dimensions( fn resize_to_bake_dimensions(
_source: libremetaverse_imaging::ManagedImage, source: libremetaverse_imaging::ManagedImage,
_width: i32, width: i32,
_height: i32, height: i32,
) -> Result<Option<libremetaverse_imaging::ManagedImage>, Error> { ) -> Result<Option<libremetaverse_imaging::ManagedImage>, Error> {
libremetaverse_types::not_implemented( crate::appearance_baker::Baker::native_resize_to_bake_dimensions(source, width, height)
"M:LibreMetaverse.Imaging.Baker.ResizeToBakeDimensions(LibreMetaverse.Imaging.ManagedImage,System.Int32,System.Int32)",
)
} }
} }

View File

@@ -57,5 +57,72 @@ Focused compatibility coverage lives in `current_outfit_folder_semantics`,
issue-relevant appearance cases. Native recording fakes additionally prove issue-relevant appearance cases. Native recording fakes additionally prove
create/remove compensation, create-before-attach ordering, replacement state, create/remove compensation, create-before-attach ordering, replacement state,
layer preservation, and cancellation before backend interaction. Texture layer preservation, and cancellation before backend interaction. Texture
baking, visual-parameter synthesis, and texture upload are a separate baking, visual-parameter synthesis, and texture upload are the next appearance
appearance pipeline and are not owned by this service slice. pipeline described below.
## Baking and texture transport
The baking pipeline retains all 45 avatar texture slots, all 11 classic and
extended baked outputs, the reference cache salts and sentinel UUIDs, and the
generated visual-parameter ordering from the pinned character definition.
Wearable assets are parsed as bounded LLWearable text, including parameter
values and texture indices. Texture retrieval is provided through
`IBakingTextureProvider`; the grid implementation downloads the asset and
decodes it through the existing JPEG2000 abstraction. At most five texture
downloads and six baked-texture uploads are in flight at once.
`Baker` composites owned image copies into 1024-by-1024 bakes (128-by-128 for
eyes). It applies the canonical head, skin-grain, hair, wearable alpha, tint,
and morph-bump layers, preserves color/alpha/bump channels, and caps a bake at
60 input layers. Character resources are resolved cross-platform from
`LIBREMETAVERSE_RESOURCE_DIR`, the configured resource directory, or the
project resource bundle. Cached images are cloned on both insertion and read,
so a caller cannot corrupt a later bake. Minimal installations have neutral
built-in base masks; JPEG2000 encoding remains behind the project codec
feature rather than a platform API.
Appearance setting first uses server-side baking when the simulator advertises
the appearance service and actually exposes `UpdateAvatarAppearance`; a region
with only the protocol flag falls back to local baking. The capability call
validates the COF version, success response, visual parameters, and required
baked texture IDs, with five cancellation-aware attempts and bounded
exponential delay. A classic region
requests cached bake hashes, incorporates the reply, downloads only necessary
inputs, bakes missing layers locally, uploads them, and sends the complete
`AgentSetAppearance` packet. Its texture entry uses the protocol's big-endian
seven-bit face masks and complete default-face attributes, while visual
parameters retain the reference's 218 normal or 251 physics-wearable fields
and drive the reported avatar height. Rebake packets cancel superseded work,
clear stale bake IDs, and force a fresh local bake. A region change before the
final send aborts and rolls back the old-region result. No synchronous state
lock crosses a network await or event callback. On cancellation or failure the
texture, wearable-asset, visual-parameter, and COF-version snapshots are
restored before the failure event is published.
Wearable discovery rebuilds both wearables and attachments from resolved COF
links with a 512-entry bound. On classic/OpenSim regions, an empty or unusable
COF result falls back to `AgentWearablesRequest` and waits up to 30 seconds for
`AgentWearablesUpdate`. The packet handler ignores server-baking dummy payloads,
preserves every valid item/asset/type tuple, wakes the pending request, and can
start a bake for a spontaneous legacy update. Missing wearable assets or
required texture downloads fail the pipeline instead of publishing a false
success with incomplete bakes.
## Initial outfit setup
First-login outfit setup searches the library iteratively with a 256-node
bound, creates missing Clothing and Trash system folders, counts the selected
tree, and copies its folder and item hierarchy into Clothing. Progress follows
`Counting`, `Copying`, `Applying`, then `Complete`; completion is reported only
after transactional COF replacement succeeds. Cancellation is checked during
planning, creation, copying, and application. A failed or cancelled copy
removes its partially created root, and a failed application removes the fully
copied root while the Current Outfit Folder restores its prior links. The
login handler starts this work once, owns its cancellation source and event
subscription, and disposes idempotently.
Focused verification covers the translated Baker cases, exact bake mappings
and cache hashes, generated visual metadata, wearable parsing, output pixels
and channels, packet/event payloads, cancellation before mutation, progress
state, and handler disposal. Live appearance tests remain opt-in because they
require grid credentials.

View File

@@ -121,10 +121,10 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case`
| `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:68` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:612 (`appearance_manager_update_last_received_cof_version_rejects_lower_version`)` | `translated` | `f4a827fcace9d50306a8c375183e056140996d2a39b02e4dd12fc6cc79e5d0f0` | | `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:68` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:612 (`appearance_manager_update_last_received_cof_version_rejects_lower_version`)` | `translated` | `f4a827fcace9d50306a8c375183e056140996d2a39b02e4dd12fc6cc79e5d0f0` |
| `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_EqualVersion_IsNotAdvanced::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_EqualVersion_IsNotAdvanced` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:81` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:627 (`appearance_manager_update_last_received_cof_version_equal_version_is_not_advanced`)` | `translated` | `e3db5962cdad5e18ad27a36d0d7cca2d471e96df7513315d0f34bb0db99d723c` | | `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_EqualVersion_IsNotAdvanced::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_EqualVersion_IsNotAdvanced` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:81` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:627 (`appearance_manager_update_last_received_cof_version_equal_version_is_not_advanced`)` | `translated` | `e3db5962cdad5e18ad27a36d0d7cca2d471e96df7513315d0f34bb0db99d723c` |
| `LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleLayerTexture_DoesNotThrow::test` | `BakerTests.Bake_GrayscaleLayerTexture_DoesNotThrow` | `` | `LibreMetaverse.Tests/BakerTests.cs:15` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:30 (`bake_grayscale_layer_texture_does_not_throw`)` | `translated` | `c3fc1d2c16b5f8d69678a6cf05a3a275e0b657f3a6e7f6788f350f2a107200b3` | | `LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleLayerTexture_DoesNotThrow::test` | `BakerTests.Bake_GrayscaleLayerTexture_DoesNotThrow` | `` | `LibreMetaverse.Tests/BakerTests.cs:15` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:30 (`bake_grayscale_layer_texture_does_not_throw`)` | `translated` | `c3fc1d2c16b5f8d69678a6cf05a3a275e0b657f3a6e7f6788f350f2a107200b3` |
| `LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow::test` | `BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow` | `` | `LibreMetaverse.Tests/BakerTests.cs:37` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:41 (`bake_grayscale_hair_texture_no_skin_does_not_throw`)` | `translated` | `e48b737d9138e63cda98b5e80bf561a80d1dab5de78901ac9e1bb320ca281eb6` | | `LibreMetaverse.Tests/BakerTests.cs::BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow::test` | `BakerTests.Bake_GrayscaleHairTextureNoSkin_DoesNotThrow` | `` | `LibreMetaverse.Tests/BakerTests.cs:37` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:46 (`bake_grayscale_hair_texture_no_skin_does_not_throw`)` | `translated` | `e48b737d9138e63cda98b5e80bf561a80d1dab5de78901ac9e1bb320ca281eb6` |
| `LibreMetaverse.Tests/BakerTests.cs::BakerTests.LoadResourceLayer_HeadColorTga_LoadsFromCharacterDirectory::test` | `BakerTests.LoadResourceLayer_HeadColorTga_LoadsFromCharacterDirectory` | `` | `LibreMetaverse.Tests/BakerTests.cs:62` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:55 (`load_resource_layer_head_color_tga_loads_from_character_directory`)` | `translated` | `5a9b80005a51f04a227ca392218725d50704d038783e04fc49bb955997c22e76` | | `LibreMetaverse.Tests/BakerTests.cs::BakerTests.LoadResourceLayer_HeadColorTga_LoadsFromCharacterDirectory::test` | `BakerTests.LoadResourceLayer_HeadColorTga_LoadsFromCharacterDirectory` | `` | `LibreMetaverse.Tests/BakerTests.cs:62` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:60 (`load_resource_layer_head_color_tga_loads_from_character_directory`)` | `translated` | `5a9b80005a51f04a227ca392218725d50704d038783e04fc49bb955997c22e76` |
| `LibreMetaverse.Tests/BakerTests.cs::BakerTests.LoadResourceLayer_CachedResourceNotCorruptedByCallerMutation::test` | `BakerTests.LoadResourceLayer_CachedResourceNotCorruptedByCallerMutation` | `` | `LibreMetaverse.Tests/BakerTests.cs:69` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:65 (`load_resource_layer_cached_resource_not_corrupted_by_caller_mutation`)` | `translated` | `18b458bbd7bb13d2b68711f7d3495193f1b4edac9c1f776930ad2c172f97cd49` | | `LibreMetaverse.Tests/BakerTests.cs::BakerTests.LoadResourceLayer_CachedResourceNotCorruptedByCallerMutation::test` | `BakerTests.LoadResourceLayer_CachedResourceNotCorruptedByCallerMutation` | `` | `LibreMetaverse.Tests/BakerTests.cs:69` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:70 (`load_resource_layer_cached_resource_not_corrupted_by_caller_mutation`)` | `translated` | `18b458bbd7bb13d2b68711f7d3495193f1b4edac9c1f776930ad2c172f97cd49` |
| `LibreMetaverse.Tests/BakerTests.cs::BakerTests.ResizeToBakeDimensions_SourceSmallerInOneAxisOnly_ScalesRatherThanTiles::test` | `BakerTests.ResizeToBakeDimensions_SourceSmallerInOneAxisOnly_ScalesRatherThanTiles` | `` | `LibreMetaverse.Tests/BakerTests.cs:93` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:84 (`resize_to_bake_dimensions_source_smaller_in_one_axis_only_scales_rather_than_tiles`)` | `translated` | `ce5bd346b48cb19dbc0248403eafefb07309d4e05e675e9a02db5a46d7cd65ff` | | `LibreMetaverse.Tests/BakerTests.cs::BakerTests.ResizeToBakeDimensions_SourceSmallerInOneAxisOnly_ScalesRatherThanTiles::test` | `BakerTests.ResizeToBakeDimensions_SourceSmallerInOneAxisOnly_ScalesRatherThanTiles` | `` | `LibreMetaverse.Tests/BakerTests.cs:93` | `Imaging` | `` | `crates/libremetaverse/src/appearance_baker_semantics.rs:89 (`resize_to_bake_dimensions_source_smaller_in_one_axis_only_scales_rather_than_tiles`)` | `translated` | `ce5bd346b48cb19dbc0248403eafefb07309d4e05e675e9a02db5a46d7cd65ff` |
| `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_ClothingWearable_ReturnsWearableTypeAndZeroLayer::test` | `BatchLinkTests.GetCofLinkDescription_ClothingWearable_ReturnsWearableTypeAndZeroLayer` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:84` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:151 (`get_cof_link_description_clothing_wearable_returns_wearable_type_and_zero_layer`)` | `translated` | `898a14ff28d953a58a01c3a0e5d9d9fcbbe61376a33fdc5caf20268aaac76242` | | `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_ClothingWearable_ReturnsWearableTypeAndZeroLayer::test` | `BatchLinkTests.GetCofLinkDescription_ClothingWearable_ReturnsWearableTypeAndZeroLayer` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:84` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:151 (`get_cof_link_description_clothing_wearable_returns_wearable_type_and_zero_layer`)` | `translated` | `898a14ff28d953a58a01c3a0e5d9d9fcbbe61376a33fdc5caf20268aaac76242` |
| `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_BodyPartShape_ReturnsEmpty::test` | `BatchLinkTests.GetCofLinkDescription_BodyPartShape_ReturnsEmpty` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:98` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:160 (`get_cof_link_description_body_part_shape_returns_empty`)` | `translated` | `4f009136d265da91afb9eafd060faf459f6c510ca0502a33ce9e128d241f9bd1` | | `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_BodyPartShape_ReturnsEmpty::test` | `BatchLinkTests.GetCofLinkDescription_BodyPartShape_ReturnsEmpty` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:98` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:160 (`get_cof_link_description_body_part_shape_returns_empty`)` | `translated` | `4f009136d265da91afb9eafd060faf459f6c510ca0502a33ce9e128d241f9bd1` |
| `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_AttachmentObject_ReturnsEmpty::test` | `BatchLinkTests.GetCofLinkDescription_AttachmentObject_ReturnsEmpty` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:112` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:169 (`get_cof_link_description_attachment_object_returns_empty`)` | `translated` | `a838329a7a23081a4022671bc794e7e706c9ab7da51598280a8befbb824d336d` | | `LibreMetaverse.Tests/BatchLinkTests.cs::BatchLinkTests.GetCofLinkDescription_AttachmentObject_ReturnsEmpty::test` | `BatchLinkTests.GetCofLinkDescription_AttachmentObject_ReturnsEmpty` | `` | `LibreMetaverse.Tests/BatchLinkTests.cs:112` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `crates/libremetaverse/src/batch_link_internal_semantics.rs:169 (`get_cof_link_description_attachment_object_returns_empty`)` | `translated` | `a838329a7a23081a4022671bc794e7e706c9ab7da51598280a8befbb824d336d` |

View File

@@ -2144,7 +2144,7 @@
"Imaging" "Imaging"
], ],
"fixture_dependencies": [], "fixture_dependencies": [],
"rust_body_sha256": "095b472ba775ae13542328da1b5e709d630699d092778132b3c1f164168c5dd8", "rust_body_sha256": "5c686ee0bd308a54e16a0ea8b50e1e75f9b085dc0cb9bb45317bde0c5cac674a",
"status": "translated", "status": "translated",
"rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs", "rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs",
"rust_line": 30, "rust_line": 30,
@@ -2168,7 +2168,7 @@
"rust_body_sha256": "e490a240143cf358cedc6562baa63dd9ee2274958744e7df00d13d9981f7597f", "rust_body_sha256": "e490a240143cf358cedc6562baa63dd9ee2274958744e7df00d13d9981f7597f",
"status": "translated", "status": "translated",
"rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs", "rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs",
"rust_line": 41, "rust_line": 46,
"rust_test": "bake_grayscale_hair_texture_no_skin_does_not_throw", "rust_test": "bake_grayscale_hair_texture_no_skin_does_not_throw",
"semantic_review": "reviewed" "semantic_review": "reviewed"
}, },
@@ -2189,7 +2189,7 @@
"rust_body_sha256": "8c7043c839d0a52cc72050120aaf3f726b962ee674e9169c33d8b1137b23d919", "rust_body_sha256": "8c7043c839d0a52cc72050120aaf3f726b962ee674e9169c33d8b1137b23d919",
"status": "translated", "status": "translated",
"rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs", "rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs",
"rust_line": 55, "rust_line": 60,
"rust_test": "load_resource_layer_head_color_tga_loads_from_character_directory", "rust_test": "load_resource_layer_head_color_tga_loads_from_character_directory",
"semantic_review": "reviewed" "semantic_review": "reviewed"
}, },
@@ -2210,7 +2210,7 @@
"rust_body_sha256": "54a79b572fdf3e22a3a00380cb98f180a2eda94f71ed035db312310b8f15a9bb", "rust_body_sha256": "54a79b572fdf3e22a3a00380cb98f180a2eda94f71ed035db312310b8f15a9bb",
"status": "translated", "status": "translated",
"rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs", "rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs",
"rust_line": 65, "rust_line": 70,
"rust_test": "load_resource_layer_cached_resource_not_corrupted_by_caller_mutation", "rust_test": "load_resource_layer_cached_resource_not_corrupted_by_caller_mutation",
"semantic_review": "reviewed" "semantic_review": "reviewed"
}, },
@@ -2231,7 +2231,7 @@
"rust_body_sha256": "16a16384fe8cfa5c8a8a4b8de9bf9428de61756882d83883a7215ef708fa202d", "rust_body_sha256": "16a16384fe8cfa5c8a8a4b8de9bf9428de61756882d83883a7215ef708fa202d",
"status": "translated", "status": "translated",
"rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs", "rust_file": "crates/libremetaverse/src/appearance_baker_semantics.rs",
"rust_line": 84, "rust_line": 89,
"rust_test": "resize_to_bake_dimensions_source_smaller_in_one_axis_only_scales_rather_than_tiles", "rust_test": "resize_to_bake_dimensions_source_smaller_in_one_axis_only_scales_rather_than_tiles",
"semantic_review": "reviewed" "semantic_review": "reviewed"
}, },
@@ -9003,7 +9003,7 @@
"body_sha256": "0e0f06d2317de91147bab1e623bed35ee03015da1498a9b9837c91c144bf7da5", "body_sha256": "0e0f06d2317de91147bab1e623bed35ee03015da1498a9b9837c91c144bf7da5",
"categories": [], "categories": [],
"fixture_dependencies": [], "fixture_dependencies": [],
"rust_body_sha256": "7ec1c1c19905ad5733028faae50f2f20f81216faecf6ab13d60c54f4e7cdc427", "rust_body_sha256": "333271eabd854cb7150441d999d3f6cb1c2fcf00234ce5e490bf54828f620b91",
"status": "translated", "status": "translated",
"rust_file": "tests/compat/tests/wire_semantics.rs", "rust_file": "tests/compat/tests/wire_semantics.rs",
"rust_line": 329, "rust_line": 329,

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Audit issue 66's appearance baking and initial-outfit 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"
BAKER = ROOT / "crates" / "libremetaverse" / "src" / "appearance_baker.rs"
PROVIDER = ROOT / "crates" / "libremetaverse" / "src" / "baking_texture_provider.rs"
OUTFIT = ROOT / "crates" / "libremetaverse" / "src" / "initial_outfit.rs"
ASSETS = ROOT / "crates" / "libremetaverse" / "src" / "asset_models.rs"
BAKER_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "appearance_baker_semantics.rs"
VISUAL_TESTS = ROOT / "crates" / "libremetaverse" / "tests" / "generated_catalogs.rs"
COMPAT = ROOT / "tests" / "compat" / "tests" / "appearance_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.InitialOutfit",
"T:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress",
"T:LibreMetaverse.Appearance.InitialOutfitHandler",
"T:LibreMetaverse.GridClientBakingTextureProvider",
"T:LibreMetaverse.Imaging.Baker",
}
REQUIRED_MEMBERS = {
"M:LibreMetaverse.AppearanceManager.DecodeWearableParams(LibreMetaverse.AppearanceManager.WearableData,LibreMetaverse.AppearanceManager.TextureData[]@)",
"M:LibreMetaverse.AppearanceManager.MakeAppearancePacket",
"M:LibreMetaverse.AppearanceManager.RequestCachedBakes",
"M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)",
"M:LibreMetaverse.AppearanceManager.ServerBakingRegion",
"M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
"M:LibreMetaverse.Imaging.Baker.Bake",
"M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)",
"M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})",
"M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})",
}
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 66 native types missing: " + ", ".join(missing_types))
missing_members = sorted(REQUIRED_MEMBERS - set(generate_api_shims.NATIVE_MEMBER_BODIES))
if missing_members:
raise SystemExit("issue 66 native members missing: " + ", ".join(missing_members))
for source in (APPEARANCE, BAKER, PROVIDER, OUTFIT, ASSETS):
if STUB_RE.search(source.read_text()):
raise SystemExit(f"issue 66 owned Rust stubs remain in {source.name}")
require_markers(APPEARANCE, (
"baked_texture_hash", "wearable_bake_map", "AgentCachedTextureResponse",
"AgentWearablesRequest", "AgentWearablesUpdate", "MAX_COF_ENTRIES",
"RebakeAvatarTextures", "tokio::sync::Semaphore::new(5)",
"tokio::sync::Semaphore::new(6)", "native_server_bake",
"old_visual_parameters", "appearance_packet_contains_all_bakes",
"legacy_wearables_packet_preserves_valid_item_asset_and_type_fields",
))
require_markers(BAKER, (
"CLASSIC_BAKE_SIZE", "EYE_BAKE_SIZE", "self.textures.len() >= 60", "head_color.tga",
"head_skingrain.tga", "apply_parameter_mask", "output.bump",
"native_deep_clone", '#[cfg(feature = "jpeg2000")]',
))
require_markers(PROVIDER, ("ClientWeakHandle", "request_image", "native_decode_image"))
require_markers(OUTFIT, (
"MAX_FOLDERS", "Phase::Counting", "Phase::Copying", "Phase::Applying",
"cleanup_folder", "compare_exchange", "native_replace_outfit",
"handler_dispose_is_idempotent",
))
require_markers(ASSETS, (
"native_from_asset", "parameters ", "textures ",
"wearable_parser_preserves_parameters_and_texture_indices",
))
require_markers(BAKER_TESTS, ("parity-case", "image.red[0]", "image.bump[0]"))
require_markers(VISUAL_TESTS, ("visual_catalog_matches_the_pinned_character_fixture", "672", "253"))
require_markers(COMPAT, ("baked_texture_hash", "wearable_bake_map", "texture_provider"))
require_markers(DOC, (
"all 11", "downloads and six baked-texture uploads",
"Counting`, `Copying`, `Applying`, then `Complete", "256-node",
"big-endian", "218 normal or 251", "UpdateAvatarAppearance",
))
print(
"issue 66 audit: native bake composition, bounded texture transport, cache/server/local "
"appearance paths, transactional initial outfit, documentation, and focused evidence are present"
)
if __name__ == "__main__":
main()

View File

@@ -292,13 +292,20 @@ NATIVE_TYPES = {
NATIVE_DECLARATIONS = { NATIVE_DECLARATIONS = {
"T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager", "T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager",
"T:LibreMetaverse.AppearanceManager": "crate::appearance_manager::AppearanceManager", "T:LibreMetaverse.AppearanceManager": "crate::appearance_manager::AppearanceManager",
"T:LibreMetaverse.AppearanceSetEventArgs": "crate::appearance_manager::AppearanceSetEventArgs",
"T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy": "crate::current_outfit::CompositeCurrentOutfitPolicy", "T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy": "crate::current_outfit::CompositeCurrentOutfitPolicy",
"T:LibreMetaverse.Appearance.CurrentOutfitFolder": "crate::current_outfit::CurrentOutfitFolder", "T:LibreMetaverse.Appearance.CurrentOutfitFolder": "crate::current_outfit::CurrentOutfitFolder",
"T:LibreMetaverse.Appearance.InitialOutfit": "crate::initial_outfit::InitialOutfit",
"T:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress": "crate::initial_outfit::InitialOutfitProgress",
"T:LibreMetaverse.Appearance.InitialOutfitHandler": "crate::initial_outfit::InitialOutfitHandler",
"T:LibreMetaverse.GridClientBakingTextureProvider": "crate::baking_texture_provider::GridClientBakingTextureProvider",
"T:LibreMetaverse.Imaging.Baker": "crate::appearance_baker::Baker",
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient", "T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
"T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager", "T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager",
"T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient", "T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient",
"T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager", "T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager",
"T:LibreMetaverse.ObjectManager": "crate::object_material::ObjectManager", "T:LibreMetaverse.ObjectManager": "crate::object_material::ObjectManager",
"T:LibreMetaverse.RebakeAvatarTexturesEventArgs": "crate::appearance_manager::RebakeAvatarTexturesEventArgs",
"T:LibreMetaverse.Settings": "crate::client_core::Settings", "T:LibreMetaverse.Settings": "crate::client_core::Settings",
"T:LibreMetaverse.Simulator": "crate::network_manager::Simulator", "T:LibreMetaverse.Simulator": "crate::network_manager::Simulator",
"T:LibreMetaverse.StructuredData.OSDParser": "crate::model::OSDParser", "T:LibreMetaverse.StructuredData.OSDParser": "crate::model::OSDParser",
@@ -371,18 +378,106 @@ NATIVE_MEMBER_BODIES = {
"self.native_set_cof(value)", "self.native_set_cof(value)",
"P:LibreMetaverse.Appearance.CurrentOutfitFolder.MaxClothingLayers": "P:LibreMetaverse.Appearance.CurrentOutfitFolder.MaxClothingLayers":
"crate::current_outfit::MAX_CLOTHING_LAYERS", "crate::current_outfit::MAX_CLOTHING_LAYERS",
"M:LibreMetaverse.Appearance.InitialOutfit.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder)":
"crate::initial_outfit::InitialOutfit::native_new(client, cof)",
"M:LibreMetaverse.Appearance.InitialOutfit.CheckSystemFoldersAsync(System.Threading.CancellationToken)":
"self.native_check_system_folders(cancellation_token).await",
"M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})":
"self.native_copy_folder(folder, destination, cancellation_token, progress).await",
"M:LibreMetaverse.Appearance.InitialOutfit.CreateFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType,System.Threading.CancellationToken)":
"self.native_create_folder(parent, name, type_, cancellation_token).await",
"M:LibreMetaverse.Appearance.InitialOutfit.FindNodeByName(LibreMetaverse.InventoryNode,System.String)":
"crate::initial_outfit::InitialOutfit::native_find_node_by_name(root, name)",
"M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})":
"self.native_set_initial_outfit(outfit, cancellation_token, progress).await",
"M:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.#ctor":
"crate::initial_outfit::InitialOutfitProgress::native_new()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName":
"self.native_current_item_name()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.CurrentItemName#set":
"self.native_set_current_item_name(value)",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied":
"self.native_items_copied()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.ItemsCopied#set":
"self.native_set_items_copied(value)",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message":
"self.native_message()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Message#set":
"self.native_set_message(value)",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase":
"self.native_phase()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.Phase#set":
"self.native_set_phase(value)",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems":
"self.native_total_items()",
"P:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress.TotalItems#set":
"self.native_set_total_items(value)",
"M:LibreMetaverse.Appearance.InitialOutfitHandler.#ctor(LibreMetaverse.GridClient,LibreMetaverse.Appearance.CurrentOutfitFolder,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})":
"crate::initial_outfit::InitialOutfitHandler::native_new(client, cof, progress)",
"M:LibreMetaverse.Appearance.InitialOutfitHandler.Dispose":
"self.native_dispose()",
"M:LibreMetaverse.AppearanceManager.#ctor(LibreMetaverse.GridClient)": "M:LibreMetaverse.AppearanceManager.#ctor(LibreMetaverse.GridClient)":
"crate::appearance_manager::AppearanceManager::native_new(client)", "crate::appearance_manager::AppearanceManager::native_new(client)",
"M:LibreMetaverse.AppearanceSetEventArgs.#ctor(System.Boolean)":
"Ok(crate::appearance_manager::AppearanceSetEventArgs::native_new(success))",
"P:LibreMetaverse.AppearanceSetEventArgs.Success":
"self.native_success()",
"M:LibreMetaverse.RebakeAvatarTexturesEventArgs.#ctor(LibreMetaverse.UUID)":
"Ok(crate::appearance_manager::RebakeAvatarTexturesEventArgs::native_new(texture_id))",
"P:LibreMetaverse.RebakeAvatarTexturesEventArgs.TextureID":
"self.native_texture_id()",
"E:LibreMetaverse.AppearanceManager.AgentWearablesReply": "E:LibreMetaverse.AppearanceManager.AgentWearablesReply":
"self.native_subscribe_agent_wearables_reply(handler)", "self.native_subscribe_agent_wearables_reply(handler)",
"E:LibreMetaverse.AppearanceManager.AppearanceSet": "E:LibreMetaverse.AppearanceManager.AppearanceSet":
"self.native_subscribe_appearance_set(handler)", "self.native_subscribe_appearance_set(handler)",
"E:LibreMetaverse.AppearanceManager.CachedBakesReply":
"self.native_subscribe_cached_bakes_reply(handler)",
"E:LibreMetaverse.AppearanceManager.RebakeAvatarRequested":
"self.native_subscribe_rebake_avatar_requested(handler)",
"F:LibreMetaverse.AppearanceManager.ATTACHMENT_ADD":
"crate::appearance_manager::ATTACHMENT_ADD",
"F:LibreMetaverse.AppearanceManager.BAKED_TEXTURE_HASH":
"crate::appearance_manager::baked_texture_hash()",
"F:LibreMetaverse.AppearanceManager.BakeIndexToTextureIndex":
"crate::appearance_manager::bake_index_to_texture_index()",
"F:LibreMetaverse.AppearanceManager.DEFAULT_AVATAR_TEXTURE":
"crate::appearance_manager::default_avatar_texture()",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX1":
"crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux1)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX2":
"crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux2)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_AUX3":
"crate::appearance_manager::img_use_baked(crate::BakeType::BakedAux3)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_EYES":
"crate::appearance_manager::img_use_baked(crate::BakeType::Eyes)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HAIR":
"crate::appearance_manager::img_use_baked(crate::BakeType::Hair)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_HEAD":
"crate::appearance_manager::img_use_baked(crate::BakeType::Head)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_INDICES":
"crate::appearance_manager::img_use_baked_indices()",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTARM":
"crate::appearance_manager::img_use_baked(crate::BakeType::BakedLeftArm)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LEFTLEG":
"crate::appearance_manager::img_use_baked(crate::BakeType::BakedLeftLeg)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_LOWER":
"crate::appearance_manager::img_use_baked(crate::BakeType::LowerBody)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_SKIRT":
"crate::appearance_manager::img_use_baked(crate::BakeType::Skirt)",
"F:LibreMetaverse.AppearanceManager.IMG_USE_BAKED_UPPER":
"crate::appearance_manager::img_use_baked(crate::BakeType::UpperBody)",
"F:LibreMetaverse.AppearanceManager.WEARABLE_BAKE_MAP":
"crate::appearance_manager::wearable_bake_map()",
"M:LibreMetaverse.AppearanceManager.AddAttachments(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean,System.Boolean)": "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))", "self.native_add_attachments(attachments, remove_existing_first, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.AddToOutfit(LibreMetaverse.InventoryItem,System.Boolean)": "M:LibreMetaverse.AppearanceManager.AddToOutfit(LibreMetaverse.InventoryItem,System.Boolean)":
"self.native_add_to_outfit(vec![wearable_item], replace.unwrap_or(false))", "self.native_add_to_outfit(vec![wearable_item], replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.AddToOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)": "M:LibreMetaverse.AppearanceManager.AddToOutfit(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Boolean)":
"self.native_add_to_outfit(wearable_items, replace.unwrap_or(false))", "self.native_add_to_outfit(wearable_items, replace.unwrap_or(false))",
"M:LibreMetaverse.AppearanceManager.BakeTypeToAgentTextureIndex(LibreMetaverse.BakeType)":
"Ok(crate::appearance_manager::bake_type_to_agent_texture_index(index))",
"M:LibreMetaverse.AppearanceManager.BakeTypeToTextures(LibreMetaverse.BakeType)":
"Ok(crate::appearance_manager::bake_type_to_textures(bake_type))",
"M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean)": "M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.InventoryItem,LibreMetaverse.AttachmentPoint,System.Boolean)":
"self.native_attach(item, attach_point, replace.unwrap_or(false))", "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)": "M:LibreMetaverse.AppearanceManager.Attach(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.Permissions,System.UInt32,LibreMetaverse.AttachmentPoint,System.Boolean)":
@@ -391,6 +486,8 @@ NATIVE_MEMBER_BODIES = {
"self.native_detach(item.base.uuid())", "self.native_detach(item.base.uuid())",
"M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.UUID)": "M:LibreMetaverse.AppearanceManager.Detach(LibreMetaverse.UUID)":
"self.native_detach(item_id)", "self.native_detach(item_id)",
"M:LibreMetaverse.AppearanceManager.DecodeWearableParams(LibreMetaverse.AppearanceManager.WearableData,LibreMetaverse.AppearanceManager.TextureData[]@)":
"crate::appearance_manager::decode_wearable_params(wearable, textures)",
"M:LibreMetaverse.AppearanceManager.Dispose": "M:LibreMetaverse.AppearanceManager.Dispose":
"self.native_dispose()", "self.native_dispose()",
"M:LibreMetaverse.AppearanceManager.GetAttachments": "M:LibreMetaverse.AppearanceManager.GetAttachments":
@@ -403,6 +500,8 @@ NATIVE_MEMBER_BODIES = {
"self.native_get_attachments_by_id()", "self.native_get_attachments_by_id()",
"M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)": "M:LibreMetaverse.AppearanceManager.GetCurrentOutfitFolderAsync(System.Threading.CancellationToken)":
"self.native_get_current_outfit_folder(cancellation_token).await", "self.native_get_current_outfit_folder(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.GetColorFromParams(System.Collections.Generic.List{LibreMetaverse.AppearanceManager.ColorParamInfo})":
"crate::appearance_manager::color_from_params(param)",
"M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": "M:LibreMetaverse.AppearanceManager.GetFolderWearablesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_get_folder_wearables(folder, cancellation_token).await", "self.native_get_folder_wearables(folder, cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.GetWearableAssets(LibreMetaverse.WearableType)": "M:LibreMetaverse.AppearanceManager.GetWearableAssets(LibreMetaverse.WearableType)":
@@ -411,6 +510,8 @@ NATIVE_MEMBER_BODIES = {
"self.native_get_wearables()", "self.native_get_wearables()",
"M:LibreMetaverse.AppearanceManager.GetWearablesByType": "M:LibreMetaverse.AppearanceManager.GetWearablesByType":
"self.native_get_wearables_by_type()", "self.native_get_wearables_by_type()",
"M:LibreMetaverse.AppearanceManager.GetCurrentParamValues":
"self.native_get_current_param_values()",
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.InventoryItem)": "M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.InventoryItem)":
"self.native_is_item_worn(item.base.uuid())", "self.native_is_item_worn(item.base.uuid())",
"M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.UUID)": "M:LibreMetaverse.AppearanceManager.IsItemWorn(LibreMetaverse.UUID)":
@@ -425,6 +526,16 @@ NATIVE_MEMBER_BODIES = {
"self.native_replace_outfit(wearable_items, safe).await", "self.native_replace_outfit(wearable_items, safe).await",
"M:LibreMetaverse.AppearanceManager.RequestAgentWornAsync(System.Threading.CancellationToken)": "M:LibreMetaverse.AppearanceManager.RequestAgentWornAsync(System.Threading.CancellationToken)":
"self.native_request_agent_worn(cancellation_token).await", "self.native_request_agent_worn(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.MakeAppearancePacket":
"self.native_make_appearance_packet()",
"M:LibreMetaverse.AppearanceManager.MorphLayerForBakeType(LibreMetaverse.BakeType)":
"Ok(crate::appearance_manager::morph_layer_for_bake_type(bake_type))",
"M:LibreMetaverse.AppearanceManager.RequestCachedBakes":
"self.native_request_cached_bakes()",
"M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)":
"self.native_request_set_appearance(force_rebake.unwrap_or(false)).await",
"M:LibreMetaverse.AppearanceManager.ServerBakingRegion":
"self.native_server_baking_region()",
"M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)": "M:LibreMetaverse.AppearanceManager.SendOutfitToCurrentSimulatorAsync(System.Threading.CancellationToken)":
"self.native_send_outfit(cancellation_token).await", "self.native_send_outfit(cancellation_token).await",
"M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)": "M:LibreMetaverse.AppearanceManager.UpdateLastReceivedCOFVersion(System.Int32)":
@@ -441,6 +552,42 @@ NATIVE_MEMBER_BODIES = {
"self.native_last_cof_version()", "self.native_last_cof_version()",
"P:LibreMetaverse.AppearanceManager.ManagerBusy": "P:LibreMetaverse.AppearanceManager.ManagerBusy":
"self.native_manager_busy()", "self.native_manager_busy()",
"P:LibreMetaverse.AppearanceManager.TextureProvider":
"self.native_texture_provider()",
"P:LibreMetaverse.AppearanceManager.TextureProvider#set":
"self.native_set_texture_provider(value)",
"M:LibreMetaverse.AppearanceManager.TextureData.#ctor":
"crate::appearance_manager::texture_data_defaults()",
"M:LibreMetaverse.AppearanceManager.TextureData.ToString":
"crate::appearance_manager::texture_data_to_string(self)",
"M:LibreMetaverse.AppearanceManager.WearableData.#ctor":
"crate::appearance_manager::wearable_data_defaults()",
"M:LibreMetaverse.AppearanceManager.WearableData.ToString":
"crate::appearance_manager::wearable_data_to_string(self)",
"M:LibreMetaverse.GridClientBakingTextureProvider.#ctor(LibreMetaverse.GridClient)":
"crate::baking_texture_provider::GridClientBakingTextureProvider::native_new(client)",
"M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_request_texture(texture_id, cancellation_token).await",
"M:LibreMetaverse.Imaging.Baker.#ctor(LibreMetaverse.BakeType)":
"crate::appearance_baker::Baker::native_new(bake_type)",
"M:LibreMetaverse.Imaging.Baker.AddTexture(LibreMetaverse.AppearanceManager.TextureData)":
"self.native_add_texture(tdata)",
"M:LibreMetaverse.Imaging.Baker.Bake":
"self.native_bake()",
"M:LibreMetaverse.Imaging.Baker.BakeTypeFor(LibreMetaverse.AvatarTextureIndex)":
"Ok(crate::appearance_baker::Baker::native_bake_type_for(index))",
"M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)":
"crate::appearance_baker::Baker::native_load_resource_layer(file_name)",
"P:LibreMetaverse.Imaging.Baker.BakeHeight":
"self.native_bake_height()",
"P:LibreMetaverse.Imaging.Baker.BakeType":
"self.native_bake_type()",
"P:LibreMetaverse.Imaging.Baker.BakeWidth":
"self.native_bake_width()",
"P:LibreMetaverse.Imaging.Baker.BakedTexture":
"self.native_baked_texture()",
"P:LibreMetaverse.Imaging.Baker.Textures":
"self.native_textures()",
"P:LibreMetaverse.GridClient.Appearance": "P:LibreMetaverse.GridClient.Appearance":
"self.native_appearance().unwrap_or_else(|_| panic!(\"failed to construct AppearanceManager\"))", "self.native_appearance().unwrap_or_else(|_| panic!(\"failed to construct AppearanceManager\"))",
"P:LibreMetaverse.GridClient.Appearance#set": "P:LibreMetaverse.GridClient.Appearance#set":
@@ -1787,7 +1934,7 @@ NATIVE_MESSAGE_CODEC_TYPES = {
} }
OPEN_ENUM_IDS = {"T:LibreMetaverse.BakeType", "T:LibreMetaverse.HoleType"} OPEN_ENUM_IDS = {"T:LibreMetaverse.BakeType", "T:LibreMetaverse.HoleType"}
TRAIT_SUPERTRAITS = { TRAIT_SUPERTRAITS = {
"T:LibreMetaverse.IBakingTextureProvider": "std::any::Any", "T:LibreMetaverse.IBakingTextureProvider": "std::any::Any + Send + Sync",
"T:LibreMetaverse.Interfaces.IMessage": "std::any::Any", "T:LibreMetaverse.Interfaces.IMessage": "std::any::Any",
"T:LibreMetaverse.Appearance.ICurrentOutfitPolicy": "Send + Sync", "T:LibreMetaverse.Appearance.ICurrentOutfitPolicy": "Send + Sync",
} }

View File

@@ -989,7 +989,9 @@ def validate_generated_shims() -> None:
"crate::asset_manager::", "crate::asset_manager::",
"crate::asset_material::", "crate::asset_material::",
"crate::asset_models::", "crate::asset_models::",
"crate::appearance_baker::",
"crate::appearance_manager::", "crate::appearance_manager::",
"crate::baking_texture_provider::",
"crate::byte_order::", "crate::byte_order::",
"crate::attention_catalog::", "crate::attention_catalog::",
"crate::binary::", "crate::binary::",
@@ -1001,6 +1003,7 @@ def validate_generated_shims() -> None:
"crate::inventory::", "crate::inventory::",
"crate::inventory_ais::", "crate::inventory_ais::",
"crate::inventory_manager::", "crate::inventory_manager::",
"crate::initial_outfit::",
"crate::current_outfit::", "crate::current_outfit::",
"crate::message_codec::", "crate::message_codec::",
"crate::message_decoder::", "crate::message_decoder::",