Implement native archive and model workflows (#74)
Some checks failed
Native code generation / deterministic (push) Failing after 1m48s
Imaging and meshing gate / native (push) Successful in 5m26s
JPEG 2000 feature / linux (push) Successful in 2m51s
Native Rust workspace compile / compile (push) Successful in 5m28s
Skia feature / linux (push) Successful in 31m53s

This commit is contained in:
2026-08-10 20:31:34 +00:00
parent 763e9e5044
commit 55424dbd7f
15 changed files with 5862 additions and 2024 deletions

2
Cargo.lock generated
View File

@@ -852,6 +852,7 @@ dependencies = [
name = "libremetaverse"
version = "0.0.1"
dependencies = [
"base64",
"bcdec_rs",
"flate2",
"futures-channel",
@@ -864,6 +865,7 @@ dependencies = [
"reqwest",
"roxmltree",
"serde_json",
"tar",
"tokio",
"vorbis_rs",
]

View File

@@ -365,6 +365,11 @@ deduplicated HTTP subscribers, and atomic size-pruned disk caching. The ownershi
correlation, and corruption contracts are documented in
[`docs/assets.md`](docs/assets.md).
Deterministic OAR/tar IO, bounded GLTF/GLB and Collada conversion, material
resolution, and opt-in mesh pricing/upload now use the same native asset and
capability layers. Their archive safety, offline determinism, and live-upload
boundaries are also documented in [`docs/assets.md`](docs/assets.md).
The client-owned `InventoryManager` now implements the packet-level create,
update, move, copy, remove, fetch, search, give, rez/derez, script-state, and
task-inventory workflows. Callback IDs and concurrent item/task waiters are

View File

@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| Assembly | Types | Members | Status |
|---|---:|---:|---|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 369 types / 16,774 members; remaining surface is callable failure-only shims |
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 401 types / 17,023 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |

View File

@@ -13,6 +13,7 @@ dds-bc67 = ["dep:bcdec_rs"]
jpeg2000 = ["libremetaverse-imaging/jpeg2000"]
[dependencies]
base64 = "0.22.1"
bcdec_rs = { version = "0.2.0", optional = true }
flate2 = "1.1.2"
futures-channel = "0.3.31"
@@ -25,6 +26,7 @@ os_info = { version = "3.15.0", default-features = false }
reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] }
roxmltree = "0.21.1"
serde_json = "1.0.143"
tar = "0.4.46"
tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time"] }
vorbis_rs = "0.5.5"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ mod animesh_runtime;
mod animesh_skinning;
mod appearance_baker;
mod appearance_manager;
mod asset_archive;
mod asset_cache;
mod asset_manager;
mod asset_material;
@@ -31,6 +32,7 @@ mod estate_tools;
mod event_queue;
mod friends_manager;
mod gesture;
mod gltf;
mod group_manager;
mod initial_outfit;
mod inventory;
@@ -47,6 +49,7 @@ mod login;
mod marketplace_runtime;
mod message_codec;
mod message_decoder;
mod model_workflow;
mod network_manager;
mod object_manager;
mod object_material;
@@ -93,17 +96,15 @@ type Terrain = Vec<Vec<f32>>;
type TerrainLoaded = Box<dyn Fn(Terrain, i64, i64) + Send + Sync>;
impl assets::OarFile {
#[allow(dead_code)]
#[allow(dead_code, clippy::needless_pass_by_value)]
fn load_terrain(
_file_path: &str,
_data: Vec<u8>,
_callback: TerrainLoaded,
_bytes_read: i64,
_total_bytes: i64,
file_path: &str,
data: Vec<u8>,
callback: TerrainLoaded,
bytes_read: i64,
total_bytes: i64,
) -> Result<bool, Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Assets.OarFile.LoadTerrain(System.String,System.Byte[],LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,System.Int64,System.Int64)",
)
crate::asset_archive::load_terrain(file_path, &data, callback, bytes_read, total_bytes)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,29 @@ task material uploads use their two-stage capability contracts and require a
complete response containing the new asset UUID. Upload size, cancellation, and
timeout failures remove their pending correlation state.
OAR and model workflows are native as well. The tar reader validates checksums,
ustar paths, entry kinds, offsets, counts, nesting depth, per-entry size, and
expanded size before exposing data. OAR output is sorted and uses fixed gzip and
tar metadata, so identical asset, object, terrain, parcel, and settings trees
produce identical bytes. Loading recognizes canonical `UUID_type.ext` asset
names, bounded RAW32 terrain, and typed region-settings XML. Archive paths never
escape their selected root, and symbolic or hard links are rejected.
`GltfDocument` accepts bounded JSON glTF 2.0 and GLB 2.0 documents, resolves data
URIs or caller-provided external buffers, validates buffer-view/accessor ranges,
and decodes normalized scalar, vector, matrix, skin, and animation data. JSON and
GLB output retain scenes, nodes, surfaces, materials, textures, skins, and
animations in stable order; multi-buffer GLB output aligns and rebases views.
Collada conversion applies declared units and up-axis orientation, resolves
profile-COMMON effects and material bindings, triangulates triangles/quads, and
confines referenced textures to the document directory. Mesh assets use stable
quantization and LLSD section ordering.
Model upload is deliberately opt-in. Offline resource/pricing payload generation
does no network I/O; live preparation discovers the completed capability and
inventory layers, includes real destination folders, permission masks, and the
configured cost, then honors cancellation through both pricing and upload calls.
The disk cache uses UUID-only filenames by default and confines custom filename
callbacks to the configured cache directory. Reads reject empty, oversized, or
non-regular files. Writes use uniquely named same-directory files, flush them,

View File

@@ -305,6 +305,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
@@ -734,6 +744,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
name = "libremetaverse"
version = "0.0.1"
dependencies = [
"base64",
"bcdec_rs",
"flate2",
"futures-channel",
@@ -746,6 +757,7 @@ dependencies = [
"reqwest",
"roxmltree",
"serde_json",
"tar",
"tokio",
"vorbis_rs",
]
@@ -865,6 +877,12 @@ dependencies = [
"libremetaverse-types",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.2"
@@ -1390,6 +1408,19 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.43"
@@ -1686,6 +1717,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -2174,6 +2216,16 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.3"

View File

@@ -0,0 +1,288 @@
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use libremetaverse::GridClient;
use libremetaverse::assets::{
OarFile, OarFileAssetLoadedCallback, OarFileSceneObjectLoadedCallback,
OarFileSettingsLoadedCallback, OarFileTerrainLoadedCallback, RegionSettings,
};
use libremetaverse::import_export::{ColladaLoader, ModelFace, ModelPrim, ModelUploader};
use libremetaverse::rendering::Vertex;
use libremetaverse_structured_data::OSD;
use libremetaverse_types::{Quaternion, UUID, Vector2, Vector3};
fn temporary_directory(label: &str) -> PathBuf {
let id = UUID::random().expect("temporary UUID").to_string();
let path = std::env::temp_dir().join(format!("metacrate-{label}-{id}"));
fs::create_dir_all(&path).expect("create temporary directory");
path
}
#[test]
fn oar_packaging_is_deterministic_and_sorted() {
let root = temporary_directory("oar");
for directory in ["assets", "objects", "terrains", "landdata", "settings"] {
fs::create_dir_all(root.join(directory)).expect("create OAR directory");
}
fs::write(root.join("assets/z.texture"), b"z").expect("write z asset");
fs::write(root.join("assets/a.texture"), b"a").expect("write a asset");
fs::write(root.join("terrains/region.r32"), 1.0_f32.to_le_bytes()).expect("write terrain");
let first = root.join("first.oar");
let second = root.join("second.oar");
OarFile::package_archive(
root.to_string_lossy().into_owned(),
first.to_string_lossy().into_owned(),
)
.expect("first package");
OarFile::package_archive(
root.to_string_lossy().into_owned(),
second.to_string_lossy().into_owned(),
)
.expect("second package");
assert_eq!(
fs::read(first).expect("first bytes"),
fs::read(second).expect("second bytes")
);
fs::remove_dir_all(root).expect("remove temporary OAR directory");
}
#[test]
fn oar_canonical_asset_and_settings_names_round_trip() {
let root = temporary_directory("oar-roundtrip");
for directory in ["assets", "objects", "terrains", "landdata", "settings"] {
fs::create_dir_all(root.join(directory)).expect("create OAR directory");
}
let asset_id = UUID::random().expect("asset UUID");
let asset_bytes = b"native-j2c-payload".to_vec();
fs::write(
root.join(format!("assets/{asset_id}_texture.jp2")),
&asset_bytes,
)
.expect("write canonical asset");
let mut settings = RegionSettings::new().expect("region settings");
settings.allow_damage = true;
settings.water_height = 23.5;
settings
.to_xml(
root.join("settings/Native Region.xml")
.to_string_lossy()
.into_owned(),
)
.expect("write settings");
let archive = root.join("roundtrip.oar");
OarFile::package_archive(
root.to_string_lossy().into_owned(),
archive.to_string_lossy().into_owned(),
)
.expect("package round trip");
let loaded_asset = Arc::new(Mutex::new(None));
let loaded_asset_out = Arc::clone(&loaded_asset);
let loaded_settings = Arc::new(Mutex::new(None));
let loaded_settings_out = Arc::clone(&loaded_settings);
OarFile::unpackage_archive(
archive.to_string_lossy().into_owned(),
OarFileAssetLoadedCallback::from_callback(move |asset, _, _| {
*loaded_asset_out.lock().expect("asset lock") =
Some((asset.asset_id(), asset.asset_type(), asset.asset_data));
}),
OarFileTerrainLoadedCallback::from_callback(|_, _, _| {}),
OarFileSceneObjectLoadedCallback::from_callback(|_, _, _| {}),
OarFileSettingsLoadedCallback::from_callback(move |name, settings| {
*loaded_settings_out.lock().expect("settings lock") =
Some((name, settings.allow_damage, settings.water_height));
}),
)
.expect("unpackage round trip");
let asset = loaded_asset.lock().expect("loaded asset lock").clone();
assert_eq!(
asset,
Some((
asset_id,
libremetaverse_types::AssetType::Texture,
asset_bytes
))
);
assert_eq!(
loaded_settings
.lock()
.expect("loaded settings lock")
.clone(),
Some(("Native Region".to_owned(), true, 23.5))
);
fs::remove_dir_all(root).expect("remove round-trip directory");
}
#[test]
fn model_face_deduplicates_vertices_and_asset_is_deterministic() {
let vertex = || Vertex {
position: Vector3 {
x: -0.5,
y: 0.25,
z: 0.5,
},
normal: Vector3 {
x: 0.0,
y: 0.0,
z: 1.0,
},
tex_coord: Vector2 { x: 0.0, y: 1.0 },
};
let mut face = ModelFace::new().expect("model face");
face.add_vertex(vertex()).expect("first vertex");
face.add_vertex(vertex()).expect("duplicate vertex");
assert_eq!(face.vertices.len(), 1);
assert_eq!(face.indices, [0, 0]);
let mut prim = ModelPrim::new().expect("model prim");
prim.faces.push(face);
prim.create_asset(UUID::zero()).expect("first model asset");
let first = prim.asset.clone();
prim.create_asset(UUID::zero()).expect("second model asset");
assert!(!first.is_empty());
assert_eq!(prim.asset, first);
}
#[test]
fn model_upload_resources_are_deterministic_and_offline() {
let mut face = ModelFace::new().expect("model face");
face.material.texture = "surface.jp2".into();
face.material.texture_data = vec![1, 2, 3, 4];
for position in [
Vector3::zero(),
Vector3 {
x: 1.0,
y: 0.0,
z: 0.0,
},
Vector3 {
x: 0.0,
y: 1.0,
z: 0.0,
},
] {
face.add_vertex(Vertex {
position,
normal: Vector3 {
x: 0.0,
y: 0.0,
z: 1.0,
},
tex_coord: Vector2::zero(),
})
.expect("upload vertex");
}
let mut prim = ModelPrim::new().expect("model prim");
prim.faces.push(face);
prim.create_asset(UUID::zero()).expect("mesh asset");
let uploader = ModelUploader::new(
GridClient::new().expect("offline client"),
vec![prim],
"Offline mesh".into(),
"No network required".into(),
)
.expect("model uploader");
let first = uploader.asset_resources(false).expect("offline resources");
let second = uploader.asset_resources(false).expect("repeat resources");
assert_eq!(first, second);
let OSD::Map(map) = first else {
panic!("resource map");
};
let OSD::Array(textures) = map.get("texture_list").expect("texture list") else {
panic!("texture array");
};
assert_eq!(textures, &[OSD::Binary(Vec::new())]);
let OSD::Map(upload) = uploader.asset_resources(true).expect("upload resources") else {
panic!("upload map");
};
assert_eq!(
upload.get("texture_list"),
Some(&OSD::Array(vec![OSD::Binary(vec![1, 2, 3, 4])]))
);
}
#[test]
fn collada_triangle_converts_to_native_model_asset() {
let root = temporary_directory("collada");
let file = root.join("triangle.dae");
fs::write(
&file,
r##"<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<library_geometries><geometry id="triangle"><mesh>
<source id="positions"><float_array id="positions-array" count="9">0 0 0 1 0 0 0 1 0</float_array>
<technique_common><accessor source="#positions-array" count="3" stride="3"/></technique_common></source>
<vertices id="vertices"><input semantic="POSITION" source="#positions"/></vertices>
<triangles count="1"><input semantic="VERTEX" source="#vertices" offset="0"/><p>0 1 2</p></triangles>
</mesh></geometry></library_geometries>
</COLLADA>"##,
)
.expect("write Collada fixture");
let prims = ColladaLoader::new(None)
.expect("Collada loader")
.load(file.to_string_lossy().into_owned(), false)
.expect("load Collada");
assert_eq!(prims.len(), 1);
assert_eq!(prims[0].faces.len(), 1);
assert_eq!(prims[0].faces[0].indices, [0, 1, 2]);
assert!(!prims[0].asset.is_empty());
assert_eq!(prims[0].rotation, Quaternion::identity());
fs::remove_dir_all(root).expect("remove temporary Collada directory");
}
#[test]
fn collada_applies_units_effects_and_instance_material_bindings() {
let root = temporary_directory("collada-material");
let file = root.join("material.dae");
fs::write(
&file,
r##"<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema">
<asset><unit meter="2"/><up_axis>Z_UP</up_axis></asset>
<library_effects><effect id="green"><profile_COMMON><technique sid="common"><lambert>
<diffuse><color>0.1 0.8 0.2 1</color></diffuse>
</lambert></technique></profile_COMMON></effect></library_effects>
<library_materials><material id="surface"><instance_effect url="#green"/></material></library_materials>
<library_geometries><geometry id="triangle"><mesh>
<source id="positions"><float_array>0 0 0 1 0 0 0 1 0</float_array>
<technique_common><accessor stride="3"/></technique_common></source>
<vertices id="vertices"><input semantic="POSITION" source="#positions"/></vertices>
<triangles count="1" material="slot"><input semantic="VERTEX" source="#vertices" offset="0"/><p>0 1 2</p></triangles>
</mesh></geometry></library_geometries>
<library_visual_scenes><visual_scene id="scene"><node id="instance"><instance_geometry url="#triangle">
<bind_material><technique_common><instance_material symbol="slot" target="#surface"/></technique_common></bind_material>
</instance_geometry></node></visual_scene></library_visual_scenes>
</COLLADA>"##,
)
.expect("write material Collada fixture");
let prims = ColladaLoader::new(None)
.expect("Collada loader")
.load(file.to_string_lossy().into_owned(), false)
.expect("load material Collada");
assert_eq!(prims.len(), 1);
assert_eq!(prims[0].scale.x, 2.0);
assert_eq!(prims[0].scale.y, 2.0);
assert_eq!(prims[0].faces[0].material.id, "surface");
assert_eq!(prims[0].faces[0].material.diffuse_color.g, 0.8);
fs::remove_dir_all(root).expect("remove material Collada directory");
}
#[test]
fn collada_texture_path_cannot_escape_document_root() {
let root = temporary_directory("collada-traversal");
let file = root.join("traversal.dae");
fs::write(
&file,
r##"<COLLADA><library_images><image id="escape"><init_from>../secret.jp2</init_from></image></library_images>
<library_materials><material id="mat"><diffuse><texture texture="escape"/></diffuse></material></library_materials></COLLADA>"##,
)
.expect("write traversal fixture");
assert!(
ColladaLoader::new(None)
.expect("Collada loader")
.load(file.to_string_lossy().into_owned(), true)
.is_err()
);
fs::remove_dir_all(root).expect("remove traversal directory");
}

View File

@@ -515,3 +515,67 @@ fn gltf_node_explicit_matrix() {
assert_close(matrix.m24, 6.0, 1e-5);
assert_close(matrix.m34, 7.0, 1e-5);
}
#[test]
fn gltf_rejects_buffer_view_outside_declared_buffer() {
let json = r#"{
"asset":{"version":"2.0"},
"buffers":[{"byteLength":4,"uri":"data:application/octet-stream;base64,AAAAAA=="}],
"bufferViews":[{"buffer":0,"byteOffset":3,"byteLength":4}]
}"#;
assert!(GltfDocument::load_gltf(json.into(), None).is_err());
}
#[test]
fn gltf_rejects_cyclic_node_graphs() {
let json = r#"{
"asset":{"version":"2.0"},
"nodes":[{"children":[1]},{"children":[0]}],
"scenes":[{"nodes":[0]}],
"scene":0
}"#;
assert!(GltfDocument::load_gltf(json.into(), None).is_err());
}
#[test]
fn gltf_surface_skin_and_animation_round_trip() {
let json = r#"{
"asset":{"version":"2.0"},
"accessors":[
{"componentType":5126,"count":0,"type":"SCALAR"},
{"componentType":5126,"count":0,"type":"VEC3"}
],
"images":[{"uri":"data:image/png;base64,iVBORw0KGgo=","mimeType":"image/png","name":"image"}],
"samplers":[{"magFilter":9729,"minFilter":9987,"wrapS":10497,"wrapT":33071,"name":"sampler"}],
"textures":[{"sampler":0,"source":0,"name":"texture"}],
"materials":[{
"name":"surface",
"pbrMetallicRoughness":{"baseColorTexture":{"index":0},"metallicRoughnessTexture":{"index":0}},
"normalTexture":{"index":0,"scale":0.5},
"occlusionTexture":{"index":0,"strength":0.75},
"emissiveTexture":{"index":0},
"emissiveFactor":[0.1,0.2,0.3]
}],
"nodes":[{"skin":0}],
"skins":[{"joints":[0],"skeleton":0,"name":"skin"}],
"animations":[{
"name":"move",
"samplers":[{"input":0,"output":1,"interpolation":"LINEAR"}],
"channels":[{"sampler":0,"target":{"node":0,"path":"translation"}}]
}]
}"#;
let document = GltfDocument::load_gltf(json.into(), None).expect("load complete surface");
let serialized = document.to_json(None).expect("serialize complete surface");
let round_trip = GltfDocument::load_gltf(serialized, None).expect("reload complete surface");
assert_eq!(round_trip.images().len(), 1);
assert_eq!(round_trip.samplers().len(), 1);
assert_eq!(round_trip.textures().len(), 1);
assert_eq!(round_trip.skins()[0].joints(), [0]);
assert_eq!(round_trip.animations()[0].channels().len(), 1);
let material = &round_trip.materials()[0];
assert!(material.base_color_texture().is_some());
assert!(material.metallic_roughness_texture().is_some());
assert!(material.normal_texture().is_some());
assert!(material.occlusion_texture().is_some());
assert!(material.emissive_texture().is_some());
}

View File

@@ -6,16 +6,8 @@ use libremetaverse::messages::linden::AgentDropGroupMessage;
use libremetaverse::packets::UseCircuitCodePacket;
use libremetaverse::{BitPack, PacketFrequency, PermissionMask, Permissions, Primitive};
fn member_id<T>(result: Result<T, libremetaverse::Error>) -> &'static str {
result
.err()
.expect("failure-only shim unexpectedly returned a value")
.csharp_member()
.expect("failure-only shim error")
}
#[test]
fn each_wire_data_domain_is_callable_and_failure_only() {
fn each_wire_data_domain_is_callable_at_its_native_boundary() {
let packet = UseCircuitCodePacket::new_with_constructor()
.expect("generated packet constructor is native");
assert_eq!(packet.circuit_code.code, 0);
@@ -26,20 +18,26 @@ fn each_wire_data_domain_is_callable_and_failure_only() {
.is_empty()
);
assert_eq!(
member_id(AssetTexture::new_with_constructor()),
"M:LibreMetaverse.Assets.AssetTexture.#ctor"
AssetTexture::new_with_constructor()
.expect("native asset texture constructor")
.components,
0
);
assert_eq!(
member_id(GltfDocument::new()),
"M:LibreMetaverse.Assets.Gltf.GltfDocument.#ctor"
GltfDocument::new()
.expect("native glTF constructor")
.version(),
"2.0"
);
assert!(
ModelPrim::new()
.expect("native model constructor")
.asset
.is_empty()
);
assert_eq!(
member_id(ModelPrim::new()),
"M:LibreMetaverse.ImportExport.ModelPrim.#ctor"
);
assert_eq!(
member_id(OarFile::package_archive(String::new(), String::new())),
"M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)"
OarFile::package_archive(String::new(), String::new()),
Err(libremetaverse::Error::InvalidOperation)
);
let bit_pack = BitPack::new(Vec::new(), 0).expect("native bit pack constructor");
assert_eq!(bit_pack.byte_pos(), 0);

View File

@@ -140,6 +140,38 @@ NATIVE_TYPES = {
"T:LibreMetaverse.Assets.AssetTexture": "crate::asset_models::AssetTexture",
"T:LibreMetaverse.Assets.AssetWearable": "crate::asset_models::AssetWearable",
"T:LibreMetaverse.Assets.GltfTextureTransform": "crate::asset_material::GltfTextureTransform",
"T:LibreMetaverse.Assets.Gltf.GltfAccessor": "crate::gltf::GltfAccessor",
"T:LibreMetaverse.Assets.Gltf.GltfAnimation": "crate::gltf::GltfAnimation",
"T:LibreMetaverse.Assets.Gltf.GltfAnimationChannel": "crate::gltf::GltfAnimationChannel",
"T:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget": "crate::gltf::GltfAnimationChannelTarget",
"T:LibreMetaverse.Assets.Gltf.GltfAnimationSampler": "crate::gltf::GltfAnimationSampler",
"T:LibreMetaverse.Assets.Gltf.GltfBuffer": "crate::gltf::GltfBuffer",
"T:LibreMetaverse.Assets.Gltf.GltfBufferView": "crate::gltf::GltfBufferView",
"T:LibreMetaverse.Assets.Gltf.GltfDocument": "crate::gltf::GltfDocument",
"T:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial": "crate::gltf::GltfDocumentMaterial",
"T:LibreMetaverse.Assets.Gltf.GltfImage": "crate::gltf::GltfImage",
"T:LibreMetaverse.Assets.Gltf.GltfMesh": "crate::gltf::GltfMesh",
"T:LibreMetaverse.Assets.Gltf.GltfNode": "crate::gltf::GltfNode",
"T:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef": "crate::gltf::GltfNormalTextureRef",
"T:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef": "crate::gltf::GltfOcclusionTextureRef",
"T:LibreMetaverse.Assets.Gltf.GltfPrimitive": "crate::gltf::GltfPrimitive",
"T:LibreMetaverse.Assets.Gltf.GltfSampler": "crate::gltf::GltfSampler",
"T:LibreMetaverse.Assets.Gltf.GltfScene": "crate::gltf::GltfScene",
"T:LibreMetaverse.Assets.Gltf.GltfSkin": "crate::gltf::GltfSkin",
"T:LibreMetaverse.Assets.Gltf.GltfTexture": "crate::gltf::GltfTexture",
"T:LibreMetaverse.Assets.Gltf.GltfTextureRef": "crate::gltf::GltfTextureRef",
"T:LibreMetaverse.Assets.TarArchiveReader": "crate::asset_archive::TarArchiveReader",
"T:LibreMetaverse.Assets.TarArchiveWriter": "crate::asset_archive::TarArchiveWriter",
"T:LibreMetaverse.Assets.OarFile.AssetLoadedCallback": "crate::asset_archive::OarFileAssetLoadedCallback",
"T:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback": "crate::asset_archive::OarFileSceneObjectLoadedCallback",
"T:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback": "crate::asset_archive::OarFileSettingsLoadedCallback",
"T:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback": "crate::asset_archive::OarFileTerrainLoadedCallback",
"T:LibreMetaverse.Assets.RegionSettings": "crate::asset_archive::RegionSettings",
"T:LibreMetaverse.ImportExport.ColladaLoader": "crate::model_workflow::ColladaLoader",
"T:LibreMetaverse.ImportExport.ModelFace": "crate::model_workflow::ModelFace",
"T:LibreMetaverse.ImportExport.ModelMaterial": "crate::model_workflow::ModelMaterial",
"T:LibreMetaverse.ImportExport.ModelPrim": "crate::model_workflow::ModelPrim",
"T:LibreMetaverse.ImportExport.ModelUploader": "crate::model_workflow::ModelUploader",
"T:LibreMetaverse.GLTFMaterialUpdate": "crate::object_material::GLTFMaterialUpdate",
"T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep",
"T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation",
@@ -474,6 +506,25 @@ NATIVE_DECLARATIONS = {
}
NATIVE_MEMBER_BODIES = {
"F:LibreMetaverse.Assets.ArchiveConstants.ASSET_TYPE_TO_EXTENSION":
"crate::asset_archive::asset_type_to_extension()",
"F:LibreMetaverse.Assets.ArchiveConstants.EXTENSION_TO_ASSET_TYPE":
"crate::asset_archive::extension_to_asset_type()",
"M:LibreMetaverse.Assets.ArchiveConstants.#ctor": "Ok(Self)",
"M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)":
"crate::asset_archive::clear_asset_folder(assets_path)",
"M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)":
"crate::asset_archive::package_archive(directory_name, filename)",
"M:LibreMetaverse.Assets.OarFile.SaveAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,System.Collections.Generic.IList{LibreMetaverse.UUID},System.String)":
"crate::asset_archive::save_assets(asset_manager, asset_type, assets, assets_path).await",
"M:LibreMetaverse.Assets.OarFile.SaveRegionSettings(LibreMetaverse.Simulator,System.String)":
"crate::asset_archive::save_region_settings(sim, settings_path)",
"M:LibreMetaverse.Assets.OarFile.SaveSimAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)":
"crate::asset_archive::save_sim_asset(asset_manager, asset_type, asset_id, item_id, prim_id, assets_path).await",
"M:LibreMetaverse.Assets.OarFile.SaveTerrain(LibreMetaverse.Simulator,System.String)":
"crate::asset_archive::save_terrain(sim, terrain_path)",
"M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)":
"crate::asset_archive::unpackage_archive(filename, asset_callback, terrain_callback, object_callback, settings_callback)",
"P:LibreMetaverse.GridClient.Estate": "self.native_estate().unwrap_or_else(|_| panic!(\"failed to construct EstateTools\"))",
"P:LibreMetaverse.GridClient.Estate#set": "self.native_set_estate(value)",
"P:LibreMetaverse.GridClient.Environment":