Generate core runtime API shims
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user