Implement appearance baking pipeline (#66)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -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).
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
605
crates/libremetaverse/src/appearance_baker.rs
Normal file
605
crates/libremetaverse/src/appearance_baker.rs
Normal 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, ¶m.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, ¶m.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
56
crates/libremetaverse/src/baking_texture_provider.rs
Normal file
56
crates/libremetaverse/src/baking_texture_provider.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
607
crates/libremetaverse/src/initial_outfit.rs
Normal file
607
crates/libremetaverse/src/initial_outfit.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
104
tools/check_milestone_09_issue_66.py
Normal file
104
tools/check_milestone_09_issue_66.py
Normal 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()
|
||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::",
|
||||||
|
|||||||
Reference in New Issue
Block a user