Complete networking integration gate (#58)
This commit is contained in:
@@ -12,6 +12,7 @@ on:
|
|||||||
- "tools/generate_*.py"
|
- "tools/generate_*.py"
|
||||||
- "tools/check_api_coverage.py"
|
- "tools/check_api_coverage.py"
|
||||||
- "tools/check_codegen_gate.py"
|
- "tools/check_codegen_gate.py"
|
||||||
|
- "tools/check_milestone_08.py"
|
||||||
- "crates/libremetaverse/Cargo.toml"
|
- "crates/libremetaverse/Cargo.toml"
|
||||||
- "crates/libremetaverse/src/generated.rs"
|
- "crates/libremetaverse/src/generated.rs"
|
||||||
- "crates/libremetaverse/src/packets.rs"
|
- "crates/libremetaverse/src/packets.rs"
|
||||||
@@ -35,6 +36,7 @@ on:
|
|||||||
- "tools/generate_*.py"
|
- "tools/generate_*.py"
|
||||||
- "tools/check_api_coverage.py"
|
- "tools/check_api_coverage.py"
|
||||||
- "tools/check_codegen_gate.py"
|
- "tools/check_codegen_gate.py"
|
||||||
|
- "tools/check_milestone_08.py"
|
||||||
- "crates/libremetaverse/Cargo.toml"
|
- "crates/libremetaverse/Cargo.toml"
|
||||||
- "crates/libremetaverse/src/generated.rs"
|
- "crates/libremetaverse/src/generated.rs"
|
||||||
- "crates/libremetaverse/src/packets.rs"
|
- "crates/libremetaverse/src/packets.rs"
|
||||||
@@ -66,6 +68,7 @@ jobs:
|
|||||||
python3 tools/generate_rust_mapping.py --check
|
python3 tools/generate_rust_mapping.py --check
|
||||||
python3 tools/generate_api_shims.py --check
|
python3 tools/generate_api_shims.py --check
|
||||||
python3 tools/check_api_coverage.py
|
python3 tools/check_api_coverage.py
|
||||||
|
python3 tools/check_milestone_08.py
|
||||||
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
|
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
|
||||||
- name: Test generated wire codecs and translated parity cases
|
- name: Test generated wire codecs and translated parity cases
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -317,3 +317,10 @@ bounded long polls preserve ack IDs, reconnect retries, shutdown `done`, and
|
|||||||
event order. Typed Linden messages share the CAPS dispatch registry; unknown
|
event order. Typed Linden messages share the CAPS dispatch registry; unknown
|
||||||
messages remain observable and then use the generated caps-to-packet catalog so
|
messages remain observable and then use the generated caps-to-packet catalog so
|
||||||
packet handlers receive them through the same bounded pipeline as UDP traffic.
|
packet handlers receive them through the same bounded pipeline as UDP traffic.
|
||||||
|
The milestone gate now drives login, UDP, seed discovery, event-queue region
|
||||||
|
enablement, capability download, simulator handoff, and logout through one
|
||||||
|
offline fake grid. Native `Simulator.SimStats` uses atomic C#-compatible
|
||||||
|
counters and receives real UDP and server-stat updates without exposing packet
|
||||||
|
payloads or endpoints. The gate asserts token redaction, bounded policies, and
|
||||||
|
joined CAPS/download/network tasks; `tools/check_milestone_08.py` prevents owned
|
||||||
|
networking stubs or required audit evidence from regressing.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
|
|||||||
|
|
||||||
| Assembly | Types | Members | Status |
|
| Assembly | Types | Members | Status |
|
||||||
|---|---:|---:|---|
|
|---|---:|---:|---|
|
||||||
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 78 types / 14,072 members; remaining surface is callable failure-only shims |
|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 79 types / 14,127 members; remaining surface is callable failure-only shims |
|
||||||
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
|
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
|
||||||
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
|
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
|
||||||
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
|
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
|
||||||
|
|||||||
@@ -319,6 +319,24 @@ impl DownloadManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of URI-deduplicated downloads currently queued or executing.
|
||||||
|
#[must_use]
|
||||||
|
pub fn active_download_count(&self) -> usize {
|
||||||
|
mutex(&self.0.active).len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the dispatcher has completed its idempotent teardown.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_disposed(&self) -> bool {
|
||||||
|
self.0.disposed.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a dispatcher thread is still owned by this manager.
|
||||||
|
#[must_use]
|
||||||
|
pub fn dispatcher_running(&self) -> bool {
|
||||||
|
mutex(&self.0.dispatcher).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn parallel_downloads(&self) -> i32 {
|
pub fn parallel_downloads(&self) -> i32 {
|
||||||
i32::try_from(self.0.parallel_downloads.load(Ordering::Acquire)).unwrap_or(i32::MAX)
|
i32::try_from(self.0.parallel_downloads.load(Ordering::Acquire)).unwrap_or(i32::MAX)
|
||||||
|
|||||||
@@ -23945,393 +23945,62 @@ impl Simulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// C# type: `T:LibreMetaverse.Simulator.SimStats`.
|
/// C# type: `T:LibreMetaverse.Simulator.SimStats`.
|
||||||
pub struct SimulatorSimStats;
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.#ctor`.
|
||||||
impl SimulatorSimStats {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.AddRecvBytes(System.Int64)`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.#ctor`.
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.AddSentBytes(System.Int64)`.
|
||||||
pub fn new() -> Result<Self, crate::Error> {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetAndIncrementLastPingID`.
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.#ctor")
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetConnectTime`.
|
||||||
}
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetDroppedPackets`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.AddRecvBytes(System.Int64)`.
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetIncomingBPS`.
|
||||||
pub fn add_recv_bytes(&self, v: i64) -> Result<(), crate::Error> {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetLastPingSent`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetOutgoingBPS`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.AddRecvBytes(System.Int64)",
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetReceivedPongs`.
|
||||||
)
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetReceivedResends`.
|
||||||
}
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetRecvBytes`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.AddSentBytes(System.Int64)`.
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetRecvPackets`.
|
||||||
pub fn add_sent_bytes(&self, v: i64) -> Result<(), crate::Error> {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetResentPackets`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentBytes`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.AddSentBytes(System.Int64)",
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentPackets`.
|
||||||
)
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentPings`.
|
||||||
}
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementDroppedPackets`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetAndIncrementLastPingID`.
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementReceivedPongs`.
|
||||||
pub fn get_and_increment_last_ping_id(&self) -> Result<i32, crate::Error> {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementReceivedResends`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementRecvPackets`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.GetAndIncrementLastPingID",
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementResentPackets`.
|
||||||
)
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementSentPackets`.
|
||||||
}
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementSentPings`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetConnectTime`.
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetConnectTime(System.Int32)`.
|
||||||
pub fn get_connect_time(&self) -> Result<i32, crate::Error> {
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetIncomingBPS(System.Int32)`.
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetConnectTime")
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetLastPingSent(System.Int32)`.
|
||||||
}
|
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetOutgoingBPS(System.Int32)`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetDroppedPackets`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ActiveScripts`.
|
||||||
pub fn get_dropped_packets(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.AgentTime`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.AgentUpdates`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.GetDroppedPackets",
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Agents`.
|
||||||
)
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ChildAgents`.
|
||||||
}
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Dilation`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetIncomingBPS`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.FPS`.
|
||||||
pub fn get_incoming_bps(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.FrameTime`.
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetIncomingBPS")
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.INPPS`.
|
||||||
}
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ImageTime`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetLastPingSent`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.LSLIPS`.
|
||||||
pub fn get_last_ping_sent(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.LastLag`.
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetLastPingSent")
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.MissedPings`.
|
||||||
}
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.NetTime`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetOutgoingBPS`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.OUTPPS`.
|
||||||
pub fn get_outgoing_bps(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Objects`.
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetOutgoingBPS")
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.OtherTime`.
|
||||||
}
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingDownloads`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetReceivedPongs`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingLocalUploads`.
|
||||||
pub fn get_received_pongs(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingUploads`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsFPS`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.GetReceivedPongs",
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsTime`.
|
||||||
)
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ResidentSize`.
|
||||||
}
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptTime`.
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetReceivedResends`.
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptedObjects`.
|
||||||
pub fn get_received_resends(&self) -> Result<i32, crate::Error> {
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.UnackedBytes`.
|
||||||
libremetaverse_types::not_implemented(
|
/// C# member: `P:LibreMetaverse.Simulator.SimStats.VirtualSize`.
|
||||||
"M:LibreMetaverse.Simulator.SimStats.GetReceivedResends",
|
pub use crate::sim_stats::SimulatorSimStats;
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetRecvBytes`.
|
|
||||||
pub fn get_recv_bytes(&self) -> Result<i64, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetRecvBytes")
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetRecvPackets`.
|
|
||||||
pub fn get_recv_packets(&self) -> Result<i64, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetRecvPackets")
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetResentPackets`.
|
|
||||||
pub fn get_resent_packets(&self) -> Result<i32, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.GetResentPackets",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentBytes`.
|
|
||||||
pub fn get_sent_bytes(&self) -> Result<i64, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetSentBytes")
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentPackets`.
|
|
||||||
pub fn get_sent_packets(&self) -> Result<i64, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetSentPackets")
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.GetSentPings`.
|
|
||||||
pub fn get_sent_pings(&self) -> Result<i32, crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Simulator.SimStats.GetSentPings")
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementDroppedPackets`.
|
|
||||||
pub fn increment_dropped_packets(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementDroppedPackets",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementReceivedPongs`.
|
|
||||||
pub fn increment_received_pongs(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementReceivedPongs",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementReceivedResends`.
|
|
||||||
pub fn increment_received_resends(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementReceivedResends",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementRecvPackets`.
|
|
||||||
pub fn increment_recv_packets(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementRecvPackets",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementResentPackets`.
|
|
||||||
pub fn increment_resent_packets(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementResentPackets",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementSentPackets`.
|
|
||||||
pub fn increment_sent_packets(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementSentPackets",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.IncrementSentPings`.
|
|
||||||
pub fn increment_sent_pings(&self) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.IncrementSentPings",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetConnectTime(System.Int32)`.
|
|
||||||
pub fn set_connect_time(&self, v: i32) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.SetConnectTime(System.Int32)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetIncomingBPS(System.Int32)`.
|
|
||||||
pub fn set_incoming_bps(&self, v: i32) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.SetIncomingBPS(System.Int32)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetLastPingSent(System.Int32)`.
|
|
||||||
pub fn set_last_ping_sent(&self, v: i32) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.SetLastPingSent(System.Int32)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `M:LibreMetaverse.Simulator.SimStats.SetOutgoingBPS(System.Int32)`.
|
|
||||||
pub fn set_outgoing_bps(&self, v: i32) -> Result<(), crate::Error> {
|
|
||||||
libremetaverse_types::not_implemented(
|
|
||||||
"M:LibreMetaverse.Simulator.SimStats.SetOutgoingBPS(System.Int32)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ActiveScripts`.
|
|
||||||
pub fn active_scripts(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.ActiveScripts"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ActiveScripts`.
|
|
||||||
pub fn set_active_scripts(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.ActiveScripts"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.AgentTime`.
|
|
||||||
pub fn agent_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.AgentTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.AgentTime`.
|
|
||||||
pub fn set_agent_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.AgentTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.AgentUpdates`.
|
|
||||||
pub fn agent_updates(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.AgentUpdates")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.AgentUpdates`.
|
|
||||||
pub fn set_agent_updates(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.AgentUpdates")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Agents`.
|
|
||||||
pub fn agents(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Agents")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.Agents`.
|
|
||||||
pub fn set_agents(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Agents")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ChildAgents`.
|
|
||||||
pub fn child_agents(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ChildAgents")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ChildAgents`.
|
|
||||||
pub fn set_child_agents(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ChildAgents")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Dilation`.
|
|
||||||
pub fn dilation(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Dilation")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.Dilation`.
|
|
||||||
pub fn set_dilation(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Dilation")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.FPS`.
|
|
||||||
pub fn fps(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.FPS")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.FPS`.
|
|
||||||
pub fn set_fps(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.FPS")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.FrameTime`.
|
|
||||||
pub fn frame_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.FrameTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.FrameTime`.
|
|
||||||
pub fn set_frame_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.FrameTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.INPPS`.
|
|
||||||
pub fn inpps(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.INPPS")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.INPPS`.
|
|
||||||
pub fn set_inpps(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.INPPS")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ImageTime`.
|
|
||||||
pub fn image_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ImageTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ImageTime`.
|
|
||||||
pub fn set_image_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ImageTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.LSLIPS`.
|
|
||||||
pub fn lslips(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.LSLIPS")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.LSLIPS`.
|
|
||||||
pub fn set_lslips(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.LSLIPS")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.LastLag`.
|
|
||||||
pub fn last_lag(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.LastLag")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.LastLag`.
|
|
||||||
pub fn set_last_lag(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.LastLag")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.MissedPings`.
|
|
||||||
pub fn missed_pings(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.MissedPings")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.MissedPings`.
|
|
||||||
pub fn set_missed_pings(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.MissedPings")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.NetTime`.
|
|
||||||
pub fn net_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.NetTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.NetTime`.
|
|
||||||
pub fn set_net_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.NetTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.OUTPPS`.
|
|
||||||
pub fn outpps(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.OUTPPS")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.OUTPPS`.
|
|
||||||
pub fn set_outpps(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.OUTPPS")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.Objects`.
|
|
||||||
pub fn objects(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Objects")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.Objects`.
|
|
||||||
pub fn set_objects(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.Objects")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.OtherTime`.
|
|
||||||
pub fn other_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.OtherTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.OtherTime`.
|
|
||||||
pub fn set_other_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.OtherTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingDownloads`.
|
|
||||||
pub fn pending_downloads(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingDownloads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.PendingDownloads`.
|
|
||||||
pub fn set_pending_downloads(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingDownloads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingLocalUploads`.
|
|
||||||
pub fn pending_local_uploads(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingLocalUploads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.PendingLocalUploads`.
|
|
||||||
pub fn set_pending_local_uploads(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingLocalUploads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PendingUploads`.
|
|
||||||
pub fn pending_uploads(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingUploads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.PendingUploads`.
|
|
||||||
pub fn set_pending_uploads(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.PendingUploads"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsFPS`.
|
|
||||||
pub fn physics_fps(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.PhysicsFPS")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsFPS`.
|
|
||||||
pub fn set_physics_fps(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.PhysicsFPS")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsTime`.
|
|
||||||
pub fn physics_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.PhysicsTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.PhysicsTime`.
|
|
||||||
pub fn set_physics_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.PhysicsTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ResidentSize`.
|
|
||||||
pub fn resident_size(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ResidentSize")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ResidentSize`.
|
|
||||||
pub fn set_resident_size(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ResidentSize")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptTime`.
|
|
||||||
pub fn script_time(&self) -> f32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ScriptTime")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptTime`.
|
|
||||||
pub fn set_script_time(&mut self, value: f32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.ScriptTime")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptedObjects`.
|
|
||||||
pub fn scripted_objects(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.ScriptedObjects"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.ScriptedObjects`.
|
|
||||||
pub fn set_scripted_objects(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!(
|
|
||||||
"P:LibreMetaverse.Simulator.SimStats.ScriptedObjects"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.UnackedBytes`.
|
|
||||||
pub fn unacked_bytes(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.UnackedBytes")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.UnackedBytes`.
|
|
||||||
pub fn set_unacked_bytes(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.UnackedBytes")
|
|
||||||
}
|
|
||||||
/// C# member: `P:LibreMetaverse.Simulator.SimStats.VirtualSize`.
|
|
||||||
pub fn virtual_size(&self) -> i32 {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.VirtualSize")
|
|
||||||
}
|
|
||||||
/// Setter for C# member: `P:LibreMetaverse.Simulator.SimStats.VirtualSize`.
|
|
||||||
pub fn set_virtual_size(&mut self, value: i32) {
|
|
||||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Simulator.SimStats.VirtualSize")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// C# type: `T:LibreMetaverse.SimulatorDataPool`.
|
/// C# type: `T:LibreMetaverse.SimulatorDataPool`.
|
||||||
pub struct SimulatorDataPool {
|
pub struct SimulatorDataPool {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ mod network_manager;
|
|||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub mod packet_catalog;
|
pub mod packet_catalog;
|
||||||
mod packet_wire;
|
mod packet_wire;
|
||||||
|
mod sim_stats;
|
||||||
mod skeleton;
|
mod skeleton;
|
||||||
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
|
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
|
||||||
mod skeleton_catalog;
|
mod skeleton_catalog;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::packets::{
|
|||||||
AgentPausePacket, AgentResumePacket, CloseCircuitPacket, CompleteAgentMovementPacket,
|
AgentPausePacket, AgentResumePacket, CloseCircuitPacket, CompleteAgentMovementPacket,
|
||||||
CompletePingCheckPacket, GenericStreamingMessagePacket, KickUserPacket, LogoutReplyPacket,
|
CompletePingCheckPacket, GenericStreamingMessagePacket, KickUserPacket, LogoutReplyPacket,
|
||||||
LogoutRequestPacket, Packet, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
LogoutRequestPacket, Packet, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
||||||
StartPingCheckPacket, UseCircuitCodePacket,
|
SimStatsPacket, StartPingCheckPacket, UseCircuitCodePacket,
|
||||||
};
|
};
|
||||||
use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig};
|
use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -35,7 +35,7 @@ use std::fmt;
|
|||||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicU64, Ordering};
|
||||||
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender, sync_channel};
|
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender, sync_channel};
|
||||||
use std::sync::{Arc, Condvar, Mutex, RwLock, Weak};
|
use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock, Weak};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -96,6 +96,13 @@ fn positive_millisecond_duration(value: i32) -> Duration {
|
|||||||
Duration::from_millis(u64::try_from(value.max(1)).unwrap_or(1))
|
Duration::from_millis(u64::try_from(value.max(1)).unwrap_or(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn environment_tick_count() -> i32 {
|
||||||
|
static START: OnceLock<std::time::Instant> = OnceLock::new();
|
||||||
|
let elapsed = START.get_or_init(std::time::Instant::now).elapsed();
|
||||||
|
let wrapped = u32::try_from(elapsed.as_millis() & u128::from(u32::MAX)).unwrap_or_default();
|
||||||
|
wrapped.cast_signed()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum LogoutWaitOutcome {
|
enum LogoutWaitOutcome {
|
||||||
Reply,
|
Reply,
|
||||||
@@ -1011,7 +1018,6 @@ pub struct SimulatorData {
|
|||||||
manager: Mutex<Weak<NetworkManagerInner>>,
|
manager: Mutex<Weak<NetworkManagerInner>>,
|
||||||
transport: UDPBase,
|
transport: UDPBase,
|
||||||
transport_thread: Mutex<Option<TransportThread>>,
|
transport_thread: Mutex<Option<TransportThread>>,
|
||||||
ping_id: AtomicU32,
|
|
||||||
pause_serial: AtomicU32,
|
pause_serial: AtomicU32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,7 +1144,7 @@ impl Simulator {
|
|||||||
sim_version: String::new(),
|
sim_version: String::new(),
|
||||||
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
|
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
|
||||||
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
|
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
|
||||||
stats: SimulatorSimStats,
|
stats: SimulatorSimStats::default(),
|
||||||
terrain: Vec::new(),
|
terrain: Vec::new(),
|
||||||
terrain_base0: UUID::zero(),
|
terrain_base0: UUID::zero(),
|
||||||
terrain_base1: UUID::zero(),
|
terrain_base1: UUID::zero(),
|
||||||
@@ -1174,7 +1180,6 @@ impl Simulator {
|
|||||||
manager: Mutex::new(Weak::new()),
|
manager: Mutex::new(Weak::new()),
|
||||||
transport,
|
transport,
|
||||||
transport_thread: Mutex::new(None),
|
transport_thread: Mutex::new(None),
|
||||||
ping_id: AtomicU32::new(0),
|
|
||||||
pause_serial: AtomicU32::new(0),
|
pause_serial: AtomicU32::new(0),
|
||||||
});
|
});
|
||||||
*mutex(&simulator_reference) = Arc::downgrade(&data);
|
*mutex(&simulator_reference) = Arc::downgrade(&data);
|
||||||
@@ -1265,6 +1270,7 @@ impl Simulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.connected.store(true, Ordering::Release);
|
self.connected.store(true, Ordering::Release);
|
||||||
|
self.stats.set_connect_time(environment_tick_count())?;
|
||||||
self.handshake_complete.store(false, Ordering::Release);
|
self.handshake_complete.store(false, Ordering::Release);
|
||||||
drop(transport_thread);
|
drop(transport_thread);
|
||||||
self.native_use_circuit_code(true)?;
|
self.native_use_circuit_code(true)?;
|
||||||
@@ -1401,8 +1407,9 @@ impl Simulator {
|
|||||||
|
|
||||||
pub fn native_send_ping(&self) -> Result<(), Error> {
|
pub fn native_send_ping(&self) -> Result<(), Error> {
|
||||||
let mut ping = StartPingCheckPacket::new_with_constructor()?;
|
let mut ping = StartPingCheckPacket::new_with_constructor()?;
|
||||||
ping.ping_id.ping_id = self.ping_id.fetch_add(1, Ordering::Relaxed).to_le_bytes()[0];
|
ping.ping_id.ping_id = self.stats.get_and_increment_last_ping_id()?.to_le_bytes()[0];
|
||||||
ping.ping_id.oldest_unacked = 0;
|
ping.ping_id.oldest_unacked = 0;
|
||||||
|
self.stats.set_last_ping_sent(environment_tick_count())?;
|
||||||
let data = ping.to_bytes_with_method()?;
|
let data = ping.to_bytes_with_method()?;
|
||||||
self.native_send_packet_data(
|
self.native_send_packet_data(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
@@ -1658,6 +1665,16 @@ impl UdpPacketHandler for SimulatorUdpHandler {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let _ = simulator
|
||||||
|
.stats
|
||||||
|
.add_recv_bytes(i64::from(buffer.data_length));
|
||||||
|
let _ = simulator.stats.increment_recv_packets();
|
||||||
|
if payload
|
||||||
|
.first()
|
||||||
|
.is_some_and(|flags| flags & Helpers::MSG_RESENT != 0)
|
||||||
|
{
|
||||||
|
let _ = simulator.stats.increment_received_resends();
|
||||||
|
}
|
||||||
simulator.note_packet_received(packet.type_);
|
simulator.note_packet_received(packet.type_);
|
||||||
let manager = mutex(&simulator.manager).upgrade();
|
let manager = mutex(&simulator.manager).upgrade();
|
||||||
if let Some(manager) = manager {
|
if let Some(manager) = manager {
|
||||||
@@ -1674,11 +1691,29 @@ impl UdpPacketHandler for SimulatorUdpHandler {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let simulator = Simulator { caps: None, data };
|
let simulator = Simulator { caps: None, data };
|
||||||
|
let _ = simulator
|
||||||
|
.stats
|
||||||
|
.add_sent_bytes(i64::try_from(bytes_sent).unwrap_or(i64::MAX));
|
||||||
|
let _ = simulator.stats.increment_sent_packets();
|
||||||
|
if buffer
|
||||||
|
.data
|
||||||
|
.first()
|
||||||
|
.is_some_and(|flags| flags & Helpers::MSG_RESENT != 0)
|
||||||
|
{
|
||||||
|
let _ = simulator.stats.increment_resent_packets();
|
||||||
|
}
|
||||||
let manager = mutex(&simulator.manager).upgrade();
|
let manager = mutex(&simulator.manager).upgrade();
|
||||||
if let Some(manager) = manager {
|
if let Some(manager) = manager {
|
||||||
manager.raise_packet_sent(buffer.data, bytes_sent, simulator);
|
manager.raise_packet_sent(buffer.data, bytes_sent, simulator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn packet_dropped(&self) {
|
||||||
|
let Some(data) = mutex(&self.simulator).upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = data.stats.increment_dropped_packets();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -1929,6 +1964,9 @@ impl NetworkManagerInner {
|
|||||||
simulator.handshake_complete.store(true, Ordering::Release);
|
simulator.handshake_complete.store(true, Ordering::Release);
|
||||||
simulator.handshake_wait.notify_all();
|
simulator.handshake_wait.notify_all();
|
||||||
}
|
}
|
||||||
|
PacketType::CompletePingCheck => {
|
||||||
|
simulator.stats.record_pong(environment_tick_count());
|
||||||
|
}
|
||||||
PacketType::StartPingCheck => {
|
PacketType::StartPingCheck => {
|
||||||
let Some(raw_data) = raw_data else {
|
let Some(raw_data) = raw_data else {
|
||||||
return;
|
return;
|
||||||
@@ -2022,6 +2060,40 @@ impl NetworkManagerInner {
|
|||||||
"ClientInitiated".to_owned(),
|
"ClientInitiated".to_owned(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
PacketType::SimStats => {
|
||||||
|
if !self.client.settings_ref().packets.enable_sim_stats {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(raw_data) = raw_data else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(mut incoming) = SimStatsPacket::new_with_constructor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
packet_end -= 1;
|
||||||
|
let mut position = 0;
|
||||||
|
let mut zero_buffer =
|
||||||
|
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
|
||||||
|
if incoming
|
||||||
|
.from_bytes_with_bytes_int32_int32_bytes(
|
||||||
|
raw_data.to_vec(),
|
||||||
|
&mut position,
|
||||||
|
&mut packet_end,
|
||||||
|
Some(&mut zero_buffer),
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for stat in incoming.stat {
|
||||||
|
simulator
|
||||||
|
.stats
|
||||||
|
.apply_server_stat(stat.stat_id, stat.stat_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
PacketType::DisableSimulator => {
|
PacketType::DisableSimulator => {
|
||||||
let _ = self.disconnect_simulator(simulator.clone(), false);
|
let _ = self.disconnect_simulator(simulator.clone(), false);
|
||||||
}
|
}
|
||||||
@@ -2196,8 +2268,8 @@ impl NetworkManagerInner {
|
|||||||
crate::NetworkManagerDisconnectType::NetworkTimeout,
|
crate::NetworkManagerDisconnectType::NetworkTimeout,
|
||||||
"NetworkTimeout".to_owned(),
|
"NetworkTimeout".to_owned(),
|
||||||
);
|
);
|
||||||
} else {
|
} else if current.native_send_ping().is_ok() {
|
||||||
let _ = current.native_send_ping();
|
let _ = current.stats.increment_sent_pings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
347
crates/libremetaverse/src/sim_stats.rs
Normal file
347
crates/libremetaverse/src/sim_stats.rs
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
//! Thread-safe simulator statistics matching `Simulator.SimStats`.
|
||||||
|
|
||||||
|
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map.
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::atomic::{AtomicI32, AtomicI64, AtomicU32, Ordering};
|
||||||
|
|
||||||
|
fn csharp_convert_to_i32(value: f32) -> Option<i32> {
|
||||||
|
let rounded = value.round_ties_even();
|
||||||
|
if !rounded.is_finite() || !(-2_147_483_648.0..2_147_483_648.0).contains(&rounded) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
Some(rounded as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! integer_property {
|
||||||
|
($get:ident, $set:ident, $field:ident) => {
|
||||||
|
#[must_use]
|
||||||
|
pub fn $get(&self) -> i32 {
|
||||||
|
self.$field.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn $set(&mut self, value: i32) {
|
||||||
|
self.$field.store(value, Ordering::Release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! float_property {
|
||||||
|
($get:ident, $set:ident, $field:ident) => {
|
||||||
|
#[must_use]
|
||||||
|
pub fn $get(&self) -> f32 {
|
||||||
|
f32::from_bits(self.$field.load(Ordering::Acquire))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn $set(&mut self, value: f32) {
|
||||||
|
self.$field.store(value.to_bits(), Ordering::Release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SimulatorSimStats {
|
||||||
|
sent_packets: AtomicI64,
|
||||||
|
recv_packets: AtomicI64,
|
||||||
|
sent_bytes: AtomicI64,
|
||||||
|
recv_bytes: AtomicI64,
|
||||||
|
connect_time: AtomicI32,
|
||||||
|
resent_packets: AtomicI32,
|
||||||
|
received_resends: AtomicI32,
|
||||||
|
dropped_packets: AtomicI32,
|
||||||
|
sent_pings: AtomicI32,
|
||||||
|
received_pongs: AtomicI32,
|
||||||
|
incoming_bps: AtomicI32,
|
||||||
|
outgoing_bps: AtomicI32,
|
||||||
|
last_ping_sent: AtomicI32,
|
||||||
|
last_ping_id: AtomicI32,
|
||||||
|
last_lag: AtomicI32,
|
||||||
|
missed_pings: AtomicI32,
|
||||||
|
dilation: AtomicU32,
|
||||||
|
fps: AtomicI32,
|
||||||
|
physics_fps: AtomicU32,
|
||||||
|
agent_updates: AtomicU32,
|
||||||
|
frame_time: AtomicU32,
|
||||||
|
net_time: AtomicU32,
|
||||||
|
physics_time: AtomicU32,
|
||||||
|
image_time: AtomicU32,
|
||||||
|
script_time: AtomicU32,
|
||||||
|
agent_time: AtomicU32,
|
||||||
|
other_time: AtomicU32,
|
||||||
|
objects: AtomicI32,
|
||||||
|
scripted_objects: AtomicI32,
|
||||||
|
agents: AtomicI32,
|
||||||
|
child_agents: AtomicI32,
|
||||||
|
active_scripts: AtomicI32,
|
||||||
|
lslips: AtomicI32,
|
||||||
|
inpps: AtomicI32,
|
||||||
|
outpps: AtomicI32,
|
||||||
|
pending_downloads: AtomicI32,
|
||||||
|
pending_uploads: AtomicI32,
|
||||||
|
virtual_size: AtomicI32,
|
||||||
|
resident_size: AtomicI32,
|
||||||
|
pending_local_uploads: AtomicI32,
|
||||||
|
unacked_bytes: AtomicI32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SimulatorSimStats {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter
|
||||||
|
.debug_struct("SimulatorSimStats")
|
||||||
|
.field("sent_packets", &self.sent_packets.load(Ordering::Relaxed))
|
||||||
|
.field("recv_packets", &self.recv_packets.load(Ordering::Relaxed))
|
||||||
|
.field("sent_bytes", &self.sent_bytes.load(Ordering::Relaxed))
|
||||||
|
.field("recv_bytes", &self.recv_bytes.load(Ordering::Relaxed))
|
||||||
|
.field(
|
||||||
|
"dropped_packets",
|
||||||
|
&self.dropped_packets.load(Ordering::Relaxed),
|
||||||
|
)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimulatorSimStats {
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_recv_bytes(&self, value: i64) -> Result<(), Error> {
|
||||||
|
self.recv_bytes.fetch_add(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_sent_bytes(&self, value: i64) -> Result<(), Error> {
|
||||||
|
self.sent_bytes.fetch_add(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_and_increment_last_ping_id(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.last_ping_id.fetch_add(1, Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_connect_time(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.connect_time.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dropped_packets(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.dropped_packets.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_incoming_bps(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.incoming_bps.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_last_ping_sent(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.last_ping_sent.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_outgoing_bps(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.outgoing_bps.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_received_pongs(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.received_pongs.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_received_resends(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.received_resends.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_recv_bytes(&self) -> Result<i64, Error> {
|
||||||
|
Ok(self.recv_bytes.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_recv_packets(&self) -> Result<i64, Error> {
|
||||||
|
Ok(self.recv_packets.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_resent_packets(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.resent_packets.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sent_bytes(&self) -> Result<i64, Error> {
|
||||||
|
Ok(self.sent_bytes.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sent_packets(&self) -> Result<i64, Error> {
|
||||||
|
Ok(self.sent_packets.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sent_pings(&self) -> Result<i32, Error> {
|
||||||
|
Ok(self.sent_pings.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_dropped_packets(&self) -> Result<(), Error> {
|
||||||
|
self.dropped_packets.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_received_pongs(&self) -> Result<(), Error> {
|
||||||
|
self.received_pongs.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_received_resends(&self) -> Result<(), Error> {
|
||||||
|
self.received_resends.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_recv_packets(&self) -> Result<(), Error> {
|
||||||
|
self.recv_packets.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_resent_packets(&self) -> Result<(), Error> {
|
||||||
|
self.resent_packets.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_sent_packets(&self) -> Result<(), Error> {
|
||||||
|
self.sent_packets.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_sent_pings(&self) -> Result<(), Error> {
|
||||||
|
self.sent_pings.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_connect_time(&self, value: i32) -> Result<(), Error> {
|
||||||
|
self.connect_time.store(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_incoming_bps(&self, value: i32) -> Result<(), Error> {
|
||||||
|
self.incoming_bps.store(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_last_ping_sent(&self, value: i32) -> Result<(), Error> {
|
||||||
|
self.last_ping_sent.store(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_outgoing_bps(&self, value: i32) -> Result<(), Error> {
|
||||||
|
self.outgoing_bps.store(value, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn record_pong(&self, tick_count: i32) {
|
||||||
|
let sent = self.last_ping_sent.load(Ordering::Acquire);
|
||||||
|
self.last_lag
|
||||||
|
.store(tick_count.wrapping_sub(sent), Ordering::Release);
|
||||||
|
self.received_pongs.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_server_stat(&self, id: u32, value: f32) {
|
||||||
|
let float = |field: &AtomicU32| field.store(value.to_bits(), Ordering::Release);
|
||||||
|
let integer = |field: &AtomicI32| {
|
||||||
|
if let Some(value) = csharp_convert_to_i32(value) {
|
||||||
|
field.store(value, Ordering::Release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match id {
|
||||||
|
0 => float(&self.dilation),
|
||||||
|
1 => integer(&self.fps),
|
||||||
|
2 => float(&self.physics_fps),
|
||||||
|
3 => float(&self.agent_updates),
|
||||||
|
4 => float(&self.frame_time),
|
||||||
|
5 => float(&self.net_time),
|
||||||
|
6 => float(&self.other_time),
|
||||||
|
7 => float(&self.physics_time),
|
||||||
|
8 => float(&self.agent_time),
|
||||||
|
9 => float(&self.image_time),
|
||||||
|
10 => float(&self.script_time),
|
||||||
|
11 => integer(&self.objects),
|
||||||
|
12 => integer(&self.scripted_objects),
|
||||||
|
13 => integer(&self.agents),
|
||||||
|
14 => integer(&self.child_agents),
|
||||||
|
15 => integer(&self.active_scripts),
|
||||||
|
16 => integer(&self.lslips),
|
||||||
|
17 => integer(&self.inpps),
|
||||||
|
18 => integer(&self.outpps),
|
||||||
|
19 => integer(&self.pending_downloads),
|
||||||
|
20 => integer(&self.pending_uploads),
|
||||||
|
21 => integer(&self.virtual_size),
|
||||||
|
22 => integer(&self.resident_size),
|
||||||
|
23 => integer(&self.pending_local_uploads),
|
||||||
|
24 => integer(&self.unacked_bytes),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
integer_property!(active_scripts, set_active_scripts, active_scripts);
|
||||||
|
float_property!(agent_time, set_agent_time, agent_time);
|
||||||
|
float_property!(agent_updates, set_agent_updates, agent_updates);
|
||||||
|
integer_property!(agents, set_agents, agents);
|
||||||
|
integer_property!(child_agents, set_child_agents, child_agents);
|
||||||
|
float_property!(dilation, set_dilation, dilation);
|
||||||
|
integer_property!(fps, set_fps, fps);
|
||||||
|
float_property!(frame_time, set_frame_time, frame_time);
|
||||||
|
integer_property!(inpps, set_inpps, inpps);
|
||||||
|
float_property!(image_time, set_image_time, image_time);
|
||||||
|
integer_property!(lslips, set_lslips, lslips);
|
||||||
|
integer_property!(last_lag, set_last_lag, last_lag);
|
||||||
|
integer_property!(missed_pings, set_missed_pings, missed_pings);
|
||||||
|
float_property!(net_time, set_net_time, net_time);
|
||||||
|
integer_property!(outpps, set_outpps, outpps);
|
||||||
|
integer_property!(objects, set_objects, objects);
|
||||||
|
float_property!(other_time, set_other_time, other_time);
|
||||||
|
integer_property!(pending_downloads, set_pending_downloads, pending_downloads);
|
||||||
|
integer_property!(
|
||||||
|
pending_local_uploads,
|
||||||
|
set_pending_local_uploads,
|
||||||
|
pending_local_uploads
|
||||||
|
);
|
||||||
|
integer_property!(pending_uploads, set_pending_uploads, pending_uploads);
|
||||||
|
float_property!(physics_fps, set_physics_fps, physics_fps);
|
||||||
|
float_property!(physics_time, set_physics_time, physics_time);
|
||||||
|
integer_property!(resident_size, set_resident_size, resident_size);
|
||||||
|
float_property!(script_time, set_script_time, script_time);
|
||||||
|
integer_property!(scripted_objects, set_scripted_objects, scripted_objects);
|
||||||
|
integer_property!(unacked_bytes, set_unacked_bytes, unacked_bytes);
|
||||||
|
integer_property!(virtual_size, set_virtual_size, virtual_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn counters_are_atomic_and_properties_preserve_float_bits() {
|
||||||
|
let stats = Arc::new(SimulatorSimStats::new().unwrap());
|
||||||
|
let workers: Vec<_> = (0..4)
|
||||||
|
.map(|_| {
|
||||||
|
let stats = Arc::clone(&stats);
|
||||||
|
thread::spawn(move || {
|
||||||
|
for _ in 0..1_000 {
|
||||||
|
stats.increment_recv_packets().unwrap();
|
||||||
|
stats.add_recv_bytes(7).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for worker in workers {
|
||||||
|
worker.join().unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(stats.get_recv_packets().unwrap(), 4_000);
|
||||||
|
assert_eq!(stats.get_recv_bytes().unwrap(), 28_000);
|
||||||
|
assert_eq!(stats.get_and_increment_last_ping_id().unwrap(), 0);
|
||||||
|
assert_eq!(stats.get_and_increment_last_ping_id().unwrap(), 1);
|
||||||
|
|
||||||
|
let mut stats = Arc::try_unwrap(stats).ok().unwrap();
|
||||||
|
stats.set_dilation(f32::from_bits(0x7fc0_1234));
|
||||||
|
assert_eq!(stats.dilation().to_bits(), 0x7fc0_1234);
|
||||||
|
|
||||||
|
stats.apply_server_stat(1, 44.5);
|
||||||
|
assert_eq!(stats.fps(), 44);
|
||||||
|
stats.apply_server_stat(1, 45.5);
|
||||||
|
assert_eq!(stats.fps(), 46);
|
||||||
|
stats.apply_server_stat(1, f32::NAN);
|
||||||
|
assert_eq!(stats.fps(), 46);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use libremetaverse::http::DownloadManager;
|
||||||
use libremetaverse::interfaces::IMessage;
|
use libremetaverse::interfaces::IMessage;
|
||||||
use libremetaverse::messages::linden::{
|
use libremetaverse::messages::linden::{
|
||||||
EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock,
|
EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock,
|
||||||
@@ -6,7 +7,7 @@ use libremetaverse::packets::{
|
|||||||
CompleteAgentMovementPacket, DisableSimulatorPacket, LogoutReplyPacket,
|
CompleteAgentMovementPacket, DisableSimulatorPacket, LogoutReplyPacket,
|
||||||
LogoutReplyPacketInventoryDataBlock, LogoutRequestPacket, Packet, PacketAckPacket,
|
LogoutReplyPacketInventoryDataBlock, LogoutRequestPacket, Packet, PacketAckPacket,
|
||||||
PacketAckPacketPacketsBlock, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
PacketAckPacketPacketsBlock, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
||||||
StartPingCheckPacket, UseCircuitCodePacket,
|
SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
|
||||||
};
|
};
|
||||||
use libremetaverse::{
|
use libremetaverse::{
|
||||||
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
|
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
|
||||||
@@ -363,6 +364,7 @@ fn sim_connecting_can_cancel_without_leaking_collection_state() {
|
|||||||
|
|
||||||
enum ServerCommand {
|
enum ServerCommand {
|
||||||
Ping,
|
Ping,
|
||||||
|
Stats,
|
||||||
Disable,
|
Disable,
|
||||||
ExpectEconomy,
|
ExpectEconomy,
|
||||||
Handoff,
|
Handoff,
|
||||||
@@ -518,6 +520,26 @@ fn spawn_fake_server() -> FakeServer {
|
|||||||
ServerCommand::Ping => {
|
ServerCommand::Ping => {
|
||||||
serve_ping_command(&socket, client_endpoint, &report_sender, &mut buffer);
|
serve_ping_command(&socket, client_endpoint, &report_sender, &mut buffer);
|
||||||
}
|
}
|
||||||
|
ServerCommand::Stats => {
|
||||||
|
let mut stats = SimStatsPacket::new_with_constructor().unwrap();
|
||||||
|
stats.stat = vec![
|
||||||
|
SimStatsPacketStatBlock {
|
||||||
|
stat_id: 0,
|
||||||
|
stat_value: 0.75,
|
||||||
|
},
|
||||||
|
SimStatsPacketStatBlock {
|
||||||
|
stat_id: 1,
|
||||||
|
stat_value: 44.5,
|
||||||
|
},
|
||||||
|
SimStatsPacketStatBlock {
|
||||||
|
stat_id: 11,
|
||||||
|
stat_value: 321.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let mut bytes = stats.to_bytes_with_method().unwrap();
|
||||||
|
bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
|
||||||
|
socket.send_to(&bytes, client_endpoint).unwrap();
|
||||||
|
}
|
||||||
ServerCommand::Disable => {
|
ServerCommand::Disable => {
|
||||||
let disable = DisableSimulatorPacket::new_with_constructor().unwrap();
|
let disable = DisableSimulatorPacket::new_with_constructor().unwrap();
|
||||||
let mut bytes = disable.to_bytes_with_method().unwrap();
|
let mut bytes = disable.to_bytes_with_method().unwrap();
|
||||||
@@ -770,6 +792,15 @@ fn fake_server_drives_circuit_handshake_ping_disable_and_disconnect_reasons() {
|
|||||||
assert_reuse_preserves_connected_override(&mut manager, &server, &sim);
|
assert_reuse_preserves_connected_override(&mut manager, &server, &sim);
|
||||||
|
|
||||||
assert_ping_exchange(&server, &packet_receiver);
|
assert_ping_exchange(&server, &packet_receiver);
|
||||||
|
server.commands.send(ServerCommand::Stats).unwrap();
|
||||||
|
wait_until(|| sim.stats.objects() == 321);
|
||||||
|
assert_eq!(sim.stats.dilation(), 0.75);
|
||||||
|
assert_eq!(sim.stats.fps(), 44);
|
||||||
|
assert!(sim.stats.get_recv_packets().unwrap() >= 3);
|
||||||
|
assert!(sim.stats.get_recv_bytes().unwrap() > 0);
|
||||||
|
assert!(sim.stats.get_sent_packets().unwrap() >= 3);
|
||||||
|
assert!(sim.stats.get_sent_bytes().unwrap() > 0);
|
||||||
|
assert_eq!(sim.stats.get_dropped_packets().unwrap(), 0);
|
||||||
|
|
||||||
server.commands.send(ServerCommand::Disable).unwrap();
|
server.commands.send(ServerCommand::Disable).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1681,6 +1712,308 @@ async fn login_hashes_credentials_parses_response_and_hands_off_session_to_udp_a
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks() {
|
||||||
|
let _network_guard = network_test_guard();
|
||||||
|
let first_server = spawn_fake_server();
|
||||||
|
let second_server = spawn_fake_server();
|
||||||
|
|
||||||
|
let mut enable = EnableSimulatorMessage::new().unwrap();
|
||||||
|
let mut simulator_info = EnableSimulatorMessageSimulatorInfoBlock::new().unwrap();
|
||||||
|
simulator_info.ip = second_server.endpoint.ip();
|
||||||
|
simulator_info.port = i32::from(second_server.endpoint.port());
|
||||||
|
simulator_info.region_handle = 0x2000;
|
||||||
|
simulator_info.region_size_x = 512;
|
||||||
|
simulator_info.region_size_y = 256;
|
||||||
|
enable.simulators = vec![simulator_info];
|
||||||
|
let enable_body = enable.serialize().unwrap().snapshot();
|
||||||
|
|
||||||
|
let release_seed = Arc::new(AtomicBool::new(false));
|
||||||
|
let release_queue = Arc::new(AtomicBool::new(false));
|
||||||
|
let queue_calls = Arc::new(AtomicUsize::new(0));
|
||||||
|
let queue_cancelled = Arc::new(AtomicBool::new(false));
|
||||||
|
let queue_done = Arc::new(AtomicBool::new(false));
|
||||||
|
let request_uris = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let handler = HttpMessageHandler::new({
|
||||||
|
let release_seed = Arc::clone(&release_seed);
|
||||||
|
let release_queue = Arc::clone(&release_queue);
|
||||||
|
let queue_calls = Arc::clone(&queue_calls);
|
||||||
|
let queue_cancelled = Arc::clone(&queue_cancelled);
|
||||||
|
let queue_done = Arc::clone(&queue_done);
|
||||||
|
let request_uris = Arc::clone(&request_uris);
|
||||||
|
let first_endpoint = first_server.endpoint;
|
||||||
|
move |request, cancellation| {
|
||||||
|
let release_seed = Arc::clone(&release_seed);
|
||||||
|
let release_queue = Arc::clone(&release_queue);
|
||||||
|
let queue_calls = Arc::clone(&queue_calls);
|
||||||
|
let queue_cancelled = Arc::clone(&queue_cancelled);
|
||||||
|
let queue_done = Arc::clone(&queue_done);
|
||||||
|
let request_uris = Arc::clone(&request_uris);
|
||||||
|
let enable_body = enable_body.clone();
|
||||||
|
async move {
|
||||||
|
let uri = request.uri.0.clone();
|
||||||
|
request_uris.lock().unwrap().push(uri.clone());
|
||||||
|
if uri.contains("/login") {
|
||||||
|
return login_http_response(successful_login_response(
|
||||||
|
first_endpoint,
|
||||||
|
"https://fake.grid/seed?token=seed-secret",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if uri.contains("/seed") {
|
||||||
|
while !release_seed.load(Ordering::Acquire) {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||||
|
}
|
||||||
|
return login_http_response(OSD::Map(HashMap::from([
|
||||||
|
(
|
||||||
|
"EventQueueGet".into(),
|
||||||
|
OSD::Uri(Uri("https://fake.grid/queue?token=queue-secret".into())),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"GetTexture".into(),
|
||||||
|
OSD::Uri(Uri("https://fake.grid/asset?token=asset-secret".into())),
|
||||||
|
),
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
if uri.contains("/queue") {
|
||||||
|
let done = matches!(
|
||||||
|
OSDParser::deserialize_llsd_xml_with_bytes(request.body.clone()),
|
||||||
|
Ok(OSD::Map(ref map))
|
||||||
|
if map
|
||||||
|
.get("done")
|
||||||
|
.and_then(|value| value.as_boolean().ok())
|
||||||
|
== Some(true)
|
||||||
|
);
|
||||||
|
if done {
|
||||||
|
queue_done.store(true, Ordering::Release);
|
||||||
|
return HttpResponse {
|
||||||
|
status_code: 499,
|
||||||
|
headers: BTreeMap::new(),
|
||||||
|
content_type: Some(HttpCapsClient::LLSD_XML.into()),
|
||||||
|
body: Vec::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let call = queue_calls.fetch_add(1, Ordering::AcqRel);
|
||||||
|
if call == 0 {
|
||||||
|
while !release_queue.load(Ordering::Acquire) {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||||
|
}
|
||||||
|
return login_http_response(OSD::Map(HashMap::from([
|
||||||
|
("id".into(), OSD::Integer(1)),
|
||||||
|
(
|
||||||
|
"events".into(),
|
||||||
|
OSD::Array(vec![OSD::Map(HashMap::from([
|
||||||
|
("message".into(), OSD::String("EnableSimulator".into())),
|
||||||
|
("body".into(), OSD::Map(enable_body)),
|
||||||
|
]))]),
|
||||||
|
),
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
let observed_cancellation = Arc::clone(&queue_cancelled);
|
||||||
|
let _cancellation_guard = cancellation.register_callback(Arc::new(move || {
|
||||||
|
observed_cancellation.store(true, Ordering::Release);
|
||||||
|
}));
|
||||||
|
cancellation.cancelled().await;
|
||||||
|
return HttpResponse {
|
||||||
|
status_code: 499,
|
||||||
|
headers: BTreeMap::new(),
|
||||||
|
content_type: Some(HttpCapsClient::LLSD_XML.into()),
|
||||||
|
body: Vec::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if uri.contains("/asset") {
|
||||||
|
return HttpResponse {
|
||||||
|
status_code: 200,
|
||||||
|
headers: BTreeMap::new(),
|
||||||
|
content_type: Some("application/octet-stream".into()),
|
||||||
|
body: b"fake-grid-asset".to_vec(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
HttpResponse {
|
||||||
|
status_code: 404,
|
||||||
|
headers: BTreeMap::new(),
|
||||||
|
content_type: None,
|
||||||
|
body: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = GridClient::new().unwrap();
|
||||||
|
client.settings().timing().login_timeout = 2_000;
|
||||||
|
client.settings().agent_settings_mut().multiple_sims = true;
|
||||||
|
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
||||||
|
let manager = client.network();
|
||||||
|
let (queue_sender, queue_receiver) = channel();
|
||||||
|
let _queue_guard = manager.subscribe_event_queue_running(Arc::new(move |args| {
|
||||||
|
queue_sender.send(args.simulator().handle).unwrap();
|
||||||
|
}));
|
||||||
|
let (logout_sender, logout_receiver) = channel();
|
||||||
|
let _logout_guard = manager.subscribe_logged_out(Arc::new(move |args| {
|
||||||
|
logout_sender.send(args.inventory_items).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut params = manager
|
||||||
|
.default_login_params(
|
||||||
|
"Ada".into(),
|
||||||
|
"Lovelace".into(),
|
||||||
|
"plain-secret".into(),
|
||||||
|
"MetaCrate".into(),
|
||||||
|
"1.0".into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
params.uri = "https://fake.grid/login".into();
|
||||||
|
assert!(
|
||||||
|
manager
|
||||||
|
.login_with_login_params_cancellation_token(params, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
first_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::Circuit(0x1234)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
first_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
|
||||||
|
);
|
||||||
|
let first = manager.current_sim().expect("initial simulator");
|
||||||
|
let caps = first.clone().caps.expect("seed capability client");
|
||||||
|
release_seed.store(true, Ordering::Release);
|
||||||
|
wait_until(|| {
|
||||||
|
caps.capability_uri("GetTexture".into())
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.is_some()
|
||||||
|
});
|
||||||
|
release_queue.store(true, Ordering::Release);
|
||||||
|
assert_eq!(
|
||||||
|
queue_receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
|
||||||
|
first.handle
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(2))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::Circuit(0x1234)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
|
||||||
|
);
|
||||||
|
let second = manager
|
||||||
|
.find_simulator_with_ip_end_point(second_server.endpoint)
|
||||||
|
.unwrap()
|
||||||
|
.expect("event-queue simulator");
|
||||||
|
|
||||||
|
let downloads = DownloadManager::new(client.clone()).unwrap();
|
||||||
|
let (_, asset) = downloads
|
||||||
|
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
|
||||||
|
caps.capability_uri("GetTexture".into()).unwrap().unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(asset, b"fake-grid-asset");
|
||||||
|
assert_eq!(downloads.active_download_count(), 0);
|
||||||
|
|
||||||
|
second_server.commands.send(ServerCommand::Handoff).unwrap();
|
||||||
|
manager
|
||||||
|
.connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
|
||||||
|
second_server.endpoint,
|
||||||
|
second.handle,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
second.size_x,
|
||||||
|
second.size_y,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(manager.current_sim(), Some(second.clone()));
|
||||||
|
assert_eq!(
|
||||||
|
second_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::Circuit(0x1234)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::Movement(0x1234)
|
||||||
|
);
|
||||||
|
|
||||||
|
let diagnostics = format!("{manager:?} {caps:?} {downloads:?} {first:?} {second:?}");
|
||||||
|
for secret in [
|
||||||
|
"seed-secret",
|
||||||
|
"queue-secret",
|
||||||
|
"asset-secret",
|
||||||
|
"plain-secret",
|
||||||
|
] {
|
||||||
|
assert!(!diagnostics.contains(secret));
|
||||||
|
}
|
||||||
|
assert!(first.stats.get_recv_packets().unwrap() > 0);
|
||||||
|
assert!(first.stats.get_sent_packets().unwrap() > 0);
|
||||||
|
assert!(second.stats.get_recv_packets().unwrap() > 0);
|
||||||
|
assert!(second.stats.get_sent_packets().unwrap() > 0);
|
||||||
|
|
||||||
|
wait_until(|| queue_calls.load(Ordering::Acquire) >= 2);
|
||||||
|
let returned_inventory =
|
||||||
|
vec![UUID::new_with_string("33333333-3333-3333-3333-333333333333".into()).unwrap()];
|
||||||
|
second_server
|
||||||
|
.commands
|
||||||
|
.send(ServerCommand::LogoutReply(returned_inventory.clone()))
|
||||||
|
.unwrap();
|
||||||
|
manager.logout_with_method().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
logout_receiver
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
returned_inventory
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_server
|
||||||
|
.reports
|
||||||
|
.recv_timeout(Duration::from_secs(1))
|
||||||
|
.unwrap(),
|
||||||
|
ServerReport::Logout(
|
||||||
|
UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
|
||||||
|
UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
wait_until(|| queue_cancelled.load(Ordering::Acquire));
|
||||||
|
assert!(!queue_done.load(Ordering::Acquire));
|
||||||
|
assert!(!caps.is_event_queue_running());
|
||||||
|
assert!(!manager.connected());
|
||||||
|
assert!(manager.simulators.is_empty());
|
||||||
|
assert_eq!(manager.pending_logout_tasks(), 0);
|
||||||
|
assert!(!first.connected());
|
||||||
|
assert!(!second.connected());
|
||||||
|
|
||||||
|
downloads.dispose().unwrap();
|
||||||
|
assert!(downloads.is_disposed());
|
||||||
|
assert!(!downloads.dispatcher_running());
|
||||||
|
assert_eq!(downloads.active_download_count(), 0);
|
||||||
|
let uris = request_uris.lock().unwrap();
|
||||||
|
assert!(uris.iter().any(|uri| uri.contains("/login")));
|
||||||
|
assert!(uris.iter().any(|uri| uri.contains("/seed")));
|
||||||
|
assert!(uris.iter().any(|uri| uri.contains("/queue")));
|
||||||
|
assert!(uris.iter().any(|uri| uri.contains("/asset")));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "current_thread")]
|
#[tokio::test(flavor = "current_thread")]
|
||||||
async fn redirect_reuses_hashed_credentials_and_failure_is_typed_without_partial_session() {
|
async fn redirect_reuses_hashed_credentials_and_failure_is_typed_without_partial_session() {
|
||||||
let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
|
let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ statuses and transport failures retry up to the request's configured retry
|
|||||||
count with bounded backoff. Disposal cancels active and queued work, wakes every
|
count with bounded backoff. Disposal cancels active and queued work, wakes every
|
||||||
waiter, joins the dispatcher, and prevents new requests.
|
waiter, joins the dispatcher, and prevents new requests.
|
||||||
|
|
||||||
|
`DownloadManager::active_download_count`, `dispatcher_running`, and
|
||||||
|
`is_disposed` expose payload-free lifecycle state for leak assertions. They do
|
||||||
|
not expose capability URIs or response data.
|
||||||
|
|
||||||
Errors expose only typed categories and `Debug` output substitutes a redacted
|
Errors expose only typed categories and `Debug` output substitutes a redacted
|
||||||
marker for capability URIs. No request URL, query token, body, or response body
|
marker for capability URIs. No request URL, query token, body, or response body
|
||||||
is logged.
|
is logged.
|
||||||
|
|||||||
@@ -113,6 +113,26 @@ canceled login leaves no simulator installed. Passwords, login tokens, MFA
|
|||||||
hashes, session identifiers, seed capabilities, and estate access tokens are
|
hashes, session identifiers, seed capabilities, and estate access tokens are
|
||||||
excluded from `Debug` output and diagnostic messages.
|
excluded from `Debug` output and diagnostic messages.
|
||||||
|
|
||||||
|
## Statistics and milestone gate
|
||||||
|
|
||||||
|
`SimulatorSimStats` is the native thread-safe counterpart of the reference
|
||||||
|
`Simulator.SimStats`. Atomic counters track received/sent packets and bytes,
|
||||||
|
resends, drops, pings, pongs, lag, and bandwidth samples. Server `SimStats`
|
||||||
|
blocks update the 25 defined simulator measurements with the reference ID and
|
||||||
|
rounding rules; unknown IDs are ignored. Debug snapshots contain aggregate
|
||||||
|
counters only—never packet bytes, endpoints, capability URLs, or credentials.
|
||||||
|
|
||||||
|
The offline fake-grid gate composes the completed layers rather than replacing
|
||||||
|
their focused tests. It authenticates through injected LLSD HTTP, establishes a
|
||||||
|
real loopback UDP circuit, discovers seed capabilities, consumes an
|
||||||
|
`EventQueueGet` message that enables a second region, downloads a capability
|
||||||
|
asset, promotes that region, and completes the logout handshake. It then proves
|
||||||
|
CAPS polling was canceled, download and network workers were joined, sockets
|
||||||
|
were closed, state was cleared, and diagnostic formatting contains none of the
|
||||||
|
fixture tokens. `tools/check_milestone_08.py` also audits owned source files for
|
||||||
|
failure-only bodies and verifies the explicit queue, payload, event, retry, and
|
||||||
|
concurrency policy markers remain present.
|
||||||
|
|
||||||
The deterministic tests use loopback fake simulators and require no live grid:
|
The deterministic tests use loopback fake simulators and require no live grid:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
106
tools/check_milestone_08.py
Normal file
106
tools/check_milestone_08.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit the native networking/client lifecycle milestone boundary."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
import generate_api_shims
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
OWNED_SOURCES = (
|
||||||
|
"caps.rs",
|
||||||
|
"caps_http.rs",
|
||||||
|
"client_core.rs",
|
||||||
|
"download_manager.rs",
|
||||||
|
"event_queue.rs",
|
||||||
|
"login.rs",
|
||||||
|
"network_manager.rs",
|
||||||
|
"sim_stats.rs",
|
||||||
|
"udp_transport.rs",
|
||||||
|
)
|
||||||
|
REQUIRED_NATIVE_TYPES = {
|
||||||
|
"T:LibreMetaverse.Caps",
|
||||||
|
"T:LibreMetaverse.GridClient",
|
||||||
|
"T:LibreMetaverse.Http.DownloadManager",
|
||||||
|
"T:LibreMetaverse.Http.EventQueueClient",
|
||||||
|
"T:LibreMetaverse.HttpCapsClient",
|
||||||
|
"T:LibreMetaverse.NetworkManager",
|
||||||
|
"T:LibreMetaverse.Settings",
|
||||||
|
"T:LibreMetaverse.Simulator",
|
||||||
|
"T:LibreMetaverse.Simulator.SimStats",
|
||||||
|
"T:LibreMetaverse.UDPBase",
|
||||||
|
"T:LibreMetaverse.UDPPacketBuffer",
|
||||||
|
}
|
||||||
|
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
|
||||||
|
|
||||||
|
|
||||||
|
def check_owned_sources() -> None:
|
||||||
|
source_root = ROOT / "crates" / "libremetaverse" / "src"
|
||||||
|
offenders = [
|
||||||
|
name for name in OWNED_SOURCES if STUB_RE.search((source_root / name).read_text())
|
||||||
|
]
|
||||||
|
if offenders:
|
||||||
|
raise SystemExit("milestone 08 owned Rust stubs remain: " + ", ".join(offenders))
|
||||||
|
|
||||||
|
|
||||||
|
def check_native_mapping() -> None:
|
||||||
|
native = generate_api_shims.NATIVE_TYPES.keys() | generate_api_shims.NATIVE_DECLARATIONS.keys()
|
||||||
|
missing = sorted(REQUIRED_NATIVE_TYPES - native)
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("milestone 08 native type mappings missing: " + ", ".join(missing))
|
||||||
|
|
||||||
|
|
||||||
|
def check_fake_grid_gate() -> None:
|
||||||
|
tests = (ROOT / "crates" / "libremetaverse" / "tests" / "network_manager.rs").read_text()
|
||||||
|
required_evidence = (
|
||||||
|
"offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks",
|
||||||
|
"EventQueueGet",
|
||||||
|
"active_download_count",
|
||||||
|
"dispatcher_running",
|
||||||
|
"get_recv_packets",
|
||||||
|
"get_sent_packets",
|
||||||
|
"seed-secret",
|
||||||
|
"queue-secret",
|
||||||
|
"asset-secret",
|
||||||
|
)
|
||||||
|
missing = [marker for marker in required_evidence if marker not in tests]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("fake-grid audit evidence missing: " + ", ".join(missing))
|
||||||
|
|
||||||
|
|
||||||
|
def check_bounded_policies() -> None:
|
||||||
|
expectations = {
|
||||||
|
"caps_http.rs": ("CapsHttpLimits", "max_redirects", "max_response_bytes"),
|
||||||
|
"download_manager.rs": ("DOWNLOAD_QUEUE_CAPACITY", "MAX_PARALLEL_DOWNLOADS"),
|
||||||
|
"event_queue.rs": ("EventQueuePolicy", "max_events_per_response", "max_event_nodes"),
|
||||||
|
"udp_transport.rs": (
|
||||||
|
"UdpTransportConfig",
|
||||||
|
"receive_queue_capacity",
|
||||||
|
"command_queue_capacity",
|
||||||
|
"write_queue_capacity",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
source_root = ROOT / "crates" / "libremetaverse" / "src"
|
||||||
|
for name, markers in expectations.items():
|
||||||
|
source = (source_root / name).read_text()
|
||||||
|
missing = [marker for marker in markers if marker not in source]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"{name}: bounded policy evidence missing: " + ", ".join(missing))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
check_owned_sources()
|
||||||
|
check_native_mapping()
|
||||||
|
check_fake_grid_gate()
|
||||||
|
check_bounded_policies()
|
||||||
|
print(
|
||||||
|
"milestone 08 audit: native networking sources contain no stubs; "
|
||||||
|
"fake-grid lifecycle, redaction, task cleanup, statistics, and bounded policies are present"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -75,6 +75,7 @@ NATIVE_TYPES = {
|
|||||||
"T:LibreMetaverse.SimConnectedEventArgs": "crate::network_manager::SimConnectedEventArgs",
|
"T:LibreMetaverse.SimConnectedEventArgs": "crate::network_manager::SimConnectedEventArgs",
|
||||||
"T:LibreMetaverse.SimConnectingEventArgs": "crate::network_manager::SimConnectingEventArgs",
|
"T:LibreMetaverse.SimConnectingEventArgs": "crate::network_manager::SimConnectingEventArgs",
|
||||||
"T:LibreMetaverse.SimDisconnectedEventArgs": "crate::network_manager::SimDisconnectedEventArgs",
|
"T:LibreMetaverse.SimDisconnectedEventArgs": "crate::network_manager::SimDisconnectedEventArgs",
|
||||||
|
"T:LibreMetaverse.Simulator.SimStats": "crate::sim_stats::SimulatorSimStats",
|
||||||
"T:LibreMetaverse.Messages.Linden.EventMessageBlock": "crate::event_queue::EventMessageBlock",
|
"T:LibreMetaverse.Messages.Linden.EventMessageBlock": "crate::event_queue::EventMessageBlock",
|
||||||
"T:LibreMetaverse.Messages.Linden.EventQueueAck": "crate::event_queue::EventQueueAck",
|
"T:LibreMetaverse.Messages.Linden.EventQueueAck": "crate::event_queue::EventQueueAck",
|
||||||
"T:LibreMetaverse.Messages.Linden.EventQueueEvent": "crate::event_queue::EventQueueEvent",
|
"T:LibreMetaverse.Messages.Linden.EventQueueEvent": "crate::event_queue::EventQueueEvent",
|
||||||
|
|||||||
Reference in New Issue
Block a user