Files
MetaCrate/docs/rust-api-guide.md
Chili Palmer 25fdffbd3b
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m51s
CI / required (push) Failing after 3m23s
feat(imaging): default to pure-Rust codecs
2026-08-13 11:24:38 +00:00

12 KiB

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; it is never loaded, invoked, or shipped. Exact type/member mappings live in RUST-TYPES.tsv and 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 and J2K/JP2; optional native 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 Default pure-Rust rust-j2k; optional native jpeg2000
libremetaverse-imaging-skia Common raster formats through native Skia or pure Rust Default pure-Rust rust-skia; optional skia native 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 the pure-Rust defaults for normal applications, and enable a native adapter only when it is specifically needed. The release CI matrix 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.

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.

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. 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.

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. 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.

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.

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 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.

Native prerequisites

The default client build needs no image-codec system library. Optional native features require:

  • OpenJPEG 2.5.4 or newer for jpeg2000;
  • no native prerequisite for the default pure-Rust rust-j2k and rust-skia features;
  • 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.

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:

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. 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:

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:

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 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.