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
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:
88
.gitea/workflows/documentation.yml
Normal file
88
.gitea/workflows/documentation.yml
Normal 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
|
||||
33
README.md
33
README.md
@@ -1,20 +1,23 @@
|
||||
# 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
|
||||
[LibreMetaverse](https://github.com/cinderblocks/libremetaverse). LibreMetaverse
|
||||
is a behavioral and API reference only: the finished project must not load,
|
||||
host, bind to, invoke, or ship the .NET implementation. The current
|
||||
stage is a compiling structural shell: crate boundaries mirror the .NET library
|
||||
projects, public C# type names have Rust declarations, every upstream NUnit
|
||||
invocation has a traceable catalog entry, and every sample/tool project has a
|
||||
Rust binary target. Types, StructuredData, Imaging, PrimMesher, the MeshFoundry rendering adapter, and
|
||||
the main assembly's packet/message/asset/primitive wire-data, core
|
||||
runtime/networking, avatar-facing manager, world/social/service manager, RLV,
|
||||
LSL tools, Utilities, Vivox, and WebRTC slices now have complete callable,
|
||||
failure-only signatures. The independent downstream fixture compiles every
|
||||
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.
|
||||
host, bind to, invoke, or ship the .NET implementation. The project is in
|
||||
release hardening: crate boundaries mirror the .NET libraries, every mapped
|
||||
public type and member has a documented native Rust destination, and every
|
||||
sample/tool project has a tested Rust binary target. Types, structured data,
|
||||
imaging, meshing/rendering, packet and message codecs, client/network runtime,
|
||||
avatar/world/social/service managers, RLV, LSL tools, utilities, Vivox, and
|
||||
WebRTC are implemented without a CLR bridge. The independent downstream
|
||||
fixture compiles every cataloged type and member with zero exclusions, and all
|
||||
1,289 upstream NUnit invocations have reviewed Rust parity cases.
|
||||
|
||||
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
|
||||
@@ -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
|
||||
[`api/RUST-MAPPING.tsv`](api/RUST-MAPPING.tsv); [`api/README.md`](api/README.md)
|
||||
documents deterministic regeneration and validation.
|
||||
All API crates expose the same typed `Error` for fallible unimplemented members;
|
||||
infallible shims use `libremetaverse_types::unimplemented_api!` so placeholders
|
||||
cannot look like successful behavior.
|
||||
API mappings record ownership, async, error, overload, and native implementation
|
||||
decisions, and the generated rustdoc keeps those contracts attached to their
|
||||
exact C# documentation IDs.
|
||||
|
||||
Building MetaCrate requires Rust 1.96 or newer.
|
||||
|
||||
|
||||
14
api/DOCUMENTATION-COVERAGE.md
Normal file
14
api/DOCUMENTATION-COVERAGE.md
Normal 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).
|
||||
14
ci/evidence/documentation-audit.json
Normal file
14
ci/evidence/documentation-audit.json
Normal 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"
|
||||
}
|
||||
@@ -6,7 +6,22 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// 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# 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# 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# 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -6,30 +6,119 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// 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# 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;
|
||||
|
||||
/// 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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# 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;
|
||||
|
||||
/// 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# 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# 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# 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# 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
@@ -6,13 +6,24 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// 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;
|
||||
impl MeshFoundry {
|
||||
/// 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> {
|
||||
Self::native_new()
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -21,6 +32,10 @@ impl MeshFoundry {
|
||||
self.native_generate_faceted_mesh(prim, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -33,6 +48,10 @@ impl MeshFoundry {
|
||||
)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -42,6 +61,10 @@ impl MeshFoundry {
|
||||
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# 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -51,6 +74,10 @@ impl MeshFoundry {
|
||||
self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -59,6 +86,10 @@ impl MeshFoundry {
|
||||
self.native_generate_simple_mesh(prim, lod, false)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -67,6 +98,10 @@ impl MeshFoundry {
|
||||
self.native_generate_simple_mesh(prim, lod, true)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -76,6 +111,10 @@ impl MeshFoundry {
|
||||
self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -84,6 +123,10 @@ impl MeshFoundry {
|
||||
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# 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -93,6 +136,10 @@ impl MeshFoundry {
|
||||
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# 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -101,6 +148,10 @@ impl MeshFoundry {
|
||||
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# 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(
|
||||
&self,
|
||||
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)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
vertices: &mut Vec<libremetaverse::rendering::Vertex>,
|
||||
@@ -122,6 +177,10 @@ impl MeshFoundry {
|
||||
self.native_transform_tex_coords(vertices, center, te_face, prim_scale)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
asset_data: Vec<u8>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -6,13 +6,24 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// 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;
|
||||
impl SimpleRenderer {
|
||||
/// 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> {
|
||||
Self::native_new()
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -21,6 +32,10 @@ impl SimpleRenderer {
|
||||
self.native_generate_faceted_mesh(prim, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -30,6 +45,10 @@ impl SimpleRenderer {
|
||||
self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -38,6 +57,10 @@ impl SimpleRenderer {
|
||||
self.native_generate_simple_mesh(prim, lod)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
prim: libremetaverse::Primitive,
|
||||
@@ -47,6 +70,10 @@ impl SimpleRenderer {
|
||||
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# 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(
|
||||
&self,
|
||||
vertices: &mut Vec<libremetaverse::rendering::Vertex>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Deterministic reference rendering for legacy prims and decoded sculpt maps.
|
||||
//!
|
||||
//! [`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.
|
||||
|
||||
extern crate self as libremetaverse_rendering_simple;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
@@ -6,9 +6,16 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// 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;
|
||||
impl ConnectionManager {
|
||||
/// 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(
|
||||
client: libremetaverse::GridClient,
|
||||
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# 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(
|
||||
client: libremetaverse::GridClient,
|
||||
first_name: String,
|
||||
@@ -32,6 +43,10 @@ impl ConnectionManager {
|
||||
)
|
||||
}
|
||||
/// 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(
|
||||
&self,
|
||||
handle: u64,
|
||||
@@ -42,15 +57,26 @@ impl ConnectionManager {
|
||||
)
|
||||
}
|
||||
/// 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> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Utilities.ConnectionManager.Stop")
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
impl Realism {
|
||||
/// 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(
|
||||
client: libremetaverse::GridClient,
|
||||
message: String,
|
||||
@@ -60,6 +86,10 @@ impl Realism {
|
||||
)
|
||||
}
|
||||
/// 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(
|
||||
client: libremetaverse::GridClient,
|
||||
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# 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(
|
||||
client: libremetaverse::GridClient,
|
||||
message: String,
|
||||
@@ -83,6 +117,10 @@ impl Realism {
|
||||
)
|
||||
}
|
||||
/// 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(
|
||||
client: libremetaverse::GridClient,
|
||||
) -> Result<bool, crate::Error> {
|
||||
@@ -91,6 +129,10 @@ impl Realism {
|
||||
)
|
||||
}
|
||||
/// 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(
|
||||
client: libremetaverse::GridClient,
|
||||
target: libremetaverse_types::Vector3,
|
||||
@@ -102,15 +144,34 @@ impl Realism {
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
#[repr(i32)]
|
||||
pub enum WaterType {
|
||||
/// 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,
|
||||
/// 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,
|
||||
/// 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,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
14
crates/libremetaverse/examples/cancellation.rs
Normal file
14
crates/libremetaverse/examples/cancellation.rs
Normal 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(())
|
||||
}
|
||||
10
crates/libremetaverse/examples/offline_client.rs
Normal file
10
crates/libremetaverse/examples/offline_client.rs
Normal 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
@@ -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;
|
||||
|
||||
|
||||
267
docs/rust-api-guide.md
Normal file
267
docs/rust-api-guide.md
Normal 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.
|
||||
606
tools/ci-matrix/src/documentation.rs
Normal file
606
tools/ci-matrix/src/documentation.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,10 @@ use std::process::{Command, ExitStatus, Stdio};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
mod dependency;
|
||||
mod documentation;
|
||||
|
||||
pub use dependency::audit_dependencies;
|
||||
pub use documentation::{audit_documentation, write_documentation_report};
|
||||
|
||||
pub const MATRIX_PATH: &str = "ci/release-matrix.json";
|
||||
const WORKFLOW_PATH: &str = ".gitea/workflows/release-matrix.yml";
|
||||
|
||||
@@ -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};
|
||||
|
||||
fn main() {
|
||||
@@ -46,9 +49,27 @@ fn execute() -> Result<(), Box<dyn std::error::Error>> {
|
||||
audit_dependencies(&root, &evidence)?;
|
||||
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(
|
||||
"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(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3205,6 +3205,11 @@ VALUE_DERIVES = {
|
||||
"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
|
||||
# direct C# metadata equivalent. C#'s `where T : class` permits returning a
|
||||
# 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))
|
||||
|
||||
|
||||
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"]]
|
||||
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:
|
||||
lines += [
|
||||
"#[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} {{",
|
||||
]
|
||||
for member in values:
|
||||
lines += [
|
||||
f" /// C# member: `{member['doc_id']}`.",
|
||||
f" pub const {mapping.pascal(member['name'])}: Self = Self({member['value']['value']});",
|
||||
]
|
||||
lines += render_member_docs(member_rows[member["doc_id"]], " ")
|
||||
lines.append(
|
||||
f" pub const {mapping.pascal(member['name'])}: Self = Self({member['value']['value']});"
|
||||
)
|
||||
lines.append("}")
|
||||
return lines
|
||||
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:
|
||||
name = mapping.snake(member["name"]).upper()
|
||||
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("}")
|
||||
return lines
|
||||
lines += [
|
||||
@@ -3423,7 +3448,8 @@ def render_enum(item: dict, rust_name: str) -> list[str]:
|
||||
f"pub enum {rust_name} {{",
|
||||
]
|
||||
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("}")
|
||||
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:
|
||||
rust_name = type_name(type_row)
|
||||
if native_path := NATIVE_TYPES.get(item["doc_id"]):
|
||||
lines = [f"/// C# type: `{item['doc_id']}`."]
|
||||
lines.extend(f"/// C# member: `{member['doc_id']}`." for member in item["members"])
|
||||
lines = render_type_docs(item, type_row)
|
||||
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};")
|
||||
return "\n".join(lines)
|
||||
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)
|
||||
suffix = generic_suffix(names)
|
||||
trait = item["kind"] == "interface"
|
||||
lines = [f"/// C# type: `{item['doc_id']}`."]
|
||||
lines = render_type_docs(item, type_row)
|
||||
if trait:
|
||||
supertrait = TRAIT_SUPERTRAITS.get(item["doc_id"])
|
||||
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"])
|
||||
if native_declaration:
|
||||
lines.append(f"pub use {native_declaration} as {rust_name};")
|
||||
lines.extend(
|
||||
f"/// C# member: `{field['doc_id']}`."
|
||||
for field in fields
|
||||
)
|
||||
for field in fields:
|
||||
lines.extend(render_member_docs(member_rows[field["doc_id"]]))
|
||||
elif derives := VALUE_DERIVES.get(item["doc_id"]):
|
||||
lines.append(f"#[derive({derives})]")
|
||||
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}")
|
||||
field_name = field_name_match.group(1)
|
||||
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:
|
||||
lines.append(f" {field_name}: {field_type},")
|
||||
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")):
|
||||
continue
|
||||
row = member_rows[member["doc_id"]]
|
||||
lines.append(f" /// C# member: `{member['doc_id']}`.")
|
||||
lines.extend(render_member_docs(row, " "))
|
||||
if member["kind"] == "constant":
|
||||
lines.append(f" {row['rust_signature']} = {rust_constant_value(member)};")
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user