Generate core runtime API shims

This commit is contained in:
2026-08-08 12:28:41 +02:00
parent 7086af9a6f
commit a007313785
12 changed files with 7223 additions and 536 deletions

View File

@@ -66,6 +66,97 @@ WIRE_ROOT_TYPES = {
"T:LibreMetaverse.Tree",
"T:LibreMetaverse.TreeDefinition",
}
CORE_NAMESPACES = (
"LibreMetaverse.Http",
"LibreMetaverse.Stats",
"LibreMetaverse.Threading",
)
CORE_ROOT_TYPES = {
"T:LibreMetaverse.CapabilitiesReceivedEventArgs",
"T:LibreMetaverse.Caps",
"T:LibreMetaverse.Caps.EventQueueCallback",
"T:LibreMetaverse.CapsCategory",
"T:LibreMetaverse.CapsEventDictionary",
"T:LibreMetaverse.CapsRateLimiter",
"T:LibreMetaverse.CapsRateLimiterOptions",
"T:LibreMetaverse.ConnectionSettings",
"T:LibreMetaverse.DisconnectedEventArgs",
"T:LibreMetaverse.DisposalHelper.DisposalGuard",
"T:LibreMetaverse.DisposalHelper",
"T:LibreMetaverse.EventQueueRunningEventArgs",
"T:LibreMetaverse.EventSubscription`1",
"T:LibreMetaverse.EventSubscriptionHelper",
"T:LibreMetaverse.FileHelper",
"T:LibreMetaverse.DictionaryChangeCallback",
"T:LibreMetaverse.DictionaryEventAction",
"T:LibreMetaverse.GenericStreamingMessageEventArgs",
"T:LibreMetaverse.GenericStreamingMethod",
"T:LibreMetaverse.GridClient",
"T:LibreMetaverse.GridClientBakingTextureProvider",
"T:LibreMetaverse.GridClientServiceCollectionExtensions",
"T:LibreMetaverse.Helpers",
"T:LibreMetaverse.HomeInfo",
"T:LibreMetaverse.HttpCapsClient",
"T:LibreMetaverse.IBakingTextureProvider",
"T:LibreMetaverse.IGridClient",
"T:LibreMetaverse.ImageCodec",
"T:LibreMetaverse.ImageDownload",
"T:LibreMetaverse.ImageReceiveProgressEventArgs",
"T:LibreMetaverse.ImageRequest",
"T:LibreMetaverse.ImageType",
"T:LibreMetaverse.IncomingPacketIDCollection",
"T:LibreMetaverse.Interfaces.IByteBufferPool",
"T:LibreMetaverse.Logger",
"T:LibreMetaverse.Logger.LogCallback",
"T:LibreMetaverse.LoggingSettings",
"T:LibreMetaverse.LoginCredential",
"T:LibreMetaverse.LoginException",
"T:LibreMetaverse.LoginParams",
"T:LibreMetaverse.LoginProgressEventArgs",
"T:LibreMetaverse.LoginResponseData",
"T:LibreMetaverse.LoginResponseData.ParseMessage",
"T:LibreMetaverse.LoginResponseData.ParseMessageLevel",
"T:LibreMetaverse.LoginResponseData.ParseResult",
"T:LibreMetaverse.LoginState",
"T:LibreMetaverse.LoginStatus",
"T:LibreMetaverse.LoggedOutEventArgs",
"T:LibreMetaverse.NetworkManager",
"T:LibreMetaverse.NetworkManager.DisconnectType",
"T:LibreMetaverse.NetworkManager.IncomingPacket",
"T:LibreMetaverse.NetworkManager.LoginResponseCallback",
"T:LibreMetaverse.NetworkManager.OutgoingPacket",
"T:LibreMetaverse.LastExecStatus",
"T:LibreMetaverse.ObservableDictionary`2",
"T:LibreMetaverse.PacketEventDictionary",
"T:LibreMetaverse.PacketReceivedEventArgs",
"T:LibreMetaverse.PacketSentEventArgs",
"T:LibreMetaverse.PacketSettings",
"T:LibreMetaverse.HttpCapsClient.ProgressReport",
"T:LibreMetaverse.ProtocolManager",
"T:LibreMetaverse.Repeat",
"T:LibreMetaverse.Settings",
"T:LibreMetaverse.SimChangedEventArgs",
"T:LibreMetaverse.SimConnectedEventArgs",
"T:LibreMetaverse.SimConnectingEventArgs",
"T:LibreMetaverse.SimDisconnectedEventArgs",
"T:LibreMetaverse.Simulator",
"T:LibreMetaverse.Simulator.SimStats",
"T:LibreMetaverse.SimulatorDataPool",
"T:LibreMetaverse.SimulatorFeatures",
"T:LibreMetaverse.SourceType",
"T:LibreMetaverse.StatusCode",
"T:LibreMetaverse.TargetType",
"T:LibreMetaverse.TexturePipeline",
"T:LibreMetaverse.TexturePipelineSettings",
"T:LibreMetaverse.TextureRequestState",
"T:LibreMetaverse.TimingSettings",
"T:LibreMetaverse.Transfer",
"T:LibreMetaverse.TransferError",
"T:LibreMetaverse.UDPBase",
"T:LibreMetaverse.UDPPacketBuffer",
"T:LibreMetaverse.XferDownload",
"T:LibreMetaverse.XferReceivedEventArgs",
}
UNDERLYING = {
"System.SByte": "i8", "System.Byte": "u8", "System.Int16": "i16", "System.UInt16": "u16",
@@ -98,6 +189,10 @@ PRIVATE_LAYOUTS = {
"T:LibreMetaverse.StructuredData.OSDUri": [("value", "libremetaverse_types::compat::Uri")],
"T:LibreMetaverse.Imaging.ManagedImage": [("data", "Vec<u8>")],
}
BOXED_FIELDS = {
"F:LibreMetaverse.Caps.Simulator",
"F:LibreMetaverse.Simulator.Caps",
}
def generic_names(item: dict) -> list[str]:
@@ -162,7 +257,7 @@ def render_enum(item: dict, rust_name: str) -> list[str]:
return lines
def render_body(signature: str, member_id: str, error_model: str, trait: bool) -> str:
def render_body(signature: str, member_id: str, error_model: str, asyncness: str, trait: bool) -> str:
if trait:
signature = signature.removeprefix("pub ")
failure = (
@@ -170,6 +265,8 @@ def render_body(signature: str, member_id: str, error_model: str, trait: bool) -
if error_model.startswith("Result")
else f"libremetaverse_types::unimplemented_api!({json.dumps(member_id)})"
)
if asyncness == "boxed_future":
failure = f"Box::pin(async {{ {failure} }})"
return f" {signature} {{ {failure} }}"
@@ -212,6 +309,12 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
generics = set(names)
for field in fields:
field_type = mapper.type(field["type"], field.get("nullability"), generics)
if field["doc_id"] in BOXED_FIELDS:
field_type = (
f"Option<Box<{field_type[7:-1]}>>"
if field_type.startswith("Option<")
else f"Box<{field_type}>"
)
lines += [f" /// C# member: `{field['doc_id']}`.", f" pub {mapping.snake(field['name'])}: {field_type},"]
for field_name, field_type in private_fields:
lines.append(f" {field_name}: {field_type},")
@@ -233,7 +336,15 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
for index, signature in enumerate(signatures):
if index:
lines.append(f" /// Setter for C# member: `{member['doc_id']}`.")
lines.append(render_body(signature, member["doc_id"], row["error_model"], trait))
lines.append(
render_body(
signature,
member["doc_id"],
row["error_model"],
row["asyncness"],
trait,
)
)
lines.append("}")
if not trait:
for interface in item.get("interfaces", []):
@@ -269,6 +380,10 @@ def wire_type(item: dict) -> bool:
)
def core_type(item: dict) -> bool:
return item["doc_id"] in CORE_ROOT_TYPES or item["namespace"].startswith(CORE_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:
@@ -295,6 +410,10 @@ 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
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)}")
interfaces = {
*{mapping.type_parts(item["signature"])[0] for assembly in catalog["assemblies"] for item in assembly["types"] if item["kind"] == "interface"},
*{
@@ -320,7 +439,11 @@ def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[in
"#![allow(non_snake_case)]",
"",
]
selected = assembly["types"] if name in TARGETS else [item for item in assembly["types"] if wire_type(item)]
selected = (
assembly["types"]
if name in TARGETS
else [item for item in assembly["types"] if wire_type(item) or core_type(item)]
)
selected_ids = {item["doc_id"] for item in selected}
rendered_items = [
(item, type_by_id[item["doc_id"]], item["doc_id"] in selected_ids)
@@ -364,11 +487,23 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -
status = "pending milestone issue"
lines.append(f"| `{name}` | {types:,} | {members:,} | {status} |")
if "LibreMetaverse" in coverage and not coverage["LibreMetaverse"][2]:
main_types = next(
assembly["types"]
for assembly in catalog["assemblies"]
if assembly["identity"]["name"] == "LibreMetaverse"
)
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)]
lines += [
"",
"The partial `LibreMetaverse` row is milestone issue #7's wire/data slice: packets, messages, assets,",
"GLTF, archives/import-export, materials, rendering DTOs, bit packing, permissions, primitives, and",
"texture/media/particle records. Other main-assembly types remain pending shells for issues #8-#10.",
"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.",
"",
"| 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):,} |",
]
lines += ["", f"Current callable coverage: **{total_types:,} types / {total_members:,} members**.", ""]
return "\n".join(lines)

View File

@@ -125,10 +125,10 @@ STDLIB = {
"System.Net.IPAddress": "std::net::IpAddr",
"System.Net.IPEndPoint": "std::net::SocketAddr",
"System.Net.EndPoint": "std::net::SocketAddr",
"System.Net.Http.HttpClient": "libremetaverse::http::Client",
"System.Net.Http.HttpResponseMessage": "libremetaverse::http::Response",
"System.Net.Http.HttpMethod": "libremetaverse::http::Method",
"System.Net.Http.Headers.AuthenticationHeaderValue": "libremetaverse::http::Authentication",
"System.Net.Http.HttpClient": "libremetaverse_types::compat::HttpClient",
"System.Net.Http.HttpResponseMessage": "libremetaverse_types::compat::HttpResponse",
"System.Net.Http.HttpMethod": "libremetaverse_types::compat::HttpMethod",
"System.Net.Http.Headers.AuthenticationHeaderValue": "libremetaverse_types::compat::AuthenticationHeaderValue",
"System.Net.Sockets.Socket": "libremetaverse::net::Socket",
"System.Net.Sockets.TcpClient": "libremetaverse::net::TcpStream",
"System.Net.Sockets.UdpClient": "libremetaverse::net::UdpSocket",
@@ -167,6 +167,9 @@ SUPPORT_TYPES = {
"LibreMetaverse.RLV.IBlacklistProvider": ("libremetaverse-rlv", "provider", "libremetaverse_rlv::provider::BlacklistProvider"),
"LibreMetaverse.RLV.IRestrictionProvider": ("libremetaverse-rlv", "provider", "libremetaverse_rlv::provider::RestrictionProvider"),
}
MEMBER_RETURN_OVERRIDES = {
"M:LibreMetaverse.Caps.Capabilities": "Vec<String>",
}
RUST_KEYWORDS = {
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
@@ -501,7 +504,9 @@ class Mapper:
target = substitute(target, mapped_arguments)
if "<" not in target and not target.startswith("(") and target not in {"Ord", "Eq", "Clone", "std::fmt::Display"}:
target += "<" + ", ".join(mapped_arguments) + ">"
if base in self.interfaces:
if base == "System.IDisposable":
target = f"Box<dyn {target}>"
elif base in self.interfaces or base == "System.IProgress":
target = f"Box<dyn {target}>"
for _ in re.findall(r"\[[,]*\]", suffix):
target = f"Vec<{target}>"
@@ -613,14 +618,16 @@ def member_signature(mapper: Mapper, owner: dict, item: dict, rust_name: str) ->
return " ; ".join(pieces), ",".join([*self_ownership, *own]) or "none", asyncness, error_model, "accessor_methods"
if kind == "event":
event_type = mapper.type(item["type"], item.get("nullability"), generics)
return f"pub fn subscribe_{rust_name}(&self, handler: {event_type}) -> Subscription", "stored_callback", asyncness, error_model, "subscription_guard"
return f"pub fn subscribe_{rust_name}(&self, handler: {event_type}) -> libremetaverse_types::compat::Subscription", "stored_callback", asyncness, error_model, "subscription_guard"
self_argument, self_ownership = method_receiver(owner, item)
arguments = ", ".join(filter(None, [self_argument, parameters]))
ownership = ",".join(([self_ownership] if self_ownership != "none" else []) + own) or "none"
raw_return = item.get("type", "System.Void")
return_type = mapper.type(raw_return, item.get("nullability"), generics)
return_type = MEMBER_RETURN_OVERRIDES.get(
item["doc_id"], mapper.type(raw_return, item.get("nullability"), generics)
)
if raw_return.startswith(("System.Threading.Tasks.Task", "System.Threading.Tasks.ValueTask")):
asyncness = "async"
asyncness = "boxed_future" if owner["kind"] == "interface" else "async"
base, task_arguments, _ = type_parts(raw_return)
child = item.get("nullability", {}).get("generic_arguments", [])
return_type = mapper.type(task_arguments[0], child[0] if child else None, generics) if task_arguments else "()"
@@ -642,6 +649,11 @@ def member_signature(mapper: Mapper, owner: dict, item: dict, rust_name: str) ->
bounds.append("Default")
generic_declarations.append(parameter["name"] + ((": " + " + ".join(bounds)) if bounds else ""))
generic_suffix = "<" + ", ".join(generic_declarations) + ">" if generic_declarations else ""
if asyncness == "boxed_future":
return_type = (
"std::pin::Pin<Box<dyn std::future::Future<Output = "
f"{return_type}> + Send + '_>>"
)
prefix = "pub async fn" if asyncness == "async" else "pub fn"
return f"{prefix} {rust_name}{generic_suffix}({arguments}) -> {return_type}", ownership, asyncness, error_model, "method"