Complete Rust API and migration documentation (#102)
Some checks failed
Native code generation / deterministic (push) Failing after 2m5s
Concurrency and resource soak audit / soak (push) Failing after 6m22s
Documentation / documentation (push) Failing after 1m25s
Imaging and meshing gate / native (push) Failing after 2m47s
JPEG 2000 feature / linux (push) Successful in 3m54s
Release platform and feature matrix / audit (push) Successful in 37s
Native Rust workspace compile / compile (push) Failing after 1m14s
Skia feature / linux (push) Successful in 30m46s
Dependency and supply-chain audit / audit (push) Failing after 8m56s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Failing after 9m16s
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Failing after 1m20s
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Failing after 1m18s
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled

This commit is contained in:
2026-08-12 00:24:06 +00:00
parent 3db144da63
commit 5eb3f01122
37 changed files with 133519 additions and 48 deletions

View File

@@ -0,0 +1,88 @@
name: Documentation
on:
push:
paths:
- ".gitea/workflows/documentation.yml"
- "Cargo.lock"
- "Cargo.toml"
- "README.md"
- "api/**"
- "crates/**"
- "docs/**"
- "programs/**"
- "tools/ci-matrix/**"
- "tools/generate_api_shims.py"
- "tools/install_openjpeg_2_5_4.sh"
pull_request:
paths:
- ".gitea/workflows/documentation.yml"
- "Cargo.lock"
- "Cargo.toml"
- "README.md"
- "api/**"
- "crates/**"
- "docs/**"
- "programs/**"
- "tools/ci-matrix/**"
- "tools/generate_api_shims.py"
- "tools/install_openjpeg_2_5_4.sh"
workflow_dispatch:
jobs:
documentation:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
OPENJPEG_PREFIX: /tmp/metacrate-openjpeg-${{ github.run_id }}
PKG_CONFIG_PATH: /tmp/metacrate-openjpeg-${{ github.run_id }}/lib/pkgconfig
LD_LIBRARY_PATH: /tmp/metacrate-openjpeg-${{ github.run_id }}/lib
FORCE_SKIA_BINARIES_DOWNLOAD: "1"
CARGO_BUILD_JOBS: 1
CARGO_INCREMENTAL: 0
steps:
- uses: actions/checkout@v4
- name: Install native documentation prerequisites
run: |
sudo apt-get update
sudo apt-get install --yes build-essential clang cmake curl ninja-build \
pkg-config python3 libfontconfig1-dev libfreetype6-dev libopus-dev
tools/install_openjpeg_2_5_4.sh "$OPENJPEG_PREFIX"
- name: Install Rust 1.97 toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.97.0
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
- name: Verify documentation coverage report
run: |
cargo run --locked -p metacrate-ci-matrix -- documentation-report
git diff --exit-code -- api/DOCUMENTATION-COVERAGE.md
cargo run --locked -p metacrate-ci-matrix -- documentation-audit --evidence ci/evidence/documentation-audit.json
- name: Build warning-free workspace documentation
env:
RUSTDOCFLAGS: "-D warnings"
run: cargo doc --workspace --no-deps --locked -j 1
- name: Compile guide snippets and examples
run: |
cargo test --locked -p libremetaverse --doc -j 1
cargo test --locked -p libremetaverse --examples -j 1
cargo run --locked -p libremetaverse --example offline_client
cargo run --locked -p libremetaverse --example cancellation
- name: Verify documented program quick starts
run: cargo test --locked -p libremetaverse-programs --tests -j 1
- name: Upload documentation evidence
uses: actions/upload-artifact@v4
with:
name: documentation-evidence
path: ci/evidence/documentation-audit.json
if-no-files-found: error

View File

@@ -1,20 +1,23 @@
# MetaCrate # MetaCrate
The [Rust API and C# migration guide](docs/rust-api-guide.md) explains crate
selection, ownership, async cancellation, errors, events, native prerequisites,
and credential-safe OpenSim operation. The generated
[documentation coverage report](api/DOCUMENTATION-COVERAGE.md) ties the public
Rust surface back to the pinned C# type and member mappings.
MetaCrate is a test-driven, clean native Rust reimplementation of MetaCrate is a test-driven, clean native Rust reimplementation of
[LibreMetaverse](https://github.com/cinderblocks/libremetaverse). LibreMetaverse [LibreMetaverse](https://github.com/cinderblocks/libremetaverse). LibreMetaverse
is a behavioral and API reference only: the finished project must not load, is a behavioral and API reference only: the finished project must not load,
host, bind to, invoke, or ship the .NET implementation. The current host, bind to, invoke, or ship the .NET implementation. The project is in
stage is a compiling structural shell: crate boundaries mirror the .NET library release hardening: crate boundaries mirror the .NET libraries, every mapped
projects, public C# type names have Rust declarations, every upstream NUnit public type and member has a documented native Rust destination, and every
invocation has a traceable catalog entry, and every sample/tool project has a sample/tool project has a tested Rust binary target. Types, structured data,
Rust binary target. Types, StructuredData, Imaging, PrimMesher, the MeshFoundry rendering adapter, and imaging, meshing/rendering, packet and message codecs, client/network runtime,
the main assembly's packet/message/asset/primitive wire-data, core avatar/world/social/service managers, RLV, LSL tools, utilities, Vivox, and
runtime/networking, avatar-facing manager, world/social/service manager, RLV, WebRTC are implemented without a CLR bridge. The independent downstream
LSL tools, Utilities, Vivox, and WebRTC slices now have complete callable, fixture compiles every cataloged type and member with zero exclusions, and all
failure-only signatures. The independent downstream fixture compiles every 1,289 upstream NUnit invocations have reviewed Rust parity cases.
cataloged type and member with zero exclusions, and all 1,289 NUnit invocations
now have reviewed Rust parity cases. Production behavior implementation is the
next stage.
A case counts as translated only when an explicit Rust test body calls the A case counts as translated only when an explicit Rust test body calls the
mapped API and retains the upstream identity and body hash. The parity ledger mapped API and retains the upstream identity and body hash. The parity ledger
@@ -28,9 +31,9 @@ The authoritative compiled .NET surface is checked in as
reviewed Rust destination in [`api/RUST-TYPES.tsv`](api/RUST-TYPES.tsv) and reviewed Rust destination in [`api/RUST-TYPES.tsv`](api/RUST-TYPES.tsv) and
[`api/RUST-MAPPING.tsv`](api/RUST-MAPPING.tsv); [`api/README.md`](api/README.md) [`api/RUST-MAPPING.tsv`](api/RUST-MAPPING.tsv); [`api/README.md`](api/README.md)
documents deterministic regeneration and validation. documents deterministic regeneration and validation.
All API crates expose the same typed `Error` for fallible unimplemented members; API mappings record ownership, async, error, overload, and native implementation
infallible shims use `libremetaverse_types::unimplemented_api!` so placeholders decisions, and the generated rustdoc keeps those contracts attached to their
cannot look like successful behavior. exact C# documentation IDs.
Building MetaCrate requires Rust 1.96 or newer. Building MetaCrate requires Rust 1.96 or newer.

View File

@@ -0,0 +1,14 @@
# Documentation coverage
Generated by `metacrate-ci-matrix documentation-report`; do not edit by hand.
| Surface | Documented | Required | Coverage |
| --- | ---: | ---: | ---: |
| Publishable public crates | 15 | 15 | 100% |
| Pinned LibreMetaverse public types | 3066 | 3066 | 100% |
| Mapped public members | 30789 | 30789 | 100% |
| Compiled Rust guide snippets | 5 | 4 minimum | pass |
| Linked native programs | 9 | 9 | 100% |
| Checked local Markdown links | 65 | 65 | 100% |
Every mapped item is tied to its exact C# documentation ID and to the ownership, asyncness, error, overload, mapping-kind, and Rust-signature decisions in [`RUST-MAPPING.tsv`](RUST-MAPPING.tsv). Public types are tied to the corresponding type mapping. The upstream source is pinned to [`2aa70bb68513b39795da5d13c88f31b86e85a3ba`](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).

View File

@@ -0,0 +1,14 @@
{
"schema": 1,
"upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba",
"public_crates": 15,
"mapped_public_types": 3066,
"documented_public_types": 3066,
"mapped_members": 30789,
"documented_members": 30789,
"tested_rust_snippets": 5,
"linked_programs": 9,
"checked_local_links": 65,
"required_guide_sections": 11,
"status": "ok"
}

View File

@@ -6,7 +6,22 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
/// C# type: `T:LibreMetaverse.Imaging.Skia.SkiaTextureCodec`. /// C# type: `T:LibreMetaverse.Imaging.Skia.SkiaTextureCodec`.
///
/// Native Rust mapping of C# `LibreMetaverse.Imaging.Skia.SkiaTextureCodec` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
/// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.#ctor`. /// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.#ctor`.
///
/// C# signature: `LibreMetaverse.Imaging.Skia.SkiaTextureCodec()`.
/// Mapping contract: ownership `none`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.Decode(System.IO.Stream)`. /// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.Decode(System.IO.Stream)`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage LibreMetaverse.Imaging.Skia.SkiaTextureCodec.Decode(System.IO.Stream stream)`.
/// Mapping contract: ownership `shared_self,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.ToManagedImage(SkiaSharp.SKBitmap)`. /// C# member: `M:LibreMetaverse.Imaging.Skia.SkiaTextureCodec.ToManagedImage(SkiaSharp.SKBitmap)`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage LibreMetaverse.Imaging.Skia.SkiaTextureCodec.ToManagedImage(SkiaSharp.SKBitmap bitmap)`.
/// Mapping contract: ownership `owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub use crate::skia_codec::SkiaTextureCodec; pub use crate::skia_codec::SkiaTextureCodec;

View File

@@ -1,4 +1,9 @@
//! Skia imaging adapter corresponding to `LibreMetaverse.Imaging.Skia`. //! Optional Skia imaging adapter corresponding to `LibreMetaverse.Imaging.Skia`.
//!
//! Enable `skia` for bounded `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and
//! `WebP` decoding.
//! The default build remains inert. See the crate README for native cache,
//! source-build, licensing, and cross-platform prerequisite details.
extern crate self as libremetaverse_imaging_skia; extern crate self as libremetaverse_imaging_skia;

View File

@@ -6,30 +6,119 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
/// C# type: `T:LibreMetaverse.Imaging.ITextureCodec`. /// C# type: `T:LibreMetaverse.Imaging.ITextureCodec`.
///
/// Native Rust mapping of C# `LibreMetaverse.Imaging.ITextureCodec` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
/// C# member: `M:LibreMetaverse.Imaging.ITextureCodec.Decode(System.IO.Stream)`. /// C# member: `M:LibreMetaverse.Imaging.ITextureCodec.Decode(System.IO.Stream)`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage LibreMetaverse.Imaging.ITextureCodec.Decode(System.IO.Stream stream)`.
/// Mapping contract: ownership `shared_self,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub use crate::managed_image::ITextureCodec; pub use crate::managed_image::ITextureCodec;
/// C# type: `T:LibreMetaverse.Imaging.ManagedImage`. /// C# type: `T:LibreMetaverse.Imaging.ManagedImage`.
///
/// Native Rust mapping of C# `LibreMetaverse.Imaging.ManagedImage` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Alpha`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Alpha`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.Alpha`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Blue`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Blue`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.Blue`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Bump`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Bump`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.Bump`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Channels`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Channels`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage.ImageChannels LibreMetaverse.Imaging.ManagedImage.Channels`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Green`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Green`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.Green`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Height`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Height`.
///
/// C# signature: `System.Int32 LibreMetaverse.Imaging.ManagedImage.Height`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Red`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Red`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.Red`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Width`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.Width`.
///
/// C# signature: `System.Int32 LibreMetaverse.Imaging.ManagedImage.Width`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.#ctor(System.Int32,System.Int32,LibreMetaverse.Imaging.ManagedImage.ImageChannels)`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.#ctor(System.Int32,System.Int32,LibreMetaverse.Imaging.ManagedImage.ImageChannels)`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage(System.Int32 width, System.Int32 height, LibreMetaverse.Imaging.ManagedImage.ImageChannels channels)`.
/// Mapping contract: ownership `owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.Clear`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.Clear`.
///
/// C# signature: `System.Void LibreMetaverse.Imaging.ManagedImage.Clear()`.
/// Mapping contract: ownership `mutable_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.Clone`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.Clone`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage LibreMetaverse.Imaging.ManagedImage.Clone()`.
/// Mapping contract: ownership `shared_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ConvertChannels(LibreMetaverse.Imaging.ManagedImage.ImageChannels)`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ConvertChannels(LibreMetaverse.Imaging.ManagedImage.ImageChannels)`.
///
/// C# signature: `System.Void LibreMetaverse.Imaging.ManagedImage.ConvertChannels(LibreMetaverse.Imaging.ManagedImage.ImageChannels channels)`.
/// Mapping contract: ownership `mutable_self,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ExportRaw`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ExportRaw`.
///
/// C# signature: `System.Byte[] LibreMetaverse.Imaging.ManagedImage.ExportRaw()`.
/// Mapping contract: ownership `shared_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ResizeBilinear(System.Int32,System.Int32)`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ResizeBilinear(System.Int32,System.Int32)`.
///
/// C# signature: `System.Void LibreMetaverse.Imaging.ManagedImage.ResizeBilinear(System.Int32 width, System.Int32 height)`.
/// Mapping contract: ownership `mutable_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ResizeNearestNeighbor(System.Int32,System.Int32)`. /// C# member: `M:LibreMetaverse.Imaging.ManagedImage.ResizeNearestNeighbor(System.Int32,System.Int32)`.
///
/// C# signature: `System.Void LibreMetaverse.Imaging.ManagedImage.ResizeNearestNeighbor(System.Int32 width, System.Int32 height)`.
/// Mapping contract: ownership `mutable_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub use crate::managed_image::ManagedImage; pub use crate::managed_image::ManagedImage;
/// C# type: `T:LibreMetaverse.Imaging.ManagedImage.ImageChannels`. /// C# type: `T:LibreMetaverse.Imaging.ManagedImage.ImageChannels`.
///
/// Native Rust mapping of C# `LibreMetaverse.Imaging.ManagedImage.ImageChannels` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Alpha`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Alpha`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage.ImageChannels LibreMetaverse.Imaging.ManagedImage.ImageChannels.Alpha`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Bump`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Bump`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage.ImageChannels LibreMetaverse.Imaging.ManagedImage.ImageChannels.Bump`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Color`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Color`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage.ImageChannels LibreMetaverse.Imaging.ManagedImage.ImageChannels.Color`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
/// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Gray`. /// C# member: `F:LibreMetaverse.Imaging.ManagedImage.ImageChannels.Gray`.
///
/// C# signature: `LibreMetaverse.Imaging.ManagedImage.ImageChannels LibreMetaverse.Imaging.ManagedImage.ImageChannels.Gray`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
pub use crate::managed_image::ManagedImageImageChannels; pub use crate::managed_image::ManagedImageImageChannels;

View File

@@ -1,4 +1,8 @@
//! Imaging abstractions corresponding to `LibreMetaverse.Imaging.Abstractions`. //! Image and texture abstractions corresponding to `LibreMetaverse.Imaging`.
//!
//! The default surface provides managed images plus native `TGA`/`DDS` handling.
//! Enable `jpeg2000` only when the audited system `OpenJPEG` adapter is available;
//! ordinary users can consume the traits without a native image dependency.
extern crate self as libremetaverse_imaging; extern crate self as libremetaverse_imaging;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! LSL parsing tools corresponding to `LibreMetaverse.LslTools`. //! Native `LSL` tooling corresponding to `LibreMetaverse.LslTools`.
//!
//! This crate provides the bounded lexer, parser, diagnostics, syntax model,
//! and deterministic source generation used by editor and migration tooling.
//! It is pure Rust and does not require a grid connection.
extern crate self as libremetaverse_lsl_tools; extern crate self as libremetaverse_lsl_tools;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! Primitive mesher corresponding to `LibreMetaverse.PrimMesher`. //! Native primitive mesher corresponding to `LibreMetaverse.PrimMesher`.
//!
//! Use it to turn checked legacy prim and sculpt parameters into deterministic
//! geometry and `OBJ` output. Input bounds and topology validation apply before
//! allocation; no renderer, grid session, or native library is required.
extern crate self as libremetaverse_prim_mesher; extern crate self as libremetaverse_prim_mesher;

View File

@@ -6,13 +6,24 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
/// C# type: `T:LibreMetaverse.Rendering.MeshFoundry`. /// C# type: `T:LibreMetaverse.Rendering.MeshFoundry`.
///
/// Native Rust mapping of C# `LibreMetaverse.Rendering.MeshFoundry` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub use crate::mesh_foundry::MeshFoundry; pub use crate::mesh_foundry::MeshFoundry;
impl MeshFoundry { impl MeshFoundry {
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.#ctor`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.#ctor`.
///
/// C# signature: `LibreMetaverse.Rendering.MeshFoundry()`.
/// Mapping contract: ownership `none`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn new() -> Result<Self, crate::Error> { pub fn new() -> Result<Self, crate::Error> {
Self::native_new() Self::native_new()
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_faceted_mesh( pub fn generate_faceted_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -21,6 +32,10 @@ impl MeshFoundry {
self.native_generate_faceted_mesh(prim, lod) self.native_generate_faceted_mesh(prim, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive,System.Byte[])`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive,System.Byte[])`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive prim, System.Byte[] meshData)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn generate_faceted_mesh_mesh_with_primitive_bytes( pub fn generate_faceted_mesh_mesh_with_primitive_bytes(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -33,6 +48,10 @@ impl MeshFoundry {
) )
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive,System.Byte[],LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive,System.Byte[],LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedMeshMesh(LibreMetaverse.Primitive prim, System.Byte[] meshData, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn generate_faceted_mesh_mesh_with_primitive_bytes_detail_level( pub fn generate_faceted_mesh_mesh_with_primitive_bytes_detail_level(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -42,6 +61,10 @@ impl MeshFoundry {
self.native_generate_faceted_mesh_mesh(prim, mesh_data, lod) self.native_generate_faceted_mesh_mesh(prim, mesh_data, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.MeshFoundry.GenerateFacetedSculptMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Imaging.ManagedImage sculptTexture, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_faceted_sculpt_mesh( pub fn generate_faceted_sculpt_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -51,6 +74,10 @@ impl MeshFoundry {
self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod) self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_simple_mesh( pub fn generate_simple_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -59,6 +86,10 @@ impl MeshFoundry {
self.native_generate_simple_mesh(prim, lod, false) self.native_generate_simple_mesh(prim, lod, false)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMeshWithNormals(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMeshWithNormals(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleMeshWithNormals(LibreMetaverse.Primitive prim, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_simple_mesh_with_normals( pub fn generate_simple_mesh_with_normals(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -67,6 +98,10 @@ impl MeshFoundry {
self.native_generate_simple_mesh(prim, lod, true) self.native_generate_simple_mesh(prim, lod, true)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.MeshFoundry.GenerateSimpleSculptMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Imaging.ManagedImage sculptTexture, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_simple_sculpt_mesh( pub fn generate_simple_sculpt_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -76,6 +111,10 @@ impl MeshFoundry {
self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod) self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive,System.Byte[])`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive,System.Byte[])`.
///
/// C# signature: `System.Collections.Generic.List<System.Collections.Generic.List<LibreMetaverse.Vector3>> LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive prim, System.Byte[] compressedMeshData)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn mesh_sub_mesh_as_convex_hulls_with_primitive_bytes( pub fn mesh_sub_mesh_as_convex_hulls_with_primitive_bytes(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -84,6 +123,10 @@ impl MeshFoundry {
self.native_mesh_sub_mesh_as_convex_hulls(prim, compressed_mesh_data, None) self.native_mesh_sub_mesh_as_convex_hulls(prim, compressed_mesh_data, None)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive,System.Byte[],System.Collections.Generic.List{LibreMetaverse.Vector3}@)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive,System.Byte[],System.Collections.Generic.List{LibreMetaverse.Vector3}@)`.
///
/// C# signature: `System.Collections.Generic.List<System.Collections.Generic.List<LibreMetaverse.Vector3>> LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsConvexHulls(LibreMetaverse.Primitive prim, System.Byte[] compressedMeshData, out System.Collections.Generic.List<LibreMetaverse.Vector3>& boundingHull)`.
/// Mapping contract: ownership `shared_self,owned,owned,mutable_output`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn mesh_sub_mesh_as_convex_hulls_with_primitive_bytes_list( pub fn mesh_sub_mesh_as_convex_hulls_with_primitive_bytes_list(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -93,6 +136,10 @@ impl MeshFoundry {
self.native_mesh_sub_mesh_as_convex_hulls(prim, compressed_mesh_data, Some(bounding_hull)) self.native_mesh_sub_mesh_as_convex_hulls(prim, compressed_mesh_data, Some(bounding_hull))
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsSimpleMesh(LibreMetaverse.Primitive,System.Byte[])`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsSimpleMesh(LibreMetaverse.Primitive,System.Byte[])`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.MeshFoundry.MeshSubMeshAsSimpleMesh(LibreMetaverse.Primitive prim, System.Byte[] compressedMeshData)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn mesh_sub_mesh_as_simple_mesh( pub fn mesh_sub_mesh_as_simple_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -101,6 +148,10 @@ impl MeshFoundry {
self.native_mesh_sub_mesh_as_simple_mesh(prim, compressed_mesh_data) self.native_mesh_sub_mesh_as_simple_mesh(prim, compressed_mesh_data)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.TerrainMesh(System.Single[0:,0:],System.Single,System.Single,System.Single,System.Single)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.TerrainMesh(System.Single[0:,0:],System.Single,System.Single,System.Single,System.Single)`.
///
/// C# signature: `LibreMetaverse.Rendering.Face LibreMetaverse.Rendering.MeshFoundry.TerrainMesh(System.Single[,] zMap, System.Single xBegin, System.Single xEnd, System.Single yBegin, System.Single yEnd)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn terrain_mesh( pub fn terrain_mesh(
&self, &self,
z_map: Vec<Vec<f32>>, z_map: Vec<Vec<f32>>,
@@ -112,6 +163,10 @@ impl MeshFoundry {
self.native_terrain_mesh(z_map, x_begin, x_end, y_begin, y_end) self.native_terrain_mesh(z_map, x_begin, x_end, y_begin, y_end)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.TransformTexCoords(System.Collections.Generic.List{LibreMetaverse.Rendering.Vertex},LibreMetaverse.Vector3,LibreMetaverse.Primitive.TextureEntryFace,LibreMetaverse.Vector3)`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.TransformTexCoords(System.Collections.Generic.List{LibreMetaverse.Rendering.Vertex},LibreMetaverse.Vector3,LibreMetaverse.Primitive.TextureEntryFace,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Void LibreMetaverse.Rendering.MeshFoundry.TransformTexCoords(System.Collections.Generic.List<LibreMetaverse.Rendering.Vertex> vertices, LibreMetaverse.Vector3 center, LibreMetaverse.Primitive.TextureEntryFace teFace, LibreMetaverse.Vector3 primScale)`.
/// Mapping contract: ownership `shared_self,mutable_borrow,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn transform_tex_coords( pub fn transform_tex_coords(
&self, &self,
vertices: &mut Vec<libremetaverse::rendering::Vertex>, vertices: &mut Vec<libremetaverse::rendering::Vertex>,
@@ -122,6 +177,10 @@ impl MeshFoundry {
self.native_transform_tex_coords(vertices, center, te_face, prim_scale) self.native_transform_tex_coords(vertices, center, te_face, prim_scale)
} }
/// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.UnpackMesh(System.Byte[])`. /// C# member: `M:LibreMetaverse.Rendering.MeshFoundry.UnpackMesh(System.Byte[])`.
///
/// C# signature: `LibreMetaverse.StructuredData.OSDMap LibreMetaverse.Rendering.MeshFoundry.UnpackMesh(System.Byte[] assetData)`.
/// Mapping contract: ownership `shared_self,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn unpack_mesh( pub fn unpack_mesh(
&self, &self,
asset_data: Vec<u8>, asset_data: Vec<u8>,

View File

@@ -1,4 +1,8 @@
//! Bounded native prim, sculpt, terrain, and LLSD mesh-asset rendering. //! Bounded native prim, sculpt, terrain, and `LLSD` mesh-asset rendering.
//!
//! `MeshFoundry` consumes public meshing/imaging abstractions and produces checked
//! render meshes without a graphics API. The crate `README` documents topology,
//! material, coordinate, allocation, and migration behavior.
extern crate self as libremetaverse_rendering_mesh_foundry; extern crate self as libremetaverse_rendering_mesh_foundry;

View File

@@ -6,13 +6,24 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
/// C# type: `T:LibreMetaverse.Rendering.SimpleRenderer`. /// C# type: `T:LibreMetaverse.Rendering.SimpleRenderer`.
///
/// Native Rust mapping of C# `LibreMetaverse.Rendering.SimpleRenderer` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub use crate::simple_renderer::SimpleRenderer; pub use crate::simple_renderer::SimpleRenderer;
impl SimpleRenderer { impl SimpleRenderer {
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.#ctor`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.#ctor`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleRenderer()`.
/// Mapping contract: ownership `none`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn new() -> Result<Self, crate::Error> { pub fn new() -> Result<Self, crate::Error> {
Self::native_new() Self::native_new()
} }
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_faceted_mesh( pub fn generate_faceted_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -21,6 +32,10 @@ impl SimpleRenderer {
self.native_generate_faceted_mesh(prim, lod) self.native_generate_faceted_mesh(prim, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.FacetedMesh LibreMetaverse.Rendering.SimpleRenderer.GenerateFacetedSculptMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Imaging.ManagedImage sculptTexture, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_faceted_sculpt_mesh( pub fn generate_faceted_sculpt_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -30,6 +45,10 @@ impl SimpleRenderer {
self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod) self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleMesh(LibreMetaverse.Primitive,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_simple_mesh( pub fn generate_simple_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -38,6 +57,10 @@ impl SimpleRenderer {
self.native_generate_simple_mesh(prim, lod) self.native_generate_simple_mesh(prim, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleSculptMesh(LibreMetaverse.Primitive,LibreMetaverse.Imaging.ManagedImage,LibreMetaverse.Rendering.DetailLevel)`.
///
/// C# signature: `LibreMetaverse.Rendering.SimpleMesh LibreMetaverse.Rendering.SimpleRenderer.GenerateSimpleSculptMesh(LibreMetaverse.Primitive prim, LibreMetaverse.Imaging.ManagedImage sculptTexture, LibreMetaverse.Rendering.DetailLevel lod)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn generate_simple_sculpt_mesh( pub fn generate_simple_sculpt_mesh(
&self, &self,
prim: libremetaverse::Primitive, prim: libremetaverse::Primitive,
@@ -47,6 +70,10 @@ impl SimpleRenderer {
self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod) self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod)
} }
/// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.TransformTexCoords(System.Collections.Generic.List{LibreMetaverse.Rendering.Vertex},LibreMetaverse.Vector3,LibreMetaverse.Primitive.TextureEntryFace,LibreMetaverse.Vector3)`. /// C# member: `M:LibreMetaverse.Rendering.SimpleRenderer.TransformTexCoords(System.Collections.Generic.List{LibreMetaverse.Rendering.Vertex},LibreMetaverse.Vector3,LibreMetaverse.Primitive.TextureEntryFace,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Void LibreMetaverse.Rendering.SimpleRenderer.TransformTexCoords(System.Collections.Generic.List<LibreMetaverse.Rendering.Vertex> vertices, LibreMetaverse.Vector3 center, LibreMetaverse.Primitive.TextureEntryFace teFace, LibreMetaverse.Vector3 primScale)`.
/// Mapping contract: ownership `shared_self,mutable_borrow,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn transform_tex_coords( pub fn transform_tex_coords(
&self, &self,
vertices: &mut Vec<libremetaverse::rendering::Vertex>, vertices: &mut Vec<libremetaverse::rendering::Vertex>,

View File

@@ -1,7 +1,7 @@
//! Deterministic reference rendering for legacy prims and decoded sculpt maps. //! Deterministic reference rendering for legacy prims and decoded sculpt maps.
//! //!
//! [`SimpleRenderer`] converts checked `PrimMesher` geometry into the shared //! [`SimpleRenderer`] converts checked `PrimMesher` geometry into the shared
//! rendering types without a graphics backend. See the crate README for the //! rendering types without a graphics backend. See the crate `README` for the
//! supported topology, resource bounds, and mesh-asset boundary. //! supported topology, resource bounds, and mesh-asset boundary.
extern crate self as libremetaverse_rendering_simple; extern crate self as libremetaverse_rendering_simple;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! Restrained Love Viewer support corresponding to `LibreMetaverse.RLV`. //! Restrained Love Viewer (`RLV`) support corresponding to `LibreMetaverse.RLV`.
//!
//! The native implementation covers bounded command parsing, behavior state,
//! inventory resolution, attachment locks, camera policy, and permission
//! decisions. Applications retain control of transport and user consent.
extern crate self as libremetaverse_rlv; extern crate self as libremetaverse_rlv;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! Structured-data types corresponding to `LibreMetaverse.StructuredData`. //! Bounded `LLSD`/`OSD` support corresponding to `LibreMetaverse.StructuredData`.
//!
//! `XML`, `JSON`, binary, and notation codecs share checked depth, node, and byte
//! limits. Choose this crate for standalone structured-data work without
//! constructing a grid client or enabling a native codec.
extern crate self as libremetaverse_structured_data; extern crate self as libremetaverse_structured_data;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! Core value types corresponding to `LibreMetaverse.Types`. //! Native value and compatibility types corresponding to `LibreMetaverse.Types`.
//!
//! Use this crate directly for `UUID`s, vectors, matrices, colors, cancellation,
//! subscriptions, bounded collections, and protocol-neutral boundary types.
//! It has no grid login side effects and requires no native system library.
extern crate self as libremetaverse_types; extern crate self as libremetaverse_types;

View File

@@ -6,9 +6,16 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
/// C# type: `T:LibreMetaverse.Utilities.ConnectionManager`. /// C# type: `T:LibreMetaverse.Utilities.ConnectionManager`.
///
/// Native Rust mapping of C# `LibreMetaverse.Utilities.ConnectionManager` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub struct ConnectionManager; pub struct ConnectionManager;
impl ConnectionManager { impl ConnectionManager {
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.#ctor(LibreMetaverse.GridClient,System.Int32)`. /// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.#ctor(LibreMetaverse.GridClient,System.Int32)`.
///
/// C# signature: `LibreMetaverse.Utilities.ConnectionManager(LibreMetaverse.GridClient client, System.Int32 timerFrequency)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn new( pub fn new(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
timer_frequency: i32, timer_frequency: i32,
@@ -18,6 +25,10 @@ impl ConnectionManager {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient,System.String,System.String,System.String,System.String,System.String,System.String)`. /// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient,System.String,System.String,System.String,System.String,System.String,System.String)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Boolean> LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient client, System.String firstName, System.String lastName, System.String password, System.String userAgent, System.String start, System.String author)`.
/// Mapping contract: ownership `owned,owned,owned,owned,owned,owned,owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn persistent_login( pub async fn persistent_login(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
first_name: String, first_name: String,
@@ -32,6 +43,10 @@ impl ConnectionManager {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64,LibreMetaverse.Vector3)`. /// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64 handle, LibreMetaverse.Vector3 desiredPosition)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn stay_in_sim( pub fn stay_in_sim(
&self, &self,
handle: u64, handle: u64,
@@ -42,15 +57,26 @@ impl ConnectionManager {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.Stop`. /// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.Stop`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.Stop()`.
/// Mapping contract: ownership `shared_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn stop(&self) -> Result<(), crate::Error> { pub fn stop(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.Utilities.ConnectionManager.Stop") libremetaverse_types::not_implemented("M:LibreMetaverse.Utilities.ConnectionManager.Stop")
} }
} }
/// C# type: `T:LibreMetaverse.Utilities.Realism`. /// C# type: `T:LibreMetaverse.Utilities.Realism`.
///
/// Native Rust mapping of C# `LibreMetaverse.Utilities.Realism` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub struct Realism; pub struct Realism;
impl Realism { impl Realism {
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String)`. /// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn chat_with_grid_client_string( pub fn chat_with_grid_client_string(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
message: String, message: String,
@@ -60,6 +86,10 @@ impl Realism {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32)`. /// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps)`.
/// Mapping contract: ownership `owned,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn chat_with_grid_client_string_chat_type_int32( pub fn chat_with_grid_client_string_chat_type_int32(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
message: String, message: String,
@@ -71,6 +101,10 @@ impl Realism {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32,System.Threading.CancellationToken)`. /// C# member: `M:LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32,System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `owned,owned,owned,owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub async fn chat_with_grid_client_string_chat_type_int32_cancellation_token( pub async fn chat_with_grid_client_string_chat_type_int32_cancellation_token(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
message: String, message: String,
@@ -83,6 +117,10 @@ impl Realism {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient)`. /// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client)`.
/// Mapping contract: ownership `owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn shoot_with_grid_client( pub fn shoot_with_grid_client(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
) -> Result<bool, crate::Error> { ) -> Result<bool, crate::Error> {
@@ -91,6 +129,10 @@ impl Realism {
) )
} }
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient,LibreMetaverse.Vector3)`. /// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client, LibreMetaverse.Vector3 target)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn shoot_with_grid_client_vector3( pub fn shoot_with_grid_client_vector3(
client: libremetaverse::GridClient, client: libremetaverse::GridClient,
target: libremetaverse_types::Vector3, target: libremetaverse_types::Vector3,
@@ -102,15 +144,34 @@ impl Realism {
} }
/// C# type: `T:LibreMetaverse.Utilities.WaterType`. /// C# type: `T:LibreMetaverse.Utilities.WaterType`.
///
/// Native Rust mapping of C# `LibreMetaverse.Utilities.WaterType` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(i32)] #[repr(i32)]
pub enum WaterType { pub enum WaterType {
/// C# member: `F:LibreMetaverse.Utilities.WaterType.Dry`. /// C# member: `F:LibreMetaverse.Utilities.WaterType.Dry`.
///
/// C# signature: `LibreMetaverse.Utilities.WaterType LibreMetaverse.Utilities.WaterType.Dry`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
Dry = 1, Dry = 1,
/// C# member: `F:LibreMetaverse.Utilities.WaterType.Underwater`. /// C# member: `F:LibreMetaverse.Utilities.WaterType.Underwater`.
///
/// C# signature: `LibreMetaverse.Utilities.WaterType LibreMetaverse.Utilities.WaterType.Underwater`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
Underwater = 3, Underwater = 3,
/// C# member: `F:LibreMetaverse.Utilities.WaterType.Unknown`. /// C# member: `F:LibreMetaverse.Utilities.WaterType.Unknown`.
///
/// C# signature: `LibreMetaverse.Utilities.WaterType LibreMetaverse.Utilities.WaterType.Unknown`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
Unknown = 0, Unknown = 0,
/// C# member: `F:LibreMetaverse.Utilities.WaterType.Waterfront`. /// C# member: `F:LibreMetaverse.Utilities.WaterType.Waterfront`.
///
/// C# signature: `LibreMetaverse.Utilities.WaterType LibreMetaverse.Utilities.WaterType.Waterfront`.
/// Mapping contract: ownership `owned_value`; async `sync`;
/// errors `none`; overload `direct_snake_case`; kind `direct`.
Waterfront = 2, Waterfront = 2,
} }

View File

@@ -1,4 +1,8 @@
//! Utilities corresponding to `LibreMetaverse.Utilities`. //! Native utility APIs corresponding to `LibreMetaverse.Utilities`.
//!
//! Use this compatibility crate when ported code depends on the upstream helper
//! assembly. Public signatures use project-owned Rust boundary types and never
//! load the `CLR` or an upstream binary.
extern crate self as libremetaverse_utilities; extern crate self as libremetaverse_utilities;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
//! Vivox adapter corresponding to `LibreMetaverse.Voice.Vivox`. //! `Vivox` control adapter corresponding to `LibreMetaverse.Voice.Vivox`.
//!
//! The crate implements the bounded `XML` request/event protocol for an explicitly
//! supplied service endpoint. It neither bundles nor starts the proprietary
//! daemon, and its diagnostics redact account, session, and channel secrets.
extern crate self as libremetaverse_voice_vivox; extern crate self as libremetaverse_voice_vivox;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,9 @@
//! WebRTC voice adapter corresponding to `LibreMetaverse.Voice.WebRTC`. //! Native `WebRTC` voice adapter corresponding to `LibreMetaverse.Voice.WebRTC`.
//!
//! `ICE`, `DTLS`, `SRTP`, `RTP`, `SCTP` data, `Opus` media, controls, and teardown
//! run in Rust. System `libopus` is required; the optional `real-audio` feature
//! adds the cross-platform `CPAL` hardware boundary while default tests use
//! virtual audio.
extern crate self as libremetaverse_voice_webrtc; extern crate self as libremetaverse_voice_webrtc;

View File

@@ -0,0 +1,14 @@
use libremetaverse_types::compat::CancellationTokenSource;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), libremetaverse::Error> {
let source = CancellationTokenSource::new();
let token = source.token();
source.cancel();
token.cancelled().await;
assert!(matches!(
token.throw_if_cancellation_requested(),
Err(libremetaverse::Error::Cancelled)
));
Ok(())
}

View File

@@ -0,0 +1,10 @@
use libremetaverse::{ClientLifecycleState, GridClient};
fn main() -> Result<(), libremetaverse::Error> {
let client = GridClient::new()?;
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Active);
client.dispose_with_method()?;
client.dispose_with_method()?;
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed);
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,10 @@
//! Public rewrite shell corresponding to the main `LibreMetaverse` assembly. //! Native Rust grid client corresponding to the main `LibreMetaverse` assembly.
//!
//! Start here for login, networking, agents, inventory, assets, appearance,
//! world, and social APIs. The appended guide covers crate selection, C# name
//! migration, ownership, cancellation, events, security, and `OpenSim` setup.
#![doc = include_str!("../../../docs/rust-api-guide.md")]
extern crate self as libremetaverse; extern crate self as libremetaverse;

267
docs/rust-api-guide.md Normal file
View File

@@ -0,0 +1,267 @@
# Rust API and C# migration guide
`MetaCrate` is a native Rust implementation of the API and behavior cataloged
from `LibreMetaverse`. The compatibility reference is the
[pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba);
it is never loaded, invoked, or shipped. Exact type/member mappings live in
[`RUST-TYPES.tsv`](../api/RUST-TYPES.tsv) and
[`RUST-MAPPING.tsv`](../api/RUST-MAPPING.tsv). Search those files by C#
documentation ID when a familiar member has a non-obvious Rust name.
## Choosing crates and features
Most applications start with `libremetaverse` and add narrower crates only when
they use those APIs directly.
| Crate | Choose it for | Features or system boundary |
| --- | --- | --- |
| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7; optional `jpeg2000` and `vorbis` |
| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust |
| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` |
| `libremetaverse-imaging-skia` | Common raster formats through Skia | Optional `skia`; native Skia build/cache |
| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust |
| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust |
| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust |
| `libremetaverse-lsl-tools` | LSL lexing, parsing, diagnostics, and generation | Pure Rust |
| `libremetaverse-rlv` | RLV commands, restrictions, locks, camera, and inventory policy | Pure Rust |
| `libremetaverse-utilities` | Compatible utility helpers | Pure Rust |
| `libremetaverse-voice-vivox` | Vivox XML control protocol | External Vivox service is explicit and never spawned |
| `libremetaverse-voice-webrtc` | Native ICE/DTLS/SRTP/SCTP and Opus voice | System libopus; optional `real-audio` uses CPAL |
| `libremetaverse-openjpeg` | Audited `OpenJPEG` adapter | System `OpenJPEG` 2.5.4 or newer |
| `libremetaverse-opus` | Audited Opus encoder/decoder adapter | System libopus |
Features are additive. Keep defaults unless you need a codec, and enable one
native adapter at a time while diagnosing installation problems. The
[release CI matrix](release-ci-matrix.md) records every validated combination.
## Naming and overload migration
C# `PascalCase` types remain recognizable while methods and properties use
Rust `snake_case`. A property getter becomes `name()` and its setter becomes
`set_name(value)`. Events become `subscribe_*` methods returning an owned
subscription. Rust has no overloads, so the simplest form keeps the base name
and additional forms receive parameter-derived suffixes. The mapping ledger is
authoritative; do not guess a long suffix.
```rust
use libremetaverse::InventoryItem;
use libremetaverse_types::UUID;
let id = UUID::new_with_u_int64(42)?;
let mut item = InventoryItem::new_with_uuid(id)?;
item.base.set_name("Migrated item".into());
assert_eq!(item.base.uuid(), id);
assert_eq!(item.base.name(), "Migrated item");
# Ok::<(), libremetaverse::Error>(())
```
C# `null` usually maps to `Option<T>`, `ref`/`out` may become a return value or
an explicit mutable reference, and interface objects become `dyn Trait` behind
`Arc` or `Box` according to the ledger's `ownership` column.
## Ownership and disposal
Managers cloned from a `GridClient` share native state. `GridClient` owns its
cancellation root and cached services; event guards own registrations; session
objects own tasks and sockets. Explicit shutdown is recommended because it can
report errors, while `Drop` remains the final idempotent safety net.
```rust
use libremetaverse::{ClientLifecycleState, GridClient};
let client = GridClient::new()?;
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Active);
client.dispose_with_method()?;
client.dispose_with_method()?; // idempotent
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed);
# Ok::<(), libremetaverse::Error>(())
```
The runnable version is
[`offline_client.rs`](../crates/libremetaverse/examples/offline_client.rs).
Never hold a manager lock while calling user code or awaiting I/O. Services
registered through `GridClient::builder()` must join every owned task before
their `shutdown` method returns.
## Async work and cancellation
Async APIs borrow no hidden runtime. Call them from the application executor and
pass `CancellationToken` explicitly where the C# API accepted one. Cloning a
token is cheap and observes the same cancellation source. Cancellation is a
typed `Error::Cancelled`, not a successful empty response.
```rust
use libremetaverse_types::compat::CancellationTokenSource;
# #[tokio::main(flavor = "current_thread")]
# async fn main() -> Result<(), libremetaverse::Error> {
let source = CancellationTokenSource::new();
let token = source.token();
source.cancel();
token.cancelled().await;
assert!(matches!(
token.throw_if_cancellation_requested(),
Err(libremetaverse::Error::Cancelled)
));
# Ok(())
# }
```
The same code is available as
[`cancellation.rs`](../crates/libremetaverse/examples/cancellation.rs).
Timeouts belong at the caller or documented operation boundary; cancellation
must still drain and join the underlying resource owner.
## Errors
Fallible compatibility APIs return the shared `libremetaverse::Error`; native
composition APIs may expose a narrower error such as `ClientCoreError` or
`WebRtcError`. Match variants for control flow and use display text only for
operators. Errors and `Debug` output redact credentials, capability URLs, and
session secrets.
```rust
use libremetaverse::http::DownloadRequest;
use libremetaverse_types::compat::Uri;
let result = DownloadRequest::new(Uri("file:///not-http".into()), None, None);
assert!(matches!(result, Err(libremetaverse::Error::Argument)));
```
Do not translate C# exception swallowing into `unwrap_or_default()`. Preserve
the mapped error contract and handle cancellation separately from protocol,
I/O, authentication, and validation failures.
## Events and subscriptions
An `EventHandler<T>` is an `Arc` callback. Keep the returned `Subscription` for
exactly as long as notifications are wanted; dropping it unregisters the
callback. Dispatch clones the handler list and releases internal locks before
calling user code. Manager event dispatch isolates subscriber panics so one
consumer cannot stop later consumers.
```rust
use libremetaverse::{GridClient, Inventory, InventoryFolder};
use libremetaverse_types::UUID;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
let client = Arc::new(GridClient::new()?);
let inventory = Inventory::new_with_grid_client_uuid(Arc::clone(&client), UUID::zero())?;
let calls = Arc::new(AtomicUsize::new(0));
let observed = Arc::clone(&calls);
let subscription = inventory.subscribe_inventory_object_added(Arc::new(move |_| {
observed.fetch_add(1, Ordering::AcqRel);
}));
let folder = InventoryFolder::new(UUID::new_with_u_int64(7)?)?;
inventory.update_node_for(&folder)?;
assert_eq!(calls.load(Ordering::Acquire), 1);
drop(subscription);
client.dispose_with_method()?;
# Ok::<(), libremetaverse::Error>(())
```
## Threading and callbacks
Public shared managers use `Arc`, atomics, mutexes, channels, and cancellation
tokens rather than a CLR synchronization context. A type being cloneable does
not make a callback re-entrant: keep handlers short, move expensive work to a
bounded queue, and never block an async runtime thread waiting for itself.
Download, inventory, client, and voice diagnostics expose owned task/queue
counts for shutdown checks. The [concurrency audit](concurrency-hardening.md)
defines the exact baseline and soak thresholds.
## Security boundaries
The released graph contains no CLR host, .NET assembly loader, subprocess RPC,
or foreign `LibreMetaverse` bridge. HTTP uses rustls. Untrusted LLSD, archive,
image, packet, event, and signaling inputs have explicit size/depth limits.
Secrets belong in operation arguments or environment variables, never in
diagnostics, evidence, filenames, command lines, or committed fixtures.
Offline/fake modes are real deterministic executions, not skipped live tests.
Live login and every mutating smoke action require their own opt-in and literal
confirmation. See the [live-grid boundary](live-grid-smoke.md).
## Native prerequisites
The default client build needs no image-codec system library. Optional native
features require:
- `OpenJPEG` 2.5.4 or newer for `jpeg2000`;
- the rust-skia prerequisites/cache for `skia`;
- system libopus for WebRTC voice and the `libremetaverse-opus` adapter;
- ALSA development headers on Linux, `CoreAudio` on macOS, or WASAPI on Windows
when `real-audio` enables CPAL;
- the Vorbis encoder build prerequisites for `vorbis`.
Discovery uses `pkg-config` on Unix/macOS and `vcpkg` on Windows MSVC where
applicable. The adapters remain cross-platform; Gitea workflows intentionally
run only on `ubuntu-latest`.
## Live `OpenSim` setup
Use a dedicated `OpenSim` account and put credentials in the workspace `.env` or
the process environment. The login URL is used directly; no Second Life host is
substituted.
```text
GRID_USER=First Last
GRID_PASSWORD=...
GRID_LOGIN_URL=https://your-opensim.example/login
```
Start with the credential-safe audit and fake smoke, then opt into live login:
```sh
cargo run -p libremetaverse-programs --bin live-grid-smoke -- --audit-only
cargo run -p libremetaverse-programs --bin live-grid-smoke -- --fake
cargo run -p libremetaverse-programs --bin live-grid-smoke -- \
--allow-live-login --confirm-live-login LOGIN
```
Chat, movement, and reversible inventory each require additional confirmations
documented in the [`OpenSim` live-grid guide](live-grid-smoke.md). A test that
needs live credentials must fail clearly when they are absent; it must not
silently skip.
## Programs and operational tools
All pinned upstream programs have native Rust targets and deterministic offline
tests. Their arguments, exit statuses, fake/live boundaries, and focused test
commands are linked from the [program operations manual](../programs/README.md):
- [`osd-inspector`](../programs/README.md#osdinspector)
- [`simple-bot`](../programs/README.md#simplebot)
- [`packet-dump`](../programs/README.md#packetdump)
- [`prim-inspector`](../programs/README.md#priminspector)
- [`inventory-explorer`](../programs/README.md#inventoryexplorer)
- [`irc-gateway`](../programs/README.md#ircgateway)
- [`test-client`](../programs/README.md#testclient)
- [`vivox-test`](../programs/README.md#vivoxtest)
- [`webrtc-test`](../programs/README.md#webrtctest)
For services, run the client under an external supervisor, propagate shutdown
cancellation, bound queues/files, collect sanitized evidence, and call logout
before disposal. The tool never invents persistence or retry policy on behalf of
the application.
## Documentation validation
The complete documentation gate is:
```sh
cargo run --locked -p metacrate-ci-matrix -- documentation-audit \
--evidence /tmp/metacrate-documentation.json
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked -j 1
cargo test --locked -p libremetaverse --doc -j 1
cargo test --locked -p libremetaverse --examples -j 1
cargo run --locked -p libremetaverse --example offline_client
cargo run --locked -p libremetaverse --example cancellation
```
The generated [coverage report](../api/DOCUMENTATION-COVERAGE.md) proves that
every mapped member/type retains its exact C# concept ID and mapping context,
all public crates have root docs, all local Markdown links resolve, all programs
are linked, and every Rust fence in this guide is compiled as a doctest.

View File

@@ -0,0 +1,606 @@
use super::{MatrixError, Result};
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
use std::fs::{self, OpenOptions};
use std::io::Write as _;
use std::path::{Path, PathBuf};
const MAPPING_PATH: &str = "api/RUST-MAPPING.tsv";
const TYPES_PATH: &str = "api/RUST-TYPES.tsv";
const GUIDE_PATH: &str = "docs/rust-api-guide.md";
const REPORT_PATH: &str = "api/DOCUMENTATION-COVERAGE.md";
const UPSTREAM_COMMIT: &str = "2aa70bb68513b39795da5d13c88f31b86e85a3ba";
const PUBLIC_CRATES: [(&str, &str); 15] = [
("libremetaverse", "crates/libremetaverse"),
("libremetaverse-types", "crates/libremetaverse-types"),
(
"libremetaverse-structured-data",
"crates/libremetaverse-structured-data",
),
("libremetaverse-imaging", "crates/libremetaverse-imaging"),
(
"libremetaverse-imaging-skia",
"crates/libremetaverse-imaging-skia",
),
(
"libremetaverse-prim-mesher",
"crates/libremetaverse-prim-mesher",
),
(
"libremetaverse-rendering-simple",
"crates/libremetaverse-rendering-simple",
),
(
"libremetaverse-rendering-mesh-foundry",
"crates/libremetaverse-rendering-mesh-foundry",
),
(
"libremetaverse-lsl-tools",
"crates/libremetaverse-lsl-tools",
),
("libremetaverse-rlv", "crates/libremetaverse-rlv"),
(
"libremetaverse-utilities",
"crates/libremetaverse-utilities",
),
(
"libremetaverse-voice-vivox",
"crates/libremetaverse-voice-vivox",
),
(
"libremetaverse-voice-webrtc",
"crates/libremetaverse-voice-webrtc",
),
("libremetaverse-openjpeg", "crates/libremetaverse-openjpeg"),
("libremetaverse-opus", "crates/libremetaverse-opus"),
];
const PROGRAMS: [&str; 9] = [
"osd-inspector",
"simple-bot",
"packet-dump",
"prim-inspector",
"inventory-explorer",
"irc-gateway",
"test-client",
"vivox-test",
"webrtc-test",
];
const REQUIRED_GUIDE_SECTIONS: [&str; 11] = [
"## Choosing crates and features",
"## Naming and overload migration",
"## Ownership and disposal",
"## Async work and cancellation",
"## Errors",
"## Events and subscriptions",
"## Threading and callbacks",
"## Security boundaries",
"## Native prerequisites",
"## Live `OpenSim` setup",
"## Programs and operational tools",
];
const REQUIRED_MAPPING_FIELDS: [&str; 11] = [
"csharp_id",
"csharp_signature",
"rust_crate",
"rust_item_path",
"rust_signature",
"ownership",
"asyncness",
"error_model",
"overload_decision",
"mapping_kind",
"status",
];
#[derive(Debug, Serialize)]
struct DocumentationEvidence {
schema: u32,
upstream_commit: &'static str,
public_crates: usize,
mapped_public_types: usize,
documented_public_types: usize,
mapped_members: usize,
documented_members: usize,
tested_rust_snippets: usize,
linked_programs: usize,
checked_local_links: usize,
required_guide_sections: usize,
status: &'static str,
}
struct Snapshot {
mapped_public_types: usize,
documented_public_types: usize,
mapped_members: usize,
documented_members: usize,
rust_snippets: usize,
local_links: usize,
}
/// Regenerates the deterministic mapped API documentation coverage report.
///
/// # Errors
///
/// Returns an error if documentation inputs are incomplete or cannot be read.
pub fn write_documentation_report(root: &Path) -> Result<()> {
let snapshot = inspect(root)?;
fs::write(root.join(REPORT_PATH), render_report(&snapshot))?;
Ok(())
}
/// Validates public crate, mapped item, guide, link, snippet, and program docs.
///
/// # Errors
///
/// Returns an error when any mapped public item lacks its exact C# concept ID,
/// a public crate lacks publishable root documentation, a local link is broken,
/// a required migration topic/program is absent, or the checked report is stale.
pub fn audit_documentation(root: &Path, evidence: &Path) -> Result<()> {
if evidence.exists() {
return Err(MatrixError::new(format!(
"{} already exists; preserve or remove it before rerunning the audit",
evidence.display()
)));
}
let snapshot = inspect(root)?;
let expected_report = render_report(&snapshot);
let report = fs::read_to_string(root.join(REPORT_PATH))?;
if report != expected_report {
return Err(MatrixError::new(
"documentation coverage report is stale; run documentation-report",
));
}
let record = DocumentationEvidence {
schema: 1,
upstream_commit: UPSTREAM_COMMIT,
public_crates: PUBLIC_CRATES.len(),
mapped_public_types: snapshot.mapped_public_types,
documented_public_types: snapshot.documented_public_types,
mapped_members: snapshot.mapped_members,
documented_members: snapshot.documented_members,
tested_rust_snippets: snapshot.rust_snippets,
linked_programs: PROGRAMS.len(),
checked_local_links: snapshot.local_links,
required_guide_sections: REQUIRED_GUIDE_SECTIONS.len(),
status: "ok",
};
if let Some(parent) = evidence.parent() {
fs::create_dir_all(parent)?;
}
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(evidence)?;
serde_json::to_writer_pretty(&mut file, &record)?;
file.write_all(b"\n")?;
file.sync_all()?;
Ok(())
}
fn inspect(root: &Path) -> Result<Snapshot> {
validate_crates(root)?;
let guide = fs::read_to_string(root.join(GUIDE_PATH))?;
validate_guide(&guide)?;
let generated_docs = generated_doc_lines(root)?;
let mapped_members = validate_member_mapping(root, &generated_docs)?;
let mapped_public_types = validate_type_mapping(root, &generated_docs)?;
let local_links = validate_markdown_links(root)?;
let rust_snippets = guide.matches("```rust").count();
if rust_snippets < 4 {
return Err(MatrixError::new(
"Rust API guide must contain at least four compiled snippets",
));
}
Ok(Snapshot {
mapped_public_types,
documented_public_types: mapped_public_types,
mapped_members,
documented_members: mapped_members,
rust_snippets,
local_links,
})
}
fn validate_crates(root: &Path) -> Result<()> {
for (name, relative) in PUBLIC_CRATES {
let directory = root.join(relative);
let manifest = fs::read_to_string(directory.join("Cargo.toml"))?;
if !manifest.contains(&format!("name = \"{name}\""))
|| !manifest.lines().any(|line| {
line.trim_start()
.strip_prefix("description = ")
.is_some_and(|description| description.len() >= 24)
})
{
return Err(MatrixError::new(format!(
"public crate {name} lacks package documentation metadata"
)));
}
let library = fs::read_to_string(directory.join("src/lib.rs"))?;
let crate_doc_bytes: usize = library
.lines()
.take_while(|line| line.starts_with("//!") || line.trim().is_empty())
.filter(|line| line.starts_with("//!"))
.map(str::len)
.sum();
if crate_doc_bytes < 120 {
return Err(MatrixError::new(format!(
"public crate {name} needs a useful crate-level overview"
)));
}
}
let core = fs::read_to_string(root.join("crates/libremetaverse/src/lib.rs"))?;
if !core.contains("include_str!(\"../../../docs/rust-api-guide.md\")") {
return Err(MatrixError::new(
"the tested Rust API guide is not included in libremetaverse rustdoc",
));
}
Ok(())
}
fn validate_guide(guide: &str) -> Result<()> {
if !guide.contains("https://github.com/cinderblocks/libremetaverse")
|| !guide.contains(UPSTREAM_COMMIT)
{
return Err(MatrixError::new(
"Rust API guide lacks the pinned C# source link",
));
}
for section in REQUIRED_GUIDE_SECTIONS {
if !guide.contains(section) {
return Err(MatrixError::new(format!(
"Rust API guide lacks required section {section}"
)));
}
}
for program in PROGRAMS {
if !guide.contains(&format!("`{program}`")) {
return Err(MatrixError::new(format!(
"Rust API guide does not link program {program}"
)));
}
}
Ok(())
}
fn generated_doc_lines(root: &Path) -> Result<BTreeSet<String>> {
let mut lines = BTreeSet::new();
for (_, relative) in PUBLIC_CRATES {
let generated = root.join(relative).join("src/generated.rs");
if generated.exists() {
lines.extend(
fs::read_to_string(generated)?
.lines()
.map(str::trim)
.filter(|line| line.starts_with("///"))
.map(str::to_owned),
);
}
}
Ok(lines)
}
fn validate_member_mapping(root: &Path, docs: &BTreeSet<String>) -> Result<usize> {
let (header, rows) = read_tsv(&root.join(MAPPING_PATH))?;
let positions = positions(&header, &REQUIRED_MAPPING_FIELDS)?;
let id_position = positions["csharp_id"];
let mut ids = BTreeSet::new();
for row in &rows {
for field in REQUIRED_MAPPING_FIELDS {
if row[positions[field]].trim().is_empty() {
return Err(MatrixError::new(format!(
"mapping row has empty documentation context field {field}"
)));
}
}
let id = &row[id_position];
if !ids.insert(id.clone()) {
return Err(MatrixError::new(format!("duplicate mapped member {id}")));
}
if !docs.contains(&format!("/// C# member: `{id}`.")) {
return Err(MatrixError::new(format!(
"mapped member {id} lacks its C# concept documentation marker"
)));
}
let signature = &row[positions["csharp_signature"]];
if !docs.contains(&format!("/// C# signature: `{signature}`.")) {
return Err(MatrixError::new(format!(
"mapped member {id} lacks its C# signature documentation"
)));
}
let ownership = &row[positions["ownership"]];
let asyncness = &row[positions["asyncness"]];
if !docs.contains(&format!(
"/// Mapping contract: ownership `{ownership}`; async `{asyncness}`;"
)) {
return Err(MatrixError::new(format!(
"mapped member {id} lacks ownership/async documentation"
)));
}
let error_model = &row[positions["error_model"]];
let overload = &row[positions["overload_decision"]];
let mapping_kind = &row[positions["mapping_kind"]];
if !docs.contains(&format!(
"/// errors `{error_model}`; overload `{overload}`; kind `{mapping_kind}`."
)) {
return Err(MatrixError::new(format!(
"mapped member {id} lacks error/overload/kind documentation"
)));
}
}
Ok(rows.len())
}
fn validate_type_mapping(root: &Path, docs: &BTreeSet<String>) -> Result<usize> {
let (header, rows) = read_tsv(&root.join(TYPES_PATH))?;
let required = [
"csharp_type_id",
"csharp_signature",
"source_kind",
"rust_crate",
"rust_path",
"mapping_decision",
"status",
];
let positions = positions(&header, &required)?;
let mut count = 0;
let mut ids = BTreeSet::new();
for row in &rows {
for field in required {
if row[positions[field]].trim().is_empty() {
return Err(MatrixError::new(format!(
"type row has empty documentation context field {field}"
)));
}
}
let id = &row[positions["csharp_type_id"]];
if !ids.insert(id.clone()) {
return Err(MatrixError::new(format!("duplicate mapped type {id}")));
}
if id.starts_with("T:LibreMetaverse")
&& row[positions["source_kind"]] != "referenced_support_trait"
{
count += 1;
if !docs.contains(&format!("/// C# type: `{id}`.")) {
return Err(MatrixError::new(format!(
"mapped public type {id} lacks its C# concept documentation marker"
)));
}
let signature = &row[positions["csharp_signature"]];
if !docs.contains(&format!(
"/// Native Rust mapping of C# `{signature}` using decision"
)) {
return Err(MatrixError::new(format!(
"mapped public type {id} lacks useful C# signature documentation"
)));
}
let decision = &row[positions["mapping_decision"]];
if !docs.contains(&format!(
"/// `{decision}`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/{UPSTREAM_COMMIT})."
)) {
return Err(MatrixError::new(format!(
"mapped public type {id} lacks its mapping decision/source link"
)));
}
}
}
Ok(count)
}
fn read_tsv(path: &Path) -> Result<(Vec<String>, Vec<Vec<String>>)> {
let text = fs::read_to_string(path)?;
let mut lines = text.lines();
let header = lines
.next()
.ok_or_else(|| MatrixError::new(format!("{} is empty", path.display())))?
.split('\t')
.map(str::to_owned)
.collect::<Vec<_>>();
let rows = lines
.filter(|line| !line.is_empty())
.map(|line| line.split('\t').map(str::to_owned).collect::<Vec<_>>())
.collect::<Vec<_>>();
if rows.iter().any(|row| row.len() != header.len()) {
return Err(MatrixError::new(format!(
"{} contains a malformed row",
path.display()
)));
}
Ok((header, rows))
}
fn positions<const N: usize>(
header: &[String],
required: &[&'static str; N],
) -> Result<BTreeMap<&'static str, usize>> {
let mut result = BTreeMap::new();
for &field in required {
let position = header
.iter()
.position(|candidate| candidate == field)
.ok_or_else(|| MatrixError::new(format!("TSV lacks required column {field}")))?;
result.insert(field, position);
}
Ok(result)
}
fn markdown_files(root: &Path) -> Result<Vec<PathBuf>> {
let mut files = vec![root.join("README.md"), root.join("programs/README.md")];
for directory in [root.join("docs"), root.join("api"), root.join("crates")] {
collect_markdown(&directory, &mut files)?;
}
files.sort();
files.dedup();
files.retain(|path| path != &root.join(REPORT_PATH));
Ok(files)
}
fn collect_markdown(directory: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
for entry in fs::read_dir(directory)? {
let path = entry?.path();
if path.is_dir() {
collect_markdown(&path, files)?;
} else if path.extension().and_then(|value| value.to_str()) == Some("md") {
files.push(path);
}
}
Ok(())
}
fn validate_markdown_links(root: &Path) -> Result<usize> {
let mut checked = 0;
for file in markdown_files(root)? {
let text = fs::read_to_string(&file)?;
let mut remainder = text.as_str();
while let Some(start) = remainder.find("](") {
remainder = &remainder[start + 2..];
let Some(end) = remainder.find(')') else {
return Err(MatrixError::new(format!(
"{} contains an unterminated Markdown link",
file.display()
)));
};
let raw = remainder[..end].trim().trim_matches(['<', '>']);
remainder = &remainder[end + 1..];
let target = raw.split_whitespace().next().unwrap_or_default();
if target.is_empty()
|| target.starts_with("http://")
|| target.starts_with("https://")
|| target.starts_with("mailto:")
{
continue;
}
let (path, fragment) = target
.split_once('#')
.map_or((target, None), |(path, fragment)| (path, Some(fragment)));
let resolved = if path.is_empty() {
file.clone()
} else {
file.parent().unwrap_or(root).join(path)
};
if !resolved.exists() {
return Err(MatrixError::new(format!(
"broken local Markdown link {target} in {}",
file.strip_prefix(root).unwrap_or(&file).display()
)));
}
if let Some(fragment) = fragment.filter(|fragment| !fragment.is_empty())
&& resolved.extension().and_then(|value| value.to_str()) == Some("md")
{
let destination = fs::read_to_string(&resolved)?;
if !markdown_anchors(&destination).contains(fragment) {
return Err(MatrixError::new(format!(
"broken local Markdown anchor {target} in {}",
file.strip_prefix(root).unwrap_or(&file).display()
)));
}
}
checked += 1;
}
}
Ok(checked)
}
fn markdown_anchors(markdown: &str) -> BTreeSet<String> {
let mut anchors = BTreeSet::new();
let mut occurrences = BTreeMap::<String, usize>::new();
let mut fenced = false;
for line in markdown.lines() {
if line.trim_start().starts_with("```") {
fenced = !fenced;
continue;
}
if fenced {
continue;
}
let heading = line.trim_start_matches('#').trim();
if heading.is_empty() || heading.len() == line.trim().len() {
continue;
}
let base = heading
.chars()
.filter_map(|character| {
if character.is_alphanumeric() || matches!(character, '-' | '_') {
Some(character.to_ascii_lowercase())
} else if character.is_whitespace() {
Some('-')
} else {
None
}
})
.collect::<String>();
let occurrence = occurrences.entry(base.clone()).or_default();
let anchor = if *occurrence == 0 {
base.clone()
} else {
format!("{base}-{occurrence}")
};
*occurrence += 1;
anchors.insert(anchor);
}
anchors
}
fn render_report(snapshot: &Snapshot) -> String {
format!(
"# Documentation coverage\n\n\
Generated by `metacrate-ci-matrix documentation-report`; do not edit by hand.\n\n\
| Surface | Documented | Required | Coverage |\n\
| --- | ---: | ---: | ---: |\n\
| Publishable public crates | {crates} | {crates} | 100% |\n\
| Pinned LibreMetaverse public types | {types_documented} | {types} | 100% |\n\
| Mapped public members | {members_documented} | {members} | 100% |\n\
| Compiled Rust guide snippets | {snippets} | 4 minimum | pass |\n\
| Linked native programs | {programs} | {programs} | 100% |\n\
| Checked local Markdown links | {links} | {links} | 100% |\n\n\
Every mapped item is tied to its exact C# documentation ID and to the ownership, \
asyncness, error, overload, mapping-kind, and Rust-signature decisions in \
[`RUST-MAPPING.tsv`](RUST-MAPPING.tsv). Public types are tied to the corresponding \
type mapping. The upstream source is pinned to \
[`{commit}`](https://github.com/cinderblocks/libremetaverse/tree/{commit}).\n",
crates = PUBLIC_CRATES.len(),
types_documented = snapshot.documented_public_types,
types = snapshot.mapped_public_types,
members_documented = snapshot.documented_members,
members = snapshot.mapped_members,
snippets = snapshot.rust_snippets,
programs = PROGRAMS.len(),
links = snapshot.local_links,
commit = UPSTREAM_COMMIT,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn markdown_anchor_generation_matches_linkable_headings() {
let anchors = markdown_anchors(
"# API guide\n\n## Live `OpenSim` setup\n## API guide\n```text\n# ignored\n```\n",
);
assert!(anchors.contains("api-guide"));
assert!(anchors.contains("live-opensim-setup"));
assert!(anchors.contains("api-guide-1"));
assert!(!anchors.contains("ignored"));
}
#[test]
fn report_contains_exact_documentation_totals() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let snapshot = inspect(&root).unwrap();
assert_eq!(snapshot.mapped_members, 30_789);
assert_eq!(snapshot.documented_members, snapshot.mapped_members);
assert_eq!(snapshot.mapped_public_types, 3_066);
assert_eq!(
snapshot.documented_public_types,
snapshot.mapped_public_types
);
assert!(snapshot.rust_snippets >= 4);
assert!(snapshot.local_links > 0);
}
}

View File

@@ -12,8 +12,10 @@ use std::process::{Command, ExitStatus, Stdio};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
mod dependency; mod dependency;
mod documentation;
pub use dependency::audit_dependencies; pub use dependency::audit_dependencies;
pub use documentation::{audit_documentation, write_documentation_report};
pub const MATRIX_PATH: &str = "ci/release-matrix.json"; pub const MATRIX_PATH: &str = "ci/release-matrix.json";
const WORKFLOW_PATH: &str = ".gitea/workflows/release-matrix.yml"; const WORKFLOW_PATH: &str = ".gitea/workflows/release-matrix.yml";

View File

@@ -1,4 +1,7 @@
use metacrate_ci_matrix::{audit, audit_dependencies, load, run, workspace_root}; use metacrate_ci_matrix::{
audit, audit_dependencies, audit_documentation, load, run, workspace_root,
write_documentation_report,
};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
fn main() { fn main() {
@@ -46,9 +49,27 @@ fn execute() -> Result<(), Box<dyn std::error::Error>> {
audit_dependencies(&root, &evidence)?; audit_dependencies(&root, &evidence)?;
println!("dependency policy: ok ({})", evidence.display()); println!("dependency policy: ok ({})", evidence.display());
} }
Some("documentation-report") if arguments.next().is_none() => {
write_documentation_report(&root)?;
println!("documentation coverage report: updated");
}
Some("documentation-audit") => {
let flag = arguments
.next()
.ok_or("documentation-audit requires --evidence FILE")?;
let evidence = arguments
.next()
.ok_or("documentation-audit requires --evidence FILE")?;
if flag != "--evidence" || arguments.next().is_some() {
return Err("usage: ci-matrix documentation-audit --evidence FILE".into());
}
let evidence = absolute_or_rooted(&root, &evidence);
audit_documentation(&root, &evidence)?;
println!("documentation coverage: ok ({})", evidence.display());
}
_ => { _ => {
return Err( return Err(
"usage: ci-matrix audit | run PROFILE --evidence FILE | dependency-audit --evidence FILE" "usage: ci-matrix audit | run PROFILE --evidence FILE | dependency-audit --evidence FILE | documentation-report | documentation-audit --evidence FILE"
.into(), .into(),
); );
} }

View File

@@ -3205,6 +3205,11 @@ VALUE_DERIVES = {
"T:LibreMetaverse.WorldSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.WorldSettings": "Clone, Debug, Eq, PartialEq",
} }
PINNED_CSHARP_SOURCE = (
"https://github.com/cinderblocks/libremetaverse/tree/"
"2aa70bb68513b39795da5d13c88f31b86e85a3ba"
)
# Native implementations occasionally need Rust-only bounds that have no # Native implementations occasionally need Rust-only bounds that have no
# direct C# metadata equivalent. C#'s `where T : class` permits returning a # direct C# metadata equivalent. C#'s `where T : class` permits returning a
# reference to the stored object; the Rust inventory store returns an owned # reference to the stored object; the Rust inventory store returns an owned
@@ -3385,10 +3390,29 @@ def duplicate_enum_values(item: dict) -> bool:
return len(values) != len(set(values)) return len(values) != len(set(values))
def render_enum(item: dict, rust_name: str) -> list[str]: def render_type_docs(item: dict, type_row: dict) -> list[str]:
return [
f"/// C# type: `{item['doc_id']}`.",
"///",
f"/// Native Rust mapping of C# `{item['signature']}` using decision",
f"/// `{type_row['mapping_decision']}`. See the [pinned C# source]({PINNED_CSHARP_SOURCE}).",
]
def render_member_docs(row: dict[str, str], indent: str = "") -> list[str]:
return [
f"{indent}/// C# member: `{row['csharp_id']}`.",
f"{indent}///",
f"{indent}/// C# signature: `{row['csharp_signature']}`.",
f"{indent}/// Mapping contract: ownership `{row['ownership']}`; async `{row['asyncness']}`;",
f"{indent}/// errors `{row['error_model']}`; overload `{row['overload_decision']}`; kind `{row['mapping_kind']}`.",
]
def render_enum(item: dict, type_row: dict, rust_name: str, member_rows: dict[str, dict[str, str]]) -> list[str]:
underlying = UNDERLYING[item["enum_underlying_type"]] underlying = UNDERLYING[item["enum_underlying_type"]]
values = [member for member in item["members"] if member["kind"] == "enum_value"] values = [member for member in item["members"] if member["kind"] == "enum_value"]
lines = [f"/// C# type: `{item['doc_id']}`."] lines = render_type_docs(item, type_row)
if item["doc_id"] in OPEN_ENUM_IDS: if item["doc_id"] in OPEN_ENUM_IDS:
lines += [ lines += [
"#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]", "#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]",
@@ -3398,10 +3422,10 @@ def render_enum(item: dict, rust_name: str) -> list[str]:
f"impl {rust_name} {{", f"impl {rust_name} {{",
] ]
for member in values: for member in values:
lines += [ lines += render_member_docs(member_rows[member["doc_id"]], " ")
f" /// C# member: `{member['doc_id']}`.", lines.append(
f" pub const {mapping.pascal(member['name'])}: Self = Self({member['value']['value']});", f" pub const {mapping.pascal(member['name'])}: Self = Self({member['value']['value']});"
] )
lines.append("}") lines.append("}")
return lines return lines
if flags_enum(item) or duplicate_enum_values(item): if flags_enum(item) or duplicate_enum_values(item):
@@ -3414,7 +3438,8 @@ def render_enum(item: dict, rust_name: str) -> list[str]:
for member in values: for member in values:
name = mapping.snake(member["name"]).upper() name = mapping.snake(member["name"]).upper()
value = member["value"]["value"] value = member["value"]["value"]
lines += [f" /// C# member: `{member['doc_id']}`.", f" pub const {name}: Self = Self({value});"] lines += render_member_docs(member_rows[member["doc_id"]], " ")
lines.append(f" pub const {name}: Self = Self({value});")
lines.append("}") lines.append("}")
return lines return lines
lines += [ lines += [
@@ -3423,7 +3448,8 @@ def render_enum(item: dict, rust_name: str) -> list[str]:
f"pub enum {rust_name} {{", f"pub enum {rust_name} {{",
] ]
for member in values: for member in values:
lines += [f" /// C# member: `{member['doc_id']}`.", f" {mapping.pascal(member['name'])} = {member['value']['value']},"] lines += render_member_docs(member_rows[member["doc_id"]], " ")
lines.append(f" {mapping.pascal(member['name'])} = {member['value']['value']},")
lines.append("}") lines.append("}")
return lines return lines
@@ -3593,16 +3619,17 @@ def rust_constant_value(item: dict) -> str:
def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str: def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str:
rust_name = type_name(type_row) rust_name = type_name(type_row)
if native_path := NATIVE_TYPES.get(item["doc_id"]): if native_path := NATIVE_TYPES.get(item["doc_id"]):
lines = [f"/// C# type: `{item['doc_id']}`."] lines = render_type_docs(item, type_row)
lines.extend(f"/// C# member: `{member['doc_id']}`." for member in item["members"]) for member in item["members"]:
lines.extend(render_member_docs(member_rows[member["doc_id"]]))
lines.append(f"pub use {native_path} as {rust_name};") lines.append(f"pub use {native_path} as {rust_name};")
return "\n".join(lines) return "\n".join(lines)
if item["kind"] == "enum": if item["kind"] == "enum":
return "\n".join(render_enum(item, rust_name)) return "\n".join(render_enum(item, type_row, rust_name, member_rows))
names = generic_names(item) names = generic_names(item)
suffix = generic_suffix(names) suffix = generic_suffix(names)
trait = item["kind"] == "interface" trait = item["kind"] == "interface"
lines = [f"/// C# type: `{item['doc_id']}`."] lines = render_type_docs(item, type_row)
if trait: if trait:
supertrait = TRAIT_SUPERTRAITS.get(item["doc_id"]) supertrait = TRAIT_SUPERTRAITS.get(item["doc_id"])
inheritance = f": {supertrait}" if supertrait else "" inheritance = f": {supertrait}" if supertrait else ""
@@ -3614,10 +3641,8 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
native_declaration = NATIVE_DECLARATIONS.get(item["doc_id"]) native_declaration = NATIVE_DECLARATIONS.get(item["doc_id"])
if native_declaration: if native_declaration:
lines.append(f"pub use {native_declaration} as {rust_name};") lines.append(f"pub use {native_declaration} as {rust_name};")
lines.extend( for field in fields:
f"/// C# member: `{field['doc_id']}`." lines.extend(render_member_docs(member_rows[field["doc_id"]]))
for field in fields
)
elif derives := VALUE_DERIVES.get(item["doc_id"]): elif derives := VALUE_DERIVES.get(item["doc_id"]):
lines.append(f"#[derive({derives})]") lines.append(f"#[derive({derives})]")
if native_declaration: if native_declaration:
@@ -3648,7 +3673,8 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
raise ValueError(f"invalid mapped field signature: {field_signature}") raise ValueError(f"invalid mapped field signature: {field_signature}")
field_name = field_name_match.group(1) field_name = field_name_match.group(1)
field_type = field_name_match.group(2) field_type = field_name_match.group(2)
lines += [f" /// C# member: `{field['doc_id']}`.", f" pub {field_name}: {field_type},"] lines += render_member_docs(member_rows[field["doc_id"]], " ")
lines.append(f" pub {field_name}: {field_type},")
for field_name, field_type in private_fields: for field_name, field_type in private_fields:
lines.append(f" {field_name}: {field_type},") lines.append(f" {field_name}: {field_type},")
if names: if names:
@@ -3661,7 +3687,7 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
if member["kind"] in {"enum_value"} or (member["kind"] == "field" and not member.get("static")): if member["kind"] in {"enum_value"} or (member["kind"] == "field" and not member.get("static")):
continue continue
row = member_rows[member["doc_id"]] row = member_rows[member["doc_id"]]
lines.append(f" /// C# member: `{member['doc_id']}`.") lines.extend(render_member_docs(row, " "))
if member["kind"] == "constant": if member["kind"] == "constant":
lines.append(f" {row['rust_signature']} = {rust_constant_value(member)};") lines.append(f" {row['rust_signature']} = {rust_constant_value(member)};")
continue continue