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 |
metacrate-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:
OpenJPEG2.5.4 or newer forjpeg2000;- no native prerequisite for the default pure-Rust
rust-j2kandrust-skiafeatures; - the rust-skia prerequisites/cache for
skia; - system libopus for WebRTC voice and the
libremetaverse-opusadapter; - ALSA development headers on Linux,
CoreAudioon macOS, or WASAPI on Windows whenreal-audioenables 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:
osd-inspectorsimple-botpacket-dumpprim-inspectorinventory-explorerirc-gatewaytest-clientvivox-testwebrtc-test
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.