diff --git a/.gitea/workflows/rust-workspace.yml b/.gitea/workflows/rust-workspace.yml
index a3dd63d..2967de2 100644
--- a/.gitea/workflows/rust-workspace.yml
+++ b/.gitea/workflows/rust-workspace.yml
@@ -10,6 +10,7 @@ on:
- "tools/check_milestone_10_issue_76.py"
- "tools/check_milestone_10_issue_77.py"
- "tools/check_milestone_10_issue_78.py"
+ - "tools/check_milestone_10_issue_79.py"
- "**/*.rs"
- "**/Cargo.toml"
- "Cargo.lock"
@@ -22,6 +23,7 @@ on:
- "tools/check_milestone_10_issue_76.py"
- "tools/check_milestone_10_issue_77.py"
- "tools/check_milestone_10_issue_78.py"
+ - "tools/check_milestone_10_issue_79.py"
- "**/*.rs"
- "**/Cargo.toml"
- "Cargo.lock"
@@ -60,6 +62,7 @@ jobs:
python3 tools/check_milestone_10_issue_76.py
python3 tools/check_milestone_10_issue_77.py
python3 tools/check_milestone_10_issue_78.py
+ python3 tools/check_milestone_10_issue_79.py
- name: Test the complete native world milestone
run: python3 tools/test_milestone_09.py
- name: Compile every workspace target with bounded memory
diff --git a/README.md b/README.md
index 51bfddb..3f8a40d 100644
--- a/README.md
+++ b/README.md
@@ -463,14 +463,17 @@ reports decode allocations and timing without replacing correctness gates. The
format, ownership, error, and tangent-output contracts are documented in the
[`MeshFoundry` guide](crates/libremetaverse-rendering-mesh-foundry/README.md).
-The native RLV protocol layer parses bounded chat messages into typed clear,
-action, restriction, and query directives without changing client state. It
+The native RLV layer parses bounded chat messages into typed clear, action,
+restriction, and query directives, then provides independent thread-safe
+restriction, inventory-map, folder-lock, camera, blacklist, and permission
+providers without network I/O. It
preserves source casing, folder paths, separators, numeric channels, UUIDs, and
attachment/wearable aliases while exposing canonical behavior names and
source-located typed errors. All 119 restriction names from the pinned
LibreMetaverse snapshot are covered, and deterministic malformed-input
-mutations exercise the parser without an external fuzzing runtime. State,
-camera, inventory, callback, and transport effects remain explicit consumers
-of this pure layer. The grammar, normalization rules, limits, and ownership
-boundary are documented in the
+mutations exercise the parser without an external fuzzing runtime. Immutable
+snapshots and post-lock update events make concurrent policy reads
+deterministic; callback execution and transport effects remain explicit
+consumers of the layer. The grammar, state precedence, normalization rules,
+limits, and ownership boundary are documented in the
[`RLV protocol guide`](crates/libremetaverse-rlv/README.md).
diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md
index 9713bbe..fa89c46 100644
--- a/api/SHIM-COVERAGE.md
+++ b/api/SHIM-COVERAGE.md
@@ -9,7 +9,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
| `LibreMetaverse.PrimMesher` | 17 | 207 | native implementation: 15 types / 200 members; remaining surface is callable failure-only shims |
-| `LibreMetaverse.RLV` | 28 | 499 | native implementation: 2 types / 11 members; remaining surface is callable failure-only shims |
+| `LibreMetaverse.RLV` | 28 | 499 | native implementation: 12 types / 147 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | native implementation: 1 type / 14 members; no generated shims remain |
| `LibreMetaverse.Rendering.Simple` | 1 | 6 | native implementation: 1 type / 6 members; no generated shims remain |
| `LibreMetaverse.StructuredData` | 16 | 295 | native implementation: 16 types / 295 members; no generated shims remain |
diff --git a/crates/libremetaverse-rlv/Cargo.toml b/crates/libremetaverse-rlv/Cargo.toml
index ed2ee15..9d6f093 100644
--- a/crates/libremetaverse-rlv/Cargo.toml
+++ b/crates/libremetaverse-rlv/Cargo.toml
@@ -5,7 +5,7 @@ edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
-description = "Bounded native RLV protocol parsing and compatibility types for MetaCrate"
+description = "Bounded native RLV protocol, state, inventory, lock, camera, and permission layer"
[dependencies]
libremetaverse-types = { path = "../libremetaverse-types" }
diff --git a/crates/libremetaverse-rlv/README.md b/crates/libremetaverse-rlv/README.md
index 8d9b141..c9d6e02 100644
--- a/crates/libremetaverse-rlv/README.md
+++ b/crates/libremetaverse-rlv/README.md
@@ -1,11 +1,13 @@
# Native RLV protocol layer
-`libremetaverse-rlv` implements the side-effect-free protocol boundary for the
+`libremetaverse-rlv` implements the protocol and pure state boundary for the
Restrained Love Viewer support in the pinned LibreMetaverse snapshot. The
-parser converts one bounded chat message into typed commands. It does not
-mutate restrictions, move the camera, touch inventory, send replies, or invoke
-callbacks; those stateful responsibilities are separate milestone work that
-consumes this layer.
+side-effect-free parser converts one bounded chat message into typed commands.
+Independent, thread-safe managers then store restrictions and evaluate
+inventory, folder locks, camera limits, blacklists, and permissions without
+performing network I/O. Command execution, viewer callbacks, replies, and
+`GridClient` integration remain separate milestone work that consumes this
+layer.
## Message contract
@@ -57,6 +59,33 @@ insertion mutations of actions, restrictions, queries, UUIDs, aliases, and
multi-command messages. Every mutation must return a value or a positioned
error without panicking. The limits apply before large parser allocations.
+## State, inventory, and permission contract
+
+`RlvSharedFolder` and `RlvInventoryItem` preserve the reference semantics of
+their C# counterparts. `InventoryMap` walks a bounded shared-inventory tree and
+publishes immutable dictionary/list membership snapshots. Path lookup handles
+hidden/private prefixes, exact names containing forward slashes, and the
+reference implementation's longest matching segment rule. Lookup by item,
+attached prim, attachment point, and wearable type does not call a client or
+inventory service.
+
+`RlvRestrictionManager` deduplicates exact restrictions, retains deterministic
+insertion order, removes all state for selected object sources, and rebuilds
+immutable locked-folder snapshots when either restrictions or inventory
+changes. Recursive and non-recursive attach/detach locks support sender-item,
+attachment, wearable, and path targets plus their exception variants. Update
+handlers run only after manager locks are released, so callbacks can safely
+query the manager again. Poisoned synchronization primitives recover their
+owned state instead of making later reads fail.
+
+`RlvPermissionsService` evaluates simple restrictions, secure and explicit
+target rules, permissive exception precedence, IM/chat/channel behavior,
+teleport limits, edit/touch/hover decisions, shared and unshared wear, and
+folder attachment locks. Camera aggregation applies the reference min/max,
+clamping, averaging, alias, texture, and lock rules. The case-insensitive
+blacklist returns a sorted snapshot. These providers are deterministic,
+thread-safe, and contain no network or callback dependencies.
+
## Reproducible verification
Run the issue-owned checks with one build job:
@@ -64,9 +93,11 @@ Run the issue-owned checks with one build job:
```sh
CARGO_BUILD_JOBS=1 cargo test -p libremetaverse-rlv --locked
CARGO_BUILD_JOBS=1 cargo test -p libremetaverse-compat-tests --test rlv_common_semantics --locked
+CARGO_BUILD_JOBS=1 cargo test -p libremetaverse-compat-tests --test rlv_inventory_map_semantics --locked
CARGO_BUILD_JOBS=1 cargo clippy -p libremetaverse-rlv --all-targets --locked -- -D warnings
RUSTDOCFLAGS='-D warnings' CARGO_BUILD_JOBS=1 cargo doc -p libremetaverse-rlv --no-deps --locked
python3 tools/check_milestone_10_issue_78.py
+python3 tools/check_milestone_10_issue_79.py
```
These commands are cross-platform. The Gitea workflow runs the audit and
diff --git a/crates/libremetaverse-rlv/src/generated.rs b/crates/libremetaverse-rlv/src/generated.rs
index d1bfa71..bda600c 100644
--- a/crates/libremetaverse-rlv/src/generated.rs
+++ b/crates/libremetaverse-rlv/src/generated.rs
@@ -49,78 +49,68 @@ impl AttachmentRequest {
}
/// C# type: `T:LibreMetaverse.RLV.CameraRestrictions`.
-pub struct CameraRestrictions;
+pub use crate::state::CameraRestrictions;
impl CameraRestrictions {
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.AvDist`.
pub fn av_dist(&self) -> Option> {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.AvDist")
+ self.native_av_dist()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.AvDistMax`.
pub fn av_dist_max(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.CameraRestrictions.AvDistMax"
- )
+ self.native_av_dist_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.AvDistMin`.
pub fn av_dist_min(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.CameraRestrictions.AvDistMin"
- )
+ self.native_av_dist_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.DrawAlphaMax`.
pub fn draw_alpha_max(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.CameraRestrictions.DrawAlphaMax"
- )
+ self.native_draw_alpha_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.DrawAlphaMin`.
pub fn draw_alpha_min(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.CameraRestrictions.DrawAlphaMin"
- )
+ self.native_draw_alpha_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.DrawColor`.
pub fn draw_color(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.CameraRestrictions.DrawColor"
- )
+ self.native_draw_color()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.DrawMax`.
pub fn draw_max(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.DrawMax")
+ self.native_draw_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.DrawMin`.
pub fn draw_min(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.DrawMin")
+ self.native_draw_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.FovMax`.
pub fn fov_max(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.FovMax")
+ self.native_fov_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.FovMin`.
pub fn fov_min(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.FovMin")
+ self.native_fov_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.IsLocked`.
pub fn is_locked(&self) -> bool {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.IsLocked")
+ self.native_is_locked()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.Texture`.
pub fn texture(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.Texture")
+ self.native_texture()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.ZoomMax`.
pub fn zoom_max(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.ZoomMax")
+ self.native_zoom_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraRestrictions.ZoomMin`.
pub fn zoom_min(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraRestrictions.ZoomMin")
+ self.native_zoom_min()
}
}
/// C# type: `T:LibreMetaverse.RLV.CameraSettings`.
-pub struct CameraSettings;
+pub use crate::state::CameraSettings;
impl CameraSettings {
/// C# member: `M:LibreMetaverse.RLV.CameraSettings.#ctor(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)`.
pub fn new(
@@ -131,33 +121,38 @@ impl CameraSettings {
zoom_min: f32,
current_fov: f32,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.CameraSettings.#ctor(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)",
+ Self::native_new(
+ av_dist_min,
+ av_dist_max,
+ fov_min,
+ fov_max,
+ zoom_min,
+ current_fov,
)
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.AvDistMax`.
pub fn av_dist_max(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.AvDistMax")
+ self.native_av_dist_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.AvDistMin`.
pub fn av_dist_min(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.AvDistMin")
+ self.native_av_dist_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.CurrentFov`.
pub fn current_fov(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.CurrentFov")
+ self.native_current_fov()
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.FovMax`.
pub fn fov_max(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.FovMax")
+ self.native_fov_max()
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.FovMin`.
pub fn fov_min(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.FovMin")
+ self.native_fov_min()
}
/// C# member: `P:LibreMetaverse.RLV.CameraSettings.ZoomMin`.
pub fn zoom_min(&self) -> f32 {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.CameraSettings.ZoomMin")
+ self.native_zoom_min()
}
}
@@ -511,16 +506,14 @@ pub trait IRlvQueryCallbacks {
}
/// C# type: `T:LibreMetaverse.RLV.InventoryMap`.
-pub struct InventoryMap;
+pub use crate::state::InventoryMap;
impl InventoryMap {
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.#ctor(LibreMetaverse.RLV.RlvSharedFolder,System.Collections.Generic.IReadOnlyCollection{LibreMetaverse.RLV.RlvInventoryItem})`.
pub fn new(
root: libremetaverse_rlv::RlvSharedFolder,
external_items: Vec,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.#ctor(LibreMetaverse.RLV.RlvSharedFolder,System.Collections.Generic.IReadOnlyCollection{LibreMetaverse.RLV.RlvInventoryItem})",
- )
+ Self::native_new(root, external_items)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.FindFoldersContaining(System.Boolean,System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})`.
pub fn find_folders_containing(
@@ -530,8 +523,11 @@ impl InventoryMap {
attachment_point: Option>,
wearable_type: Option >,
) -> Result>, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.FindFoldersContaining(System.Boolean,System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})",
+ self.native_find_folders_containing(
+ limit_to_one_result,
+ attached_prim_id,
+ attachment_point,
+ wearable_type,
)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.GetItemsByAttachmentPoint(LibreMetaverse.RLV.RlvAttachmentPoint)`.
@@ -539,36 +535,28 @@ impl InventoryMap {
&self,
attachment_point: libremetaverse_rlv::RlvAttachmentPoint,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.GetItemsByAttachmentPoint(LibreMetaverse.RLV.RlvAttachmentPoint)",
- )
+ self.native_get_items_by_attachment_point(attachment_point)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.GetItemsById(System.Guid)`.
pub fn get_items_by_id(
&self,
item_id: libremetaverse_types::compat::Guid,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.GetItemsById(System.Guid)",
- )
+ self.native_get_items_by_id(item_id)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.GetItemsByPrimId(System.Guid)`.
pub fn get_items_by_prim_id(
&self,
prim_id: libremetaverse_types::compat::Guid,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.GetItemsByPrimId(System.Guid)",
- )
+ self.native_get_items_by_prim_id(prim_id)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.GetItemsByWearableType(LibreMetaverse.RLV.RlvWearableType)`.
pub fn get_items_by_wearable_type(
&self,
wearable_type: libremetaverse_rlv::RlvWearableType,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.InventoryMap.GetItemsByWearableType(LibreMetaverse.RLV.RlvWearableType)",
- )
+ self.native_get_items_by_wearable_type(wearable_type)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.TryBuildPathToFolder(System.Guid,System.String@)`.
pub fn try_build_path_to_folder(
@@ -576,9 +564,7 @@ impl InventoryMap {
folder_id: libremetaverse_types::compat::Guid,
final_path: &mut Option,
) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.InventoryMap.TryBuildPathToFolder(System.Guid,System.String@)"
- )
+ self.native_try_build_path_to_folder(folder_id, final_path)
}
/// C# member: `M:LibreMetaverse.RLV.InventoryMap.TryGetFolderFromPath(System.String,System.Boolean,LibreMetaverse.RLV.RlvSharedFolder@)`.
pub fn try_get_folder_from_path(
@@ -587,9 +573,7 @@ impl InventoryMap {
skip_private_folders: bool,
folder: &mut Option,
) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.InventoryMap.TryGetFolderFromPath(System.String,System.Boolean,LibreMetaverse.RLV.RlvSharedFolder@)"
- )
+ self.native_try_get_folder_from_path(path, skip_private_folders, folder)
}
/// C# member: `P:LibreMetaverse.RLV.InventoryMap.ExternalItems`.
pub fn external_items(
@@ -598,7 +582,7 @@ impl InventoryMap {
libremetaverse_types::compat::Guid,
libremetaverse_rlv::RlvInventoryItem,
> {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.InventoryMap.ExternalItems")
+ self.native_external_items()
}
/// C# member: `P:LibreMetaverse.RLV.InventoryMap.Folders`.
pub fn folders(
@@ -607,7 +591,7 @@ impl InventoryMap {
libremetaverse_types::compat::Guid,
libremetaverse_rlv::RlvSharedFolder,
> {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.InventoryMap.Folders")
+ self.native_folders()
}
/// C# member: `P:LibreMetaverse.RLV.InventoryMap.Items`.
pub fn items(
@@ -616,68 +600,56 @@ impl InventoryMap {
libremetaverse_types::compat::Guid,
libremetaverse_types::compat::ImmutableList,
> {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.InventoryMap.Items")
+ self.native_items()
}
/// C# member: `P:LibreMetaverse.RLV.InventoryMap.Root`.
pub fn root(&self) -> libremetaverse_rlv::RlvSharedFolder {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.InventoryMap.Root")
+ self.native_root()
}
}
/// C# type: `T:LibreMetaverse.RLV.LockedFolderPublic`.
-pub struct LockedFolderPublic;
+pub use crate::state::LockedFolderPublic;
impl LockedFolderPublic {
/// C# member: `M:LibreMetaverse.RLV.LockedFolderPublic.ToString`.
pub fn to_string(&self) -> String {
- libremetaverse_types::unimplemented_api!("M:LibreMetaverse.RLV.LockedFolderPublic.ToString")
+ self.native_to_string()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.AttachExceptions`.
pub fn attach_exceptions(&self) -> Vec {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.AttachExceptions"
- )
+ self.native_attach_exceptions()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.AttachRestrictions`.
pub fn attach_restrictions(&self) -> Vec {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.AttachRestrictions"
- )
+ self.native_attach_restrictions()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.CanAttach`.
pub fn can_attach(&self) -> bool {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.CanAttach"
- )
+ self.native_can_attach()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.CanDetach`.
pub fn can_detach(&self) -> bool {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.CanDetach"
- )
+ self.native_can_detach()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.DetachExceptions`.
pub fn detach_exceptions(&self) -> Vec {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.DetachExceptions"
- )
+ self.native_detach_exceptions()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.DetachRestrictions`.
pub fn detach_restrictions(&self) -> Vec {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.LockedFolderPublic.DetachRestrictions"
- )
+ self.native_detach_restrictions()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.Id`.
pub fn id(&self) -> libremetaverse_types::compat::Guid {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.LockedFolderPublic.Id")
+ self.native_id()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.IsLocked`.
pub fn is_locked(&self) -> bool {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.LockedFolderPublic.IsLocked")
+ self.native_is_locked()
}
/// C# member: `P:LibreMetaverse.RLV.LockedFolderPublic.Name`.
pub fn name(&self) -> String {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.LockedFolderPublic.Name")
+ self.native_name()
}
}
@@ -982,29 +954,23 @@ pub enum RlvAttachmentPoint {
}
/// C# type: `T:LibreMetaverse.RLV.RlvBlacklist`.
-pub struct RlvBlacklist;
+pub use crate::state::RlvBlacklist;
impl RlvBlacklist {
/// C# member: `M:LibreMetaverse.RLV.RlvBlacklist.BlacklistBehavior(System.String)`.
pub fn blacklist_behavior(&self, behavior: String) -> Result<(), crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvBlacklist.BlacklistBehavior(System.String)",
- )
+ self.native_blacklist_behavior(behavior)
}
/// C# member: `M:LibreMetaverse.RLV.RlvBlacklist.GetBlacklist`.
pub fn get_blacklist(&self) -> Result, crate::Error> {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvBlacklist.GetBlacklist")
+ self.native_get_blacklist()
}
/// C# member: `M:LibreMetaverse.RLV.RlvBlacklist.IsBlacklisted(System.String)`.
pub fn is_blacklisted(&self, behavior: String) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvBlacklist.IsBlacklisted(System.String)",
- )
+ self.native_is_blacklisted(behavior)
}
/// C# member: `M:LibreMetaverse.RLV.RlvBlacklist.UnBlacklistBehavior(System.String)`.
pub fn un_blacklist_behavior(&self, behavior: String) -> Result<(), crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvBlacklist.UnBlacklistBehavior(System.String)",
- )
+ self.native_un_blacklist_behavior(behavior)
}
}
@@ -1283,7 +1249,7 @@ pub enum RlvGetEnvType {
}
/// C# type: `T:LibreMetaverse.RLV.RlvInventoryItem`.
-pub struct RlvInventoryItem;
+pub use crate::state::RlvInventoryItem;
impl RlvInventoryItem {
/// C# member: `M:LibreMetaverse.RLV.RlvInventoryItem.#ctor(System.Guid,System.String,System.Boolean,System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvWearableType},System.Nullable{LibreMetaverse.RLV.RlvGestureState})`.
pub fn new(
@@ -1296,107 +1262,102 @@ impl RlvInventoryItem {
worn_on: Option>,
gesture_state: Option >,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvInventoryItem.#ctor(System.Guid,System.String,System.Boolean,System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvWearableType},System.Nullable{LibreMetaverse.RLV.RlvGestureState})",
+ Self::native_new(
+ id,
+ name,
+ is_link,
+ external_folder_id,
+ attached_to,
+ attached_prim_id,
+ worn_on,
+ gesture_state,
)
}
/// C# member: `M:LibreMetaverse.RLV.RlvInventoryItem.ToString`.
pub fn to_string(&self) -> String {
- libremetaverse_types::unimplemented_api!("M:LibreMetaverse.RLV.RlvInventoryItem.ToString")
+ self.native_to_string()
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.AttachedPrimId`.
pub fn attached_prim_id(&self) -> Option> {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.RlvInventoryItem.AttachedPrimId"
- )
+ self.native_attached_prim_id()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.AttachedPrimId`.
pub fn set_attached_prim_id(
&mut self,
value: Option >,
) {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.RlvInventoryItem.AttachedPrimId"
- )
+ self.native_set_attached_prim_id(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.AttachedTo`.
pub fn attached_to(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.AttachedTo")
+ self.native_attached_to()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.AttachedTo`.
pub fn set_attached_to(
&mut self,
value: Option >,
) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.AttachedTo")
+ self.native_set_attached_to(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.Folder`.
pub fn folder(&self) -> Option {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.Folder")
+ self.native_folder()
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.FolderId`.
pub fn folder_id(&self) -> Option> {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.FolderId")
+ self.native_folder_id()
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.GestureState`.
pub fn gesture_state(&self) -> Option > {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.RlvInventoryItem.GestureState"
- )
+ self.native_gesture_state()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.GestureState`.
pub fn set_gesture_state(
&mut self,
value: Option >,
) {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.RlvInventoryItem.GestureState"
- )
+ self.native_set_gesture_state(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.Id`.
pub fn id(&self) -> libremetaverse_types::compat::Guid {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.Id")
+ self.native_id()
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.IsLink`.
pub fn is_link(&self) -> bool {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.IsLink")
+ self.native_is_link()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.IsLink`.
pub fn set_is_link(&mut self, value: bool) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.IsLink")
+ self.native_set_is_link(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.Name`.
pub fn name(&self) -> String {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.Name")
+ self.native_name()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.Name`.
pub fn set_name(&mut self, value: String) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.Name")
+ self.native_set_name(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.WornOn`.
pub fn worn_on(&self) -> Option > {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.WornOn")
+ self.native_worn_on()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvInventoryItem.WornOn`.
pub fn set_worn_on(&mut self, value: Option >) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvInventoryItem.WornOn")
+ self.native_set_worn_on(value)
}
}
/// C# type: `T:LibreMetaverse.RLV.RlvPermissionsService`.
-pub struct RlvPermissionsService;
+pub use crate::state::RlvPermissionsService;
impl RlvPermissionsService {
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanAllowIdle`.
pub fn can_allow_idle(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanAllowIdle",
- )
+ self.native_can_allow_idle()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanAlwaysRun`.
pub fn can_always_run(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanAlwaysRun",
- )
+ self.native_can_always_run()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanAttach(LibreMetaverse.RLV.RlvInventoryItem,System.Boolean)`.
pub fn can_attach_with_rlv_inventory_item_boolean(
@@ -1404,9 +1365,7 @@ impl RlvPermissionsService {
item: libremetaverse_rlv::RlvInventoryItem,
is_shared: bool,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanAttach(LibreMetaverse.RLV.RlvInventoryItem,System.Boolean)",
- )
+ self.native_can_attach_with_item(item, is_shared)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanAttach(System.Nullable{System.Guid},System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})`.
pub fn can_attach_with_nullable_boolean_nullable_nullable(
@@ -1416,48 +1375,34 @@ impl RlvPermissionsService {
attachment_point: Option>,
wearable_type: Option >,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanAttach(System.Nullable{System.Guid},System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})",
- )
+ self.native_can_attach(object_folder_id, is_shared, attachment_point, wearable_type)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanChat(System.Int32,System.String)`.
pub fn can_chat(&self, channel: i32, message: String) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanChat(System.Int32,System.String)",
- )
+ self.native_can_chat(channel, message)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanChatNormal`.
pub fn can_chat_normal(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanChatNormal",
- )
+ self.native_can_chat_normal()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanChatShout`.
pub fn can_chat_shout(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanChatShout",
- )
+ self.native_can_chat_shout()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanChatWhisper`.
pub fn can_chat_whisper(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanChatWhisper",
- )
+ self.native_can_chat_whisper()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanDefaultWear`.
pub fn can_default_wear(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanDefaultWear",
- )
+ self.native_can_default_wear()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanDetach(LibreMetaverse.RLV.RlvInventoryItem)`.
pub fn can_detach_with_rlv_inventory_item(
&self,
item: libremetaverse_rlv::RlvInventoryItem,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanDetach(LibreMetaverse.RLV.RlvInventoryItem)",
- )
+ self.native_can_detach_with_item(item)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanDetach(System.Nullable{System.Guid},System.Nullable{System.Guid},System.Nullable{System.Guid},System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})`.
pub fn can_detach_with_nullable_nullable_nullable_boolean_nullable_nullable(
@@ -1469,8 +1414,13 @@ impl RlvPermissionsService {
attachment_point: Option>,
wearable_type: Option >,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanDetach(System.Nullable{System.Guid},System.Nullable{System.Guid},System.Nullable{System.Guid},System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{LibreMetaverse.RLV.RlvWearableType})",
+ self.native_can_detach(
+ item_id,
+ prim_id,
+ folder_id,
+ is_shared,
+ attachment_point,
+ wearable_type,
)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanEdit(LibreMetaverse.RLV.RlvPermissionsService.ObjectLocation,System.Nullable{System.Guid})`.
@@ -1479,27 +1429,23 @@ impl RlvPermissionsService {
object_location: libremetaverse_rlv::RlvPermissionsServiceObjectLocation,
object_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanEdit(LibreMetaverse.RLV.RlvPermissionsService.ObjectLocation,System.Nullable{System.Guid})",
- )
+ self.native_can_edit(object_location, object_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanEmote`.
pub fn can_emote(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanEmote")
+ self.native_can_emote()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanFly`.
pub fn can_fly(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanFly")
+ self.native_can_fly()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanInteract`.
pub fn can_interact(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanInteract",
- )
+ self.native_can_interact()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanJump`.
pub fn can_jump(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanJump")
+ self.native_can_jump()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanReceiveChat(System.String,System.Guid)`.
pub fn can_receive_chat(
@@ -1507,9 +1453,7 @@ impl RlvPermissionsService {
message: String,
user_id: libremetaverse_types::compat::Guid,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanReceiveChat(System.String,System.Guid)",
- )
+ self.native_can_receive_chat(message, user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanReceiveIM(System.String,System.Nullable{System.Guid},System.String)`.
pub fn can_receive_im(
@@ -1518,25 +1462,19 @@ impl RlvPermissionsService {
user_id: Option>,
group_name: Option,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanReceiveIM(System.String,System.Nullable{System.Guid},System.String)",
- )
+ self.native_can_receive_im(message, user_id, group_name)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanRez`.
pub fn can_rez(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanRez")
+ self.native_can_rez()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSendChat`.
pub fn can_send_chat(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSendChat",
- )
+ self.native_can_send_chat()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSendGesture`.
pub fn can_send_gesture(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSendGesture",
- )
+ self.native_can_send_gesture()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSendIM(System.String,System.Nullable{System.Guid},System.String)`.
pub fn can_send_im(
@@ -1545,36 +1483,26 @@ impl RlvPermissionsService {
user_id: Option>,
group_name: Option,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSendIM(System.String,System.Nullable{System.Guid},System.String)",
- )
+ self.native_can_send_im(message, user_id, group_name)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSetDebug`.
pub fn can_set_debug(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSetDebug",
- )
+ self.native_can_set_debug()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSetEnv`.
pub fn can_set_env(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSetEnv",
- )
+ self.native_can_set_env()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSetGroup`.
pub fn can_set_group(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSetGroup",
- )
+ self.native_can_set_group()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShare(System.Nullable{System.Guid})`.
pub fn can_share(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShare(System.Nullable{System.Guid})",
- )
+ self.native_can_share(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowHoverText(LibreMetaverse.RLV.RlvPermissionsService.HoverTextLocation,System.Nullable{System.Guid})`.
pub fn can_show_hover_text(
@@ -1582,97 +1510,71 @@ impl RlvPermissionsService {
location: libremetaverse_rlv::RlvPermissionsServiceHoverTextLocation,
object_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowHoverText(LibreMetaverse.RLV.RlvPermissionsService.HoverTextLocation,System.Nullable{System.Guid})",
- )
+ self.native_can_show_hover_text(location, object_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowInv`.
pub fn can_show_inv(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowInv",
- )
+ self.native_can_show_inv()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowLoc`.
pub fn can_show_loc(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowLoc",
- )
+ self.native_can_show_loc()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowMiniMap`.
pub fn can_show_mini_map(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowMiniMap",
- )
+ self.native_can_show_mini_map()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNameTags(System.Nullable{System.Guid})`.
pub fn can_show_name_tags(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNameTags(System.Nullable{System.Guid})",
- )
+ self.native_can_show_name_tags(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNames(System.Nullable{System.Guid})`.
pub fn can_show_names(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNames(System.Nullable{System.Guid})",
- )
+ self.native_can_show_names(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNearby`.
pub fn can_show_nearby(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowNearby",
- )
+ self.native_can_show_nearby()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanShowWorldMap`.
pub fn can_show_world_map(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanShowWorldMap",
- )
+ self.native_can_show_world_map()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSit`.
pub fn can_sit(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanSit")
+ self.native_can_sit()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanSitTp(System.Single@)`.
pub fn can_sit_tp(&self, max_distance: &mut f32) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanSitTp(System.Single@)",
- )
+ self.native_can_sit_tp(max_distance)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanStandTp`.
pub fn can_stand_tp(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanStandTp",
- )
+ self.native_can_stand_tp()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanStartIM(System.Guid)`.
pub fn can_start_im(
&self,
user_id: libremetaverse_types::compat::Guid,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanStartIM(System.Guid)",
- )
+ self.native_can_start_im(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTPLure(System.Nullable{System.Guid})`.
pub fn can_tp_lure(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanTPLure(System.Nullable{System.Guid})",
- )
+ self.native_can_tp_lure(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTempRun`.
pub fn can_temp_run(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanTempRun",
- )
+ self.native_can_temp_run()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTouch(LibreMetaverse.RLV.RlvPermissionsService.TouchLocation,System.Guid,System.Nullable{System.Guid},System.Nullable{System.Single})`.
pub fn can_touch(
@@ -1682,116 +1584,86 @@ impl RlvPermissionsService {
user_id: Option>,
distance: Option >,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanTouch(LibreMetaverse.RLV.RlvPermissionsService.TouchLocation,System.Guid,System.Nullable{System.Guid},System.Nullable{System.Single})",
- )
+ self.native_can_touch(location, prim_id, user_id, distance)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLm`.
pub fn can_tp_lm(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLm")
+ self.native_can_tp_lm()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLoc`.
pub fn can_tp_loc(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLoc")
+ self.native_can_tp_loc()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLocal(System.Single@)`.
pub fn can_tp_local(&self, max_distance: &mut f32) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanTpLocal(System.Single@)",
- )
+ self.native_can_tp_local(max_distance)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanTpRequest(System.Nullable{System.Guid})`.
pub fn can_tp_request(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanTpRequest(System.Nullable{System.Guid})",
- )
+ self.native_can_tp_request(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanUnsit`.
pub fn can_unsit(&self) -> Result {
- libremetaverse_types::not_implemented("M:LibreMetaverse.RLV.RlvPermissionsService.CanUnsit")
+ self.native_can_unsit()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanViewNote`.
pub fn can_view_note(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanViewNote",
- )
+ self.native_can_view_note()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanViewScript`.
pub fn can_view_script(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanViewScript",
- )
+ self.native_can_view_script()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.CanViewTexture`.
pub fn can_view_texture(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.CanViewTexture",
- )
+ self.native_can_view_texture()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.GetCameraRestrictions`.
pub fn get_camera_restrictions(
&self,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.GetCameraRestrictions",
- )
+ self.native_get_camera_restrictions()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptPermissions`.
pub fn is_auto_accept_permissions(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptPermissions",
- )
+ self.native_is_auto_accept_permissions()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptTp(System.Nullable{System.Guid})`.
pub fn is_auto_accept_tp(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptTp(System.Nullable{System.Guid})",
- )
+ self.native_is_auto_accept_tp(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptTpRequest(System.Nullable{System.Guid})`.
pub fn is_auto_accept_tp_request(
&self,
user_id: Option>,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoAcceptTpRequest(System.Nullable{System.Guid})",
- )
+ self.native_is_auto_accept_tp_request(user_id)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoDenyPermissions`.
pub fn is_auto_deny_permissions(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.IsAutoDenyPermissions",
- )
+ self.native_is_auto_deny_permissions()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.IsPermissive`.
pub fn is_permissive(&self) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvPermissionsService.IsPermissive",
- )
+ self.native_is_permissive()
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.TryGetMaxFarTouchDistance(System.Single@)`.
pub fn try_get_max_far_touch_distance(&self, max_distance: &mut f32) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.RlvPermissionsService.TryGetMaxFarTouchDistance(System.Single@)"
- )
+ self.native_try_get_max_far_touch_distance(max_distance)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.TryGetRedirChatChannels(System.Collections.Generic.IReadOnlyList{System.Int32}@)`.
pub fn try_get_redir_chat_channels(&self, channels: &mut Vec) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.RlvPermissionsService.TryGetRedirChatChannels(System.Collections.Generic.IReadOnlyList{System.Int32}@)"
- )
+ self.native_try_get_redir_chat_channels(channels)
}
/// C# member: `M:LibreMetaverse.RLV.RlvPermissionsService.TryGetRedirEmoteChannels(System.Collections.Generic.IReadOnlyList{System.Int32}@)`.
pub fn try_get_redir_emote_channels(&self, channels: &mut Vec) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.RlvPermissionsService.TryGetRedirEmoteChannels(System.Collections.Generic.IReadOnlyList{System.Int32}@)"
- )
+ self.native_try_get_redir_emote_channels(channels)
}
}
@@ -1884,7 +1756,7 @@ impl RlvRestriction {
}
/// C# type: `T:LibreMetaverse.RLV.RlvRestrictionManager`.
-pub struct RlvRestrictionManager;
+pub use crate::state::RlvRestrictionManager;
impl RlvRestrictionManager {
/// C# member: `E:LibreMetaverse.RLV.RlvRestrictionManager.RestrictionUpdated`.
pub fn subscribe_restriction_updated(
@@ -1895,9 +1767,7 @@ impl RlvRestrictionManager {
>,
>,
) -> libremetaverse_types::compat::Subscription {
- libremetaverse_types::unimplemented_api!(
- "E:LibreMetaverse.RLV.RlvRestrictionManager.RestrictionUpdated"
- )
+ self.native_subscribe_restriction_updated(handler)
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.FindRestrictions(System.String,System.Nullable{System.Guid})`.
pub fn find_restrictions(
@@ -1905,9 +1775,7 @@ impl RlvRestrictionManager {
behavior_name_filter: Option,
sender_filter: Option>,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.FindRestrictions(System.String,System.Nullable{System.Guid})",
- )
+ self.native_find_restrictions(behavior_name_filter, sender_filter)
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.GetLockedFolders`.
pub fn get_locked_folders(
@@ -1919,26 +1787,20 @@ impl RlvRestrictionManager {
>,
crate::Error,
> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.GetLockedFolders",
- )
+ self.native_get_locked_folders()
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.GetRestrictionsByType(LibreMetaverse.RLV.RlvRestrictionType)`.
pub fn get_restrictions_by_type(
&self,
restriction_type: libremetaverse_rlv::RlvRestrictionType,
) -> Result, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.GetRestrictionsByType(LibreMetaverse.RLV.RlvRestrictionType)",
- )
+ self.native_get_restrictions_by_type(restriction_type)
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.GetTrackedPrimIds`.
pub fn get_tracked_prim_ids(
&self,
) -> Result>, crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.GetTrackedPrimIds",
- )
+ self.native_get_tracked_prim_ids()
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.RemoveRestrictionsForObjectsAsync(System.Collections.Generic.IEnumerable{System.Guid},System.Threading.CancellationToken)`.
pub async fn remove_restrictions_for_objects(
@@ -1946,9 +1808,8 @@ impl RlvRestrictionManager {
prim_ids: Box>,
cancellation_token: Option,
) -> Result<(), crate::Error> {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.RemoveRestrictionsForObjectsAsync(System.Collections.Generic.IEnumerable{System.Guid},System.Threading.CancellationToken)",
- )
+ self.native_remove_restrictions_for_objects(prim_ids, cancellation_token)
+ .await
}
/// C# member: `M:LibreMetaverse.RLV.RlvRestrictionManager.TryGetLockedFolder(System.Guid,LibreMetaverse.RLV.LockedFolderPublic@)`.
pub fn try_get_locked_folder(
@@ -1956,9 +1817,7 @@ impl RlvRestrictionManager {
folder_id: libremetaverse_types::compat::Guid,
locked_folder: &mut Option,
) -> bool {
- libremetaverse_types::unimplemented_api!(
- "M:LibreMetaverse.RLV.RlvRestrictionManager.TryGetLockedFolder(System.Guid,LibreMetaverse.RLV.LockedFolderPublic@)"
- )
+ self.native_try_get_locked_folder(folder_id, locked_folder)
}
}
@@ -2386,13 +2245,11 @@ impl RlvService {
}
/// C# type: `T:LibreMetaverse.RLV.RlvSharedFolder`.
-pub struct RlvSharedFolder;
+pub use crate::state::RlvSharedFolder;
impl RlvSharedFolder {
/// C# member: `M:LibreMetaverse.RLV.RlvSharedFolder.#ctor(System.Guid,System.String)`.
pub fn new(id: libremetaverse_types::compat::Guid, name: String) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvSharedFolder.#ctor(System.Guid,System.String)",
- )
+ Self::native_new(id, name)
}
/// C# member: `M:LibreMetaverse.RLV.RlvSharedFolder.AddChild(System.Guid,System.String)`.
pub fn add_child(
@@ -2400,9 +2257,7 @@ impl RlvSharedFolder {
id: libremetaverse_types::compat::Guid,
name: String,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvSharedFolder.AddChild(System.Guid,System.String)",
- )
+ self.native_add_child(id, name)
}
/// C# member: `M:LibreMetaverse.RLV.RlvSharedFolder.AddItem(System.Guid,System.String,System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvWearableType},System.Nullable{LibreMetaverse.RLV.RlvGestureState})`.
pub fn add_item(
@@ -2415,41 +2270,47 @@ impl RlvSharedFolder {
worn_on: Option>,
gesture_state: Option >,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.RlvSharedFolder.AddItem(System.Guid,System.String,System.Boolean,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint},System.Nullable{System.Guid},System.Nullable{LibreMetaverse.RLV.RlvWearableType},System.Nullable{LibreMetaverse.RLV.RlvGestureState})",
+ self.native_add_item(
+ id,
+ name,
+ is_link,
+ attached_to,
+ attached_prim_id,
+ worn_on,
+ gesture_state,
)
}
/// C# member: `M:LibreMetaverse.RLV.RlvSharedFolder.ToString`.
pub fn to_string(&self) -> String {
- libremetaverse_types::unimplemented_api!("M:LibreMetaverse.RLV.RlvSharedFolder.ToString")
+ self.native_to_string()
}
/// C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Children`.
pub fn children(&self) -> Vec {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Children")
+ self.native_children()
}
/// C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Id`.
pub fn id(&self) -> libremetaverse_types::compat::Guid {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Id")
+ self.native_id()
}
/// C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Items`.
pub fn items(&self) -> Vec {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Items")
+ self.native_items()
}
/// C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Name`.
pub fn name(&self) -> String {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Name")
+ self.native_name()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Name`.
pub fn set_name(&mut self, value: String) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Name")
+ self.native_set_name(value)
}
/// C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Parent`.
pub fn parent(&self) -> Option {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Parent")
+ self.native_parent()
}
/// Setter for C# member: `P:LibreMetaverse.RLV.RlvSharedFolder.Parent`.
pub fn set_parent(&mut self, value: Option) {
- libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RLV.RlvSharedFolder.Parent")
+ self.native_set_parent(value)
}
}
@@ -2497,7 +2358,7 @@ pub enum RlvWearableType {
pub mod event_arguments {
/// C# type: `T:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs`.
- pub struct RestrictionUpdatedEventArgs;
+ pub use crate::state::RestrictionUpdatedEventArgs;
impl RestrictionUpdatedEventArgs {
/// C# member: `M:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.#ctor(LibreMetaverse.RLV.RlvRestriction,System.Boolean,System.Boolean)`.
pub fn new(
@@ -2505,27 +2366,19 @@ pub mod event_arguments {
is_new: bool,
is_deleted: bool,
) -> Result {
- libremetaverse_types::not_implemented(
- "M:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.#ctor(LibreMetaverse.RLV.RlvRestriction,System.Boolean,System.Boolean)",
- )
+ Self::native_new(restriction, is_new, is_deleted)
}
/// C# member: `P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.IsDeleted`.
pub fn is_deleted(&self) -> bool {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.IsDeleted"
- )
+ self.native_is_deleted()
}
/// C# member: `P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.IsNew`.
pub fn is_new(&self) -> bool {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.IsNew"
- )
+ self.native_is_new()
}
/// C# member: `P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.Restriction`.
pub fn restriction(&self) -> libremetaverse_rlv::RlvRestriction {
- libremetaverse_types::unimplemented_api!(
- "P:LibreMetaverse.RLV.EventArguments.RestrictionUpdatedEventArgs.Restriction"
- )
+ self.native_restriction()
}
}
}
diff --git a/crates/libremetaverse-rlv/src/lib.rs b/crates/libremetaverse-rlv/src/lib.rs
index dea1d19..4d7ae65 100644
--- a/crates/libremetaverse-rlv/src/lib.rs
+++ b/crates/libremetaverse-rlv/src/lib.rs
@@ -4,7 +4,9 @@ extern crate self as libremetaverse_rlv;
mod generated;
mod protocol;
+mod state;
pub use generated::*;
pub use libremetaverse_types::Error;
pub use protocol::*;
+pub use state::*;
diff --git a/crates/libremetaverse-rlv/src/state.rs b/crates/libremetaverse-rlv/src/state.rs
new file mode 100644
index 0000000..1c9bac9
--- /dev/null
+++ b/crates/libremetaverse-rlv/src/state.rs
@@ -0,0 +1,2174 @@
+//! Pure, synchronized RLV restriction, inventory, lock, and permission state.
+
+#![allow(clippy::missing_errors_doc)] // Mapped Result signatures use the shared compatibility error.
+#![allow(clippy::must_use_candidate)] // Mapped property signatures intentionally mirror C#.
+#![allow(clippy::needless_pass_by_value)] // Owned arguments preserve mapped signatures.
+#![allow(clippy::option_option)] // Nullable value types retain the generated three-state signature.
+#![allow(clippy::unnecessary_wraps)] // Constructors retain mapped fallibility.
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::fmt;
+use std::sync::{
+ Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
+ atomic::{AtomicU64, Ordering},
+};
+
+use libremetaverse_types::compat::{
+ CancellationToken, EventHandler, Guid, ImmutableDictionary, ImmutableList, Subscription,
+ Vector3,
+};
+use libremetaverse_types::{Error, UUID};
+
+use crate::{
+ RlvAttachmentPoint, RlvGestureState, RlvPermissionsServiceHoverTextLocation,
+ RlvPermissionsServiceObjectLocation, RlvPermissionsServiceTouchLocation, RlvRestriction,
+ RlvRestrictionType, RlvValue, RlvWearableType, restriction_name,
+};
+
+/// Maximum number of folders accepted in one immutable inventory snapshot.
+pub const MAX_RLV_INVENTORY_FOLDERS: usize = 100_000;
+/// Maximum number of internal and external items accepted in one snapshot.
+pub const MAX_RLV_INVENTORY_ITEMS: usize = 1_000_000;
+/// Maximum number of simultaneously retained restrictions.
+pub const MAX_RLV_RESTRICTIONS: usize = 100_000;
+
+fn read_lock(lock: &RwLock) -> RwLockReadGuard<'_, T> {
+ lock.read()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
+fn write_lock(lock: &RwLock) -> RwLockWriteGuard<'_, T> {
+ lock.write()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
+fn nullable(value: Option>) -> Option {
+ value.flatten()
+}
+
+fn mapped_nullable(value: Option) -> Option> {
+ Some(value)
+}
+
+fn starts_with_ignore_case(value: &str, prefix: &str) -> bool {
+ value
+ .get(..prefix.len())
+ .is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
+ || value.to_lowercase().starts_with(&prefix.to_lowercase())
+}
+
+#[derive(Debug)]
+struct FolderData {
+ id: Guid,
+ name: String,
+ parent: Option>>,
+ children: Vec,
+ items: Vec,
+}
+
+/// Shared RLV inventory folder with reference semantics and short-held locks.
+#[derive(Clone)]
+pub struct RlvSharedFolder {
+ inner: Arc>,
+}
+
+impl fmt::Debug for RlvSharedFolder {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RlvSharedFolder")
+ .field("id", &self.native_id())
+ .field("name", &self.native_name())
+ .finish_non_exhaustive()
+ }
+}
+
+impl RlvSharedFolder {
+ pub(crate) fn native_new(id: Guid, name: String) -> Result {
+ Ok(Self {
+ inner: Arc::new(RwLock::new(FolderData {
+ id,
+ name,
+ parent: None,
+ children: Vec::new(),
+ items: Vec::new(),
+ })),
+ })
+ }
+
+ pub(crate) fn native_add_child(&self, id: Guid, name: String) -> Result {
+ if name.is_empty() {
+ return Err(Error::Argument);
+ }
+ let child = Self {
+ inner: Arc::new(RwLock::new(FolderData {
+ id,
+ name,
+ parent: Some(Arc::downgrade(&self.inner)),
+ children: Vec::new(),
+ items: Vec::new(),
+ })),
+ };
+ write_lock(&self.inner).children.push(child.clone());
+ Ok(child)
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) fn native_add_item(
+ &self,
+ id: Guid,
+ name: String,
+ is_link: bool,
+ attached_to: Option>,
+ attached_prim_id: Option >,
+ worn_on: Option >,
+ gesture_state: Option >,
+ ) -> Result {
+ let item = RlvInventoryItem::new_internal(
+ id,
+ name,
+ is_link,
+ self,
+ nullable(attached_to),
+ nullable(attached_prim_id),
+ nullable(worn_on),
+ nullable(gesture_state),
+ )?;
+ write_lock(&self.inner).items.push(item.clone());
+ Ok(item)
+ }
+
+ pub(crate) fn native_to_string(&self) -> String {
+ let data = read_lock(&self.inner);
+ format!(
+ "{} (Children: {}, Items: {})",
+ data.name,
+ data.children.len(),
+ data.items.len()
+ )
+ }
+
+ pub(crate) fn native_children(&self) -> Vec {
+ read_lock(&self.inner).children.clone()
+ }
+
+ pub(crate) fn native_id(&self) -> Guid {
+ read_lock(&self.inner).id
+ }
+
+ pub(crate) fn native_items(&self) -> Vec {
+ read_lock(&self.inner).items.clone()
+ }
+
+ pub(crate) fn native_name(&self) -> String {
+ read_lock(&self.inner).name.clone()
+ }
+
+ pub(crate) fn native_set_name(&mut self, value: String) {
+ write_lock(&self.inner).name = value;
+ }
+
+ pub(crate) fn native_parent(&self) -> Option {
+ read_lock(&self.inner)
+ .parent
+ .as_ref()
+ .and_then(Weak::upgrade)
+ .map(|inner| Self { inner })
+ }
+
+ pub(crate) fn native_set_parent(&mut self, value: Option) {
+ write_lock(&self.inner).parent = value.map(|parent| Arc::downgrade(&parent.inner));
+ }
+}
+
+#[derive(Debug)]
+struct InventoryItemData {
+ id: Guid,
+ name: String,
+ is_link: bool,
+ folder: Option>>,
+ folder_id: Option,
+ attached_to: Option,
+ attached_prim_id: Option,
+ worn_on: Option,
+ gesture_state: Option,
+}
+
+/// Shared mutable inventory item corresponding to the C# reference type.
+#[derive(Clone)]
+pub struct RlvInventoryItem {
+ inner: Arc>,
+}
+
+impl fmt::Debug for RlvInventoryItem {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RlvInventoryItem")
+ .field("id", &self.native_id())
+ .field("name", &self.native_name())
+ .finish_non_exhaustive()
+ }
+}
+
+impl RlvInventoryItem {
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) fn native_new(
+ id: Guid,
+ name: String,
+ is_link: bool,
+ external_folder_id: Option>,
+ attached_to: Option >,
+ attached_prim_id: Option >,
+ worn_on: Option >,
+ gesture_state: Option >,
+ ) -> Result {
+ if name.is_empty() {
+ return Err(Error::Argument);
+ }
+ Ok(Self {
+ inner: Arc::new(RwLock::new(InventoryItemData {
+ id,
+ name,
+ is_link,
+ folder: None,
+ folder_id: nullable(external_folder_id),
+ attached_to: nullable(attached_to),
+ attached_prim_id: nullable(attached_prim_id),
+ worn_on: nullable(worn_on),
+ gesture_state: nullable(gesture_state),
+ })),
+ })
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn new_internal(
+ id: Guid,
+ name: String,
+ is_link: bool,
+ folder: &RlvSharedFolder,
+ attached_to: Option,
+ attached_prim_id: Option,
+ worn_on: Option,
+ gesture_state: Option,
+ ) -> Result {
+ if name.is_empty() {
+ return Err(Error::Argument);
+ }
+ Ok(Self {
+ inner: Arc::new(RwLock::new(InventoryItemData {
+ id,
+ name,
+ is_link,
+ folder: Some(Arc::downgrade(&folder.inner)),
+ folder_id: Some(folder.native_id()),
+ attached_to,
+ attached_prim_id,
+ worn_on,
+ gesture_state,
+ })),
+ })
+ }
+
+ pub(crate) fn native_to_string(&self) -> String {
+ self.native_name()
+ }
+
+ pub(crate) fn native_attached_prim_id(&self) -> Option> {
+ mapped_nullable(read_lock(&self.inner).attached_prim_id)
+ }
+
+ pub(crate) fn native_set_attached_prim_id(&mut self, value: Option >) {
+ write_lock(&self.inner).attached_prim_id = nullable(value);
+ }
+
+ pub(crate) fn native_attached_to(&self) -> Option > {
+ mapped_nullable(read_lock(&self.inner).attached_to)
+ }
+
+ pub(crate) fn native_set_attached_to(&mut self, value: Option >) {
+ write_lock(&self.inner).attached_to = nullable(value);
+ }
+
+ pub(crate) fn native_folder(&self) -> Option {
+ read_lock(&self.inner)
+ .folder
+ .as_ref()
+ .and_then(Weak::upgrade)
+ .map(|inner| RlvSharedFolder { inner })
+ }
+
+ pub(crate) fn native_folder_id(&self) -> Option> {
+ mapped_nullable(read_lock(&self.inner).folder_id)
+ }
+
+ pub(crate) fn native_gesture_state(&self) -> Option > {
+ mapped_nullable(read_lock(&self.inner).gesture_state)
+ }
+
+ pub(crate) fn native_set_gesture_state(&mut self, value: Option >) {
+ write_lock(&self.inner).gesture_state = nullable(value);
+ }
+
+ pub(crate) fn native_id(&self) -> Guid {
+ read_lock(&self.inner).id
+ }
+
+ pub(crate) fn native_is_link(&self) -> bool {
+ read_lock(&self.inner).is_link
+ }
+
+ pub(crate) fn native_set_is_link(&mut self, value: bool) {
+ write_lock(&self.inner).is_link = value;
+ }
+
+ pub(crate) fn native_name(&self) -> String {
+ read_lock(&self.inner).name.clone()
+ }
+
+ pub(crate) fn native_set_name(&mut self, value: String) {
+ write_lock(&self.inner).name = value;
+ }
+
+ pub(crate) fn native_worn_on(&self) -> Option > {
+ mapped_nullable(read_lock(&self.inner).worn_on)
+ }
+
+ pub(crate) fn native_set_worn_on(&mut self, value: Option >) {
+ write_lock(&self.inner).worn_on = nullable(value);
+ }
+
+ pub fn attached_prim_id_value(&self) -> Option {
+ read_lock(&self.inner).attached_prim_id
+ }
+
+ pub fn attached_to_value(&self) -> Option {
+ read_lock(&self.inner).attached_to
+ }
+
+ pub fn folder_id_value(&self) -> Option {
+ read_lock(&self.inner).folder_id
+ }
+
+ pub fn gesture_state_value(&self) -> Option {
+ read_lock(&self.inner).gesture_state
+ }
+
+ pub fn worn_on_value(&self) -> Option {
+ read_lock(&self.inner).worn_on
+ }
+}
+
+/// Immutable index over one shared-folder tree and external worn items.
+#[derive(Clone, Debug)]
+pub struct InventoryMap {
+ root: RlvSharedFolder,
+ items: ImmutableDictionary>,
+ external_items: ImmutableDictionary,
+ folders: ImmutableDictionary,
+ all_items: Arc<[RlvInventoryItem]>,
+ external_ordered: Arc<[RlvInventoryItem]>,
+}
+
+impl InventoryMap {
+ pub(crate) fn native_new(
+ root: RlvSharedFolder,
+ external_items: Vec,
+ ) -> Result {
+ let mut folders = HashMap::new();
+ let mut items: HashMap> = HashMap::new();
+ let mut all_items = Vec::new();
+ // The pinned compatibility surface observes child-folder matches
+ // before parent-folder matches for the deprecated single-result query.
+ // An explicit post-order stack preserves that behavior without
+ // recursive call-stack growth.
+ let mut stack = vec![(root.clone(), false)];
+ while let Some((folder, expanded)) = stack.pop() {
+ if expanded {
+ let folder_items = folder.native_items();
+ if all_items.len().saturating_add(folder_items.len()) > MAX_RLV_INVENTORY_ITEMS {
+ return Err(Error::Argument);
+ }
+ for item in folder_items {
+ items
+ .entry(item.native_id())
+ .or_default()
+ .push(item.clone());
+ all_items.push(item);
+ }
+ continue;
+ }
+ if folders.contains_key(&folder.native_id()) {
+ continue;
+ }
+ if folders.len() >= MAX_RLV_INVENTORY_FOLDERS {
+ return Err(Error::Argument);
+ }
+ folders.insert(folder.native_id(), folder.clone());
+ stack.push((folder.clone(), true));
+ let mut children = folder.native_children();
+ children.reverse();
+ stack.extend(children.into_iter().map(|child| (child, false)));
+ }
+ if all_items.len().saturating_add(external_items.len()) > MAX_RLV_INVENTORY_ITEMS {
+ return Err(Error::Argument);
+ }
+ let mut external_map = HashMap::new();
+ for item in &external_items {
+ if external_map
+ .insert(item.native_id(), item.clone())
+ .is_some()
+ {
+ return Err(Error::Argument);
+ }
+ }
+ let items = items
+ .into_iter()
+ .map(|(id, values)| (id, values.into()))
+ .collect();
+ Ok(Self {
+ root,
+ items: ImmutableDictionary::from_map(items),
+ external_items: ImmutableDictionary::from_map(external_map),
+ folders: ImmutableDictionary::from_map(folders),
+ all_items: all_items.into(),
+ external_ordered: external_items.into(),
+ })
+ }
+
+ pub(crate) fn native_find_folders_containing(
+ &self,
+ limit_to_one_result: bool,
+ attached_prim_id: Option>,
+ attachment_point: Option >,
+ wearable_type: Option >,
+ ) -> Result>, Error> {
+ let attached_prim_id = nullable(attached_prim_id);
+ let attachment_point = nullable(attachment_point);
+ let wearable_type = nullable(wearable_type);
+ let found = if let Some(prim_id) = attached_prim_id {
+ self.items_by(|item| item.attached_prim_id_value() == Some(prim_id))
+ } else if let Some(point) = attachment_point {
+ self.items_by(|item| item.attached_to_value() == Some(point))
+ } else if let Some(wearable) = wearable_type {
+ self.items_by(|item| item.worn_on_value() == Some(wearable))
+ } else {
+ Vec::new()
+ };
+ let mut seen = HashSet::new();
+ let mut folders = Vec::new();
+ for item in found {
+ let Some(folder) = item.native_folder() else {
+ return Ok(Box::new(Vec::new().into_iter()));
+ };
+ if seen.insert(folder.native_id()) {
+ folders.push(folder);
+ if limit_to_one_result {
+ break;
+ }
+ }
+ }
+ Ok(Box::new(folders.into_iter()))
+ }
+
+ pub(crate) fn native_get_items_by_attachment_point(
+ &self,
+ attachment_point: RlvAttachmentPoint,
+ ) -> Result, Error> {
+ Ok(self.items_by(|item| item.attached_to_value() == Some(attachment_point)))
+ }
+
+ pub(crate) fn native_get_items_by_id(
+ &self,
+ item_id: Guid,
+ ) -> Result, Error> {
+ let mut found = self
+ .items
+ .get(&item_id)
+ .map_or_else(Vec::new, |values| values.as_slice().to_vec());
+ if let Some(item) = self.external_items.get(&item_id) {
+ found.push(item.clone());
+ }
+ Ok(found)
+ }
+
+ pub(crate) fn native_get_items_by_prim_id(
+ &self,
+ prim_id: Guid,
+ ) -> Result, Error> {
+ Ok(self.items_by(|item| item.attached_prim_id_value() == Some(prim_id)))
+ }
+
+ pub(crate) fn native_get_items_by_wearable_type(
+ &self,
+ wearable_type: RlvWearableType,
+ ) -> Result, Error> {
+ Ok(self.items_by(|item| item.worn_on_value() == Some(wearable_type)))
+ }
+
+ pub(crate) fn native_try_build_path_to_folder(
+ &self,
+ folder_id: Guid,
+ final_path: &mut Option,
+ ) -> bool {
+ *final_path = None;
+ let Some(mut folder) = self.folders.get(&folder_id).cloned() else {
+ return false;
+ };
+ let mut path = Vec::new();
+ let mut visited = HashSet::new();
+ loop {
+ if !visited.insert(folder.native_id()) {
+ return false;
+ }
+ let Some(parent) = folder.native_parent() else {
+ break;
+ };
+ path.push(folder.native_name());
+ folder = parent;
+ }
+ path.reverse();
+ *final_path = Some(path.join("/"));
+ true
+ }
+
+ pub(crate) fn native_try_get_folder_from_path(
+ &self,
+ path: String,
+ skip_private_folders: bool,
+ folder: &mut Option,
+ ) -> bool {
+ *folder = None;
+ if path.is_empty() {
+ return false;
+ }
+ let mut variants = vec![path.as_str()];
+ if let Some(value) = path.strip_prefix('/') {
+ variants.push(value);
+ }
+ if let Some(value) = path.strip_suffix('/') {
+ variants.push(value);
+ }
+ if let Some(value) = path
+ .strip_prefix('/')
+ .and_then(|value| value.strip_suffix('/'))
+ {
+ variants.push(value);
+ }
+ let found = variants
+ .into_iter()
+ .find_map(|candidate| self.folder_from_path(candidate, skip_private_folders));
+ *folder = found;
+ folder.is_some()
+ }
+
+ pub(crate) fn native_external_items(&self) -> ImmutableDictionary {
+ self.external_items.clone()
+ }
+
+ pub(crate) fn native_folders(&self) -> ImmutableDictionary {
+ self.folders.clone()
+ }
+
+ pub(crate) fn native_items(
+ &self,
+ ) -> ImmutableDictionary> {
+ self.items.clone()
+ }
+
+ pub(crate) fn native_root(&self) -> RlvSharedFolder {
+ self.root.clone()
+ }
+
+ pub fn current_outfit(&self) -> Vec {
+ self.items_by(|item| {
+ item.worn_on_value().is_some()
+ || item.attached_to_value().is_some()
+ || item.gesture_state_value() == Some(RlvGestureState::Active)
+ })
+ }
+
+ fn items_by(&self, predicate: impl Fn(&RlvInventoryItem) -> bool) -> Vec {
+ self.all_items
+ .iter()
+ .chain(self.external_ordered.iter())
+ .filter(|item| predicate(item))
+ .cloned()
+ .collect()
+ }
+
+ fn folder_from_path(&self, path: &str, skip_private: bool) -> Option {
+ if path.is_empty() {
+ return None;
+ }
+ let mut remaining = path;
+ let mut current = self.root.clone();
+ let mut visited = HashSet::new();
+ loop {
+ if !visited.insert(current.native_id()) {
+ return None;
+ }
+ let mut selected: Option<(RlvSharedFolder, usize, bool)> = None;
+ for child in current.native_children() {
+ let name = child.native_name();
+ if skip_private && name.starts_with('.') {
+ continue;
+ }
+ let exact = starts_with_ignore_case(remaining, &name);
+ let prefixed =
+ !exact && matches!(name.as_bytes().first(), Some(b'.' | b'~' | b'+'));
+ let fixed = if prefixed { &name[1..] } else { &name };
+ if !starts_with_ignore_case(remaining, fixed) {
+ continue;
+ }
+ let length = fixed.len();
+ let valid_boundary = remaining.len() == length
+ || remaining.as_bytes().get(length).copied() == Some(b'/');
+ if !valid_boundary {
+ continue;
+ }
+ let replace = selected
+ .as_ref()
+ .is_none_or(|(_, best_len, best_prefixed)| {
+ length > *best_len || (length == *best_len && !prefixed && *best_prefixed)
+ });
+ if replace {
+ selected = Some((child, length, prefixed));
+ if remaining.len() == length && !prefixed {
+ break;
+ }
+ }
+ }
+ let (folder, consumed, _) = selected?;
+ if remaining.len() == consumed {
+ return Some(folder);
+ }
+ remaining = remaining.get(consumed + 1..)?;
+ current = folder;
+ }
+ }
+}
+
+/// Thread-safe, case-insensitive behavior blacklist.
+#[derive(Clone, Debug, Default)]
+pub struct RlvBlacklist {
+ entries: Arc>>,
+}
+
+impl RlvBlacklist {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub(crate) fn native_blacklist_behavior(&self, behavior: String) -> Result<(), Error> {
+ if behavior.trim().is_empty() {
+ return Err(Error::Argument);
+ }
+ write_lock(&self.entries).insert(behavior.to_lowercase());
+ Ok(())
+ }
+
+ pub(crate) fn native_get_blacklist(&self) -> Result, Error> {
+ Ok(read_lock(&self.entries).iter().cloned().collect())
+ }
+
+ pub(crate) fn native_is_blacklisted(&self, behavior: String) -> Result {
+ if behavior.trim().is_empty() {
+ return Ok(false);
+ }
+ Ok(read_lock(&self.entries).contains(&behavior.to_lowercase()))
+ }
+
+ pub(crate) fn native_un_blacklist_behavior(&self, behavior: String) -> Result<(), Error> {
+ if behavior.trim().is_empty() {
+ return Err(Error::Argument);
+ }
+ write_lock(&self.entries).remove(&behavior.to_lowercase());
+ Ok(())
+ }
+}
+
+/// Snapshot of effective camera restrictions.
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct CameraRestrictions {
+ is_locked: bool,
+ zoom_min: Option,
+ zoom_max: Option,
+ draw_min: Option,
+ draw_max: Option,
+ draw_alpha_min: Option,
+ draw_alpha_max: Option,
+ fov_min: Option,
+ fov_max: Option,
+ av_dist_min: Option,
+ av_dist_max: Option,
+ av_dist: Option,
+ draw_color: Option,
+ texture: Option,
+}
+
+impl CameraRestrictions {
+ fn from_manager(manager: &RlvRestrictionManager) -> Self {
+ let maximum = |kind| manager.float_values(kind).into_iter().reduce(f32::max);
+ let minimum = |kind| manager.float_values(kind).into_iter().reduce(f32::min);
+ let color_values: Vec<[f32; 3]> = manager
+ .restrictions_by_type(RlvRestrictionType::CamDrawColor)
+ .into_iter()
+ .filter_map(|restriction| match restriction.values() {
+ [RlvValue::Real(x), RlvValue::Real(y), RlvValue::Real(z)] => {
+ Some([x.clamp(0.0, 1.0), y.clamp(0.0, 1.0), z.clamp(0.0, 1.0)])
+ }
+ _ => None,
+ })
+ .collect();
+ let draw_color = (!color_values.is_empty()).then(|| {
+ let (sums, divisor) =
+ color_values
+ .iter()
+ .fold(([0.0; 3], 0.0_f32), |(mut sums, count), color| {
+ sums[0] += color[0];
+ sums[1] += color[1];
+ sums[2] += color[2];
+ (sums, count + 1.0)
+ });
+ Vector3([sums[0] / divisor, sums[1] / divisor, sums[2] / divisor])
+ });
+ let texture = manager
+ .restrictions_by_type(RlvRestrictionType::SetCamTextures)
+ .last()
+ .and_then(|restriction| match restriction.values() {
+ [] => Some(Guid([0; 16])),
+ [RlvValue::Uuid(value)] => Some(*value),
+ _ => None,
+ });
+ Self {
+ zoom_min: maximum(RlvRestrictionType::CamZoomMin),
+ fov_min: maximum(RlvRestrictionType::SetCamFovMin),
+ av_dist_min: maximum(RlvRestrictionType::SetCamAvDistMin),
+ zoom_max: minimum(RlvRestrictionType::CamZoomMax),
+ draw_min: minimum(RlvRestrictionType::CamDrawMin),
+ draw_max: minimum(RlvRestrictionType::CamDrawMax),
+ draw_alpha_min: minimum(RlvRestrictionType::CamDrawAlphaMin),
+ draw_alpha_max: minimum(RlvRestrictionType::CamDrawAlphaMax),
+ fov_max: minimum(RlvRestrictionType::SetCamFovMax),
+ av_dist_max: minimum(RlvRestrictionType::SetCamAvDistMax),
+ av_dist: minimum(RlvRestrictionType::CamAvDist),
+ draw_color,
+ texture,
+ is_locked: !manager
+ .restrictions_by_type(RlvRestrictionType::SetCamUnlock)
+ .is_empty(),
+ }
+ }
+
+ pub(crate) fn native_av_dist(&self) -> Option> {
+ mapped_nullable(self.av_dist)
+ }
+ pub(crate) fn native_av_dist_max(&self) -> Option > {
+ mapped_nullable(self.av_dist_max)
+ }
+ pub(crate) fn native_av_dist_min(&self) -> Option > {
+ mapped_nullable(self.av_dist_min)
+ }
+ pub(crate) fn native_draw_alpha_max(&self) -> Option > {
+ mapped_nullable(self.draw_alpha_max)
+ }
+ pub(crate) fn native_draw_alpha_min(&self) -> Option > {
+ mapped_nullable(self.draw_alpha_min)
+ }
+ pub(crate) fn native_draw_color(&self) -> Option > {
+ mapped_nullable(self.draw_color)
+ }
+ pub(crate) fn native_draw_max(&self) -> Option > {
+ mapped_nullable(self.draw_max)
+ }
+ pub(crate) fn native_draw_min(&self) -> Option > {
+ mapped_nullable(self.draw_min)
+ }
+ pub(crate) fn native_fov_max(&self) -> Option > {
+ mapped_nullable(self.fov_max)
+ }
+ pub(crate) fn native_fov_min(&self) -> Option > {
+ mapped_nullable(self.fov_min)
+ }
+ pub(crate) fn native_is_locked(&self) -> bool {
+ self.is_locked
+ }
+ pub(crate) fn native_texture(&self) -> Option > {
+ mapped_nullable(self.texture)
+ }
+ pub(crate) fn native_zoom_max(&self) -> Option > {
+ mapped_nullable(self.zoom_max)
+ }
+ pub(crate) fn native_zoom_min(&self) -> Option > {
+ mapped_nullable(self.zoom_min)
+ }
+}
+
+/// Immutable current viewer camera settings supplied to query handlers.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct CameraSettings {
+ av_dist_min: f32,
+ av_dist_max: f32,
+ fov_min: f32,
+ fov_max: f32,
+ zoom_min: f32,
+ current_fov: f32,
+}
+
+impl CameraSettings {
+ pub(crate) fn native_new(
+ av_dist_min: f32,
+ av_dist_max: f32,
+ fov_min: f32,
+ fov_max: f32,
+ zoom_min: f32,
+ current_fov: f32,
+ ) -> Result {
+ Ok(Self {
+ av_dist_min,
+ av_dist_max,
+ fov_min,
+ fov_max,
+ zoom_min,
+ current_fov,
+ })
+ }
+
+ pub(crate) fn native_av_dist_max(&self) -> f32 {
+ self.av_dist_max
+ }
+ pub(crate) fn native_av_dist_min(&self) -> f32 {
+ self.av_dist_min
+ }
+ pub(crate) fn native_current_fov(&self) -> f32 {
+ self.current_fov
+ }
+ pub(crate) fn native_fov_max(&self) -> f32 {
+ self.fov_max
+ }
+ pub(crate) fn native_fov_min(&self) -> f32 {
+ self.fov_min
+ }
+ pub(crate) fn native_zoom_min(&self) -> f32 {
+ self.zoom_min
+ }
+}
+
+/// Immutable event payload emitted after a restriction state update.
+#[derive(Clone, Debug)]
+pub struct RestrictionUpdatedEventArgs {
+ restriction: RlvRestriction,
+ is_new: bool,
+ is_deleted: bool,
+}
+
+impl RestrictionUpdatedEventArgs {
+ pub(crate) fn native_new(
+ restriction: RlvRestriction,
+ is_new: bool,
+ is_deleted: bool,
+ ) -> Result {
+ Ok(Self {
+ restriction,
+ is_new,
+ is_deleted,
+ })
+ }
+
+ pub(crate) fn native_is_deleted(&self) -> bool {
+ self.is_deleted
+ }
+
+ pub(crate) fn native_is_new(&self) -> bool {
+ self.is_new
+ }
+
+ pub(crate) fn native_restriction(&self) -> RlvRestriction {
+ self.restriction.clone()
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+struct LockedFolderAccumulator {
+ folder: Option,
+ detach_restrictions: Vec,
+ attach_restrictions: Vec,
+ detach_exceptions: Vec,
+ attach_exceptions: Vec,
+}
+
+/// Public immutable snapshot of one shared folder's effective locks.
+#[derive(Clone, Debug)]
+pub struct LockedFolderPublic {
+ id: Guid,
+ name: String,
+ detach_restrictions: Arc<[RlvRestriction]>,
+ attach_restrictions: Arc<[RlvRestriction]>,
+ detach_exceptions: Arc<[RlvRestriction]>,
+ attach_exceptions: Arc<[RlvRestriction]>,
+}
+
+impl LockedFolderPublic {
+ fn from_accumulator(folder: LockedFolderAccumulator) -> Option {
+ let source = folder.folder?;
+ Some(Self {
+ id: source.native_id(),
+ name: source.native_name(),
+ detach_restrictions: folder.detach_restrictions.into(),
+ attach_restrictions: folder.attach_restrictions.into(),
+ detach_exceptions: folder.detach_exceptions.into(),
+ attach_exceptions: folder.attach_exceptions.into(),
+ })
+ }
+
+ pub(crate) fn native_to_string(&self) -> String {
+ format!(
+ "LockedFolder: {} ({}) - Locked: {}, CanAttach: {}, CanDetach: {}",
+ self.name,
+ guid_text(self.id),
+ self.native_is_locked(),
+ self.native_can_attach(),
+ self.native_can_detach()
+ )
+ }
+
+ pub(crate) fn native_attach_exceptions(&self) -> Vec {
+ self.attach_exceptions.to_vec()
+ }
+ pub(crate) fn native_attach_restrictions(&self) -> Vec {
+ self.attach_restrictions.to_vec()
+ }
+ pub(crate) fn native_can_attach(&self) -> bool {
+ !self.attach_exceptions.is_empty() || self.attach_restrictions.is_empty()
+ }
+ pub(crate) fn native_can_detach(&self) -> bool {
+ !self.detach_exceptions.is_empty() || self.detach_restrictions.is_empty()
+ }
+ pub(crate) fn native_detach_exceptions(&self) -> Vec {
+ self.detach_exceptions.to_vec()
+ }
+ pub(crate) fn native_detach_restrictions(&self) -> Vec {
+ self.detach_restrictions.to_vec()
+ }
+ pub(crate) fn native_id(&self) -> Guid {
+ self.id
+ }
+ pub(crate) fn native_is_locked(&self) -> bool {
+ !self.detach_restrictions.is_empty() || !self.attach_restrictions.is_empty()
+ }
+ pub(crate) fn native_name(&self) -> String {
+ self.name.clone()
+ }
+}
+
+#[derive(Debug, Default)]
+struct RestrictionState {
+ restrictions: Vec,
+ locked_folders: HashMap,
+ inventory: Option,
+ revision: u64,
+}
+
+struct RestrictionManagerInner {
+ state: RwLock,
+ handlers: Arc>>>,
+ next_handler: AtomicU64,
+}
+
+/// Thread-safe restriction provider with snapshot reads and post-lock events.
+#[derive(Clone)]
+pub struct RlvRestrictionManager {
+ inner: Arc,
+}
+
+impl fmt::Debug for RlvRestrictionManager {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RlvRestrictionManager")
+ .field(
+ "restriction_count",
+ &read_lock(&self.inner.state).restrictions.len(),
+ )
+ .finish_non_exhaustive()
+ }
+}
+
+impl Default for RlvRestrictionManager {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl RlvRestrictionManager {
+ pub fn new() -> Self {
+ Self {
+ inner: Arc::new(RestrictionManagerInner {
+ state: RwLock::new(RestrictionState::default()),
+ handlers: Arc::new(Mutex::new(BTreeMap::new())),
+ next_handler: AtomicU64::new(1),
+ }),
+ }
+ }
+
+ /// Replaces the shared-inventory snapshot used for folder-lock decisions.
+ pub fn set_inventory_map(&self, inventory: Option) {
+ let mut state = write_lock(&self.inner.state);
+ state.inventory = inventory;
+ state.revision = state.revision.wrapping_add(1);
+ drop(state);
+ self.rebuild_locked_folders();
+ }
+
+ /// Adds a unique restriction and emits an update after releasing state locks.
+ pub fn add_restriction(&self, restriction: RlvRestriction) -> Result {
+ let added = {
+ let mut state = write_lock(&self.inner.state);
+ if state.restrictions.contains(&restriction) {
+ false
+ } else {
+ if state.restrictions.len() >= MAX_RLV_RESTRICTIONS {
+ return Err(Error::Argument);
+ }
+ state.restrictions.push(restriction.clone());
+ state.revision = state.revision.wrapping_add(1);
+ true
+ }
+ };
+ if added {
+ self.rebuild_locked_folders();
+ self.emit(RestrictionUpdatedEventArgs {
+ restriction,
+ is_new: true,
+ is_deleted: false,
+ });
+ }
+ Ok(added)
+ }
+
+ /// Removes one restriction equal by behavior, sender, and exact arguments.
+ pub fn remove_restriction(&self, restriction: &RlvRestriction) -> bool {
+ let removed = {
+ let mut state = write_lock(&self.inner.state);
+ let removed = state
+ .restrictions
+ .iter()
+ .position(|candidate| candidate == restriction)
+ .map(|index| state.restrictions.remove(index));
+ if removed.is_some() {
+ state.revision = state.revision.wrapping_add(1);
+ }
+ removed
+ };
+ if let Some(removed) = removed {
+ self.rebuild_locked_folders();
+ self.emit(RestrictionUpdatedEventArgs {
+ restriction: removed,
+ is_new: false,
+ is_deleted: true,
+ });
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Removes restrictions from one sender whose canonical name contains the filter.
+ pub fn clear_sender(&self, sender: Guid, behavior_filter: &str) -> Vec {
+ let filter = behavior_filter.to_lowercase();
+ let removed = {
+ let mut state = write_lock(&self.inner.state);
+ let mut removed = Vec::new();
+ state.restrictions.retain(|restriction| {
+ let matches = restriction.sender() == sender
+ && restriction_name(restriction.behavior())
+ .to_lowercase()
+ .contains(&filter);
+ if matches {
+ removed.push(restriction.clone());
+ }
+ !matches
+ });
+ if !removed.is_empty() {
+ state.revision = state.revision.wrapping_add(1);
+ }
+ removed
+ };
+ if !removed.is_empty() {
+ self.rebuild_locked_folders();
+ for restriction in &removed {
+ self.emit(RestrictionUpdatedEventArgs {
+ restriction: restriction.clone(),
+ is_new: false,
+ is_deleted: true,
+ });
+ }
+ }
+ removed
+ }
+
+ pub(crate) fn native_subscribe_restriction_updated(
+ &self,
+ handler: Option>,
+ ) -> Subscription {
+ let Some(handler) = handler else {
+ return Subscription::detached();
+ };
+ let id = self.inner.next_handler.fetch_add(1, Ordering::Relaxed);
+ self.inner
+ .handlers
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .insert(id, handler);
+ let handlers = Arc::downgrade(&self.inner.handlers);
+ Subscription::new(move || {
+ let Some(handlers) = handlers.upgrade() else {
+ return;
+ };
+ handlers
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .remove(&id);
+ })
+ }
+
+ pub(crate) fn native_find_restrictions(
+ &self,
+ behavior_name_filter: Option,
+ sender_filter: Option>,
+ ) -> Result, Error> {
+ let filter = behavior_name_filter.unwrap_or_default().to_lowercase();
+ let sender = nullable(sender_filter);
+ Ok(read_lock(&self.inner.state)
+ .restrictions
+ .iter()
+ .filter(|restriction| {
+ restriction_name(restriction.behavior())
+ .to_lowercase()
+ .contains(&filter)
+ && sender.is_none_or(|sender| sender == restriction.sender())
+ })
+ .cloned()
+ .collect())
+ }
+
+ pub(crate) fn native_get_locked_folders(
+ &self,
+ ) -> Result, Error> {
+ Ok(read_lock(&self.inner.state).locked_folders.clone())
+ }
+
+ pub(crate) fn native_get_restrictions_by_type(
+ &self,
+ restriction_type: RlvRestrictionType,
+ ) -> Result, Error> {
+ Ok(self.restrictions_by_type(restriction_type))
+ }
+
+ pub(crate) fn native_get_tracked_prim_ids(
+ &self,
+ ) -> Result>, Error> {
+ let mut seen = HashSet::new();
+ let ids: Vec<_> = read_lock(&self.inner.state)
+ .restrictions
+ .iter()
+ .filter_map(|restriction| {
+ let sender = restriction.sender();
+ seen.insert(sender).then_some(sender)
+ })
+ .collect();
+ Ok(Box::new(ids.into_iter()))
+ }
+
+ #[allow(clippy::unused_async)] // The mapped C# contract is asynchronous.
+ pub(crate) async fn native_remove_restrictions_for_objects(
+ &self,
+ prim_ids: Box>,
+ cancellation_token: Option,
+ ) -> Result<(), Error> {
+ if let Some(token) = &cancellation_token {
+ token.throw_if_cancellation_requested()?;
+ }
+ let ids = prim_ids.take(MAX_RLV_RESTRICTIONS + 1).collect::>();
+ if ids.len() > MAX_RLV_RESTRICTIONS {
+ return Err(Error::Argument);
+ }
+ let ids: HashSet<_> = ids.into_iter().collect();
+ let removed = {
+ let mut state = write_lock(&self.inner.state);
+ let mut removed = Vec::new();
+ state.restrictions.retain(|restriction| {
+ if ids.contains(&restriction.sender()) {
+ removed.push(restriction.clone());
+ false
+ } else {
+ true
+ }
+ });
+ if !removed.is_empty() {
+ state.revision = state.revision.wrapping_add(1);
+ }
+ removed
+ };
+ if !removed.is_empty() {
+ self.rebuild_locked_folders();
+ for restriction in removed {
+ if let Some(token) = &cancellation_token {
+ token.throw_if_cancellation_requested()?;
+ }
+ self.emit(RestrictionUpdatedEventArgs {
+ restriction,
+ is_new: false,
+ is_deleted: true,
+ });
+ }
+ }
+ Ok(())
+ }
+
+ pub(crate) fn native_try_get_locked_folder(
+ &self,
+ folder_id: Guid,
+ locked_folder: &mut Option,
+ ) -> bool {
+ let value = read_lock(&self.inner.state)
+ .locked_folders
+ .get(&folder_id)
+ .cloned();
+ *locked_folder = value;
+ locked_folder.is_some()
+ }
+
+ fn restrictions_by_type(&self, restriction_type: RlvRestrictionType) -> Vec {
+ let canonical = canonical_restriction(restriction_type);
+ read_lock(&self.inner.state)
+ .restrictions
+ .iter()
+ .filter(|restriction| restriction.behavior() == canonical)
+ .cloned()
+ .collect()
+ }
+
+ fn float_values(&self, restriction_type: RlvRestrictionType) -> Vec {
+ self.restrictions_by_type(restriction_type)
+ .into_iter()
+ .filter_map(|restriction| match restriction.values().first() {
+ Some(RlvValue::Real(value)) => Some(*value),
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn emit(&self, event: RestrictionUpdatedEventArgs) {
+ let handlers: Vec<_> = self
+ .inner
+ .handlers
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .values()
+ .cloned()
+ .collect();
+ for handler in handlers {
+ handler(event.clone());
+ }
+ }
+
+ fn rebuild_locked_folders(&self) {
+ loop {
+ let (revision, inventory, restrictions) = {
+ let state = read_lock(&self.inner.state);
+ (
+ state.revision,
+ state.inventory.clone(),
+ state.restrictions.clone(),
+ )
+ };
+ let locked_folders = inventory.map_or_else(HashMap::new, |inventory| {
+ build_locked_folders(&inventory, &restrictions)
+ });
+ let mut state = write_lock(&self.inner.state);
+ if state.revision == revision {
+ state.locked_folders = locked_folders;
+ return;
+ }
+ }
+ }
+}
+
+fn build_locked_folders(
+ inventory: &InventoryMap,
+ restrictions: &[RlvRestriction],
+) -> HashMap {
+ let mut result: HashMap = HashMap::new();
+ for restriction in restrictions {
+ let behavior = restriction.behavior();
+ if !matches!(
+ behavior,
+ RlvRestrictionType::DetachThis
+ | RlvRestrictionType::DetachAllThis
+ | RlvRestrictionType::AttachThis
+ | RlvRestrictionType::AttachAllThis
+ | RlvRestrictionType::DetachThisExcept
+ | RlvRestrictionType::DetachAllThisExcept
+ | RlvRestrictionType::AttachThisExcept
+ | RlvRestrictionType::AttachAllThisExcept
+ ) {
+ continue;
+ }
+ let folders = folders_for_lock(inventory, restriction);
+ let recursive = matches!(
+ behavior,
+ RlvRestrictionType::DetachAllThis
+ | RlvRestrictionType::AttachAllThis
+ | RlvRestrictionType::DetachAllThisExcept
+ | RlvRestrictionType::AttachAllThisExcept
+ );
+ for folder in folders {
+ add_folder_lock(
+ &mut result,
+ folder,
+ restriction,
+ recursive,
+ &mut HashSet::new(),
+ );
+ }
+ }
+ result
+ .into_iter()
+ .filter_map(|(id, folder)| {
+ LockedFolderPublic::from_accumulator(folder).map(|value| (id, value))
+ })
+ .collect()
+}
+
+fn folders_for_lock(
+ inventory: &InventoryMap,
+ restriction: &RlvRestriction,
+) -> Vec {
+ match restriction.values() {
+ [] => inventory
+ .native_get_items_by_prim_id(restriction.sender())
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|item| item.native_folder())
+ .collect(),
+ [RlvValue::WearableType(value)] => inventory
+ .native_get_items_by_wearable_type(*value)
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|item| item.native_folder())
+ .collect(),
+ [RlvValue::AttachmentPoint(value)] => inventory
+ .native_get_items_by_attachment_point(*value)
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|item| item.native_folder())
+ .collect(),
+ [RlvValue::String(path)] => {
+ let mut folder = None;
+ if inventory.native_try_get_folder_from_path(path.clone(), false, &mut folder) {
+ folder.into_iter().collect()
+ } else {
+ Vec::new()
+ }
+ }
+ _ => Vec::new(),
+ }
+}
+
+fn add_folder_lock(
+ result: &mut HashMap,
+ folder: RlvSharedFolder,
+ restriction: &RlvRestriction,
+ recursive: bool,
+ visited: &mut HashSet,
+) {
+ if !visited.insert(folder.native_id()) {
+ return;
+ }
+ let entry = result.entry(folder.native_id()).or_default();
+ entry.folder.get_or_insert_with(|| folder.clone());
+ match restriction.behavior() {
+ RlvRestrictionType::DetachThis | RlvRestrictionType::DetachAllThis => {
+ entry.detach_restrictions.push(restriction.clone());
+ }
+ RlvRestrictionType::AttachThis | RlvRestrictionType::AttachAllThis => {
+ entry.attach_restrictions.push(restriction.clone());
+ }
+ RlvRestrictionType::DetachThisExcept | RlvRestrictionType::DetachAllThisExcept => {
+ entry.detach_exceptions.push(restriction.clone());
+ }
+ RlvRestrictionType::AttachThisExcept | RlvRestrictionType::AttachAllThisExcept => {
+ entry.attach_exceptions.push(restriction.clone());
+ }
+ _ => {}
+ }
+ if recursive {
+ for child in folder.native_children() {
+ add_folder_lock(result, child, restriction, true, visited);
+ }
+ }
+}
+
+fn canonical_restriction(value: RlvRestrictionType) -> RlvRestrictionType {
+ match value {
+ RlvRestrictionType::CamDistMax => RlvRestrictionType::SetCamAvDistMax,
+ RlvRestrictionType::CamDistMin => RlvRestrictionType::SetCamAvDistMin,
+ RlvRestrictionType::CamUnlock => RlvRestrictionType::SetCamUnlock,
+ RlvRestrictionType::CamTextures => RlvRestrictionType::SetCamTextures,
+ RlvRestrictionType::FarTouch => RlvRestrictionType::TouchFar,
+ other => other,
+ }
+}
+
+fn guid_text(value: Guid) -> String {
+ UUID::new_with_guid(value).map_or_else(|_| format!("{value:?}"), |value| value.to_string())
+}
+
+/// Pure permission evaluator over a shared restriction manager.
+#[derive(Clone, Debug)]
+pub struct RlvPermissionsService {
+ restrictions: RlvRestrictionManager,
+}
+
+impl RlvPermissionsService {
+ pub fn new(restrictions: RlvRestrictionManager) -> Self {
+ Self { restrictions }
+ }
+
+ pub fn restriction_manager(&self) -> RlvRestrictionManager {
+ self.restrictions.clone()
+ }
+
+ fn restrictions(&self, kind: RlvRestrictionType) -> Vec {
+ self.restrictions.restrictions_by_type(kind)
+ }
+
+ fn absent(&self, kind: RlvRestrictionType) -> bool {
+ self.restrictions(kind).is_empty()
+ }
+
+ fn optional_min(&self, kind: RlvRestrictionType, default: f32) -> (bool, f32) {
+ let restrictions = self.restrictions(kind);
+ if restrictions.is_empty() {
+ return (false, default);
+ }
+ if restrictions
+ .iter()
+ .any(|restriction| restriction.values().is_empty())
+ {
+ return (true, default);
+ }
+ let value = restrictions
+ .iter()
+ .filter_map(|restriction| match restriction.values().first() {
+ Some(RlvValue::Real(value)) => Some(*value),
+ _ => None,
+ })
+ .reduce(f32::min)
+ .unwrap_or(default);
+ (true, value)
+ }
+
+ fn secure_allowed(
+ &self,
+ user_id: Option,
+ group_name: Option<&str>,
+ normal: RlvRestrictionType,
+ secure: Option,
+ explicit: Option,
+ ) -> bool {
+ if explicit.is_some_and(|kind| {
+ self.restrictions(kind)
+ .iter()
+ .any(|restriction| restriction_matches_target(restriction, user_id, group_name))
+ }) {
+ return false;
+ }
+ let normal_restrictions = self.restrictions(normal);
+ let exceptions: Vec<_> = normal_restrictions
+ .iter()
+ .filter(|restriction| {
+ restriction.is_exception()
+ && restriction_matches_target(restriction, user_id, group_name)
+ })
+ .collect();
+ if let Some(secure) = secure {
+ for restriction in self.restrictions(secure) {
+ if exceptions
+ .iter()
+ .any(|exception| exception.sender() == restriction.sender())
+ {
+ continue;
+ }
+ return false;
+ }
+ }
+ let permissive = self.is_permissive_value();
+ for restriction in normal_restrictions
+ .iter()
+ .filter(|restriction| !restriction.is_exception() && restriction.values().is_empty())
+ {
+ if exceptions
+ .iter()
+ .any(|exception| permissive || exception.sender() == restriction.sender())
+ {
+ continue;
+ }
+ return false;
+ }
+ true
+ }
+
+ fn is_permissive_value(&self) -> bool {
+ self.absent(RlvRestrictionType::Permissive)
+ }
+
+ pub(crate) fn native_can_allow_idle(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::AllowIdle))
+ }
+ pub(crate) fn native_can_always_run(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::AlwaysRun))
+ }
+ pub(crate) fn native_can_fly(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::Fly))
+ }
+ pub(crate) fn native_can_jump(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::Jump))
+ }
+ pub(crate) fn native_can_temp_run(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::TempRun))
+ }
+ pub(crate) fn native_can_unsit(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::Unsit))
+ }
+ pub(crate) fn native_can_interact(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::Interact))
+ }
+ pub(crate) fn native_can_sit(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::Interact) && self.absent(RlvRestrictionType::Sit))
+ }
+ pub(crate) fn native_can_tp_lm(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::TpLm))
+ }
+ pub(crate) fn native_can_tp_loc(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::TpLoc))
+ }
+ pub(crate) fn native_can_stand_tp(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::StandTp))
+ }
+ pub(crate) fn native_can_show_inv(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::ShowInv))
+ }
+ pub(crate) fn native_can_view_note(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::ViewNote))
+ }
+ pub(crate) fn native_can_view_script(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::ViewScript))
+ }
+ pub(crate) fn native_can_view_texture(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::ViewTexture))
+ }
+ pub(crate) fn native_can_default_wear(&self) -> Result {
+ Ok(self.absent(RlvRestrictionType::DefaultWear))
+ }
+ pub(crate) fn native_can_set_group(&self) -> Result