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

269 lines
12 KiB
Markdown

# 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 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](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`;
- 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.
```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.