Implement native client core lifecycle (#51)
Some checks failed
Native code generation / deterministic (push) Failing after 6m8s
Imaging and meshing gate / native (push) Failing after 17s
JPEG 2000 feature / linux (push) Failing after 59s
Skia feature / linux (push) Failing after 1m33s

This commit is contained in:
2026-08-09 10:53:33 +00:00
parent 167f469fc2
commit b413cd6f4d
11 changed files with 1470 additions and 79 deletions

View File

@@ -127,10 +127,86 @@ NATIVE_TYPES = {
# below. This is used for static namespace types such as OSDParser: the type is
# hand-written, while its fixed public methods remain generator-audited.
NATIVE_DECLARATIONS = {
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
"T:LibreMetaverse.Settings": "crate::client_core::Settings",
"T:LibreMetaverse.StructuredData.OSDParser": "crate::model::OSDParser",
}
NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.AgentSettings.#ctor":
"Ok(crate::client_core::agent_settings_defaults())",
"M:LibreMetaverse.AssetCacheSettings.#ctor":
"Ok(crate::client_core::asset_cache_settings_defaults())",
"M:LibreMetaverse.ConnectionSettings.#ctor":
"Ok(crate::client_core::connection_settings_defaults())",
"M:LibreMetaverse.LoggingSettings.#ctor":
"Ok(crate::client_core::logging_settings_defaults())",
"M:LibreMetaverse.PacketSettings.#ctor":
"Ok(crate::client_core::packet_settings_defaults())",
"M:LibreMetaverse.ParcelSettings.#ctor":
"Ok(crate::client_core::parcel_settings_defaults())",
"M:LibreMetaverse.TexturePipelineSettings.#ctor":
"Ok(crate::client_core::texture_pipeline_settings_defaults())",
"M:LibreMetaverse.TimingSettings.#ctor":
"Ok(crate::client_core::timing_settings_defaults())",
"M:LibreMetaverse.WorldSettings.#ctor":
"Ok(crate::client_core::world_settings_defaults())",
"M:LibreMetaverse.GridClient.#ctor":
"crate::client_core::GridClientBuilder::default().build().map_err(Into::into)",
"M:LibreMetaverse.GridClient.Dispose":
"self.shutdown().map_err(Into::into)",
"M:LibreMetaverse.GridClient.DisposeAsync":
"self.shutdown().map_err(Into::into)",
"M:LibreMetaverse.GridClient.ToString":
"String::new()",
"P:LibreMetaverse.GridClient.Settings":
"&mut self.settings",
"P:LibreMetaverse.GridClient.TimeProvider":
"self.time_provider.clone()",
"P:LibreMetaverse.GridClient.TimeProvider#set":
"self.time_provider = value",
"F:LibreMetaverse.Settings.BindAddress":
"std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)",
"F:LibreMetaverse.Settings.LogLevel":
"crate::logging::LogLevel(1)",
"F:LibreMetaverse.Settings.MaxHttpConnections":
"32",
"F:LibreMetaverse.Settings.PacketArchiveSize":
"1000",
"F:LibreMetaverse.Settings.ResourceDir":
'"linden".to_owned()',
"F:LibreMetaverse.Settings.SimulatorPoolTimeout":
"120_000",
"F:LibreMetaverse.Settings.TexturePipelineRefreshInterval":
"500.0",
"F:LibreMetaverse.Settings.UdpReceiveQueueCapacity":
"512",
"F:LibreMetaverse.Settings.UserAgent":
'"LibreMetaverse".to_owned()',
"M:LibreMetaverse.Settings.#ctor(LibreMetaverse.GridClient)":
"Ok(crate::client_core::Settings::defaults())",
"P:LibreMetaverse.Settings.Agent":
"self.agent.clone()",
"P:LibreMetaverse.Settings.AssetCache":
"self.asset_cache.clone()",
"P:LibreMetaverse.Settings.Connection":
"self.connection.clone()",
"P:LibreMetaverse.Settings.Logging":
"self.logging.clone()",
"P:LibreMetaverse.Settings.Packets":
"self.packets.clone()",
"P:LibreMetaverse.Settings.Parcel":
"self.parcel.clone()",
"P:LibreMetaverse.Settings.TexturePipeline":
"self.texture_pipeline.clone()",
"P:LibreMetaverse.Settings.Timing":
"&mut self.timing",
"P:LibreMetaverse.Settings.UploadCost":
"self.upload_cost",
"P:LibreMetaverse.Settings.UploadCost#set":
"self.upload_cost = value",
"P:LibreMetaverse.Settings.World":
"self.world.clone()",
"M:LibreMetaverse.Avatar.#ctor":
"crate::visual_catalog::new_avatar()",
"M:LibreMetaverse.Avatar.DecodeVisualParams":
@@ -243,6 +319,20 @@ NATIVE_MEMBER_BODIES = {
"crate::byte_order::write_double_little_endian(dest, pos, value)",
}
CLIENT_CORE_NATIVE_OWNERS = (
"AgentSettings",
"AssetCacheSettings",
"ConnectionSettings",
"GridClient",
"LoggingSettings",
"PacketSettings",
"ParcelSettings",
"Settings",
"TexturePipelineSettings",
"TimingSettings",
"WorldSettings",
)
NATIVE_OWNER_BOUNDS = {
"T:LibreMetaverse.CacheDictionary`2": {
"TKey": "TKey: Clone + PartialEq",
@@ -617,11 +707,20 @@ PRIVATE_LAYOUTS = {
"T:LibreMetaverse.StructuredData.OSDUri": [("value", "libremetaverse_types::compat::Uri")],
}
VALUE_DERIVES = {
"T:LibreMetaverse.AgentSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.AssetCacheSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.ConnectionSettings": "Clone, Eq, PartialEq",
"T:LibreMetaverse.LoggingSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.Matrix4": "Clone, Copy, Debug",
"T:LibreMetaverse.Quaternion": "Clone, Copy, Debug",
"T:LibreMetaverse.PacketSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.ParcelSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.TexturePipelineSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.TimingSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.UUID": "Clone, Copy, Debug, Default, Eq, Hash, PartialEq",
"T:LibreMetaverse.Vector3": "Clone, Copy, Debug",
"T:LibreMetaverse.Vector3d": "Clone, Copy, Debug",
"T:LibreMetaverse.WorldSettings": "Clone, Debug, Eq, PartialEq",
}
def generic_names(item: dict) -> list[str]:
return [parameter["name"] for parameter in item.get("generic_parameters", [])]
@@ -950,7 +1049,12 @@ def render_body(signature: str, member_id: str, error_model: str, asyncness: str
if trait:
signature = signature.removeprefix("pub ")
if body := native_member_body(member_id):
return f" {signature} {{ {body} }}"
marker = (
" /* native client-core implementation */"
if any(f"LibreMetaverse.{owner}." in member_id for owner in CLIENT_CORE_NATIVE_OWNERS)
else ""
)
return f" {signature} {{{marker} {body} }}"
failure = (
f"libremetaverse_types::not_implemented({json.dumps(member_id)})"
if error_model.startswith("Result")
@@ -998,6 +1102,10 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
native_declaration = NATIVE_DECLARATIONS.get(item["doc_id"])
if native_declaration:
lines.append(f"pub use {native_declaration} as {rust_name};")
lines.extend(
f"/// C# member: `{field['doc_id']}`."
for field in fields
)
elif derives := VALUE_DERIVES.get(item["doc_id"]):
lines.append(f"#[derive({derives})]")
if native_declaration:
@@ -1049,10 +1157,14 @@ 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']}`.")
body_member_id = member["doc_id"]
setter_id = body_member_id + "#set"
if index and setter_id in NATIVE_MEMBER_BODIES:
body_member_id = setter_id
lines.append(
render_body(
signature,
member["doc_id"],
body_member_id,
row["error_model"],
row["asyncness"],
trait,
@@ -1069,6 +1181,8 @@ def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str
# of receiving an invalid empty generated impl.
if f"T:{base}" in NATIVE_TYPES:
continue
if item["doc_id"] == "T:LibreMetaverse.GridClient" and base == "LibreMetaverse.IGridClient":
continue
mapped_arguments = [mapper.type(argument, None, set(names)) for argument in arguments]
trait_path = mapper.resolved[base] + generic_suffix(mapped_arguments)
lines.append(f"impl{suffix} {trait_path} for {rust_name}{suffix} {{}}")
@@ -1208,7 +1322,8 @@ def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[in
raise ValueError(
f"generated catalog ID mismatch for {name}: "
f"types missing/stale={len(expected_types - emitted_types)}/{len(emitted_types - expected_types)}, "
f"members missing/stale={len(expected_members - emitted_members)}/{len(emitted_members - expected_members)}"
f"members missing/stale={len(expected_members - emitted_members)}/{len(emitted_members - expected_members)}; "
f"missing={sorted(expected_members - emitted_members)}, stale={sorted(emitted_members - expected_members)}"
)
outputs[TARGETS.get(name, MAIN_TARGET)] = source
coverage[name] = (len(selected), sum(len(item["members"]) for item in selected), len(selected) == len(assembly["types"]))
@@ -1240,7 +1355,18 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -
member["doc_id"]
for item in assembly["types"]
for member in item["members"]
if item["doc_id"] in native_type_ids or native_member_body(member["doc_id"]) is not None
if item["doc_id"] in NATIVE_TYPES
or (name == "LibreMetaverse.Types" and item["kind"] == "enum")
or (
item["doc_id"] in NATIVE_DECLARATIONS
and member["kind"] == "field"
and not member.get("static")
)
or (
item["doc_id"] in NATIVE_DECLARATIONS
and member["kind"] == "constant"
)
or native_member_body(member["doc_id"]) is not None
}
if native_type_ids or native_member_ids:
type_noun = "type" if len(native_type_ids) == 1 else "types"