Generate avatar-facing API shims

This commit is contained in:
2026-08-08 12:39:29 +02:00
parent a007313785
commit 376e63ac6d
7 changed files with 12787 additions and 285 deletions

View File

@@ -8,10 +8,11 @@ stage is a compiling structural shell: crate boundaries mirror the .NET library
projects, public C# type names have Rust declarations, every upstream NUnit case
has a traceable placeholder, and every sample/tool project has a Rust binary
target. Types, StructuredData, Imaging, PrimMesher, both rendering adapters, and
the main assembly's packet/message/asset/primitive wire-data and core
runtime/networking slices now have complete callable, failure-only signatures;
the remaining avatar/world manager and extension slices plus semantic Rust test
translations are still required before the public API shim is complete.
the main assembly's packet/message/asset/primitive wire-data, core
runtime/networking, and avatar-facing manager slices now have complete callable,
failure-only signatures; the remaining world manager and extension slices plus
semantic Rust test translations are still required before the public API shim
is complete.
The source snapshot, compatibility rules, dependency research, and ordered
implementation plan are in [RUSTREWRITE.md](RUSTREWRITE.md).

View File

@@ -52,22 +52,28 @@ The current workspace is a structural baseline, not a working client:
matching independent ECMA-335 counts, with all 142 external signature types
resolved to cross-platform Rust or project-owned boundary types;
- the completed Types, StructuredData, Imaging, PrimMesher, rendering, main
wire/data, and core runtime/networking slices expose 2,404 final Rust-facing
types and all 25,257 callable members with real fields/constants/enum values
and standardized failing bodies;
wire/data, core runtime/networking, and avatar-facing manager slices expose
2,597 final Rust-facing types and all 27,191 callable members with real
fields/constants/enum values and standardized failing bodies;
- all 1,295 NUnit `[Test]`/`[TestCase]` invocations have compiling Rust test
entries that intentionally panic with their source identity and body hash;
- all nine sample/tool projects have compiling Rust binary targets;
- the 128 TestClient command source files are retained as a command inventory.
The remaining 662 generated type shims preserve names and module placement, but
do not yet expose their 5,532 callable members. `api/SURFACE.tsv` is an inventory aid,
The remaining 469 generated type shims preserve names and module placement, but
do not yet expose their 3,598 callable members. `api/SURFACE.tsv` is an inventory aid,
not proof of API coverage: it records declaration lines but does not fully parse
multiline signatures. Likewise, a generated `pending` test is a parity
placeholder, not a semantic translation. **This repository is therefore at
Stage 0 and does not yet satisfy either the full API-shim gate or the full
test-suite gate.**
The avatar-facing slice preserves the C# agent, movement/camera, appearance and
outfit-policy, inventory/AIS/store, asset/cache, avatar, and animesh concepts.
Long-running mapped calls retain an explicit cross-platform cancellation token
and return the shared `NotImplemented { csharp_member }` error; events return a
typed subscription guard so callback lifetime is visible to Rust callers.
No functional porting starts until both missing gates are complete across the
entire workspace, rather than one crate at a time:

View File

@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| Assembly | Types | Members | Status |
|---|---:|---:|---|
| `LibreMetaverse` | 2,711 | 27,281 | partial callable shim (2,320 types / 23,770 members) |
| `LibreMetaverse` | 2,711 | 27,281 | partial callable shim (2,513 types / 25,704 members) |
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | callable failure-only shim |
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | callable failure-only shim |
| `LibreMetaverse.LslTools` | 164 | 768 | pending milestone issue |
@@ -18,13 +18,14 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| `LibreMetaverse.Voice.Vivox` | 64 | 531 | pending milestone issue |
| `LibreMetaverse.Voice.WebRTC` | 12 | 210 | pending milestone issue |
The partial `LibreMetaverse` row contains the completed issue #7 wire/data slice and issue #8 core
runtime/networking slice. Avatar-facing and world/social/service manager types remain pending shells
for issues #9 and #10.
The partial `LibreMetaverse` row contains the completed issue #7 wire/data, issue #8 core
runtime/networking, and issue #9 avatar-facing manager slices. World/social/service manager types
remain pending shells for issue #10.
| Main assembly slice | Types | Members |
|---|---:|---:|
| Issue #7 wire/data | 2,209 | 22,805 |
| Issue #8 core runtime/networking | 111 | 965 |
| Issue #9 avatar-facing managers | 193 | 1,934 |
Current callable coverage: **2,404 types / 25,257 members**.
Current callable coverage: **2,597 types / 27,191 members**.

View File

@@ -130,3 +130,7 @@ pub struct StringWriter(pub String);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct TypeId(pub &'static str);
pub struct Span<T>(pub PhantomData<fn(T)>);
pub struct RandomSource;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
use libremetaverse::animesh::AnimeshManager;
use libremetaverse::appearance::CurrentOutfitFolder;
use libremetaverse::{
AgentManager, AppearanceManagerWearableData, AssetDownload, AssetManager, AssetUpload,
ChatEventArgs, ChatType, InventoryException, InventoryItem, InventoryManager,
TeleportEventArgs,
};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
use libremetaverse_types::{AssetType, AttachmentPoint, UUID};
fn member_id<T>(result: Result<T, libremetaverse::Error>) -> &'static str {
result
.err()
.expect("failure-only shim unexpectedly returned a value")
.csharp_member()
}
fn compile_bot_and_animation_calls(
agent: &AgentManager,
chat_type: ChatType,
animation: UUID,
chat_handler: EventHandler<ChatEventArgs>,
) {
let _: Subscription = agent.subscribe_chat_from_simulator(chat_handler);
let _ = agent.chat(String::new(), 0, chat_type, Some(true));
let _ = agent.animation_start(animation, true);
}
async fn compile_outfit_call(
outfit: &CurrentOutfitFolder,
item: InventoryItem,
point: AttachmentPoint,
token: CancellationToken,
) {
let _ = outfit.attach(item, point, true, Some(token)).await;
}
async fn compile_inventory_call(
inventory: &InventoryManager,
item_id: UUID,
owner_id: UUID,
token: CancellationToken,
) {
let _ = inventory
.request_fetch_inventory_with_uuid_uuid_cancellation_token_6647b02a(
item_id,
owner_id,
Some(token),
)
.await;
}
async fn compile_asset_download_call(
assets: &AssetManager,
asset_id: UUID,
asset_type: AssetType,
token: CancellationToken,
) {
let _ = assets
.request_asset_with_uuid_asset_type_boolean_cancellation_token(
asset_id,
asset_type,
true,
Some(token),
)
.await;
}
async fn compile_teleport_call(
agent: &AgentManager,
landmark: UUID,
token: CancellationToken,
handler: EventHandler<TeleportEventArgs>,
) {
let _: Subscription = agent.subscribe_teleport_progress(handler);
let _ = agent
.teleport_with_uuid_cancellation_token(landmark, Some(token))
.await;
}
fn compile_animesh_call(animesh: &AnimeshManager, object_id: UUID) {
let _ = animesh.get_player(object_id);
let _ = animesh.update(1.0 / 60.0);
}
#[test]
fn avatar_facing_flows_have_typed_callable_signatures() {
let _ = compile_bot_and_animation_calls;
let _ = compile_outfit_call;
let _ = compile_inventory_call;
let _ = compile_asset_download_call;
let _ = compile_teleport_call;
let _ = compile_animesh_call;
}
#[test]
fn avatar_facing_constructors_fail_with_catalog_ids() {
assert_eq!(
member_id(AssetDownload::new()),
"M:LibreMetaverse.AssetDownload.#ctor"
);
assert_eq!(
member_id(AssetUpload::new()),
"M:LibreMetaverse.AssetUpload.#ctor"
);
assert_eq!(
member_id(AppearanceManagerWearableData::new()),
"M:LibreMetaverse.AppearanceManager.WearableData.#ctor"
);
assert_eq!(
member_id(InventoryException::new_with_constructor()),
"M:LibreMetaverse.InventoryException.#ctor"
);
}

View File

@@ -157,6 +157,114 @@ CORE_ROOT_TYPES = {
"T:LibreMetaverse.XferDownload",
"T:LibreMetaverse.XferReceivedEventArgs",
}
AVATAR_NAMESPACES = (
"LibreMetaverse.Animesh",
"LibreMetaverse.Appearance",
)
AVATAR_TYPE_PREFIXES = (
"T:LibreMetaverse.Agent",
"T:LibreMetaverse.Animations",
"T:LibreMetaverse.AnimeshSkinning",
"T:LibreMetaverse.Appearance",
"T:LibreMetaverse.ArchetypeParam",
"T:LibreMetaverse.Asset",
"T:LibreMetaverse.Attention",
"T:LibreMetaverse.Avatar",
"T:LibreMetaverse.Inventory",
)
AVATAR_ROOT_TYPES = {
"T:LibreMetaverse.AISResponseMeta",
"T:LibreMetaverse.AccountLevelBenefits",
"T:LibreMetaverse.AlertMessageEventArgs",
"T:LibreMetaverse.BakeType",
"T:LibreMetaverse.BalanceEventArgs",
"T:LibreMetaverse.BinBVHAnimationReader",
"T:LibreMetaverse.BorderCrossingDirection",
"T:LibreMetaverse.CallingCardOfferedEventArgs",
"T:LibreMetaverse.CameraConstraintEventArgs",
"T:LibreMetaverse.ChannelType",
"T:LibreMetaverse.ChatAudibleLevel",
"T:LibreMetaverse.ChatEventArgs",
"T:LibreMetaverse.ChatSessionMember",
"T:LibreMetaverse.ChatSessionMemberAddedEventArgs",
"T:LibreMetaverse.ChatSessionMemberLeftEventArgs",
"T:LibreMetaverse.ChatSourceType",
"T:LibreMetaverse.ChatType",
"T:LibreMetaverse.ClassifiedAd",
"T:LibreMetaverse.ClassifiedInfoReplyEventArgs",
"T:LibreMetaverse.DeRezDestination",
"T:LibreMetaverse.DisplayNameUpdateEventArgs",
"T:LibreMetaverse.DrivenParamInfo",
"T:LibreMetaverse.EffectType",
"T:LibreMetaverse.ExperienceInfoEventArgs",
"T:LibreMetaverse.ExperiencePreferencesEventArgs",
"T:LibreMetaverse.FindObjectByPathReplyEventArgs",
"T:LibreMetaverse.FolderUpdatedEventArgs",
"T:LibreMetaverse.Genepool",
"T:LibreMetaverse.GenepoolArchetype",
"T:LibreMetaverse.GroupChatJoinedEventArgs",
"T:LibreMetaverse.HandPose",
"T:LibreMetaverse.InitiateDownloadEventArgs",
"T:LibreMetaverse.InstantMessage",
"T:LibreMetaverse.InstantMessageDialog",
"T:LibreMetaverse.InstantMessageEventArgs",
"T:LibreMetaverse.InstantMessageOnline",
"T:LibreMetaverse.ItemReceivedEventArgs",
"T:LibreMetaverse.LindenAttentions",
"T:LibreMetaverse.LoadUrlEventArgs",
"T:LibreMetaverse.LookAtType",
"T:LibreMetaverse.MeanCollisionEventArgs",
"T:LibreMetaverse.MeanCollisionType",
"T:LibreMetaverse.MoneyBalanceReplyEventArgs",
"T:LibreMetaverse.MoneyTransactionType",
"T:LibreMetaverse.MuteEntry",
"T:LibreMetaverse.MuteFlags",
"T:LibreMetaverse.MuteType",
"T:LibreMetaverse.NavMeshStatusUpdateEventArgs",
"T:LibreMetaverse.PayPriceReplyEventArgs",
"T:LibreMetaverse.PayPriceType",
"T:LibreMetaverse.PickInfoReplyEventArgs",
"T:LibreMetaverse.PointAtType",
"T:LibreMetaverse.ProductInfoEventArgs",
"T:LibreMetaverse.ProfileFlags",
"T:LibreMetaverse.ProfilePick",
"T:LibreMetaverse.RebakeAvatarTexturesEventArgs",
"T:LibreMetaverse.RegionCrossedEventArgs",
"T:LibreMetaverse.RegionCrossingPredictionEventArgs",
"T:LibreMetaverse.RegionExperiencesEventArgs",
"T:LibreMetaverse.SaveAssetToInventoryEventArgs",
"T:LibreMetaverse.ScriptControlChange",
"T:LibreMetaverse.ScriptControlEventArgs",
"T:LibreMetaverse.ScriptDialogEventArgs",
"T:LibreMetaverse.ScriptPermission",
"T:LibreMetaverse.ScriptQuestionEventArgs",
"T:LibreMetaverse.ScriptRunningReplyEventArgs",
"T:LibreMetaverse.ScriptSensorReplyEventArgs",
"T:LibreMetaverse.ScriptSensorTypeFlags",
"T:LibreMetaverse.SetDisplayNameReplyEventArgs",
"T:LibreMetaverse.SkeletalBoneInfo",
"T:LibreMetaverse.TaskInventoryReplyEventArgs",
"T:LibreMetaverse.TaskItemReceivedEventArgs",
"T:LibreMetaverse.TeleportEventArgs",
"T:LibreMetaverse.TeleportFlags",
"T:LibreMetaverse.TeleportLureFlags",
"T:LibreMetaverse.TeleportStatus",
"T:LibreMetaverse.TransactionFlags",
"T:LibreMetaverse.TransactionInfo",
"T:LibreMetaverse.UUIDNameReplyEventArgs",
"T:LibreMetaverse.ViewerBenefitsEventArgs",
"T:LibreMetaverse.ViewerEffectEventArgs",
"T:LibreMetaverse.ViewerEffectLookAtEventArgs",
"T:LibreMetaverse.ViewerEffectPointAtEventArgs",
"T:LibreMetaverse.VisualAlphaParam",
"T:LibreMetaverse.VisualColorOperation",
"T:LibreMetaverse.VisualColorParam",
"T:LibreMetaverse.VisualParam",
"T:LibreMetaverse.VisualParams",
"T:LibreMetaverse.VolumeMorphInfo",
"T:LibreMetaverse.binBVHJoint",
"T:LibreMetaverse.binBVHJointKey",
}
UNDERLYING = {
"System.SByte": "i8", "System.Byte": "u8", "System.Int16": "i16", "System.UInt16": "u16",
@@ -384,6 +492,14 @@ def core_type(item: dict) -> bool:
return item["doc_id"] in CORE_ROOT_TYPES or item["namespace"].startswith(CORE_NAMESPACES)
def avatar_type(item: dict) -> bool:
return (
item["doc_id"] in AVATAR_ROOT_TYPES
or item["doc_id"].startswith(AVATAR_TYPE_PREFIXES)
or item["namespace"].startswith(AVATAR_NAMESPACES)
)
def render_modules(items: list[tuple[dict, dict, bool]], member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str:
tree: dict = {"items": [], "children": {}}
for item, type_row, complete in items:
@@ -410,7 +526,7 @@ def render_modules(items: list[tuple[dict, dict, bool]], member_rows: dict[str,
def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[int, int, bool]]]:
type_rows, resolved = mapping.build_type_rows(catalog)
type_by_id = {row["csharp_type_id"]: row for row in type_rows}
configured_main_ids = WIRE_ROOT_TYPES | CORE_ROOT_TYPES
configured_main_ids = WIRE_ROOT_TYPES | CORE_ROOT_TYPES | AVATAR_ROOT_TYPES
missing_configured_ids = configured_main_ids - type_by_id.keys()
if missing_configured_ids:
raise ValueError(f"unknown configured main-slice IDs: {sorted(missing_configured_ids)}")
@@ -442,7 +558,7 @@ def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[in
selected = (
assembly["types"]
if name in TARGETS
else [item for item in assembly["types"] if wire_type(item) or core_type(item)]
else [item for item in assembly["types"] if wire_type(item) or core_type(item) or avatar_type(item)]
)
selected_ids = {item["doc_id"] for item in selected}
rendered_items = [
@@ -494,16 +610,22 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -
)
wire_types = [item for item in main_types if wire_type(item)]
core_types = [item for item in main_types if core_type(item) and not wire_type(item)]
avatar_types = [
item
for item in main_types
if avatar_type(item) and not wire_type(item) and not core_type(item)
]
lines += [
"",
"The partial `LibreMetaverse` row contains the completed issue #7 wire/data slice and issue #8 core",
"runtime/networking slice. Avatar-facing and world/social/service manager types remain pending shells",
"for issues #9 and #10.",
"The partial `LibreMetaverse` row contains the completed issue #7 wire/data, issue #8 core",
"runtime/networking, and issue #9 avatar-facing manager slices. World/social/service manager types",
"remain pending shells for issue #10.",
"",
"| Main assembly slice | Types | Members |",
"|---|---:|---:|",
f"| Issue #7 wire/data | {len(wire_types):,} | {sum(len(item['members']) for item in wire_types):,} |",
f"| Issue #8 core runtime/networking | {len(core_types):,} | {sum(len(item['members']) for item in core_types):,} |",
f"| Issue #9 avatar-facing managers | {len(avatar_types):,} | {sum(len(item['members']) for item in avatar_types):,} |",
]
lines += ["", f"Current callable coverage: **{total_types:,} types / {total_members:,} members**.", ""]
return "\n".join(lines)