Complete native extension milestone gate (#84)
Some checks failed
Native code generation / deterministic (push) Failing after 1m48s
Native Rust workspace compile / compile (push) Has been cancelled

This commit is contained in:
2026-08-11 04:09:22 +00:00
parent 2558e2f49f
commit 1c2848c780
11 changed files with 604 additions and 196 deletions

View File

@@ -15,8 +15,12 @@ on:
- "tools/check_milestone_10_issue_81.py"
- "tools/check_milestone_10_issue_82.py"
- "tools/check_milestone_10_issue_83.py"
- "tools/check_milestone_10.py"
- "tools/test_milestone_10.py"
- "tools/generate_lsl_tables.py"
- "codegen/inputs/lsl_tools_grammar.json"
- "docs/extension-milestone-gate.md"
- "api/SHIM-COVERAGE.md"
- "**/*.rs"
- "**/Cargo.toml"
- "Cargo.lock"
@@ -34,8 +38,12 @@ on:
- "tools/check_milestone_10_issue_81.py"
- "tools/check_milestone_10_issue_82.py"
- "tools/check_milestone_10_issue_83.py"
- "tools/check_milestone_10.py"
- "tools/test_milestone_10.py"
- "tools/generate_lsl_tables.py"
- "codegen/inputs/lsl_tools_grammar.json"
- "docs/extension-milestone-gate.md"
- "api/SHIM-COVERAGE.md"
- "**/*.rs"
- "**/Cargo.toml"
- "Cargo.lock"
@@ -48,11 +56,15 @@ jobs:
OPENJPEG_PREFIX: /tmp/metacrate-openjpeg-${{ github.run_id }}
PKG_CONFIG_PATH: /tmp/metacrate-openjpeg-${{ github.run_id }}/lib/pkgconfig
LD_LIBRARY_PATH: /tmp/metacrate-openjpeg-${{ github.run_id }}/lib
CARGO_BUILD_JOBS: 1
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
components: rustfmt, clippy
- name: Install audited OpenJPEG 2.5.4
run: |
sudo apt-get update
@@ -80,7 +92,16 @@ jobs:
python3 tools/check_milestone_10_issue_82.py
python3 tools/check_milestone_10_issue_83.py
python3 tools/generate_lsl_tables.py --check
python3 tools/check_milestone_10.py
- name: Test the complete native world milestone
run: python3 tools/test_milestone_09.py
- name: Test the complete native extension milestone
run: python3 tools/test_milestone_10.py
- name: Compile every workspace target with bounded memory
run: cargo check --workspace --all-targets --locked -j 1
- name: Lint every native extension target with bounded memory
run: cargo clippy -p libremetaverse-rendering-simple -p libremetaverse-rendering-mesh-foundry -p libremetaverse-rlv -p libremetaverse-lsl-tools --all-targets --locked -j 1 -- -D warnings
- name: Document every native extension crate
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc -p libremetaverse-rendering-simple -p libremetaverse-rendering-mesh-foundry -p libremetaverse-rlv -p libremetaverse-lsl-tools --no-deps --locked -j 1

View File

@@ -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 | native implementation: 164 types / 768 members; no generated shims remain |
| `LibreMetaverse.PrimMesher` | 17 | 207 | native implementation: 15 types / 200 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.RLV` | 28 | 499 | native implementation: 17 types / 200 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.RLV` | 28 | 499 | native implementation: 28 types / 499 members; no generated shims remain |
| `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 |

View File

@@ -131,188 +131,90 @@ pub trait IRlvActionCallbacks: Send + Sync {
factor: f32,
delta: f32,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.AdjustHeightAsync(System.Single,System.Single,System.Single,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.AttachAsync(System.Collections.Generic.IReadOnlyList{LibreMetaverse.RLV.AttachmentRequest},System.Threading.CancellationToken)`.
fn attach(
&self,
items_to_attach: Vec<libremetaverse_rlv::AttachmentRequest>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.AttachAsync(System.Collections.Generic.IReadOnlyList{LibreMetaverse.RLV.AttachmentRequest},System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.DetachAsync(System.Collections.Generic.IReadOnlyList{System.Guid},System.Threading.CancellationToken)`.
fn detach(
&self,
item_ids: Vec<libremetaverse_types::compat::Guid>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.DetachAsync(System.Collections.Generic.IReadOnlyList{System.Guid},System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.RemOutfitAsync(System.Collections.Generic.IReadOnlyList{System.Guid},System.Threading.CancellationToken)`.
fn rem_outfit(
&self,
item_ids: Vec<libremetaverse_types::compat::Guid>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.RemOutfitAsync(System.Collections.Generic.IReadOnlyList{System.Guid},System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SendInstantMessageAsync(System.Guid,System.String,System.Threading.CancellationToken)`.
fn send_instant_message(
&self,
target_user: libremetaverse_types::compat::Guid,
message: String,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SendInstantMessageAsync(System.Guid,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SendReplyAsync(System.Int32,System.String,System.Threading.CancellationToken)`.
fn send_reply(
&self,
channel: i32,
message: String,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SendReplyAsync(System.Int32,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetCamFOVAsync(System.Single,System.Threading.CancellationToken)`.
fn set_cam_fov(
&self,
fov_in_radians: f32,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetCamFOVAsync(System.Single,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetDebugAsync(System.String,System.String,System.Threading.CancellationToken)`.
fn set_debug(
&self,
setting_name: String,
setting_value: String,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetDebugAsync(System.String,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetEnvAsync(System.String,System.String,System.Threading.CancellationToken)`.
fn set_env(
&self,
setting_name: String,
setting_value: String,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetEnvAsync(System.String,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetGroupAsync(System.Guid,System.String,System.Threading.CancellationToken)`.
fn set_group_with_guid_string_cancellation_token(
&self,
group_id: libremetaverse_types::compat::Guid,
role_name: Option<String>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetGroupAsync(System.Guid,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetGroupAsync(System.String,System.String,System.Threading.CancellationToken)`.
fn set_group_with_string_string_cancellation_token(
&self,
group_name: String,
role_name: Option<String>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetGroupAsync(System.String,System.String,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SetRotAsync(System.Single,System.Threading.CancellationToken)`.
fn set_rot(
&self,
angle_in_radians: f32,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SetRotAsync(System.Single,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SitAsync(System.Guid,System.Threading.CancellationToken)`.
fn sit(
&self,
target: libremetaverse_types::compat::Guid,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SitAsync(System.Guid,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.SitGroundAsync(System.Threading.CancellationToken)`.
fn sit_ground(
&self,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.SitGroundAsync(System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.TpToAsync(System.Single,System.Single,System.Single,System.String,System.Nullable{System.Single},System.Threading.CancellationToken)`.
fn tp_to(
&self,
@@ -322,26 +224,12 @@ pub trait IRlvActionCallbacks: Send + Sync {
region_name: Option<String>,
lookat: Option<Option<f32>>,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.TpToAsync(System.Single,System.Single,System.Single,System.String,System.Nullable{System.Single},System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvActionCallbacks.UnsitAsync(System.Threading.CancellationToken)`.
fn unsit(
&self,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvActionCallbacks.UnsitAsync(System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), crate::Error>> + Send + '_>>;
}
/// C# type: `T:LibreMetaverse.RLV.IRlvQueryCallbacks`.
@@ -350,40 +238,20 @@ pub trait IRlvQueryCallbacks: Send + Sync {
fn is_sitting(
&self,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<bool, crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.IsSittingAsync(System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<bool, crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.ObjectExistsAsync(System.Guid,System.Threading.CancellationToken)`.
fn object_exists(
&self,
object_id: libremetaverse_types::compat::Guid,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<bool, crate::Error>> + Send + '_>>
{
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.ObjectExistsAsync(System.Guid,System.Threading.CancellationToken)",
)
})
}
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<bool, crate::Error>> + Send + '_>>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetActiveGroupNameAsync(System.Threading.CancellationToken)`.
fn try_get_active_group_name(
&self,
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(bool, String), crate::Error>> + Send + '_>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetActiveGroupNameAsync(System.Threading.CancellationToken)",
)
})
}
>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetCameraSettingsAsync(System.Threading.CancellationToken)`.
fn try_get_camera_settings(
&self,
@@ -398,13 +266,7 @@ pub trait IRlvQueryCallbacks: Send + Sync {
> + Send
+ '_,
>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetCameraSettingsAsync(System.Threading.CancellationToken)",
)
})
}
>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetDebugSettingValueAsync(System.String,System.Threading.CancellationToken)`.
fn try_get_debug_setting_value(
&self,
@@ -412,13 +274,7 @@ pub trait IRlvQueryCallbacks: Send + Sync {
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(bool, String), crate::Error>> + Send + '_>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetDebugSettingValueAsync(System.String,System.Threading.CancellationToken)",
)
})
}
>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetEnvironmentSettingValueAsync(System.String,System.Threading.CancellationToken)`.
fn try_get_environment_setting_value(
&self,
@@ -426,13 +282,7 @@ pub trait IRlvQueryCallbacks: Send + Sync {
cancellation_token: libremetaverse_types::compat::CancellationToken,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(bool, String), crate::Error>> + Send + '_>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetEnvironmentSettingValueAsync(System.String,System.Threading.CancellationToken)",
)
})
}
>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetInventoryMapAsync(System.Threading.CancellationToken)`.
fn try_get_inventory_map(
&self,
@@ -444,13 +294,7 @@ pub trait IRlvQueryCallbacks: Send + Sync {
> + Send
+ '_,
>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetInventoryMapAsync(System.Threading.CancellationToken)",
)
})
}
>;
/// C# member: `M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetSitIdAsync(System.Threading.CancellationToken)`.
fn try_get_sit_id(
&self,
@@ -462,13 +306,7 @@ pub trait IRlvQueryCallbacks: Send + Sync {
> + Send
+ '_,
>,
> {
Box::pin(async {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.RLV.IRlvQueryCallbacks.TryGetSitIdAsync(System.Threading.CancellationToken)",
)
})
}
>;
}
/// C# type: `T:LibreMetaverse.RLV.InventoryMap`.

View File

@@ -0,0 +1,113 @@
# Rendering, RLV, and LSL extension gate
Milestone 10 replaces the pinned rendering, Restrained Love Viewer, and LSL
tooling assemblies with native Rust. The four extension crates are normal
workspace dependencies with no opt-in compatibility feature: adding one of
them selects its Rust implementation on every supported platform. None loads
a CLR assembly, starts a .NET process, or delegates to a platform-only API.
## Choosing an extension
| Previous C# assembly | Rust crate | Primary entry points |
| --- | --- | --- |
| `LibreMetaverse.Rendering.Simple` | `libremetaverse-rendering-simple` | `SimpleRenderer`, `IRendering` |
| `LibreMetaverse.Rendering.MeshFoundry` | `libremetaverse-rendering-mesh-foundry` | `MeshFoundry`, `IRendering`, `MeshFaceAux` |
| `LibreMetaverse.RLV` | `libremetaverse-rlv` | `RlvService`, `RlvPermissionsService`, callback traits |
| `LibreMetaverse.LslTools` | `libremetaverse-lsl-tools` | `Lexer`, `Parser`, `Grammar`, generated tables |
The mapped PascalCase C# surface remains represented in the API ledger, while
the callable Rust names follow the workspace's snake-case mapping. New code
should use owned Rust values and `Result` instead of nullable CLR containers or
exceptions. `api/SHIM-COVERAGE.md` is generated evidence that all 194 types and
1,287 members in these four assemblies are native; the milestone audit rejects
an owned `not_implemented`, `unimplemented_api`, `todo!`, or `unimplemented!`.
## Rendering migration
Both renderers consume the native core `Primitive` and common `IRendering`
types. Use `SimpleRenderer` for prim and decoded sculpt geometry. Use
`MeshFoundry` when an LLSD mesh asset, skin weights, secondary UVs, tangents,
terrain, or physics hulls are required. Both keep coordinates prim-local; the
world service remains responsible for object transforms, asset retrieval, and
scene ownership.
```rust,no_run
use libremetaverse::Primitive;
use libremetaverse::rendering::{DetailLevel, IRendering};
use libremetaverse_rendering_simple::SimpleRenderer;
fn render(primitive: Primitive) -> Result<usize, libremetaverse_types::Error> {
let renderer = SimpleRenderer::new()?;
Ok(renderer
.generate_simple_mesh(primitive, DetailLevel::High)?
.indices
.len())
}
```
Inputs are bounded before allocation: a face stays in the 16-bit index domain,
and MeshFoundry caps encoded/decompressed assets, sections, total geometry,
joints, and convex data. Rendering is deterministic and performs no network,
GPU, window-system, or callback work.
## RLV host integration and teardown
`RlvService` separates deterministic protocol/state handling from world side
effects. A host implements every required method of `IRlvQueryCallbacks` and
`IRlvActionCallbacks`, then passes owned trait objects to `RlvService::new`.
There are no generated fallback methods: an incomplete adapter fails to
compile. `RlvCallbacksDefault` and `RlvActionCallbacksDefault` are complete,
cancellation-aware adapters for hosts that intentionally use conservative
query values and no-op actions.
```rust,no_run
use libremetaverse_rlv::{RlvActionCallbacksDefault, RlvCallbacksDefault, RlvService};
fn service() -> Result<RlvService, libremetaverse_types::Error> {
RlvService::new(
Box::new(RlvCallbacksDefault::new()?),
Box::new(RlvActionCallbacksDefault::new()?),
true,
)
}
```
Core/world adapters should snapshot manager data before returning a future and
perform network actions through the corresponding manager APIs. The RLV crate
releases its locks before awaiting a callback, checks cancellation between
commands, bounds each message to 64 KiB and 128 commands, and never starts a
background task. Drop event `Subscription` guards when a host shuts down; drop
the service and its callback objects after outstanding host futures complete.
## LSL tooling migration
Use `generated_lexer()` and `generated_parser()` for the reviewed checked-in
grammar, or build typed `Dfa` and `Grammar` values for another language. Source
locations use UTF-16 code units to preserve the mapped contract. Generation is
an explicit development operation; parsing never generates or compiles code at
runtime.
```rust,no_run
use libremetaverse_lsl_tools::{Lexer, Parser, generated_lexer, generated_parser};
fn parse(source: String) -> Result<String, libremetaverse_types::Error> {
let lexer = Lexer::new(generated_lexer()?)?;
let mut parser = Parser::new(generated_parser()?, lexer)?;
Ok(parser.parse_with_string(source)?.yyname())
}
```
Source, token, DFA, parser-state, stack, operation, recovery, and generator
sizes have fixed limits. The checked table is reproducible with
`python3 tools/generate_lsl_tables.py --check`; API generation is checked with
`python3 tools/generate_api_shims.py --check`.
## Complete milestone verification
`python3 tools/check_milestone_10.py` audits native ownership, reviewed parity
metadata, deterministic generation, resource limits, integration evidence,
migration documentation, and the ubuntu-only workflow. Run
`python3 tools/test_milestone_10.py` to execute the four crate suites, all 616
reviewed RLV compatibility cases, rendering compatibility, the cross-extension
core/world integration, and the compile-only mapped API fixture. The workflow
runs these gates on `ubuntu-latest`.

View File

@@ -67,10 +67,7 @@ fn extension_flows_have_typed_callable_signatures() {
#[test]
fn extension_constructors_have_expected_native_or_failure_behavior() {
assert!(YyParser::new().is_ok());
assert_eq!(
member_id(RlvActionCallbacksDefault::new()),
"M:LibreMetaverse.RLV.RlvActionCallbacksDefault.#ctor"
);
assert!(RlvActionCallbacksDefault::new().is_ok());
assert_eq!(
member_id(TCPPipe::new()),
"M:LibreMetaverse.Voice.Vivox.TCPPipe.#ctor"

View File

@@ -0,0 +1,80 @@
//! Milestone 10 integration coverage across core primitives and native extensions.
use libremetaverse::rendering::{DetailLevel, SimpleMesh};
use libremetaverse::{Primitive, PrimitiveConstructionData};
use libremetaverse_compat_tests::block_on;
use libremetaverse_compat_tests::rlv_support::RlvHarness;
use libremetaverse_lsl_tools::{Lexer, Parser, generated_lexer, generated_parser};
use libremetaverse_rendering_mesh_foundry::MeshFoundry;
use libremetaverse_rendering_simple::SimpleRenderer;
use libremetaverse_types::{HoleType, PCode, PathCurve, ProfileCurve};
fn world_primitive() -> Primitive {
let mut primitive = Primitive::new_with_constructor().expect("Primitive constructor");
let mut construction = PrimitiveConstructionData::new_with_constructor()
.expect("PrimitiveConstructionData constructor");
construction.p_code = PCode::Prim;
construction.path_curve = PathCurve::Line;
construction.set_profile_curve_with_property(ProfileCurve::Square);
construction.set_profile_hole(HoleType::Same);
construction.path_scale_x = 1.0;
construction.path_scale_y = 1.0;
construction.path_end = 1.0;
construction.profile_end = 1.0;
construction.path_revolutions = 1.0;
primitive.prim_data = construction;
primitive
}
fn mesh_signature(mesh: &SimpleMesh) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
for vertex in &mesh.vertices {
for value in [
vertex.position.x,
vertex.position.y,
vertex.position.z,
vertex.normal.x,
vertex.normal.y,
vertex.normal.z,
vertex.tex_coord.x,
vertex.tex_coord.y,
] {
hash = (hash ^ u64::from(value.to_bits())).wrapping_mul(0x0000_0100_0000_01b3);
}
}
hash
}
#[test]
fn native_extensions_consume_core_models_and_preserve_service_state() {
let primitive = world_primitive();
let simple = SimpleRenderer::new()
.expect("simple renderer")
.generate_simple_mesh(primitive.clone(), DetailLevel::High)
.expect("simple mesh");
let foundry = MeshFoundry::new()
.expect("mesh foundry")
.generate_simple_mesh(primitive, DetailLevel::High)
.expect("foundry mesh")
.expect("legacy primitive is renderable");
assert_eq!(simple.vertices.len(), foundry.vertices.len());
assert_eq!(mesh_signature(&simple), mesh_signature(&foundry));
assert_eq!(simple.indices, foundry.indices);
let lexer = Lexer::new(generated_lexer().expect("generated lexer")).expect("lexer");
let mut parser =
Parser::new(generated_parser().expect("generated parser"), lexer).expect("parser");
let symbol = parser
.parse_with_string("Widget(value;):base(new Child[])".to_owned())
.expect("generated class body");
assert_eq!(
(symbol.yyname(), symbol.yynum()),
("ClassBody".to_owned(), 19)
);
let rlv = RlvHarness::new().expect("RLV service");
assert!(block_on(rlv.process("@fly=n")).expect("add restriction"));
assert!(!rlv.service.permissions().can_fly().expect("permission"));
assert!(block_on(rlv.process("@fly=y")).expect("remove restriction"));
assert!(rlv.service.permissions().can_fly().expect("permission"));
}

249
tools/check_milestone_10.py Normal file
View File

@@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Audit the complete native rendering, RLV, and LSL milestone boundary."""
from __future__ import annotations
import json
import re
import subprocess
from pathlib import Path
import generate_api_shims
ROOT = Path(__file__).resolve().parents[1]
ASSEMBLIES = {
"LibreMetaverse.Rendering.Simple": (1, 6),
"LibreMetaverse.Rendering.MeshFoundry": (1, 14),
"LibreMetaverse.RLV": (28, 499),
"LibreMetaverse.LslTools": (164, 768),
}
CRATES = (
"libremetaverse-rendering-simple",
"libremetaverse-rendering-mesh-foundry",
"libremetaverse-rlv",
"libremetaverse-lsl-tools",
)
RLV_GENERATED_TYPES = {
"T:LibreMetaverse.RLV.IRlvActionCallbacks",
"T:LibreMetaverse.RLV.IRlvQueryCallbacks",
"T:LibreMetaverse.RLV.RlvAttachmentPoint",
"T:LibreMetaverse.RLV.RlvGestureState",
"T:LibreMetaverse.RLV.RlvGetDebugType",
"T:LibreMetaverse.RLV.RlvGetEnvType",
"T:LibreMetaverse.RLV.RlvPermissionsService.HoverTextLocation",
"T:LibreMetaverse.RLV.RlvPermissionsService.ObjectLocation",
"T:LibreMetaverse.RLV.RlvPermissionsService.TouchLocation",
"T:LibreMetaverse.RLV.RlvRestrictionType",
"T:LibreMetaverse.RLV.RlvWearableType",
}
STUB_RE = re.compile(
r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\("
)
PLATFORM_RE = re.compile(r"unsafe\s*\{|unsafe\s+impl|target_os\s*=\s*\"macos\"")
def require_markers(path: Path, markers: tuple[str, ...]) -> None:
text = path.read_text()
missing = [marker for marker in markers if marker not in text]
if missing:
raise SystemExit(f"{path}: audit evidence missing: " + ", ".join(missing))
def catalog_assemblies() -> dict[str, dict]:
catalog = json.loads((ROOT / "api" / "public-api.json").read_text())
return {assembly["identity"]["name"]: assembly for assembly in catalog["assemblies"]}
def member_is_native(item: dict, member: dict) -> bool:
doc_id = item["doc_id"]
return (
doc_id in generate_api_shims.NATIVE_TYPES
or doc_id in generate_api_shims.NATIVE_GENERATED_TYPES
or (
doc_id in generate_api_shims.NATIVE_DECLARATIONS
and member["kind"] in {"field", "constant"}
)
or generate_api_shims.native_member_body(member["doc_id"]) is not None
)
def check_catalog_ownership() -> None:
assemblies = catalog_assemblies()
native_types = (
set(generate_api_shims.NATIVE_TYPES)
| set(generate_api_shims.NATIVE_DECLARATIONS)
| set(generate_api_shims.NATIVE_GENERATED_TYPES)
)
for name, expected in ASSEMBLIES.items():
assembly = assemblies[name]
actual = (
len(assembly["types"]),
sum(len(item["members"]) for item in assembly["types"]),
)
if actual != expected:
raise SystemExit(f"{name}: expected catalog counts {expected}, found {actual}")
missing_types = [
item["doc_id"] for item in assembly["types"] if item["doc_id"] not in native_types
]
missing_members = [
member["doc_id"]
for item in assembly["types"]
for member in item["members"]
if not member_is_native(item, member)
]
if missing_types or missing_members:
raise SystemExit(
f"{name}: non-native types={missing_types}, members={missing_members}"
)
if generate_api_shims.NATIVE_GENERATED_TYPES != RLV_GENERATED_TYPES:
raise SystemExit("milestone 10 generated-native RLV declaration set changed")
def check_sources_and_manifests() -> None:
offenders: list[str] = []
platform_offenders: list[str] = []
for crate in CRATES:
crate_root = ROOT / "crates" / crate
manifest = (crate_root / "Cargo.toml").read_text()
if "[features]" in manifest:
raise SystemExit(f"{crate}: native implementation must be active without a feature")
if re.search(r"\b(?:dotnet|csc|mcs)\b", manifest, re.IGNORECASE):
raise SystemExit(f"{crate}: manifest depends on a C#/.NET tool")
for source in (crate_root / "src").glob("*.rs"):
text = source.read_text()
if STUB_RE.search(text):
offenders.append(str(source.relative_to(ROOT)))
if PLATFORM_RE.search(text):
platform_offenders.append(str(source.relative_to(ROOT)))
if offenders:
raise SystemExit("milestone 10 owned Rust stubs remain: " + ", ".join(offenders))
if platform_offenders:
raise SystemExit(
"milestone 10 unsafe or macOS-only sources remain: "
+ ", ".join(platform_offenders)
)
def check_parity() -> None:
catalog = json.loads((ROOT / "tests" / "upstream-tests.json").read_text())
related = [
test
for test in catalog["tests"]
if test["source"].startswith("LibreMetaverse.Tests/RLV/")
or test["source"].startswith("LibreMetaverse.Rendering.Tests/")
]
if len(related) != 691:
raise SystemExit(f"milestone 10 expected 691 upstream parity cases, found {len(related)}")
bad = [
test["id"]
for test in related
if test["status"] != "translated" or test.get("semantic_review") != "reviewed"
]
if bad:
raise SystemExit("milestone 10 parity cases are not reviewed: " + ", ".join(bad))
missing = sorted(
{
test["rust_file"]
for test in related
if not (ROOT / test["rust_file"]).is_file()
}
)
if missing:
raise SystemExit("milestone 10 translated files are missing: " + ", ".join(missing))
def check_contract_evidence() -> None:
require_markers(
ROOT / "crates" / "libremetaverse-rendering-simple" / "src" / "simple_renderer.rs",
("MAX_FACE_VERTICES", "checked_add", "u16::try_from"),
)
require_markers(
ROOT / "crates" / "libremetaverse-rendering-mesh-foundry" / "src" / "mesh_foundry.rs",
("MAX_ASSET_BYTES", "MAX_TOTAL_VERTICES", "MAX_JOINTS", "checked_add"),
)
require_markers(
ROOT / "crates" / "libremetaverse-rlv" / "src" / "service.rs",
(
"MAX_RLV_MESSAGE_BYTES",
"MAX_RLV_COMMANDS",
"Arc<dyn IRlvQueryCallbacks>",
"Arc<dyn IRlvActionCallbacks>",
"throw_if_cancellation_requested",
),
)
require_markers(
ROOT / "crates" / "libremetaverse-lsl-tools" / "src" / "parser.rs",
("MAX_PARSER_STATES", "MAX_PARSER_STACK", "MAX_PARSER_STEPS", "MAX_RECOVERY_ERRORS"),
)
require_markers(
ROOT / "crates" / "libremetaverse-lsl-tools" / "src" / "lexer.rs",
("MAX_SOURCE_UNITS", "MAX_TOKEN_UNITS"),
)
require_markers(
ROOT / "tests" / "compat" / "tests" / "milestone_10_integration.rs",
(
"native_extensions_consume_core_models_and_preserve_service_state",
"SimpleRenderer",
"MeshFoundry",
"generated_parser",
"RlvHarness",
),
)
require_markers(
ROOT / "docs" / "extension-milestone-gate.md",
(
"Choosing an extension",
"Rendering migration",
"RLV host integration and teardown",
"LSL tooling migration",
"194 types",
"1,287 members",
"tools/test_milestone_10.py",
"ubuntu-latest",
),
)
require_markers(
ROOT / "api" / "SHIM-COVERAGE.md",
tuple(
f"`{name}` | {types} | {members} | native implementation"
for name, (types, members) in ASSEMBLIES.items()
),
)
def check_generation_and_ci() -> None:
subprocess.run(
["python3", "tools/generate_api_shims.py", "--check"], cwd=ROOT, check=True
)
subprocess.run(
["python3", "tools/generate_lsl_tables.py", "--check"], cwd=ROOT, check=True
)
workflow = ROOT / ".gitea" / "workflows" / "rust-workspace.yml"
require_markers(
workflow,
(
"python3 tools/check_milestone_10.py",
"python3 tools/test_milestone_10.py",
"runs-on: ubuntu-latest",
),
)
if re.search(r"runs-on:\s*(?:macos|windows)", workflow.read_text(), re.IGNORECASE):
raise SystemExit("milestone 10 workflow is not ubuntu-only")
def main() -> None:
check_catalog_ownership()
check_sources_and_manifests()
check_parity()
check_contract_evidence()
check_generation_and_ci()
print(
"milestone 10 audit: 194 types and 1,287 members are native; no owned "
"stubs remain; bounds, callback teardown, 691 reviewed parity cases, "
"integration, migration docs, deterministic generation, and ubuntu-only CI are present"
)
if __name__ == "__main__":
main()

View File

@@ -132,7 +132,7 @@ def main() -> None:
raise SystemExit(f"issue 81 expected 27 focused fixtures, found {test_count}")
require_markers(DOC, (
"UTF-16 code-unit offsets", "maximum munch", "structured `Diagnostic`",
"64 Mi UTF-16", "16 Mi units", "Lexer::iter", "27 focused",
"64 Mi UTF-16", "16 Mi units", "Lexer::iter", "46 focused",
"No C#, .NET runtime", "ubuntu-latest",
))
require_markers(WORKFLOW, ("python3 tools/check_milestone_10_issue_81.py",))

View File

@@ -122,7 +122,7 @@ def main() -> None:
require_markers(EXTENSION_TESTS, ("assert!(YyParser::new().is_ok())",))
require_markers(DOC, (
"canonical LR(0)", "deterministic LALR(1)", "Shift/reduce conflicts",
"nonassociative", "1,048,576", "16,777,216", "37 focused",
"nonassociative", "1,048,576", "16,777,216", "46 focused",
"issue 83", "ubuntu-latest",
))
require_markers(WORKFLOW, ("python3 tools/check_milestone_10_issue_82.py",))

View File

@@ -601,6 +601,26 @@ NATIVE_DECLARATIONS = {
"T:LibreMetaverse.Rendering.MeshFoundry": "crate::mesh_foundry::MeshFoundry",
}
# Catalog declarations which are themselves complete native Rust. Unlike
# NATIVE_TYPES, these types intentionally remain in generated.rs: the RLV
# enums are closed value tables and the callback interfaces are Rust traits
# whose required methods are implemented by the host and default adapters.
# Keeping this set explicit lets coverage distinguish concrete generated code
# from a callable failure-only compatibility shell.
NATIVE_GENERATED_TYPES = {
"T:LibreMetaverse.RLV.IRlvActionCallbacks",
"T:LibreMetaverse.RLV.IRlvQueryCallbacks",
"T:LibreMetaverse.RLV.RlvAttachmentPoint",
"T:LibreMetaverse.RLV.RlvGestureState",
"T:LibreMetaverse.RLV.RlvGetDebugType",
"T:LibreMetaverse.RLV.RlvGetEnvType",
"T:LibreMetaverse.RLV.RlvPermissionsService.HoverTextLocation",
"T:LibreMetaverse.RLV.RlvPermissionsService.ObjectLocation",
"T:LibreMetaverse.RLV.RlvPermissionsService.TouchLocation",
"T:LibreMetaverse.RLV.RlvRestrictionType",
"T:LibreMetaverse.RLV.RlvWearableType",
}
NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.RLV.RlvCommon.TryGetAttachmentPointFromItemName(System.String,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint}@)":
"Self::native_try_get_attachment_point_from_item_name(item_name, attachment_point)",
@@ -3523,9 +3543,18 @@ def native_member_body(member_id: str) -> str | None:
return None
def render_body(signature: str, member_id: str, error_model: str, asyncness: str, trait: bool) -> str:
def render_body(
signature: str,
member_id: str,
error_model: str,
asyncness: str,
trait: bool,
required_trait_method: bool = False,
) -> str:
if trait:
signature = signature.removeprefix("pub ")
if required_trait_method:
return f" {signature};"
if body := native_member_body(member_id):
if any(f"LibreMetaverse.{owner}." in member_id for owner in CLIENT_CORE_NATIVE_OWNERS):
marker = " /* native client-core implementation */"
@@ -3647,6 +3676,7 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
row["error_model"],
row["asyncness"],
trait,
trait and item["doc_id"] in NATIVE_GENERATED_TYPES,
)
)
lines.append("}")
@@ -3844,7 +3874,9 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -
native_type_ids = {
item["doc_id"]
for item in assembly["types"]
if item["doc_id"] in NATIVE_TYPES or item["doc_id"] in NATIVE_DECLARATIONS
if item["doc_id"] in NATIVE_TYPES
or item["doc_id"] in NATIVE_DECLARATIONS
or item["doc_id"] in NATIVE_GENERATED_TYPES
or (name == "LibreMetaverse.Types" and item["kind"] == "enum")
}
native_member_ids = {
@@ -3852,6 +3884,7 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -
for item in assembly["types"]
for member in item["members"]
if item["doc_id"] in NATIVE_TYPES
or item["doc_id"] in NATIVE_GENERATED_TYPES
or (name == "LibreMetaverse.Types" and item["kind"] == "enum")
or (
item["doc_id"] in NATIVE_DECLARATIONS

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Run only tests owned by the rendering, RLV, and LSL milestone."""
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ENV = os.environ.copy()
ENV.setdefault("CARGO_BUILD_JOBS", "1")
ENV.setdefault("CARGO_INCREMENTAL", "0")
ENV.setdefault("CARGO_PROFILE_DEV_DEBUG", "0")
ENV.setdefault("CARGO_PROFILE_TEST_DEBUG", "0")
ENV.pop("RUN_LIVE_TESTS", None)
def run(command: list[str]) -> None:
print("+", " ".join(command), flush=True)
subprocess.run(command, cwd=ROOT, env=ENV, check=True)
run(
[
"cargo",
"test",
"-p",
"libremetaverse-rendering-simple",
"-p",
"libremetaverse-rendering-mesh-foundry",
"-p",
"libremetaverse-rlv",
"-p",
"libremetaverse-lsl-tools",
"--locked",
"-j",
"1",
]
)
catalog = json.loads((ROOT / "tests" / "upstream-tests.json").read_text())
rlv_targets = sorted(
{
Path(test["rust_file"]).stem
for test in catalog["tests"]
if test["source"].startswith("LibreMetaverse.Tests/RLV/")
}
)
if len(rlv_targets) != 24:
raise SystemExit(f"milestone 10 expected 24 RLV test targets, found {rlv_targets}")
compat = ["cargo", "test", "-p", "libremetaverse-compat-tests"]
for target in (
"imaging_meshing_semantics",
"rendering_shims",
"extension_shims",
"milestone_10_integration",
*rlv_targets,
):
compat.extend(("--test", target))
compat.extend(("--locked", "-j", "1"))
run(compat)
run(
[
"cargo",
"check",
"--manifest-path",
"tests/api-compile/Cargo.toml",
"--locked",
"-j",
"1",
]
)