Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
4067 lines
138 KiB
Rust
4067 lines
138 KiB
Rust
//! Native network-manager, packet-dispatch, and simulator lifecycle runtime.
|
|
//!
|
|
//! The C# implementation uses reference-identity objects protected by several
|
|
//! locks and channel processors. Rust models those objects as cheap `Arc`
|
|
//! handles. Callback lists are snapshotted while locked and always invoked
|
|
//! after the lock is released.
|
|
|
|
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map.
|
|
#![allow(clippy::large_enum_variant)] // Commands move through a bounded channel without cloning.
|
|
#![allow(clippy::needless_pass_by_value)] // Owned mapped parameters preserve the public API.
|
|
#![allow(clippy::too_many_arguments)] // Login overloads preserve the mapped public API.
|
|
#![allow(clippy::too_many_lines)] // Packet dispatch keeps the complete protocol switch together.
|
|
#![allow(clippy::type_complexity)] // Delegate signatures are fixed by the compatibility map.
|
|
|
|
use crate::interfaces::IMessage;
|
|
use crate::packets::{
|
|
AgentPausePacket, AgentResumePacket, CloseCircuitPacket, CompleteAgentMovementPacket,
|
|
CompletePingCheckPacket, GenericStreamingMessagePacket, KickUserPacket, LogoutReplyPacket,
|
|
LogoutRequestPacket, Packet, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
|
SimStatsPacket, StartPingCheckPacket, UseCircuitCodePacket,
|
|
};
|
|
use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig};
|
|
use crate::{
|
|
AccountLevelBenefits, Avatar, Caps, Error, GenericStreamingMethod, GridClient, Helpers,
|
|
LoginCredential, LoginParams, LoginProgressEventArgs, LoginResponseData, LoginStatus,
|
|
NetworkManagerLoginResponseCallback, Parcel, Primitive, RegionFlags, RegionProtocols,
|
|
SimAccess, SimulatorDataPool, SimulatorFeatures, SimulatorSimStats, TerrainPatch,
|
|
};
|
|
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
|
|
use libremetaverse_types::compat::{
|
|
CancellationToken, CancellationTokenSource, EventHandler, Object, Subscription, Uri,
|
|
};
|
|
use libremetaverse_types::{UUID, Vector2};
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicU64, Ordering};
|
|
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender, sync_channel};
|
|
use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock, Weak};
|
|
use std::thread::{self, JoinHandle};
|
|
use std::time::Duration;
|
|
|
|
impl Clone for crate::packets::Header {
|
|
fn clone(&self) -> Self {
|
|
crate::packet_wire::clone_header(self)
|
|
}
|
|
}
|
|
|
|
impl Clone for Packet {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
has_variable_blocks: self.has_variable_blocks,
|
|
header: self.header.clone(),
|
|
type_: self.type_,
|
|
native_body: self.native_body.clone(),
|
|
native_payload_length: self.native_payload_length,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
|
value
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
|
|
fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
|
|
value
|
|
.read()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
|
|
fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
|
|
value
|
|
.write()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
|
|
fn invoke_safely<T>(handler: &EventHandler<T>, value: T) {
|
|
let _ = catch_unwind(AssertUnwindSafe(|| handler(value)));
|
|
}
|
|
|
|
fn wait_for_cancellation(
|
|
token: &libremetaverse_types::compat::CancellationToken,
|
|
duration: Duration,
|
|
) -> bool {
|
|
let started = std::time::Instant::now();
|
|
while !token.is_cancellation_requested() {
|
|
let remaining = duration.saturating_sub(started.elapsed());
|
|
if remaining.is_zero() {
|
|
return false;
|
|
}
|
|
thread::sleep(remaining.min(Duration::from_millis(50)));
|
|
}
|
|
true
|
|
}
|
|
|
|
fn positive_millisecond_duration(value: i32) -> Duration {
|
|
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)]
|
|
enum LogoutWaitOutcome {
|
|
Reply,
|
|
Timeout,
|
|
Cancelled,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
#[repr(u8)]
|
|
enum NetworkLifecycleState {
|
|
Disconnected,
|
|
Connecting,
|
|
Connected,
|
|
LoggingOut,
|
|
ShuttingDown,
|
|
}
|
|
|
|
impl NetworkLifecycleState {
|
|
fn from_u8(value: u8) -> Self {
|
|
match value {
|
|
1 => Self::Connecting,
|
|
2 => Self::Connected,
|
|
3 => Self::LoggingOut,
|
|
4 => Self::ShuttingDown,
|
|
_ => Self::Disconnected,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn wait_for_logout(
|
|
receiver: &Receiver<()>,
|
|
timeout: Duration,
|
|
cancellations: &[CancellationToken],
|
|
) -> LogoutWaitOutcome {
|
|
let started = std::time::Instant::now();
|
|
loop {
|
|
if receiver.try_recv().is_ok() {
|
|
return LogoutWaitOutcome::Reply;
|
|
}
|
|
if cancellations
|
|
.iter()
|
|
.any(CancellationToken::is_cancellation_requested)
|
|
{
|
|
return LogoutWaitOutcome::Cancelled;
|
|
}
|
|
let remaining = timeout.saturating_sub(started.elapsed());
|
|
if remaining.is_zero() {
|
|
return LogoutWaitOutcome::Timeout;
|
|
}
|
|
match receiver.recv_timeout(remaining.min(Duration::from_millis(20))) {
|
|
Ok(()) => return LogoutWaitOutcome::Reply,
|
|
Err(RecvTimeoutError::Timeout) => {}
|
|
Err(RecvTimeoutError::Disconnected) => return LogoutWaitOutcome::Cancelled,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn validate_login_uri(value: &str) -> Result<Uri, Error> {
|
|
let parsed = reqwest::Url::parse(value).map_err(|_| Error::Argument)?;
|
|
if !matches!(parsed.scheme(), "http" | "https") || parsed.host_str().is_none() {
|
|
return Err(Error::Argument);
|
|
}
|
|
Ok(Uri(parsed.to_string()))
|
|
}
|
|
|
|
struct LoginWireSecrets {
|
|
password: String,
|
|
token: String,
|
|
mfa_hash: String,
|
|
}
|
|
|
|
impl Drop for LoginWireSecrets {
|
|
fn drop(&mut self) {
|
|
// Overwrite the live buffers before releasing them. This cannot erase
|
|
// allocator copies, but it prevents the owned request state surviving
|
|
// the login/redirect sequence.
|
|
self.password
|
|
.replace_range(.., &"\0".repeat(self.password.len()));
|
|
self.token.replace_range(.., &"\0".repeat(self.token.len()));
|
|
self.mfa_hash
|
|
.replace_range(.., &"\0".repeat(self.mfa_hash.len()));
|
|
self.password.clear();
|
|
self.token.clear();
|
|
self.mfa_hash.clear();
|
|
}
|
|
}
|
|
|
|
fn login_request(params: &LoginParams, secrets: &LoginWireSecrets) -> OSD {
|
|
let mut map = HashMap::from([
|
|
("first".to_owned(), OSD::String(params.first_name.clone())),
|
|
("last".to_owned(), OSD::String(params.last_name.clone())),
|
|
("passwd".to_owned(), OSD::String(secrets.password.clone())),
|
|
("start".to_owned(), OSD::String(params.start.clone())),
|
|
("channel".to_owned(), OSD::String(params.channel.clone())),
|
|
("version".to_owned(), OSD::String(params.version.clone())),
|
|
("platform".to_owned(), OSD::String(params.platform.clone())),
|
|
(
|
|
"platform_version".to_owned(),
|
|
OSD::String(params.platform_version.clone()),
|
|
),
|
|
("mac".to_owned(), OSD::String(params.mac.clone())),
|
|
("agree_to_tos".to_owned(), OSD::Boolean(params.agree_to_tos)),
|
|
(
|
|
"read_critical".to_owned(),
|
|
OSD::Boolean(params.read_critical),
|
|
),
|
|
(
|
|
"viewer_digest".to_owned(),
|
|
OSD::String(params.viewer_digest.clone()),
|
|
),
|
|
("id0".to_owned(), OSD::String(params.id0.clone())),
|
|
(
|
|
"last_exec_event".to_owned(),
|
|
OSD::Integer(params.last_exec_event as i32),
|
|
),
|
|
(
|
|
"options".to_owned(),
|
|
OSD::Array(params.options.iter().cloned().map(OSD::String).collect()),
|
|
),
|
|
]);
|
|
if params.mfa_enabled {
|
|
map.insert("token".to_owned(), OSD::String(secrets.token.clone()));
|
|
map.insert("mfa_hash".to_owned(), OSD::String(secrets.mfa_hash.clone()));
|
|
}
|
|
OSD::Map(map)
|
|
}
|
|
|
|
async fn run_blocking_compat<T: Send + 'static>(
|
|
name: &str,
|
|
operation: impl FnOnce() -> T + Send + 'static,
|
|
) -> Result<T, Error> {
|
|
let (sender, receiver) = tokio::sync::oneshot::channel();
|
|
thread::Builder::new()
|
|
.name(name.to_owned())
|
|
.spawn(move || {
|
|
let _ = sender.send(operation());
|
|
})
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
receiver.await.map_err(|_| Error::Cancelled)
|
|
}
|
|
|
|
struct EventRegistryState<T> {
|
|
next_id: AtomicU64,
|
|
handlers: Mutex<Vec<(u64, EventHandler<T>)>>,
|
|
}
|
|
|
|
struct EventRegistry<T> {
|
|
state: Arc<EventRegistryState<T>>,
|
|
}
|
|
|
|
impl<T> Default for EventRegistry<T> {
|
|
fn default() -> Self {
|
|
Self {
|
|
state: Arc::new(EventRegistryState {
|
|
next_id: AtomicU64::new(1),
|
|
handlers: Mutex::new(Vec::new()),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: 'static> EventRegistry<T> {
|
|
fn subscribe(&self, handler: EventHandler<T>) -> Subscription {
|
|
let id = self.state.next_id.fetch_add(1, Ordering::Relaxed);
|
|
mutex(&self.state.handlers).push((id, handler));
|
|
let state = Arc::downgrade(&self.state);
|
|
Subscription::new(move || {
|
|
let Some(state) = state.upgrade() else {
|
|
return;
|
|
};
|
|
mutex(&state.handlers).retain(|(candidate, _)| *candidate != id);
|
|
})
|
|
}
|
|
|
|
fn emit_with(&self, mut value: impl FnMut() -> T) {
|
|
let handlers: Vec<_> = mutex(&self.state.handlers)
|
|
.iter()
|
|
.map(|(_, handler)| Arc::clone(handler))
|
|
.collect();
|
|
for handler in handlers {
|
|
invoke_safely(&handler, value());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// C# `PacketReceivedEventArgs` with shared simulator identity.
|
|
#[derive(Clone)]
|
|
pub struct PacketReceivedEventArgs {
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
raw_data: Option<Vec<u8>>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct RawPacketReceivedEventArgs {
|
|
pub packet_type: PacketType,
|
|
pub data: Vec<u8>,
|
|
pub simulator: Simulator,
|
|
}
|
|
|
|
impl PacketReceivedEventArgs {
|
|
pub fn new(packet: Packet, simulator: Simulator) -> Result<Self, Error> {
|
|
Self::new_with_raw_data(packet, simulator, None)
|
|
}
|
|
|
|
pub fn new_with_raw_data(
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
raw_data: Option<Vec<u8>>,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
packet,
|
|
simulator,
|
|
raw_data,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn packet(&self) -> Packet {
|
|
self.packet.clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
|
|
/// Returns the original datagram when this event came from the native UDP
|
|
/// receive path. Synthetic decoded events intentionally return `None`.
|
|
#[must_use]
|
|
pub fn raw_data(&self) -> Option<Vec<u8>> {
|
|
self.raw_data.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct PacketSentEventArgs {
|
|
data: Vec<u8>,
|
|
sent_bytes: i32,
|
|
simulator: Simulator,
|
|
}
|
|
|
|
impl PacketSentEventArgs {
|
|
pub fn new(data: Vec<u8>, sent_bytes: i32, simulator: Simulator) -> Result<Self, Error> {
|
|
let sent = usize::try_from(sent_bytes).map_err(|_| Error::Argument)?;
|
|
if sent > data.len() {
|
|
return Err(Error::Argument);
|
|
}
|
|
Ok(Self {
|
|
data,
|
|
sent_bytes,
|
|
simulator,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn data(&self) -> Vec<u8> {
|
|
self.data.clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn sent_bytes(&self) -> i32 {
|
|
self.sent_bytes
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SimChangedEventArgs {
|
|
previous_simulator: Option<Simulator>,
|
|
}
|
|
|
|
/// Inventory item identifiers returned by a successful logout handshake.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct LoggedOutEventArgs {
|
|
pub inventory_items: Vec<UUID>,
|
|
}
|
|
|
|
impl LoggedOutEventArgs {
|
|
pub const fn new(inventory_items: Vec<UUID>) -> Result<Self, Error> {
|
|
Ok(Self { inventory_items })
|
|
}
|
|
}
|
|
|
|
impl SimChangedEventArgs {
|
|
pub fn new(previous_simulator: Option<Simulator>) -> Result<Self, Error> {
|
|
Ok(Self { previous_simulator })
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn previous_simulator(&self) -> Option<Simulator> {
|
|
self.previous_simulator.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SimConnectedEventArgs {
|
|
simulator: Simulator,
|
|
}
|
|
|
|
/// Event arguments raised when a simulator's `EventQueueGet` poller is live.
|
|
#[derive(Clone)]
|
|
pub struct EventQueueRunningEventArgs {
|
|
simulator: Simulator,
|
|
}
|
|
|
|
impl EventQueueRunningEventArgs {
|
|
pub const fn new(simulator: Simulator) -> Result<Self, Error> {
|
|
Ok(Self { simulator })
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
}
|
|
|
|
impl SimConnectedEventArgs {
|
|
pub fn new(simulator: Simulator) -> Result<Self, Error> {
|
|
Ok(Self { simulator })
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SimConnectingEventArgs {
|
|
simulator: Simulator,
|
|
cancel: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl SimConnectingEventArgs {
|
|
pub fn new(simulator: Simulator) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
simulator,
|
|
cancel: Arc::new(AtomicBool::new(false)),
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn cancel(&self) -> bool {
|
|
self.cancel.load(Ordering::Acquire)
|
|
}
|
|
|
|
pub fn set_cancel(&mut self, value: bool) {
|
|
self.cancel.store(value, Ordering::Release);
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SimDisconnectedEventArgs {
|
|
simulator: Simulator,
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
was_connected: bool,
|
|
}
|
|
|
|
impl SimDisconnectedEventArgs {
|
|
pub fn new(
|
|
simulator: Simulator,
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
was_connected: bool,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
simulator,
|
|
reason,
|
|
was_connected,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn reason(&self) -> crate::NetworkManagerDisconnectType {
|
|
self.reason
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn was_connected(&self) -> bool {
|
|
self.was_connected
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct DisconnectedEventArgs {
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
message: String,
|
|
}
|
|
|
|
impl DisconnectedEventArgs {
|
|
pub fn new(
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
message: String,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self { reason, message })
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn message(&self) -> String {
|
|
self.message.clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn reason(&self) -> crate::NetworkManagerDisconnectType {
|
|
self.reason
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct GenericStreamingMessageEventArgs {
|
|
simulator: Simulator,
|
|
method: GenericStreamingMethod,
|
|
data: Vec<u8>,
|
|
}
|
|
|
|
impl GenericStreamingMessageEventArgs {
|
|
pub fn new(
|
|
simulator: Simulator,
|
|
method: GenericStreamingMethod,
|
|
data: Vec<u8>,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
simulator,
|
|
method,
|
|
data,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn data(&self) -> Vec<u8> {
|
|
self.data.clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn method(&self) -> GenericStreamingMethod {
|
|
self.method
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn simulator(&self) -> Simulator {
|
|
self.simulator.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
struct RegisteredPacketCallbacks {
|
|
handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
|
|
is_async: bool,
|
|
}
|
|
|
|
struct AsyncPacketBatch {
|
|
default_handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
|
|
specific_handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
raw_data: Option<Vec<u8>>,
|
|
}
|
|
|
|
enum PacketWorkerCommand {
|
|
Dispatch(AsyncPacketBatch),
|
|
Stop,
|
|
}
|
|
|
|
struct PacketEventState {
|
|
callbacks: Mutex<HashMap<PacketType, RegisteredPacketCallbacks>>,
|
|
async_sender: SyncSender<PacketWorkerCommand>,
|
|
worker: Mutex<Option<JoinHandle<()>>>,
|
|
}
|
|
|
|
impl Drop for PacketEventState {
|
|
fn drop(&mut self) {
|
|
let _ = self.async_sender.try_send(PacketWorkerCommand::Stop);
|
|
if let Some(worker) = self
|
|
.worker
|
|
.get_mut()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.take()
|
|
&& worker.thread().id() != thread::current().id()
|
|
{
|
|
let _ = worker.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ordered packet callback registry matching `PacketEventDictionary`.
|
|
pub struct PacketEventDictionary {
|
|
pub client: GridClient,
|
|
state: Arc<PacketEventState>,
|
|
}
|
|
|
|
impl PacketEventDictionary {
|
|
pub fn new(client: GridClient) -> Result<Self, Error> {
|
|
let (sender, receiver) = sync_channel(256);
|
|
let state = Arc::new(PacketEventState {
|
|
callbacks: Mutex::new(HashMap::new()),
|
|
async_sender: sender,
|
|
worker: Mutex::new(None),
|
|
});
|
|
let weak = Arc::downgrade(&state);
|
|
let worker = thread::Builder::new()
|
|
.name("libremetaverse-packet-events".to_owned())
|
|
.spawn(move || packet_event_worker(receiver, weak))
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
*mutex(&state.worker) = Some(worker);
|
|
Ok(Self { client, state })
|
|
}
|
|
|
|
pub fn register_event(
|
|
&self,
|
|
packet_type: PacketType,
|
|
event_handler: EventHandler<PacketReceivedEventArgs>,
|
|
is_async: bool,
|
|
) -> Result<(), Error> {
|
|
let mut table = mutex(&self.state.callbacks);
|
|
let callbacks = table.entry(packet_type).or_default();
|
|
callbacks.is_async |= is_async;
|
|
callbacks.handlers.push(event_handler);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn subscribe(
|
|
&self,
|
|
packet_type: PacketType,
|
|
event_handler: EventHandler<PacketReceivedEventArgs>,
|
|
is_async: bool,
|
|
) -> Subscription {
|
|
let identity = Arc::as_ptr(&event_handler).cast::<()>() as usize;
|
|
let _ = self.register_event(packet_type, event_handler, is_async);
|
|
let state = Arc::downgrade(&self.state);
|
|
Subscription::new(move || {
|
|
let Some(state) = state.upgrade() else {
|
|
return;
|
|
};
|
|
let mut table = mutex(&state.callbacks);
|
|
let Some(callbacks) = table.get_mut(&packet_type) else {
|
|
return;
|
|
};
|
|
if let Some(index) = callbacks
|
|
.handlers
|
|
.iter()
|
|
.rposition(|handler| Arc::as_ptr(handler).cast::<()>() as usize == identity)
|
|
{
|
|
callbacks.handlers.remove(index);
|
|
}
|
|
if callbacks.handlers.is_empty() {
|
|
table.remove(&packet_type);
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn unregister_event(
|
|
&self,
|
|
packet_type: PacketType,
|
|
event_handler: EventHandler<PacketReceivedEventArgs>,
|
|
) -> Result<(), Error> {
|
|
let mut table = mutex(&self.state.callbacks);
|
|
let Some(callbacks) = table.get_mut(&packet_type) else {
|
|
return Ok(());
|
|
};
|
|
if let Some(index) = callbacks
|
|
.handlers
|
|
.iter()
|
|
.rposition(|handler| Arc::ptr_eq(handler, &event_handler))
|
|
{
|
|
callbacks.handlers.remove(index);
|
|
}
|
|
if callbacks.handlers.is_empty() {
|
|
table.remove(&packet_type);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn invoke_raise_event(
|
|
&self,
|
|
packet_type: PacketType,
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
) -> Result<(), Error> {
|
|
self.raise_event(packet_type, packet, simulator, None)
|
|
}
|
|
|
|
/// Dispatches a decoded packet together with its original wire datagram.
|
|
/// This is primarily useful to deterministic packet-capture fixtures.
|
|
pub fn invoke_raise_event_with_raw_data(
|
|
&self,
|
|
packet_type: PacketType,
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
raw_data: Vec<u8>,
|
|
) -> Result<(), Error> {
|
|
self.raise_event(packet_type, packet, simulator, Some(raw_data))
|
|
}
|
|
|
|
fn raise_event(
|
|
&self,
|
|
packet_type: PacketType,
|
|
packet: Packet,
|
|
simulator: Simulator,
|
|
raw_data: Option<Vec<u8>>,
|
|
) -> Result<(), Error> {
|
|
let (defaults, specific) = {
|
|
let table = mutex(&self.state.callbacks);
|
|
(
|
|
table.get(&PacketType::Default).cloned().unwrap_or_default(),
|
|
table.get(&packet_type).cloned().unwrap_or_default(),
|
|
)
|
|
};
|
|
let default_async = defaults.is_async;
|
|
let specific_async = specific.is_async;
|
|
let args = PacketReceivedEventArgs::new_with_raw_data(
|
|
packet.clone(),
|
|
simulator.clone(),
|
|
raw_data.clone(),
|
|
)?;
|
|
|
|
if !default_async {
|
|
for handler in &defaults.handlers {
|
|
invoke_safely(handler, args.clone());
|
|
}
|
|
}
|
|
if !specific_async {
|
|
for handler in &specific.handlers {
|
|
invoke_safely(handler, args.clone());
|
|
}
|
|
// The reference implementation returns as soon as a concrete
|
|
// packet type has synchronous handlers. This also deliberately
|
|
// suppresses a default asynchronous chain for that dispatch.
|
|
if !specific.handlers.is_empty() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
let default_handlers: Vec<EventHandler<PacketReceivedEventArgs>> = if default_async {
|
|
defaults.handlers
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
let specific_handlers: Vec<EventHandler<PacketReceivedEventArgs>> = if specific_async {
|
|
specific.handlers
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
if default_handlers.is_empty() && specific_handlers.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.state
|
|
.async_sender
|
|
.try_send(PacketWorkerCommand::Dispatch(AsyncPacketBatch {
|
|
default_handlers,
|
|
specific_handlers,
|
|
packet,
|
|
simulator,
|
|
raw_data,
|
|
}))
|
|
.map_err(|_| Error::InvalidOperation)
|
|
}
|
|
}
|
|
|
|
fn packet_event_worker(receiver: Receiver<PacketWorkerCommand>, state: Weak<PacketEventState>) {
|
|
while state.strong_count() != 0 {
|
|
match receiver.recv_timeout(Duration::from_millis(100)) {
|
|
Ok(PacketWorkerCommand::Dispatch(batch)) => {
|
|
let Ok(args) = PacketReceivedEventArgs::new_with_raw_data(
|
|
batch.packet,
|
|
batch.simulator,
|
|
batch.raw_data,
|
|
) else {
|
|
continue;
|
|
};
|
|
for handler in batch
|
|
.default_handlers
|
|
.into_iter()
|
|
.chain(batch.specific_handlers)
|
|
{
|
|
invoke_safely(&handler, args.clone());
|
|
}
|
|
}
|
|
Ok(PacketWorkerCommand::Stop) | Err(RecvTimeoutError::Disconnected) => break,
|
|
Err(RecvTimeoutError::Timeout) => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
type CapsHandler = dyn Fn(&str, &dyn IMessage, Simulator) + Send + Sync;
|
|
|
|
#[derive(Clone)]
|
|
pub struct CapsEventQueueCallback {
|
|
id: u64,
|
|
handler: Arc<CapsHandler>,
|
|
}
|
|
|
|
impl CapsEventQueueCallback {
|
|
pub fn from_handler(
|
|
handler: impl Fn(&str, &dyn IMessage, Simulator) + Send + Sync + 'static,
|
|
) -> Self {
|
|
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
|
Self {
|
|
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
|
|
handler: Arc::new(handler),
|
|
}
|
|
}
|
|
|
|
pub fn new(_object: Object, _method: isize) -> Result<Self, Error> {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
|
|
pub fn begin_invoke(
|
|
&self,
|
|
caps_key: String,
|
|
message: Box<dyn IMessage>,
|
|
simulator: Simulator,
|
|
callback: Box<dyn Fn(&dyn std::any::Any) + Send + Sync>,
|
|
object: Object,
|
|
) -> Result<Box<dyn std::any::Any + Send + Sync>, Error> {
|
|
(self.handler)(&caps_key, message.as_ref(), simulator);
|
|
callback(&object);
|
|
Ok(Box::new(object))
|
|
}
|
|
|
|
pub fn end_invoke(&self, _result: Box<dyn std::any::Any + Send + Sync>) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
|
|
pub fn invoke(
|
|
&self,
|
|
caps_key: String,
|
|
message: Box<dyn IMessage>,
|
|
simulator: Simulator,
|
|
) -> Result<(), Error> {
|
|
(self.handler)(&caps_key, message.as_ref(), simulator);
|
|
Ok(())
|
|
}
|
|
|
|
fn invoke_ref(&self, caps_key: &str, message: &dyn IMessage, simulator: Simulator) {
|
|
let _ = catch_unwind(AssertUnwindSafe(|| {
|
|
(self.handler)(caps_key, message, simulator);
|
|
}));
|
|
}
|
|
}
|
|
|
|
pub struct CapsEventDictionary {
|
|
pub client: GridClient,
|
|
callbacks: Arc<Mutex<HashMap<String, Vec<CapsEventQueueCallback>>>>,
|
|
}
|
|
|
|
impl CapsEventDictionary {
|
|
pub fn new(client: GridClient) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
client,
|
|
callbacks: Arc::new(Mutex::new(HashMap::new())),
|
|
})
|
|
}
|
|
|
|
pub fn register_event(
|
|
&self,
|
|
caps_event: String,
|
|
event_handler: CapsEventQueueCallback,
|
|
) -> Result<(), Error> {
|
|
mutex(&self.callbacks)
|
|
.entry(caps_event)
|
|
.or_default()
|
|
.push(event_handler);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn subscribe(
|
|
&self,
|
|
caps_event: String,
|
|
event_handler: CapsEventQueueCallback,
|
|
) -> Subscription {
|
|
let id = event_handler.id;
|
|
let _ = self.register_event(caps_event.clone(), event_handler);
|
|
let callbacks = Arc::downgrade(&self.callbacks);
|
|
Subscription::new(move || {
|
|
let Some(callbacks) = callbacks.upgrade() else {
|
|
return;
|
|
};
|
|
let mut table = mutex(&callbacks);
|
|
let Some(entries) = table.get_mut(&caps_event) else {
|
|
return;
|
|
};
|
|
entries.retain(|entry| entry.id != id);
|
|
if entries.is_empty() {
|
|
table.remove(&caps_event);
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn unregister_event(
|
|
&self,
|
|
caps_event: String,
|
|
event_handler: CapsEventQueueCallback,
|
|
) -> Result<(), Error> {
|
|
let mut table = mutex(&self.callbacks);
|
|
if let Some(entries) = table.get_mut(&caps_event) {
|
|
if let Some(index) = entries
|
|
.iter()
|
|
.rposition(|entry| entry.id == event_handler.id)
|
|
{
|
|
entries.remove(index);
|
|
}
|
|
if entries.is_empty() {
|
|
table.remove(&caps_event);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn invoke_raise_event(
|
|
&self,
|
|
caps_event: &str,
|
|
message: &dyn IMessage,
|
|
simulator: Simulator,
|
|
) {
|
|
let (defaults, specific) = {
|
|
let table = mutex(&self.callbacks);
|
|
(
|
|
table.get("").cloned().unwrap_or_default(),
|
|
table.get(caps_event).cloned().unwrap_or_default(),
|
|
)
|
|
};
|
|
for callback in defaults.into_iter().chain(specific) {
|
|
callback.invoke_ref(caps_event, message, simulator.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mutable, thread-safe equivalent of the C# public simulator list.
|
|
#[derive(Clone, Default)]
|
|
pub struct SimulatorCollection(Arc<RwLock<Vec<Simulator>>>);
|
|
|
|
impl SimulatorCollection {
|
|
#[must_use]
|
|
pub fn snapshot(&self) -> Vec<Simulator> {
|
|
read(&self.0).clone()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn len(&self) -> usize {
|
|
read(&self.0).len()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
fn push_if_missing(&self, simulator: Simulator) -> Simulator {
|
|
let mut simulators = write(&self.0);
|
|
if let Some(existing) = simulators
|
|
.iter()
|
|
.find(|existing| existing.ip_end_point() == simulator.ip_end_point())
|
|
{
|
|
return existing.clone();
|
|
}
|
|
simulators.push(simulator.clone());
|
|
simulator
|
|
}
|
|
|
|
fn remove(&self, simulator: &Simulator) -> bool {
|
|
let mut simulators = write(&self.0);
|
|
let old_len = simulators.len();
|
|
simulators.retain(|candidate| candidate != simulator);
|
|
old_len != simulators.len()
|
|
}
|
|
|
|
fn drain(&self) -> Vec<Simulator> {
|
|
std::mem::take(&mut *write(&self.0))
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for SimulatorCollection {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter
|
|
.debug_list()
|
|
.entries(self.snapshot().iter())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
struct TransportThread {
|
|
shutdown: libremetaverse_types::compat::CancellationTokenSource,
|
|
handle: JoinHandle<()>,
|
|
}
|
|
|
|
/// Shared data exposed through the C# `Simulator` field surface.
|
|
pub struct SimulatorData {
|
|
pub access: SimAccess,
|
|
pub agent_movement_complete: bool,
|
|
pub billable_factor: f32,
|
|
pub cpu_class: i32,
|
|
pub cpu_ratio: i32,
|
|
pub client: GridClient,
|
|
pub colo_location: String,
|
|
pub data_pool: Option<SimulatorDataPool>,
|
|
pub features: SimulatorFeatures,
|
|
pub flags: RegionFlags,
|
|
pub global_to_local_id: RwLock<HashMap<UUID, u32>>,
|
|
pub handle: u64,
|
|
pub id: UUID,
|
|
pub is_estate_manager: bool,
|
|
pub name: String,
|
|
pub objects_avatars: RwLock<HashMap<u32, Avatar>>,
|
|
pub objects_primitives: RwLock<HashMap<u32, Primitive>>,
|
|
pub parcel_overlay: RwLock<Vec<u8>>,
|
|
pub parcel_overlays_received: AtomicI32,
|
|
parcel_overlay_segments: AtomicU8,
|
|
pub parcels: RwLock<HashMap<i32, Parcel>>,
|
|
pub parcel_map: RwLock<Vec<i32>>,
|
|
pub product_name: String,
|
|
pub product_sku: String,
|
|
pub protocols: RegionProtocols,
|
|
pub region_id: UUID,
|
|
pub sequence: i32,
|
|
pub sim_owner: UUID,
|
|
pub sim_version: String,
|
|
pub size_x: u32,
|
|
pub size_y: u32,
|
|
pub stats: SimulatorSimStats,
|
|
pub terrain: RwLock<Vec<TerrainPatch>>,
|
|
pub terrain_base0: UUID,
|
|
pub terrain_base1: UUID,
|
|
pub terrain_base2: UUID,
|
|
pub terrain_base3: UUID,
|
|
pub terrain_detail0: UUID,
|
|
pub terrain_detail1: UUID,
|
|
pub terrain_detail2: UUID,
|
|
pub terrain_detail3: UUID,
|
|
pub terrain_height_range00: f32,
|
|
pub terrain_height_range01: f32,
|
|
pub terrain_height_range10: f32,
|
|
pub terrain_height_range11: f32,
|
|
pub terrain_start_height00: f32,
|
|
pub terrain_start_height01: f32,
|
|
pub terrain_start_height10: f32,
|
|
pub terrain_start_height11: f32,
|
|
pub water_height: f32,
|
|
pub wind_speeds: RwLock<Option<Vec<Vector2>>>,
|
|
prey_id: RwLock<UUID>,
|
|
endpoint: std::net::SocketAddr,
|
|
connected: AtomicBool,
|
|
movement_complete: AtomicBool,
|
|
handshake_complete: AtomicBool,
|
|
handshake_wait: Condvar,
|
|
handshake_wait_lock: Mutex<()>,
|
|
disconnect_candidate: AtomicBool,
|
|
circuit_code: AtomicU32,
|
|
agent_id: RwLock<UUID>,
|
|
session_id: RwLock<UUID>,
|
|
seed_caps: RwLock<Option<Uri>>,
|
|
caps_state: RwLock<Option<Arc<crate::caps::CapsInner>>>,
|
|
event_queue_generation: Mutex<u64>,
|
|
event_queue_wait: Condvar,
|
|
manager: Mutex<Weak<NetworkManagerInner>>,
|
|
transport: UDPBase,
|
|
transport_thread: Mutex<Option<TransportThread>>,
|
|
pause_serial: AtomicU32,
|
|
}
|
|
|
|
impl Drop for SimulatorData {
|
|
fn drop(&mut self) {
|
|
if let Some(caps) = self
|
|
.caps_state
|
|
.get_mut()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.take()
|
|
{
|
|
caps.disconnect(true);
|
|
}
|
|
let runtime = self
|
|
.transport_thread
|
|
.get_mut()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.take();
|
|
if let Some(runtime) = runtime {
|
|
runtime.shutdown.cancel();
|
|
if runtime.handle.thread().id() != thread::current().id() {
|
|
let _ = runtime.handle.join();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reference-identity simulator handle.
|
|
pub struct Simulator {
|
|
/// Capability client associated with this simulator.
|
|
///
|
|
/// This remains on the value surface, rather than behind `Deref`, because
|
|
/// existing translated code consumes the public C# field by value.
|
|
pub caps: Option<Box<Caps>>,
|
|
data: Arc<SimulatorData>,
|
|
}
|
|
|
|
impl Clone for Simulator {
|
|
fn clone(&self) -> Self {
|
|
let mut clone = self.native_clone_without_caps();
|
|
if let Some(inner) = read(&self.data.caps_state).clone() {
|
|
clone.caps = Some(Box::new(Caps::native_from_inner(
|
|
inner,
|
|
clone.native_clone_without_caps(),
|
|
)));
|
|
}
|
|
clone
|
|
}
|
|
}
|
|
|
|
impl std::ops::Deref for Simulator {
|
|
type Target = SimulatorData;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.data
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Simulator {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.ip_end_point() == other.ip_end_point()
|
|
}
|
|
}
|
|
|
|
impl Eq for Simulator {}
|
|
|
|
impl fmt::Debug for Simulator {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter
|
|
.debug_struct("Simulator")
|
|
.field("endpoint", &self.native_ip_end_point())
|
|
.field("handle", &self.handle)
|
|
.field("connected", &self.native_is_connected())
|
|
.field("handshake_complete", &self.native_handshake_complete())
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl Simulator {
|
|
pub(crate) fn native_prey_id(&self) -> UUID {
|
|
*read(&self.data.prey_id)
|
|
}
|
|
|
|
pub(crate) fn native_set_prey_id(&self, value: UUID) {
|
|
*write(&self.data.prey_id) = value;
|
|
}
|
|
|
|
pub fn native_new(
|
|
client: GridClient,
|
|
address: std::net::SocketAddr,
|
|
handle: u64,
|
|
size_x: Option<u32>,
|
|
size_y: Option<u32>,
|
|
) -> Result<Self, Error> {
|
|
let cancellation = client.cancellation_token();
|
|
let simulator_reference = Arc::new(Mutex::new(Weak::new()));
|
|
let handler: Arc<dyn UdpPacketHandler> = Arc::new(SimulatorUdpHandler {
|
|
simulator: Arc::clone(&simulator_reference),
|
|
});
|
|
let transport = UDPBase::client(
|
|
address,
|
|
UdpTransportConfig::default(),
|
|
handler,
|
|
cancellation,
|
|
)?;
|
|
let data = Arc::new(SimulatorData {
|
|
access: SimAccess::UNKNOWN,
|
|
agent_movement_complete: false,
|
|
billable_factor: 0.0,
|
|
cpu_class: 0,
|
|
cpu_ratio: 0,
|
|
client,
|
|
colo_location: String::new(),
|
|
data_pool: None,
|
|
features: SimulatorFeatures::default(),
|
|
flags: RegionFlags(0),
|
|
global_to_local_id: RwLock::new(HashMap::new()),
|
|
handle,
|
|
id: UUID::zero(),
|
|
is_estate_manager: false,
|
|
name: String::new(),
|
|
objects_avatars: RwLock::new(HashMap::new()),
|
|
objects_primitives: RwLock::new(HashMap::new()),
|
|
parcel_overlay: RwLock::new(vec![0; 4096]),
|
|
parcel_overlays_received: AtomicI32::new(0),
|
|
parcel_overlay_segments: AtomicU8::new(0),
|
|
parcels: RwLock::new(HashMap::new()),
|
|
parcel_map: RwLock::new(vec![0; 4096]),
|
|
product_name: String::new(),
|
|
product_sku: String::new(),
|
|
protocols: RegionProtocols(0),
|
|
region_id: UUID::zero(),
|
|
sequence: 0,
|
|
sim_owner: UUID::zero(),
|
|
sim_version: String::new(),
|
|
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
|
|
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
|
|
stats: SimulatorSimStats::default(),
|
|
terrain: RwLock::new(Vec::new()),
|
|
terrain_base0: UUID::zero(),
|
|
terrain_base1: UUID::zero(),
|
|
terrain_base2: UUID::zero(),
|
|
terrain_base3: UUID::zero(),
|
|
terrain_detail0: UUID::zero(),
|
|
terrain_detail1: UUID::zero(),
|
|
terrain_detail2: UUID::zero(),
|
|
terrain_detail3: UUID::zero(),
|
|
terrain_height_range00: 0.0,
|
|
terrain_height_range01: 0.0,
|
|
terrain_height_range10: 0.0,
|
|
terrain_height_range11: 0.0,
|
|
terrain_start_height00: 0.0,
|
|
terrain_start_height01: 0.0,
|
|
terrain_start_height10: 0.0,
|
|
terrain_start_height11: 0.0,
|
|
water_height: 0.0,
|
|
wind_speeds: RwLock::new(None),
|
|
prey_id: RwLock::new(UUID::zero()),
|
|
endpoint: address,
|
|
connected: AtomicBool::new(false),
|
|
movement_complete: AtomicBool::new(false),
|
|
handshake_complete: AtomicBool::new(false),
|
|
handshake_wait: Condvar::new(),
|
|
handshake_wait_lock: Mutex::new(()),
|
|
disconnect_candidate: AtomicBool::new(false),
|
|
circuit_code: AtomicU32::new(0),
|
|
agent_id: RwLock::new(UUID::zero()),
|
|
session_id: RwLock::new(UUID::zero()),
|
|
seed_caps: RwLock::new(None),
|
|
caps_state: RwLock::new(None),
|
|
event_queue_generation: Mutex::new(0),
|
|
event_queue_wait: Condvar::new(),
|
|
manager: Mutex::new(Weak::new()),
|
|
transport,
|
|
transport_thread: Mutex::new(None),
|
|
pause_serial: AtomicU32::new(0),
|
|
});
|
|
*mutex(&simulator_reference) = Arc::downgrade(&data);
|
|
Ok(Self { caps: None, data })
|
|
}
|
|
|
|
pub(crate) fn native_clone_without_caps(&self) -> Self {
|
|
Self {
|
|
caps: None,
|
|
data: Arc::clone(&self.data),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_data_weak(&self) -> Weak<SimulatorData> {
|
|
Arc::downgrade(&self.data)
|
|
}
|
|
|
|
pub(crate) fn native_data_arc(&self) -> Arc<SimulatorData> {
|
|
Arc::clone(&self.data)
|
|
}
|
|
|
|
pub(crate) fn native_caps(&self) -> Option<Caps> {
|
|
read(&self.data.caps_state)
|
|
.clone()
|
|
.map(|inner| Caps::native_from_inner(inner, self.native_clone_without_caps()))
|
|
}
|
|
|
|
/// Looks up one capability without exposing the simulator's mutable caps state.
|
|
///
|
|
/// Capability URLs are bearer credentials. Callers must not log or persist
|
|
/// the returned URI.
|
|
pub fn native_capability_uri(&self, capability: &str) -> Result<Option<Uri>, Error> {
|
|
let Some(caps) = self.native_caps() else {
|
|
return Ok(None);
|
|
};
|
|
caps.capability_uri(capability.to_owned())
|
|
}
|
|
|
|
pub(crate) fn native_id(&self) -> UUID {
|
|
self.data.id
|
|
}
|
|
|
|
pub(crate) fn native_store_terrain_patch(
|
|
&self,
|
|
patch: TerrainPatch,
|
|
large_region: bool,
|
|
) -> Result<(), Error> {
|
|
let (width, height) = if large_region {
|
|
(
|
|
usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?,
|
|
usize::try_from(self.size_y.max(16) / 16).map_err(|_| Error::Argument)?,
|
|
)
|
|
} else {
|
|
(16, 16)
|
|
};
|
|
let x = usize::try_from(patch.x).map_err(|_| Error::Argument)?;
|
|
let y = usize::try_from(patch.y).map_err(|_| Error::Argument)?;
|
|
if x >= width || y >= height || width > 256 || height > 256 {
|
|
return Err(Error::Argument);
|
|
}
|
|
let index = y
|
|
.checked_mul(width)
|
|
.and_then(|value| value.checked_add(x))
|
|
.ok_or(Error::Argument)?;
|
|
let patch_count = width.checked_mul(height).ok_or(Error::Argument)?;
|
|
let mut terrain = write(&self.terrain);
|
|
terrain.resize_with(patch_count, || TerrainPatch {
|
|
data: Vec::new(),
|
|
x: 0,
|
|
y: 0,
|
|
});
|
|
terrain[index] = patch;
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn native_store_wind(&self, values: Vec<Vector2>) {
|
|
*write(&self.wind_speeds) = Some(values);
|
|
}
|
|
|
|
pub(crate) fn native_apply_parcel_overlay(
|
|
&self,
|
|
sequence_id: i32,
|
|
data: Vec<u8>,
|
|
) -> Result<bool, Error> {
|
|
if !(0..4).contains(&sequence_id) {
|
|
return Err(Error::Argument);
|
|
}
|
|
let mut overlay = write(&self.parcel_overlay);
|
|
let segment_size = overlay.len().checked_div(4).ok_or(Error::Argument)?;
|
|
if data.len() != segment_size {
|
|
return Err(Error::Argument);
|
|
}
|
|
let sequence = usize::try_from(sequence_id).map_err(|_| Error::Argument)?;
|
|
let offset = sequence.checked_mul(segment_size).ok_or(Error::Argument)?;
|
|
let end = offset.checked_add(segment_size).ok_or(Error::Argument)?;
|
|
overlay[offset..end].copy_from_slice(&data);
|
|
|
|
// The overlay may be retransmitted or arrive out of order. Count unique
|
|
// sequence IDs so duplicates cannot publish an incomplete map.
|
|
let bit = 1_u8 << sequence;
|
|
let old_segments = self.parcel_overlay_segments.load(Ordering::Acquire);
|
|
let segments = old_segments | bit;
|
|
self.parcel_overlay_segments
|
|
.store(segments, Ordering::Release);
|
|
let received = i32::try_from(segments.count_ones()).map_err(|_| Error::Argument)?;
|
|
self.parcel_overlays_received
|
|
.store(received, Ordering::Release);
|
|
if segments == 0b1111 {
|
|
self.parcel_overlay_segments.store(0, Ordering::Release);
|
|
self.parcel_overlays_received.store(0, Ordering::Release);
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_store_parcel(&self, parcel: Parcel) {
|
|
if parcel.bitmap.len() == 512 {
|
|
let mut map = write(&self.parcel_map);
|
|
for cell in 0..4096 {
|
|
if parcel.bitmap[cell / 8] & (1 << (cell % 8)) != 0 {
|
|
map[cell] = parcel.local_id;
|
|
}
|
|
}
|
|
}
|
|
write(&self.parcels).insert(parcel.local_id, parcel);
|
|
}
|
|
|
|
pub(crate) fn native_parcel(&self, local_id: i32) -> Option<Parcel> {
|
|
read(&self.parcels).get(&local_id).cloned()
|
|
}
|
|
|
|
pub(crate) fn native_parcel_map_at(&self, x: usize, y: usize) -> Option<i32> {
|
|
if x >= 64 || y >= 64 {
|
|
return None;
|
|
}
|
|
read(&self.parcel_map).get(y * 64 + x).copied()
|
|
}
|
|
|
|
pub(crate) fn native_parcels_snapshot(&self) -> HashMap<i32, Parcel> {
|
|
read(&self.parcels).clone()
|
|
}
|
|
|
|
pub(crate) fn native_parcel_map_snapshot(&self) -> Vec<i32> {
|
|
read(&self.parcel_map).clone()
|
|
}
|
|
|
|
pub(crate) fn native_is_parcel_map_full(&self) -> bool {
|
|
read(&self.parcel_map).iter().all(|value| *value > 0)
|
|
}
|
|
|
|
pub(crate) fn native_terrain_height_at_point(
|
|
&self,
|
|
x: i32,
|
|
y: i32,
|
|
height: &mut f32,
|
|
) -> Result<bool, Error> {
|
|
let (Ok(x), Ok(y)) = (u32::try_from(x), u32::try_from(y)) else {
|
|
return Ok(false);
|
|
};
|
|
if x >= self.size_x || y >= self.size_y {
|
|
return Ok(false);
|
|
}
|
|
let per_edge = usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?;
|
|
let patch_x = usize::try_from(x / 16).map_err(|_| Error::Argument)?;
|
|
let patch_y = usize::try_from(y / 16).map_err(|_| Error::Argument)?;
|
|
let point = usize::try_from((y % 16) * 16 + (x % 16)).map_err(|_| Error::Argument)?;
|
|
let terrain = read(&self.terrain);
|
|
let Some(patch) = terrain.get(patch_y * per_edge + patch_x) else {
|
|
return Ok(false);
|
|
};
|
|
let Some(value) = patch.data.get(point) else {
|
|
return Ok(false);
|
|
};
|
|
*height = *value;
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn native_clear_parcels(&self) {
|
|
write(&self.parcels).clear();
|
|
write(&self.parcel_map).fill(0);
|
|
}
|
|
|
|
pub(crate) fn native_update_parcel_dwell(&self, local_id: i32, dwell: f32) {
|
|
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
|
|
parcel.dwell = dwell;
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_update_parcel_access(
|
|
&self,
|
|
local_id: i32,
|
|
flags: u32,
|
|
entries: Vec<crate::ParcelManagerParcelAccessEntry>,
|
|
) {
|
|
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
|
|
if flags & crate::AccessList::BAN.0 != 0 {
|
|
parcel.access_black_list = entries;
|
|
} else {
|
|
parcel.access_white_list = entries;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_send_parcel_clean_time(
|
|
&self,
|
|
local_id: i32,
|
|
clean_time: i32,
|
|
) -> Result<(), Error> {
|
|
let mut packet = crate::packets::ParcelSetOtherCleanTimePacket::new_with_constructor()?;
|
|
packet.agent_data.agent_id = *read(&self.data.agent_id);
|
|
packet.agent_data.session_id = *read(&self.data.session_id);
|
|
packet.parcel_data.local_id = local_id;
|
|
packet.parcel_data.other_clean_time = clean_time;
|
|
let data = packet.to_bytes_with_method()?;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::ParcelSetOtherCleanTime,
|
|
false,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn native_from_weak(data: &Weak<SimulatorData>) -> Option<Self> {
|
|
data.upgrade().map(|data| Self { caps: None, data })
|
|
}
|
|
|
|
pub(crate) fn native_from_data(data: Arc<SimulatorData>) -> Self {
|
|
Self { caps: None, data }
|
|
}
|
|
|
|
fn attach_manager(&self, manager: &Arc<NetworkManagerInner>, circuit_code: u32) {
|
|
*mutex(&self.manager) = Arc::downgrade(manager);
|
|
self.circuit_code.store(circuit_code, Ordering::Release);
|
|
*write(&self.agent_id) = *read(&manager.agent_id);
|
|
*write(&self.session_id) = *read(&manager.session_id);
|
|
}
|
|
|
|
pub fn native_connect(&self, move_to_sim: bool) -> Result<bool, Error> {
|
|
if self.native_is_connected() {
|
|
self.native_use_circuit_code(true)?;
|
|
if move_to_sim {
|
|
self.native_complete_agent_movement()?;
|
|
}
|
|
return Ok(true);
|
|
}
|
|
let mut transport_thread = mutex(&self.transport_thread);
|
|
if transport_thread.is_none() {
|
|
let shutdown = libremetaverse_types::compat::CancellationTokenSource::default();
|
|
let shutdown_token = shutdown.token();
|
|
let transport = self.transport.clone();
|
|
let (ready_sender, ready_receiver) = sync_channel(1);
|
|
let handle = thread::Builder::new()
|
|
.name(format!("libremetaverse-udp-{}", self.endpoint))
|
|
.spawn(move || {
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build();
|
|
let Ok(runtime) = runtime else {
|
|
let _ =
|
|
ready_sender.send(Err(crate::UdpTransportError::RuntimeUnavailable));
|
|
return;
|
|
};
|
|
runtime.block_on(async move {
|
|
let result = transport.start_transport();
|
|
let started = result.is_ok();
|
|
let _ = ready_sender.send(result);
|
|
if started {
|
|
shutdown_token.cancelled().await;
|
|
transport.stop_async().await;
|
|
}
|
|
});
|
|
})
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
let timeout =
|
|
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
|
|
match ready_receiver.recv_timeout(timeout) {
|
|
Ok(Ok(())) => {
|
|
*transport_thread = Some(TransportThread { shutdown, handle });
|
|
}
|
|
Ok(Err(error)) => {
|
|
let _ = handle.join();
|
|
return Err(error.into());
|
|
}
|
|
Err(_) => {
|
|
shutdown.cancel();
|
|
let _ = handle.join();
|
|
return Ok(false);
|
|
}
|
|
}
|
|
}
|
|
self.connected.store(true, Ordering::Release);
|
|
self.stats.set_connect_time(environment_tick_count())?;
|
|
self.handshake_complete.store(false, Ordering::Release);
|
|
drop(transport_thread);
|
|
self.native_use_circuit_code(true)?;
|
|
if move_to_sim {
|
|
self.native_complete_agent_movement()?;
|
|
}
|
|
let timeout =
|
|
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
|
|
let guard = mutex(&self.handshake_wait_lock);
|
|
let (guard, _) = self
|
|
.handshake_wait
|
|
.wait_timeout_while(guard, timeout, |()| {
|
|
!self.handshake_complete.load(Ordering::Acquire)
|
|
&& !self.client.cancellation_token().is_cancellation_requested()
|
|
})
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
drop(guard);
|
|
if !self.handshake_complete.load(Ordering::Acquire)
|
|
&& let Some(manager) = mutex(&self.manager).upgrade()
|
|
{
|
|
manager.simulators.remove(self);
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
pub async fn native_connect_async(&self, move_to_sim: bool) -> Result<bool, Error> {
|
|
let simulator = self.clone();
|
|
run_blocking_compat("libremetaverse-simulator-connect", move || {
|
|
simulator.native_connect(move_to_sim)
|
|
})
|
|
.await?
|
|
}
|
|
|
|
pub fn native_disconnect(&self, send_close_circuit: bool) -> Result<(), Error> {
|
|
if let Some(caps) = write(&self.caps_state).take() {
|
|
caps.disconnect(true);
|
|
}
|
|
*write(&self.seed_caps) = None;
|
|
self.disconnect_candidate.store(false, Ordering::Release);
|
|
let was_connected = self.connected.swap(false, Ordering::AcqRel);
|
|
self.handshake_complete.store(false, Ordering::Release);
|
|
self.handshake_wait.notify_all();
|
|
if !was_connected {
|
|
return Ok(());
|
|
}
|
|
if send_close_circuit {
|
|
let close = CloseCircuitPacket::new_with_constructor()?;
|
|
let data = close.to_bytes_with_method()?;
|
|
let _ = self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::CloseCircuit,
|
|
false,
|
|
);
|
|
}
|
|
if let Some(runtime) = mutex(&self.transport_thread).take() {
|
|
runtime.shutdown.cancel();
|
|
if runtime.handle.thread().id() != thread::current().id() {
|
|
let _ = runtime.handle.join();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn native_dispose(&self) -> Result<(), Error> {
|
|
self.native_disconnect(false)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_equals(&self, obj: Option<Object>) -> bool {
|
|
obj.and_then(|value| value.downcast_arc::<Simulator>())
|
|
.is_some_and(|other| *self == *other)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_hash_code(&self) -> i32 {
|
|
let folded = self.handle ^ (self.handle >> 32);
|
|
let low = u32::try_from(folded & u64::from(u32::MAX)).unwrap_or_default();
|
|
low.cast_signed()
|
|
}
|
|
|
|
pub fn native_pause(&self) -> Result<(), Error> {
|
|
let mut packet = AgentPausePacket::new_with_constructor()?;
|
|
packet.agent_data.agent_id = UUID::zero();
|
|
packet.agent_data.session_id = UUID::zero();
|
|
packet.agent_data.serial_num = self.pause_serial.fetch_add(1, Ordering::AcqRel);
|
|
let data = packet.to_bytes_with_method()?;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::AgentPause,
|
|
false,
|
|
)
|
|
}
|
|
|
|
pub fn native_resume(&self) -> Result<(), Error> {
|
|
let mut packet = AgentResumePacket::new_with_constructor()?;
|
|
packet.agent_data.agent_id = UUID::zero();
|
|
packet.agent_data.session_id = UUID::zero();
|
|
packet.agent_data.serial_num = self.pause_serial.fetch_add(1, Ordering::AcqRel);
|
|
let data = packet.to_bytes_with_method()?;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::AgentResume,
|
|
false,
|
|
)
|
|
}
|
|
|
|
pub fn native_send_packet(&self, packet: Packet) -> Result<(), Error> {
|
|
let data = crate::packet_wire::encode_base_packet(&packet)?;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
packet.type_,
|
|
packet.header.zerocoded,
|
|
)
|
|
}
|
|
|
|
pub fn native_send_packet_data(
|
|
&self,
|
|
data: Vec<u8>,
|
|
data_length: i32,
|
|
packet_type: PacketType,
|
|
do_zerocode: bool,
|
|
) -> Result<(), Error> {
|
|
let length = usize::try_from(data_length).map_err(|_| Error::Argument)?;
|
|
let payload = data.get(..length).ok_or(Error::Argument)?.to_vec();
|
|
self.transport
|
|
.try_send_packet(payload, self.endpoint, packet_type, do_zerocode)
|
|
.map(|_| ())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn native_send_ping(&self) -> Result<(), Error> {
|
|
let mut ping = StartPingCheckPacket::new_with_constructor()?;
|
|
ping.ping_id.ping_id = self.stats.get_and_increment_last_ping_id()?.to_le_bytes()[0];
|
|
ping.ping_id.oldest_unacked = 0;
|
|
self.stats.set_last_ping_sent(environment_tick_count())?;
|
|
let data = ping.to_bytes_with_method()?;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::StartPingCheck,
|
|
false,
|
|
)
|
|
}
|
|
|
|
pub fn native_set_seed_caps(
|
|
&self,
|
|
seed_caps: Option<Uri>,
|
|
changed_sim: Option<bool>,
|
|
) -> Result<(), Error> {
|
|
if !changed_sim.unwrap_or(false) && *read(&self.seed_caps) == seed_caps {
|
|
return Ok(());
|
|
}
|
|
if let Some(previous) = write(&self.caps_state).take() {
|
|
previous.disconnect(true);
|
|
}
|
|
write(&self.seed_caps).clone_from(&seed_caps);
|
|
if let Some(seed_caps) = seed_caps {
|
|
let caps = Caps::native_create(self, seed_caps)?;
|
|
*write(&self.caps_state) = Some(caps);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn native_is_event_queue_running(
|
|
&self,
|
|
block_until_running: Option<bool>,
|
|
) -> Result<bool, Error> {
|
|
if self.caps_running() {
|
|
return Ok(true);
|
|
}
|
|
if !block_until_running.unwrap_or(false) {
|
|
return Ok(false);
|
|
}
|
|
let mut generation = mutex(&self.event_queue_generation);
|
|
for delay in [1_u64, 2, 4, 8] {
|
|
if self.caps_running() || self.current_sim_caps_running() {
|
|
return Ok(true);
|
|
}
|
|
self.start_current_event_queue();
|
|
let observed = *generation;
|
|
let (next, _) = self
|
|
.event_queue_wait
|
|
.wait_timeout_while(generation, Duration::from_secs(delay), |value| {
|
|
*value == observed
|
|
&& !self.client.cancellation_token().is_cancellation_requested()
|
|
})
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
generation = next;
|
|
if *generation != observed {
|
|
break;
|
|
}
|
|
}
|
|
Ok(self.caps_running() || self.current_sim_caps_running())
|
|
}
|
|
|
|
pub(crate) fn native_start_event_queue(&self) {
|
|
self.start_current_event_queue();
|
|
}
|
|
|
|
fn caps_running(&self) -> bool {
|
|
read(&self.caps_state).as_ref().is_some_and(|inner| {
|
|
Caps::native_from_inner(Arc::clone(inner), self.native_clone_without_caps())
|
|
.is_event_queue_running()
|
|
})
|
|
}
|
|
|
|
fn current_sim_caps_running(&self) -> bool {
|
|
mutex(&self.manager)
|
|
.upgrade()
|
|
.and_then(|manager| read(&manager.current_sim).clone())
|
|
.is_some_and(|simulator| simulator.caps_running())
|
|
}
|
|
|
|
fn start_current_event_queue(&self) {
|
|
let simulator = mutex(&self.manager)
|
|
.upgrade()
|
|
.and_then(|manager| read(&manager.current_sim).clone())
|
|
.unwrap_or_else(|| self.native_clone_without_caps());
|
|
if let Some(inner) = read(&simulator.caps_state).clone() {
|
|
let caps = Caps::native_from_inner(inner, simulator.native_clone_without_caps());
|
|
if let Some(queue) = caps.event_queue() {
|
|
let _ = queue.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_raise_event_queue_running(&self) {
|
|
let mut generation = mutex(&self.event_queue_generation);
|
|
*generation = generation.wrapping_add(1);
|
|
drop(generation);
|
|
self.event_queue_wait.notify_all();
|
|
if let Some(manager) = mutex(&self.manager).upgrade() {
|
|
let args = EventQueueRunningEventArgs {
|
|
simulator: self.clone(),
|
|
};
|
|
manager
|
|
.events
|
|
.event_queue_running
|
|
.emit_with(|| args.clone());
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_dispatch_caps_event(&self, event_name: &str, message: &dyn IMessage) {
|
|
if let Some(manager) = mutex(&self.manager).upgrade() {
|
|
manager
|
|
.caps_events
|
|
.invoke_raise_event(event_name, message, self.clone());
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_enqueue_caps_packet(&self, packet: Packet) -> Result<(), Error> {
|
|
let manager = mutex(&self.manager)
|
|
.upgrade()
|
|
.ok_or(Error::InvalidOperation)?;
|
|
manager.enqueue_incoming(NetworkManagerIncomingPacket {
|
|
packet: Some(packet),
|
|
simulator: Some(self.clone()),
|
|
raw_data: None,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn seed_capability(&self) -> Option<Uri> {
|
|
read(&self.seed_caps).clone()
|
|
}
|
|
|
|
pub fn native_use_circuit_code(&self, wait_for_ack: bool) -> Result<(), Error> {
|
|
let data = self.use_circuit_code_bytes()?;
|
|
if wait_for_ack {
|
|
let receiver = self
|
|
.transport
|
|
.try_send_packet_wait_ack(data, self.endpoint, PacketType::UseCircuitCode, false)
|
|
.map_err(Error::from)?;
|
|
let timeout =
|
|
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
|
|
let _ = receiver.recv_timeout(timeout);
|
|
} else {
|
|
self.transport
|
|
.try_send_packet(data, self.endpoint, PacketType::UseCircuitCode, false)
|
|
.map_err(Error::from)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn native_use_circuit_code_async(&self, wait_for_ack: bool) -> Result<bool, Error> {
|
|
let data = self.use_circuit_code_bytes()?;
|
|
if !wait_for_ack {
|
|
self.transport
|
|
.try_send_packet(data, self.endpoint, PacketType::UseCircuitCode, false)
|
|
.map_err(Error::from)?;
|
|
return Ok(true);
|
|
}
|
|
let receiver = self
|
|
.transport
|
|
.try_send_packet_wait_ack(data, self.endpoint, PacketType::UseCircuitCode, false)
|
|
.map_err(Error::from)?;
|
|
let timeout =
|
|
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
|
|
run_blocking_compat("libremetaverse-circuit-ack", move || {
|
|
receiver.recv_timeout(timeout).is_ok()
|
|
})
|
|
.await
|
|
}
|
|
|
|
fn use_circuit_code_bytes(&self) -> Result<Vec<u8>, Error> {
|
|
let mut packet = UseCircuitCodePacket::new_with_constructor()?;
|
|
packet.circuit_code.code = self.circuit_code.load(Ordering::Acquire);
|
|
packet.circuit_code.id = *read(&self.agent_id);
|
|
packet.circuit_code.session_id = *read(&self.session_id);
|
|
packet.to_bytes_with_method()
|
|
}
|
|
|
|
fn native_complete_agent_movement(&self) -> Result<(), Error> {
|
|
let mut packet = CompleteAgentMovementPacket::new_with_constructor()?;
|
|
packet.agent_data.agent_id = *read(&self.agent_id);
|
|
packet.agent_data.session_id = *read(&self.session_id);
|
|
packet.agent_data.circuit_code = self.circuit_code.load(Ordering::Acquire);
|
|
let mut data = packet.to_bytes_with_method()?;
|
|
// Match the reference packet constructor: this transition is reliable.
|
|
let flags = data.first_mut().ok_or(Error::Argument)?;
|
|
*flags |= Helpers::MSG_RELIABLE;
|
|
self.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::CompleteAgentMovement,
|
|
false,
|
|
)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_is_connected(&self) -> bool {
|
|
self.connected.load(Ordering::Acquire)
|
|
}
|
|
|
|
#[must_use]
|
|
pub(crate) fn native_agent_movement_complete(&self) -> bool {
|
|
self.movement_complete.load(Ordering::Acquire)
|
|
}
|
|
|
|
pub(crate) fn native_set_agent_movement_complete(&self, value: bool) {
|
|
self.movement_complete.store(value, Ordering::Release);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn native_set_connected_for_tests(&self, connected: bool) {
|
|
self.connected.store(connected, Ordering::Release);
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_handshake_complete(&self) -> bool {
|
|
self.handshake_complete.load(Ordering::Acquire)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_ip_end_point(&self) -> std::net::SocketAddr {
|
|
self.data.endpoint
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_to_string(&self) -> String {
|
|
if self.name.is_empty() {
|
|
format!("({})", self.endpoint)
|
|
} else {
|
|
format!("{} ({})", self.name, self.endpoint)
|
|
}
|
|
}
|
|
|
|
fn note_packet_received(&self, _packet_type: PacketType) {
|
|
self.disconnect_candidate.store(false, Ordering::Release);
|
|
}
|
|
|
|
fn mark_disconnect_candidate(&self) -> bool {
|
|
self.disconnect_candidate.swap(true, Ordering::AcqRel)
|
|
}
|
|
|
|
fn send_buffer(&self, buffer: UDPPacketBuffer) -> Result<(), Error> {
|
|
self.transport.async_begin_send(buffer)
|
|
}
|
|
}
|
|
|
|
struct SimulatorUdpHandler {
|
|
simulator: Arc<Mutex<Weak<SimulatorData>>>,
|
|
}
|
|
|
|
impl UdpPacketHandler for SimulatorUdpHandler {
|
|
fn packet_received(&self, buffer: UDPPacketBuffer) {
|
|
let Some(data) = mutex(&self.simulator).upgrade() else {
|
|
return;
|
|
};
|
|
let simulator = Simulator { caps: None, data };
|
|
let Ok(length) = usize::try_from(buffer.data_length) else {
|
|
return;
|
|
};
|
|
let Some(payload) = buffer.data.get(..length) else {
|
|
return;
|
|
};
|
|
let Ok(payload_length) = i32::try_from(payload.len()) else {
|
|
return;
|
|
};
|
|
let mut packet_end = payload_length - 1;
|
|
let mut zero_buffer = vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
|
|
let Ok(packet) =
|
|
crate::packet_wire::build_packet_from_bytes(payload, &mut packet_end, &mut zero_buffer)
|
|
else {
|
|
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_);
|
|
let manager = mutex(&simulator.manager).upgrade();
|
|
if let Some(manager) = manager {
|
|
let _ = manager.enqueue_incoming(NetworkManagerIncomingPacket {
|
|
simulator: Some(simulator),
|
|
packet: Some(packet),
|
|
raw_data: Some(payload.to_vec()),
|
|
});
|
|
}
|
|
}
|
|
|
|
fn packet_sent(&self, buffer: UDPPacketBuffer, bytes_sent: usize) {
|
|
let Some(data) = mutex(&self.simulator).upgrade() else {
|
|
return;
|
|
};
|
|
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();
|
|
if let Some(manager) = manager {
|
|
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)]
|
|
pub struct NetworkManagerIncomingPacket {
|
|
pub packet: Option<Packet>,
|
|
pub simulator: Option<Simulator>,
|
|
raw_data: Option<Vec<u8>>,
|
|
}
|
|
|
|
impl NetworkManagerIncomingPacket {
|
|
pub fn new() -> Result<Self, Error> {
|
|
Ok(Self {
|
|
packet: None,
|
|
simulator: None,
|
|
raw_data: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct NetworkManagerOutgoingPacket {
|
|
pub buffer: UDPPacketBuffer,
|
|
pub resend_count: i32,
|
|
pub sequence_number: u32,
|
|
pub simulator: Simulator,
|
|
pub tick_count: i32,
|
|
pub type_: PacketType,
|
|
}
|
|
|
|
impl NetworkManagerOutgoingPacket {
|
|
pub fn new(
|
|
simulator: Simulator,
|
|
buffer: UDPPacketBuffer,
|
|
type_: PacketType,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
simulator,
|
|
buffer,
|
|
sequence_number: 0,
|
|
resend_count: 0,
|
|
tick_count: 0,
|
|
type_,
|
|
})
|
|
}
|
|
}
|
|
|
|
struct NetworkWorkers {
|
|
incoming: SyncSender<NetworkManagerIncomingPacket>,
|
|
outgoing: SyncSender<NetworkManagerOutgoingPacket>,
|
|
cancellation: libremetaverse_types::compat::CancellationTokenSource,
|
|
handles: Vec<JoinHandle<()>>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct NetworkEvents {
|
|
disconnected: EventRegistry<DisconnectedEventArgs>,
|
|
event_queue_running: EventRegistry<EventQueueRunningEventArgs>,
|
|
generic_streaming_message: EventRegistry<GenericStreamingMessageEventArgs>,
|
|
logged_out: EventRegistry<LoggedOutEventArgs>,
|
|
login_progress: EventRegistry<LoginProgressEventArgs>,
|
|
packet_sent: EventRegistry<PacketSentEventArgs>,
|
|
raw_packet_received: EventRegistry<RawPacketReceivedEventArgs>,
|
|
sim_changed: EventRegistry<SimChangedEventArgs>,
|
|
sim_connected: EventRegistry<SimConnectedEventArgs>,
|
|
sim_connecting: EventRegistry<SimConnectingEventArgs>,
|
|
sim_disconnected: EventRegistry<SimDisconnectedEventArgs>,
|
|
}
|
|
|
|
struct LoginRuntimeState {
|
|
next_generation: u64,
|
|
active: Option<(u64, CancellationTokenSource)>,
|
|
response_callbacks: Vec<(NetworkManagerLoginResponseCallback, Option<Vec<String>>)>,
|
|
}
|
|
|
|
impl Default for LoginRuntimeState {
|
|
fn default() -> Self {
|
|
Self {
|
|
next_generation: 1,
|
|
active: None,
|
|
response_callbacks: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct NetworkManagerInner {
|
|
client: GridClient,
|
|
simulators: SimulatorCollection,
|
|
packet_events: PacketEventDictionary,
|
|
caps_events: CapsEventDictionary,
|
|
events: NetworkEvents,
|
|
current_sim: RwLock<Option<Simulator>>,
|
|
connected: AtomicBool,
|
|
circuit_code: AtomicU32,
|
|
agent_id: RwLock<UUID>,
|
|
session_id: RwLock<UUID>,
|
|
login_status: RwLock<LoginStatus>,
|
|
login_error_key: RwLock<String>,
|
|
login_message: RwLock<String>,
|
|
login_seed_capability: RwLock<Option<Uri>>,
|
|
login_response: LoginResponseData,
|
|
login_runtime: Mutex<LoginRuntimeState>,
|
|
logout_cancellation: Mutex<CancellationTokenSource>,
|
|
inbox_count: AtomicI32,
|
|
outbox_count: AtomicI32,
|
|
workers: Mutex<Option<NetworkWorkers>>,
|
|
disconnect_lock: Mutex<()>,
|
|
lifecycle_state: AtomicU8,
|
|
logout_task_count: AtomicI32,
|
|
last_logout_error: Mutex<Option<Error>>,
|
|
}
|
|
|
|
impl NetworkManagerInner {
|
|
fn lifecycle_state(&self) -> NetworkLifecycleState {
|
|
NetworkLifecycleState::from_u8(self.lifecycle_state.load(Ordering::Acquire))
|
|
}
|
|
|
|
fn set_lifecycle_state(&self, state: NetworkLifecycleState) {
|
|
self.lifecycle_state.store(state as u8, Ordering::Release);
|
|
}
|
|
|
|
fn ensure_workers(self: &Arc<Self>) -> Result<(), Error> {
|
|
let mut workers = mutex(&self.workers);
|
|
if workers.is_some() {
|
|
return Ok(());
|
|
}
|
|
let capacity = usize::try_from(crate::Settings::udp_receive_queue_capacity())
|
|
.map_err(|_| Error::InvalidOperation)?
|
|
.max(1);
|
|
let (incoming_sender, incoming_receiver) =
|
|
sync_channel::<NetworkManagerIncomingPacket>(capacity);
|
|
let (outgoing_sender, outgoing_receiver) =
|
|
sync_channel::<NetworkManagerOutgoingPacket>(capacity);
|
|
let cancellation = libremetaverse_types::compat::CancellationTokenSource::default();
|
|
let incoming_token = cancellation.token();
|
|
let outgoing_token = cancellation.token();
|
|
let keepalive_token = cancellation.token();
|
|
let incoming_weak = Arc::downgrade(self);
|
|
let outgoing_weak = Arc::downgrade(self);
|
|
let keepalive_weak = Arc::downgrade(self);
|
|
|
|
let incoming_handle = thread::Builder::new()
|
|
.name("libremetaverse-network-incoming".to_owned())
|
|
.spawn(move || {
|
|
while !incoming_token.is_cancellation_requested() {
|
|
match incoming_receiver.recv_timeout(Duration::from_millis(50)) {
|
|
Ok(incoming) => {
|
|
let Some(inner) = incoming_weak.upgrade() else {
|
|
break;
|
|
};
|
|
inner.inbox_count.fetch_sub(1, Ordering::AcqRel);
|
|
let raw_data = incoming.raw_data;
|
|
let (Some(packet), Some(simulator)) =
|
|
(incoming.packet, incoming.simulator)
|
|
else {
|
|
continue;
|
|
};
|
|
inner.process_internal_packet(&packet, &simulator, raw_data.as_deref());
|
|
if let Some(data) = raw_data.as_ref() {
|
|
inner.events.raw_packet_received.emit_with(|| {
|
|
RawPacketReceivedEventArgs {
|
|
packet_type: packet.type_,
|
|
data: data.clone(),
|
|
simulator: simulator.clone(),
|
|
}
|
|
});
|
|
}
|
|
let _ = inner.packet_events.raise_event(
|
|
packet.type_,
|
|
packet,
|
|
simulator,
|
|
raw_data,
|
|
);
|
|
}
|
|
Err(RecvTimeoutError::Timeout) => {}
|
|
Err(RecvTimeoutError::Disconnected) => break,
|
|
}
|
|
}
|
|
})
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
|
|
let outgoing_handle = thread::Builder::new()
|
|
.name("libremetaverse-network-outgoing".to_owned())
|
|
.spawn(move || {
|
|
while !outgoing_token.is_cancellation_requested() {
|
|
match outgoing_receiver.recv_timeout(Duration::from_millis(50)) {
|
|
Ok(outgoing) => {
|
|
let Some(inner) = outgoing_weak.upgrade() else {
|
|
break;
|
|
};
|
|
inner.outbox_count.fetch_sub(1, Ordering::AcqRel);
|
|
let _ = outgoing.simulator.send_buffer(outgoing.buffer);
|
|
}
|
|
Err(RecvTimeoutError::Timeout) => {}
|
|
Err(RecvTimeoutError::Disconnected) => break,
|
|
}
|
|
}
|
|
})
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
|
|
let timeout =
|
|
positive_millisecond_duration(self.client.settings_ref().timing.simulator_timeout);
|
|
let keepalive_handle = thread::Builder::new()
|
|
.name("libremetaverse-network-keepalive".to_owned())
|
|
.spawn(move || {
|
|
while !wait_for_cancellation(&keepalive_token, timeout) {
|
|
let Some(inner) = keepalive_weak.upgrade() else {
|
|
break;
|
|
};
|
|
inner.keepalive_tick();
|
|
}
|
|
})
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
|
|
*workers = Some(NetworkWorkers {
|
|
incoming: incoming_sender,
|
|
outgoing: outgoing_sender,
|
|
cancellation,
|
|
handles: vec![incoming_handle, outgoing_handle, keepalive_handle],
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn process_internal_packet(
|
|
&self,
|
|
packet: &Packet,
|
|
simulator: &Simulator,
|
|
raw_data: Option<&[u8]>,
|
|
) {
|
|
match packet.type_ {
|
|
PacketType::RegionHandshake => {
|
|
let Some(raw_data) = raw_data else {
|
|
return;
|
|
};
|
|
let mut position = 0;
|
|
let Ok(_incoming) =
|
|
RegionHandshakePacket::new_with_bytes_int32(raw_data.to_vec(), &mut position)
|
|
else {
|
|
return;
|
|
};
|
|
|
|
let Ok(mut reply) = RegionHandshakeReplyPacket::new_with_constructor() else {
|
|
return;
|
|
};
|
|
reply.agent_data.agent_id = *read(&self.agent_id);
|
|
reply.agent_data.session_id = *read(&self.session_id);
|
|
reply.region_info.flags = 0x1 | 0x2 | 0x4;
|
|
let Ok(data) = reply.to_bytes_with_method() else {
|
|
return;
|
|
};
|
|
let Ok(length) = i32::try_from(data.len()) else {
|
|
return;
|
|
};
|
|
if simulator
|
|
.native_send_packet_data(data, length, PacketType::RegionHandshakeReply, true)
|
|
.is_err()
|
|
{
|
|
return;
|
|
}
|
|
simulator.connected.store(true, Ordering::Release);
|
|
simulator.handshake_complete.store(true, Ordering::Release);
|
|
simulator.handshake_wait.notify_all();
|
|
}
|
|
PacketType::CompletePingCheck => {
|
|
simulator.stats.record_pong(environment_tick_count());
|
|
}
|
|
PacketType::StartPingCheck => {
|
|
let Some(raw_data) = raw_data else {
|
|
return;
|
|
};
|
|
let Ok(mut incoming) = StartPingCheckPacket::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;
|
|
}
|
|
if incoming.ping_id.oldest_unacked > 0 {
|
|
let _ = simulator.transport.try_flush_acks(simulator.endpoint);
|
|
}
|
|
let Ok(mut response) = CompletePingCheckPacket::new_with_constructor() else {
|
|
return;
|
|
};
|
|
response.ping_id.ping_id = incoming.ping_id.ping_id;
|
|
let Ok(mut data) = response.to_bytes_with_method() else {
|
|
return;
|
|
};
|
|
if let Some(flags) = data.first_mut() {
|
|
*flags &= !Helpers::MSG_RELIABLE;
|
|
}
|
|
let Ok(length) = i32::try_from(data.len()) else {
|
|
return;
|
|
};
|
|
let _ = simulator.native_send_packet_data(
|
|
data,
|
|
length,
|
|
PacketType::CompletePingCheck,
|
|
false,
|
|
);
|
|
}
|
|
PacketType::LogoutReply => {
|
|
let Some(raw_data) = raw_data else {
|
|
return;
|
|
};
|
|
let Ok(mut incoming) = LogoutReplyPacket::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;
|
|
}
|
|
if incoming.agent_data.agent_id != *read(&self.agent_id)
|
|
|| incoming.agent_data.session_id != *read(&self.session_id)
|
|
{
|
|
return;
|
|
}
|
|
let Ok(args) = LoggedOutEventArgs::new(
|
|
incoming
|
|
.inventory_data
|
|
.into_iter()
|
|
.map(|item| item.item_id)
|
|
.collect(),
|
|
) else {
|
|
return;
|
|
};
|
|
self.events.logged_out.emit_with(|| args.clone());
|
|
let _ = self.shutdown(
|
|
crate::NetworkManagerDisconnectType::ClientInitiated,
|
|
"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 => {
|
|
let _ = self.disconnect_simulator(simulator.clone(), false);
|
|
}
|
|
PacketType::KickUser => {
|
|
let Some(raw_data) = raw_data else {
|
|
return;
|
|
};
|
|
let Ok(mut incoming) = KickUserPacket::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;
|
|
}
|
|
let message = String::from_utf8_lossy(&incoming.user_info.reason)
|
|
.trim_end_matches('\0')
|
|
.to_owned();
|
|
let _ = self.shutdown(
|
|
crate::NetworkManagerDisconnectType::ServerInitiated,
|
|
message,
|
|
);
|
|
}
|
|
PacketType::GenericStreamingMessage => {
|
|
let Some(raw_data) = raw_data else {
|
|
return;
|
|
};
|
|
let Ok(mut incoming) = GenericStreamingMessagePacket::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;
|
|
}
|
|
let method = if incoming.method_data.method
|
|
== GenericStreamingMethod::GltfMaterialOverride as u16
|
|
{
|
|
GenericStreamingMethod::GltfMaterialOverride
|
|
} else {
|
|
GenericStreamingMethod::Unknown
|
|
};
|
|
let Ok(args) = GenericStreamingMessageEventArgs::new(
|
|
simulator.clone(),
|
|
method,
|
|
incoming.data_block.data,
|
|
) else {
|
|
return;
|
|
};
|
|
self.events
|
|
.generic_streaming_message
|
|
.emit_with(|| args.clone());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn disconnect_simulator(
|
|
&self,
|
|
simulator: Simulator,
|
|
send_close_circuit: bool,
|
|
) -> Result<(), Error> {
|
|
let disconnect_guard = mutex(&self.disconnect_lock);
|
|
let was_connected = simulator.connected();
|
|
simulator.native_disconnect(send_close_circuit)?;
|
|
if !self.simulators.remove(&simulator) {
|
|
return Ok(());
|
|
}
|
|
if read(&self.current_sim).as_ref() == Some(&simulator) {
|
|
*write(&self.current_sim) = None;
|
|
}
|
|
let args = SimDisconnectedEventArgs::new(
|
|
simulator.clone(),
|
|
crate::NetworkManagerDisconnectType::NetworkTimeout,
|
|
was_connected,
|
|
)?;
|
|
drop(disconnect_guard);
|
|
self.events.sim_disconnected.emit_with(|| args.clone());
|
|
if self.simulators.is_empty() {
|
|
self.shutdown(
|
|
crate::NetworkManagerDisconnectType::SimShutdown,
|
|
format!(
|
|
"You have disconnected from {}.",
|
|
simulator.native_to_string()
|
|
),
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn enqueue_incoming(&self, incoming: NetworkManagerIncomingPacket) -> Result<(), Error> {
|
|
let workers = mutex(&self.workers);
|
|
let Some(workers) = workers.as_ref() else {
|
|
return Err(Error::InvalidOperation);
|
|
};
|
|
workers
|
|
.incoming
|
|
.try_send(incoming)
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
self.inbox_count.fetch_add(1, Ordering::AcqRel);
|
|
Ok(())
|
|
}
|
|
|
|
fn enqueue_outgoing(&self, outgoing: NetworkManagerOutgoingPacket) -> Result<(), Error> {
|
|
let workers = mutex(&self.workers);
|
|
let Some(workers) = workers.as_ref() else {
|
|
return Err(Error::InvalidOperation);
|
|
};
|
|
workers
|
|
.outgoing
|
|
.try_send(outgoing)
|
|
.map_err(|_| Error::InvalidOperation)?;
|
|
self.outbox_count.fetch_add(1, Ordering::AcqRel);
|
|
Ok(())
|
|
}
|
|
|
|
fn stop_workers(&self) {
|
|
let workers = mutex(&self.workers).take();
|
|
let Some(workers) = workers else {
|
|
return;
|
|
};
|
|
workers.cancellation.cancel();
|
|
drop(workers.incoming);
|
|
drop(workers.outgoing);
|
|
for handle in workers.handles {
|
|
if handle.thread().id() != thread::current().id() {
|
|
let _ = handle.join();
|
|
}
|
|
}
|
|
self.inbox_count.store(0, Ordering::Release);
|
|
self.outbox_count.store(0, Ordering::Release);
|
|
}
|
|
|
|
fn keepalive_tick(&self) {
|
|
if !self.connected.load(Ordering::Acquire)
|
|
|| self.lifecycle_state() != NetworkLifecycleState::Connected
|
|
{
|
|
return;
|
|
}
|
|
let current = read(&self.current_sim).clone();
|
|
let Some(current) = current else {
|
|
return;
|
|
};
|
|
if current.mark_disconnect_candidate() {
|
|
let _ = self.shutdown(
|
|
crate::NetworkManagerDisconnectType::NetworkTimeout,
|
|
"NetworkTimeout".to_owned(),
|
|
);
|
|
} else if current.native_send_ping().is_ok() {
|
|
let _ = current.stats.increment_sent_pings();
|
|
}
|
|
}
|
|
|
|
fn raise_packet_sent(&self, data: Vec<u8>, bytes_sent: usize, simulator: Simulator) {
|
|
let Ok(bytes_sent) = i32::try_from(bytes_sent) else {
|
|
return;
|
|
};
|
|
let Ok(args) = PacketSentEventArgs::new(data, bytes_sent, simulator) else {
|
|
return;
|
|
};
|
|
self.events.packet_sent.emit_with(|| args.clone());
|
|
}
|
|
|
|
fn set_current_sim(&self, simulator: Simulator, seed_caps: Option<Uri>) -> Result<(), Error> {
|
|
let transition = mutex(&self.disconnect_lock);
|
|
let previous = read(&self.current_sim).clone();
|
|
if previous.as_ref() == Some(&simulator) {
|
|
drop(transition);
|
|
simulator.native_set_seed_caps(seed_caps, Some(false))?;
|
|
return Ok(());
|
|
}
|
|
simulator.native_set_seed_caps(seed_caps, Some(previous.as_ref() != Some(&simulator)))?;
|
|
*write(&self.current_sim) = Some(simulator.clone());
|
|
drop(transition);
|
|
let args = SimChangedEventArgs::new(previous)?;
|
|
self.events.sim_changed.emit_with(|| args.clone());
|
|
Ok(())
|
|
}
|
|
|
|
fn shutdown(
|
|
&self,
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
message: String,
|
|
) -> Result<(), Error> {
|
|
loop {
|
|
let state = self.lifecycle_state();
|
|
if matches!(
|
|
state,
|
|
NetworkLifecycleState::Disconnected | NetworkLifecycleState::ShuttingDown
|
|
) {
|
|
return Ok(());
|
|
}
|
|
if self
|
|
.lifecycle_state
|
|
.compare_exchange(
|
|
state as u8,
|
|
NetworkLifecycleState::ShuttingDown as u8,
|
|
Ordering::AcqRel,
|
|
Ordering::Acquire,
|
|
)
|
|
.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if let Some((_, cancellation)) = mutex(&self.login_runtime).active.take() {
|
|
cancellation.cancel();
|
|
}
|
|
mutex(&self.logout_cancellation).cancel();
|
|
let current = write(&self.current_sim).take();
|
|
let mut simulators: Vec<_> = self
|
|
.simulators
|
|
.drain()
|
|
.into_iter()
|
|
.filter(|simulator| current.as_ref() != Some(simulator))
|
|
.collect();
|
|
if let Some(current) = current {
|
|
simulators.push(current);
|
|
}
|
|
let send_close = matches!(
|
|
reason,
|
|
crate::NetworkManagerDisconnectType::ClientInitiated
|
|
| crate::NetworkManagerDisconnectType::NetworkTimeout
|
|
);
|
|
for simulator in simulators {
|
|
let was_connected = simulator.connected();
|
|
let _ = simulator.native_disconnect(send_close);
|
|
if let Ok(args) = SimDisconnectedEventArgs::new(simulator, reason, was_connected) {
|
|
self.events.sim_disconnected.emit_with(|| args.clone());
|
|
}
|
|
}
|
|
self.stop_workers();
|
|
self.connected.store(false, Ordering::Release);
|
|
self.circuit_code.store(0, Ordering::Release);
|
|
*write(&self.agent_id) = UUID::zero();
|
|
*write(&self.session_id) = UUID::zero();
|
|
*write(&self.login_seed_capability) = None;
|
|
self.login_response.clear_secrets();
|
|
let event_result = DisconnectedEventArgs::new(reason, message);
|
|
if let Ok(args) = &event_result {
|
|
self.events.disconnected.emit_with(|| args.clone());
|
|
}
|
|
self.set_lifecycle_state(NetworkLifecycleState::Disconnected);
|
|
event_result.map(|_| ())
|
|
}
|
|
}
|
|
|
|
impl Drop for NetworkManagerInner {
|
|
fn drop(&mut self) {
|
|
mutex(&self.logout_cancellation).cancel();
|
|
self.stop_workers();
|
|
for simulator in self.simulators.drain() {
|
|
let _ = simulator.native_disconnect(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Native implementation of C# `NetworkManager`.
|
|
#[derive(Clone)]
|
|
pub struct NetworkManager {
|
|
pub login_response_data: Option<LoginResponseData>,
|
|
pub simulators: SimulatorCollection,
|
|
inner: Arc<NetworkManagerInner>,
|
|
}
|
|
|
|
impl fmt::Debug for NetworkManager {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter
|
|
.debug_struct("NetworkManager")
|
|
.field("connected", &self.native_connected())
|
|
.field("circuit_code", &self.native_circuit_code())
|
|
.field("simulators", &self.simulators)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl NetworkManager {
|
|
pub(crate) fn native_from_inner(inner: Arc<NetworkManagerInner>) -> Self {
|
|
Self {
|
|
login_response_data: Some(inner.login_response.clone()),
|
|
simulators: inner.simulators.clone(),
|
|
inner,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn native_inner_weak(&self) -> Weak<NetworkManagerInner> {
|
|
Arc::downgrade(&self.inner)
|
|
}
|
|
|
|
pub(crate) fn native_agent_id(&self) -> UUID {
|
|
*read(&self.inner.agent_id)
|
|
}
|
|
|
|
pub(crate) fn native_session_id(&self) -> UUID {
|
|
*read(&self.inner.session_id)
|
|
}
|
|
|
|
pub fn native_new(client: GridClient) -> Result<Self, Error> {
|
|
let simulators = SimulatorCollection::default();
|
|
let packet_events = PacketEventDictionary::new(client.clone())?;
|
|
let caps_events = CapsEventDictionary::new(client.clone())?;
|
|
let login_response = LoginResponseData::new()?;
|
|
let inner = Arc::new(NetworkManagerInner {
|
|
client,
|
|
simulators: simulators.clone(),
|
|
packet_events,
|
|
caps_events,
|
|
events: NetworkEvents::default(),
|
|
current_sim: RwLock::new(None),
|
|
connected: AtomicBool::new(false),
|
|
circuit_code: AtomicU32::new(0),
|
|
agent_id: RwLock::new(UUID::zero()),
|
|
session_id: RwLock::new(UUID::zero()),
|
|
login_status: RwLock::new(LoginStatus::None),
|
|
login_error_key: RwLock::new(String::new()),
|
|
login_message: RwLock::new(String::new()),
|
|
login_seed_capability: RwLock::new(None),
|
|
login_response: login_response.clone(),
|
|
login_runtime: Mutex::new(LoginRuntimeState::default()),
|
|
logout_cancellation: Mutex::new(CancellationTokenSource::new()),
|
|
inbox_count: AtomicI32::new(0),
|
|
outbox_count: AtomicI32::new(0),
|
|
workers: Mutex::new(None),
|
|
disconnect_lock: Mutex::new(()),
|
|
lifecycle_state: AtomicU8::new(NetworkLifecycleState::Disconnected as u8),
|
|
logout_task_count: AtomicI32::new(0),
|
|
last_logout_error: Mutex::new(None),
|
|
});
|
|
let weak_inner = Arc::downgrade(&inner);
|
|
inner.caps_events.register_event(
|
|
"EnableSimulator".to_owned(),
|
|
CapsEventQueueCallback::from_handler(move |_, message, _| {
|
|
let Some(inner) = weak_inner.upgrade() else {
|
|
return;
|
|
};
|
|
if !inner.client.settings_ref().agent.multiple_sims {
|
|
return;
|
|
}
|
|
let any_message = message as &dyn std::any::Any;
|
|
let Some(message) =
|
|
any_message.downcast_ref::<crate::messages::linden::EnableSimulatorMessage>()
|
|
else {
|
|
return;
|
|
};
|
|
let manager = NetworkManager {
|
|
login_response_data: Some(inner.login_response.clone()),
|
|
simulators: inner.simulators.clone(),
|
|
inner: Arc::clone(&inner),
|
|
};
|
|
for simulator in &message.simulators {
|
|
let Ok(port) = u16::try_from(simulator.port) else {
|
|
continue;
|
|
};
|
|
let endpoint = std::net::SocketAddr::new(simulator.ip, port);
|
|
if manager.native_find_endpoint(endpoint).is_some() {
|
|
continue;
|
|
}
|
|
let _ = manager.native_connect(
|
|
endpoint,
|
|
simulator.region_handle,
|
|
false,
|
|
None,
|
|
simulator.region_size_x,
|
|
simulator.region_size_y,
|
|
);
|
|
}
|
|
}),
|
|
)?;
|
|
Ok(Self {
|
|
login_response_data: Some(login_response),
|
|
simulators,
|
|
inner,
|
|
})
|
|
}
|
|
|
|
pub fn native_subscribe_login_progress(
|
|
&self,
|
|
handler: EventHandler<LoginProgressEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.login_progress.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_logged_out(
|
|
&self,
|
|
handler: EventHandler<LoggedOutEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.logged_out.subscribe(handler)
|
|
}
|
|
|
|
fn update_login_status(&self, status: LoginStatus, message: String) {
|
|
if status == LoginStatus::Failed {
|
|
*write(&self.inner.agent_id) = UUID::zero();
|
|
*write(&self.inner.session_id) = UUID::zero();
|
|
*write(&self.inner.login_seed_capability) = None;
|
|
self.inner.login_response.clear_secrets();
|
|
}
|
|
*write(&self.inner.login_status) = status;
|
|
write(&self.inner.login_message).clone_from(&message);
|
|
let fail_reason = read(&self.inner.login_error_key).clone();
|
|
if let Ok(args) = LoginProgressEventArgs::new(status, message, fail_reason) {
|
|
self.inner.events.login_progress.emit_with(|| args.clone());
|
|
}
|
|
}
|
|
|
|
fn set_login_error(&self, value: &str) {
|
|
let mut error = write(&self.inner.login_error_key);
|
|
error.clear();
|
|
error.push_str(value);
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_login_status_code(&self) -> LoginStatus {
|
|
*read(&self.inner.login_status)
|
|
}
|
|
|
|
pub fn native_set_login_status_code(&self, value: LoginStatus) {
|
|
*write(&self.inner.login_status) = value;
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_login_error_key(&self) -> String {
|
|
read(&self.inner.login_error_key).clone()
|
|
}
|
|
|
|
pub fn native_set_login_error_key(&self, value: String) {
|
|
*write(&self.inner.login_error_key) = value;
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_login_message(&self) -> String {
|
|
read(&self.inner.login_message).clone()
|
|
}
|
|
|
|
pub fn native_set_login_message(&self, value: String) {
|
|
*write(&self.inner.login_message) = value;
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_login_seed_capability(&self) -> Option<Uri> {
|
|
read(&self.inner.login_seed_capability).clone()
|
|
}
|
|
|
|
pub fn native_set_login_seed_capability(&self, value: Option<Uri>) {
|
|
*write(&self.inner.login_seed_capability) = value;
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_account_level_benefits(&self) -> Option<AccountLevelBenefits> {
|
|
self.inner.login_response.account_level_benefits()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_agent_appearance_service_url(&self) -> Option<String> {
|
|
let value = self.inner.login_response.agent_appearance_service_url();
|
|
(!value.is_empty()).then_some(value)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_max_agent_groups(&self) -> i32 {
|
|
let fallback = self.inner.login_response.max_agent_groups();
|
|
self.inner
|
|
.login_response
|
|
.account_level_benefits()
|
|
.map_or(fallback, |benefits| {
|
|
let limit = benefits.group_membership_limit();
|
|
if limit > 0 { limit } else { fallback }
|
|
})
|
|
}
|
|
|
|
pub fn native_default_login_params(
|
|
&self,
|
|
first_name: String,
|
|
last_name: String,
|
|
password: String,
|
|
channel: String,
|
|
version: String,
|
|
) -> Result<LoginParams, Error> {
|
|
LoginParams::new_with_grid_client_string_string_string_string_string(
|
|
self.inner.client.clone(),
|
|
first_name,
|
|
last_name,
|
|
password,
|
|
channel,
|
|
version,
|
|
)
|
|
}
|
|
|
|
pub fn native_register_login_response_callback(
|
|
&self,
|
|
callback: NetworkManagerLoginResponseCallback,
|
|
options: Option<Vec<String>>,
|
|
) -> Result<(), Error> {
|
|
let mut state = mutex(&self.inner.login_runtime);
|
|
if state
|
|
.response_callbacks
|
|
.iter()
|
|
.any(|(candidate, _)| candidate.ptr_eq(&callback))
|
|
{
|
|
return Err(Error::Argument);
|
|
}
|
|
state.response_callbacks.push((callback, options));
|
|
Ok(())
|
|
}
|
|
|
|
pub fn native_unregister_login_response_callback(
|
|
&self,
|
|
callback: &NetworkManagerLoginResponseCallback,
|
|
) -> Result<(), Error> {
|
|
let mut state = mutex(&self.inner.login_runtime);
|
|
state
|
|
.response_callbacks
|
|
.retain(|(candidate, _)| !candidate.ptr_eq(callback));
|
|
Ok(())
|
|
}
|
|
|
|
fn reserve_login(
|
|
&self,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<(u64, CancellationTokenSource), Error> {
|
|
let mut runtime = mutex(&self.inner.login_runtime);
|
|
if runtime.active.is_some() {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
let caller = cancellation_token.unwrap_or_default();
|
|
let source =
|
|
CancellationTokenSource::new_linked(&[caller, self.inner.client.cancellation_token()]);
|
|
let generation = runtime.next_generation;
|
|
runtime.next_generation = runtime.next_generation.wrapping_add(1).max(1);
|
|
runtime.active = Some((generation, source.clone()));
|
|
Ok((generation, source))
|
|
}
|
|
|
|
fn release_login(&self, generation: u64) {
|
|
let mut runtime = mutex(&self.inner.login_runtime);
|
|
if runtime
|
|
.active
|
|
.as_ref()
|
|
.is_some_and(|(active, _)| *active == generation)
|
|
{
|
|
runtime.active = None;
|
|
}
|
|
}
|
|
|
|
pub fn native_abort_login(&self) -> Result<(), Error> {
|
|
let active = mutex(&self.inner.login_runtime).active.take();
|
|
if let Some((_, source)) = active {
|
|
source.cancel();
|
|
}
|
|
self.update_login_status(LoginStatus::Failed, "Abort Requested".to_owned());
|
|
Ok(())
|
|
}
|
|
|
|
pub fn native_begin_login(&self, login_params: LoginParams) -> Result<(), Error> {
|
|
let (generation, source) = self.reserve_login(None)?;
|
|
let manager = Self {
|
|
login_response_data: Some(self.inner.login_response.clone()),
|
|
simulators: self.simulators.clone(),
|
|
inner: Arc::clone(&self.inner),
|
|
};
|
|
thread::Builder::new()
|
|
.name("libremetaverse-login".to_owned())
|
|
.spawn(move || {
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build();
|
|
let result = match runtime {
|
|
Ok(runtime) => {
|
|
runtime.block_on(manager.perform_login(login_params, source.token()))
|
|
}
|
|
Err(_) => Err(Error::InvalidOperation),
|
|
};
|
|
if let Err(error) = result {
|
|
manager.login_transport_failure(&error);
|
|
}
|
|
manager.release_login(generation);
|
|
})
|
|
.map_err(|_| {
|
|
self.release_login(generation);
|
|
Error::InvalidOperation
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn native_login(
|
|
&self,
|
|
login_params: LoginParams,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<bool, Error> {
|
|
Ok(self
|
|
.native_login_response_async(login_params, cancellation_token)
|
|
.await?
|
|
.is_some_and(|response| response.success()))
|
|
}
|
|
|
|
pub async fn native_login_response_async(
|
|
&self,
|
|
login_params: LoginParams,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<Option<LoginResponseData>, Error> {
|
|
let timeout = positive_millisecond_duration(login_params.timeout);
|
|
let (generation, source) = self.reserve_login(cancellation_token)?;
|
|
let token = source.token();
|
|
let result = tokio::select! {
|
|
result = tokio::time::timeout(timeout, self.perform_login(login_params, token.clone())) => {
|
|
if let Ok(result) = result {
|
|
result
|
|
} else {
|
|
source.cancel();
|
|
self.set_login_error("timeout");
|
|
self.update_login_status(LoginStatus::Failed, "Login timed out".to_owned());
|
|
Ok(None)
|
|
}
|
|
}
|
|
() = token.cancelled() => {
|
|
self.set_login_error("canceled");
|
|
self.update_login_status(LoginStatus::Failed, "Canceled".to_owned());
|
|
Ok(None)
|
|
}
|
|
};
|
|
self.release_login(generation);
|
|
match result {
|
|
Ok(value) => Ok(value),
|
|
Err(error) => {
|
|
self.login_transport_failure(&error);
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn native_login_credential(
|
|
&self,
|
|
credential: LoginCredential,
|
|
channel: String,
|
|
start: Option<String>,
|
|
version: String,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<bool, Error> {
|
|
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
|
|
self.inner.client.clone(),
|
|
credential,
|
|
channel,
|
|
version,
|
|
)?;
|
|
if let Some(start) = start {
|
|
login_params.start = start;
|
|
}
|
|
self.native_login(login_params, cancellation_token).await
|
|
}
|
|
|
|
pub async fn native_login_credential_response(
|
|
&self,
|
|
credential: LoginCredential,
|
|
channel: String,
|
|
start: Option<String>,
|
|
version: String,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<Option<LoginResponseData>, Error> {
|
|
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
|
|
self.inner.client.clone(),
|
|
credential,
|
|
channel,
|
|
version,
|
|
)?;
|
|
if let Some(start) = start {
|
|
login_params.start = start;
|
|
}
|
|
self.native_login_response_async(login_params, cancellation_token)
|
|
.await
|
|
}
|
|
|
|
pub async fn native_login_strings(
|
|
&self,
|
|
first_name: String,
|
|
last_name: String,
|
|
password: String,
|
|
channel: String,
|
|
start: String,
|
|
version: String,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<bool, Error> {
|
|
let mut params =
|
|
self.native_default_login_params(first_name, last_name, password, channel, version)?;
|
|
params.start = start;
|
|
self.native_login(params, cancellation_token).await
|
|
}
|
|
|
|
pub async fn native_login_strings_response(
|
|
&self,
|
|
first_name: String,
|
|
last_name: String,
|
|
password: String,
|
|
channel: String,
|
|
start: String,
|
|
version: String,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<Option<LoginResponseData>, Error> {
|
|
let mut params =
|
|
self.native_default_login_params(first_name, last_name, password, channel, version)?;
|
|
params.start = start;
|
|
self.native_login_response_async(params, cancellation_token)
|
|
.await
|
|
}
|
|
|
|
async fn perform_login(
|
|
&self,
|
|
mut params: LoginParams,
|
|
cancellation_token: CancellationToken,
|
|
) -> Result<Option<LoginResponseData>, Error> {
|
|
if params.password.len() != 35 && !params.password.starts_with("$1$") {
|
|
params.password = libremetaverse_types::Utils::md5_with_string(params.password)?;
|
|
}
|
|
if params.channel.trim().is_empty() {
|
|
params.channel = crate::Settings::user_agent();
|
|
}
|
|
if params.version.trim().is_empty() {
|
|
params.version = String::from("?.?.?");
|
|
}
|
|
let wire_secrets = LoginWireSecrets {
|
|
password: std::mem::take(&mut params.password),
|
|
token: std::mem::take(&mut params.token),
|
|
mfa_hash: std::mem::take(&mut params.mfa_hash),
|
|
};
|
|
params.clear_secrets();
|
|
let requested_start = if params.login_location.trim().is_empty() {
|
|
params.start.clone()
|
|
} else {
|
|
params.login_location.clone()
|
|
};
|
|
params.start = crate::login::normalize_start(&requested_start)?;
|
|
|
|
let callback_options: Vec<String> = mutex(&self.inner.login_runtime)
|
|
.response_callbacks
|
|
.iter()
|
|
.filter_map(|(_, options)| options.as_ref())
|
|
.flatten()
|
|
.cloned()
|
|
.collect();
|
|
for option in callback_options {
|
|
if !params.options.contains(&option) {
|
|
params.options.push(option);
|
|
}
|
|
}
|
|
|
|
let mut redirects = 0_usize;
|
|
loop {
|
|
cancellation_token.throw_if_cancellation_requested()?;
|
|
let login_uri = validate_login_uri(¶ms.uri)?;
|
|
self.update_login_status(
|
|
LoginStatus::ConnectingToLogin,
|
|
format!(
|
|
"Logging in as {} {}...",
|
|
params.first_name, params.last_name
|
|
),
|
|
);
|
|
let request = login_request(¶ms, &wire_secrets);
|
|
let response = self
|
|
.inner
|
|
.client
|
|
.native_http_caps_client()
|
|
.post_with_uri_osd_format_osd_cancellation_token_i_progress(
|
|
login_uri,
|
|
OSDFormat::Xml,
|
|
request,
|
|
cancellation_token.clone(),
|
|
None,
|
|
)
|
|
.await;
|
|
let (http_response, response_data) = match response {
|
|
Ok(response) => response,
|
|
Err(Error::Cancelled) => {
|
|
self.set_login_error("canceled");
|
|
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
|
|
return Ok(None);
|
|
}
|
|
Err(error) => {
|
|
self.login_transport_failure(&error);
|
|
return Ok(None);
|
|
}
|
|
};
|
|
if !http_response.is_success_status_code() {
|
|
self.set_login_error("bad response");
|
|
self.update_login_status(
|
|
LoginStatus::Failed,
|
|
format!("Login service returned HTTP {}", http_response.status_code),
|
|
);
|
|
return Ok(None);
|
|
}
|
|
self.update_login_status(
|
|
LoginStatus::ReadingResponse,
|
|
"Reading login response...".to_owned(),
|
|
);
|
|
let parsed = match OSDParser::deserialize_with_bytes(response_data) {
|
|
Ok(OSD::Map(map)) => OSDMap::new_with_dictionary(map),
|
|
_ => Err(Error::Argument),
|
|
};
|
|
let Ok(parsed) = parsed else {
|
|
self.set_login_error("bad response");
|
|
self.update_login_status(
|
|
LoginStatus::Failed,
|
|
"Empty or corrupt login response".to_owned(),
|
|
);
|
|
return Ok(None);
|
|
};
|
|
let has_login = parsed.get("login").is_some();
|
|
self.inner.login_response.parse_with_osd_map(parsed)?;
|
|
let response = self.inner.login_response.clone();
|
|
if !has_login {
|
|
self.update_login_status(
|
|
LoginStatus::Failed,
|
|
"Login parameter missing in the response".to_owned(),
|
|
);
|
|
return Ok(None);
|
|
}
|
|
match response.login() {
|
|
crate::LoginState::Indeterminate => {
|
|
redirects += 1;
|
|
if redirects > 10 {
|
|
self.set_login_error("redirect loop");
|
|
self.update_login_status(
|
|
LoginStatus::Failed,
|
|
"Too many login redirects".to_owned(),
|
|
);
|
|
return Ok(None);
|
|
}
|
|
self.update_login_status(LoginStatus::Redirecting, response.message());
|
|
params.uri = response.next_url();
|
|
validate_login_uri(¶ms.uri)?;
|
|
let delay = Duration::from_secs(
|
|
u64::try_from(response.next_duration().clamp(0, 300)).unwrap_or(0),
|
|
);
|
|
tokio::select! {
|
|
() = tokio::time::sleep(delay) => {}
|
|
() = cancellation_token.cancelled() => {
|
|
self.set_login_error("canceled");
|
|
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
|
|
return Ok(None);
|
|
}
|
|
}
|
|
}
|
|
crate::LoginState::True => {
|
|
self.invoke_login_response(true, false, &response);
|
|
if response.seed_capability().is_empty() {
|
|
self.set_login_error("bad response");
|
|
self.update_login_status(
|
|
LoginStatus::Failed,
|
|
"Login response did not include a seed capability".to_owned(),
|
|
);
|
|
return Ok(None);
|
|
}
|
|
if self.initialize_login_response(
|
|
response.clone(),
|
|
response.message(),
|
|
"Unable to establish a UDP connection to the simulator",
|
|
"Login server did not return a simulator address",
|
|
)? {
|
|
return Ok(Some(response));
|
|
}
|
|
return Ok(None);
|
|
}
|
|
crate::LoginState::False => {
|
|
let reason = response.reason();
|
|
self.set_login_error(if reason.is_empty() {
|
|
"unknown"
|
|
} else {
|
|
&reason
|
|
});
|
|
self.update_login_status(LoginStatus::Failed, response.message());
|
|
return Ok(None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn invoke_login_response(&self, success: bool, redirect: bool, response: &LoginResponseData) {
|
|
let callbacks: Vec<_> = mutex(&self.inner.login_runtime)
|
|
.response_callbacks
|
|
.iter()
|
|
.map(|(callback, _)| callback.clone())
|
|
.collect();
|
|
for callback in callbacks {
|
|
let _ = callback.invoke(
|
|
success,
|
|
redirect,
|
|
response.message(),
|
|
response.reason(),
|
|
Some(response.clone()),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn login_transport_failure(&self, error: &Error) {
|
|
let (key, message) = match error {
|
|
Error::Cancelled => ("canceled", "Login canceled"),
|
|
Error::HttpRequest => ("no connection", "Unable to contact login service"),
|
|
Error::Argument => ("bad response", "Invalid login request or response"),
|
|
_ => ("no connection", "Login failed"),
|
|
};
|
|
self.set_login_error(key);
|
|
self.update_login_status(LoginStatus::Failed, message.to_owned());
|
|
}
|
|
|
|
pub fn native_login_response(&self, response: LoginResponseData) -> Result<bool, Error> {
|
|
self.initialize_login_response(
|
|
response,
|
|
"Login success".to_owned(),
|
|
"Unable to connect to simulator",
|
|
"Unable to connect to simulator",
|
|
)
|
|
}
|
|
|
|
fn initialize_login_response(
|
|
&self,
|
|
response: LoginResponseData,
|
|
success_message: String,
|
|
connect_failure_message: &str,
|
|
address_failure_message: &str,
|
|
) -> Result<bool, Error> {
|
|
self.inner.login_response.copy_from(&response);
|
|
self.inner
|
|
.circuit_code
|
|
.store(response.circuit_code().cast_unsigned(), Ordering::Release);
|
|
*write(&self.inner.agent_id) = response.agent_id();
|
|
*write(&self.inner.session_id) = response.session_id();
|
|
let seed = if response.seed_capability().is_empty() {
|
|
None
|
|
} else {
|
|
Some(validate_login_uri(&response.seed_capability())?)
|
|
};
|
|
write(&self.inner.login_seed_capability).clone_from(&seed);
|
|
self.update_login_status(
|
|
LoginStatus::ConnectingToSim,
|
|
"Connecting to simulator...".to_owned(),
|
|
);
|
|
let Some(ip) = response.sim_ip() else {
|
|
self.set_login_error("bad response");
|
|
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
|
|
return Ok(false);
|
|
};
|
|
if response.sim_port() == 0 {
|
|
self.set_login_error("bad response");
|
|
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
|
|
return Ok(false);
|
|
}
|
|
let handle = (u64::from(response.region_x()) << 32) | u64::from(response.region_y());
|
|
let endpoint = std::net::SocketAddr::new(ip, response.sim_port());
|
|
let Ok(connected) = self.native_connect(
|
|
endpoint,
|
|
handle,
|
|
true,
|
|
seed,
|
|
response.region_size_x(),
|
|
response.region_size_y(),
|
|
) else {
|
|
self.cleanup_failed_login_connection(endpoint);
|
|
self.set_login_error("no connection");
|
|
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
|
|
return Ok(false);
|
|
};
|
|
if let Some(simulator) = connected {
|
|
let economy_request =
|
|
Packet::build_packet_with_packet_type(PacketType::EconomyDataRequest);
|
|
if economy_request
|
|
.and_then(|packet| simulator.native_send_packet(packet))
|
|
.is_err()
|
|
{
|
|
self.cleanup_failed_login_connection(endpoint);
|
|
self.set_login_error("no connection");
|
|
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
|
|
return Ok(false);
|
|
}
|
|
self.update_login_status(LoginStatus::Success, success_message);
|
|
Ok(true)
|
|
} else {
|
|
self.cleanup_failed_login_connection(endpoint);
|
|
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
fn cleanup_failed_login_connection(&self, endpoint: std::net::SocketAddr) {
|
|
if let Some(simulator) = self.native_find_endpoint(endpoint) {
|
|
let _ = simulator.native_disconnect(false);
|
|
self.simulators.remove(&simulator);
|
|
let mut current = write(&self.inner.current_sim);
|
|
if current.as_ref() == Some(&simulator) {
|
|
*current = None;
|
|
}
|
|
}
|
|
if self.simulators.is_empty() {
|
|
self.inner.connected.store(false, Ordering::Release);
|
|
self.inner
|
|
.set_lifecycle_state(NetworkLifecycleState::Disconnected);
|
|
}
|
|
}
|
|
|
|
pub fn native_subscribe_disconnected(
|
|
&self,
|
|
handler: EventHandler<DisconnectedEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.disconnected.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_event_queue_running(
|
|
&self,
|
|
handler: EventHandler<EventQueueRunningEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.event_queue_running.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_generic_streaming_message(
|
|
&self,
|
|
handler: EventHandler<GenericStreamingMessageEventArgs>,
|
|
) -> Subscription {
|
|
self.inner
|
|
.events
|
|
.generic_streaming_message
|
|
.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_packet_sent(
|
|
&self,
|
|
handler: EventHandler<PacketSentEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.packet_sent.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_sim_changed(
|
|
&self,
|
|
handler: EventHandler<SimChangedEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.sim_changed.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_sim_connected(
|
|
&self,
|
|
handler: EventHandler<SimConnectedEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.sim_connected.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_sim_connecting(
|
|
&self,
|
|
handler: EventHandler<SimConnectingEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.sim_connecting.subscribe(handler)
|
|
}
|
|
|
|
pub fn native_subscribe_sim_disconnected(
|
|
&self,
|
|
handler: EventHandler<SimDisconnectedEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.sim_disconnected.subscribe(handler)
|
|
}
|
|
|
|
pub fn subscribe_packet(
|
|
&self,
|
|
packet_type: PacketType,
|
|
handler: EventHandler<PacketReceivedEventArgs>,
|
|
is_async: bool,
|
|
) -> Subscription {
|
|
self.inner
|
|
.packet_events
|
|
.subscribe(packet_type, handler, is_async)
|
|
}
|
|
|
|
pub(crate) fn subscribe_raw_packet(
|
|
&self,
|
|
handler: EventHandler<RawPacketReceivedEventArgs>,
|
|
) -> Subscription {
|
|
self.inner.events.raw_packet_received.subscribe(handler)
|
|
}
|
|
|
|
pub fn subscribe_caps(
|
|
&self,
|
|
caps_event: String,
|
|
handler: CapsEventQueueCallback,
|
|
) -> Subscription {
|
|
self.inner.caps_events.subscribe(caps_event, handler)
|
|
}
|
|
|
|
/// Dispatches a decoded CAPS event through the default and named handler
|
|
/// chains. This is the native boundary used by a CAPS event-queue client.
|
|
pub fn dispatch_caps_event(
|
|
&self,
|
|
caps_event: &str,
|
|
message: &dyn IMessage,
|
|
simulator: Simulator,
|
|
) {
|
|
self.inner
|
|
.caps_events
|
|
.invoke_raise_event(caps_event, message, simulator);
|
|
}
|
|
|
|
pub fn native_register_callback(
|
|
&self,
|
|
packet_type: PacketType,
|
|
callback: EventHandler<PacketReceivedEventArgs>,
|
|
is_async: bool,
|
|
) -> Result<(), Error> {
|
|
self.inner
|
|
.packet_events
|
|
.register_event(packet_type, callback, is_async)
|
|
}
|
|
|
|
pub fn native_unregister_callback(
|
|
&self,
|
|
packet_type: PacketType,
|
|
callback: EventHandler<PacketReceivedEventArgs>,
|
|
) -> Result<(), Error> {
|
|
self.inner
|
|
.packet_events
|
|
.unregister_event(packet_type, callback)
|
|
}
|
|
|
|
pub fn native_register_caps(
|
|
&self,
|
|
caps_event: String,
|
|
callback: CapsEventQueueCallback,
|
|
) -> Result<(), Error> {
|
|
self.inner.caps_events.register_event(caps_event, callback)
|
|
}
|
|
|
|
pub fn native_unregister_caps(
|
|
&self,
|
|
caps_event: String,
|
|
callback: CapsEventQueueCallback,
|
|
) -> Result<(), Error> {
|
|
self.inner
|
|
.caps_events
|
|
.unregister_event(caps_event, callback)
|
|
}
|
|
|
|
pub fn native_connect(
|
|
&self,
|
|
endpoint: std::net::SocketAddr,
|
|
handle: u64,
|
|
set_default: bool,
|
|
seed_caps: Option<Uri>,
|
|
size_x: u32,
|
|
size_y: u32,
|
|
) -> Result<Option<Simulator>, Error> {
|
|
if matches!(
|
|
self.inner.lifecycle_state(),
|
|
NetworkLifecycleState::LoggingOut | NetworkLifecycleState::ShuttingDown
|
|
) {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
self.inner.ensure_workers()?;
|
|
let existing = self.native_find_endpoint(endpoint);
|
|
let newly_created = existing.is_none();
|
|
let simulator = existing.unwrap_or(Simulator::native_new(
|
|
self.inner.client.clone(),
|
|
endpoint,
|
|
handle,
|
|
Some(size_x),
|
|
Some(size_y),
|
|
)?);
|
|
simulator.attach_manager(&self.inner, self.inner.circuit_code.load(Ordering::Acquire));
|
|
let simulator = self.simulators.push_if_missing(simulator);
|
|
|
|
if !simulator.connected() {
|
|
self.inner
|
|
.set_lifecycle_state(NetworkLifecycleState::Connecting);
|
|
self.inner.connected.store(true, Ordering::Release);
|
|
self.inner
|
|
.set_lifecycle_state(NetworkLifecycleState::Connected);
|
|
let args = SimConnectingEventArgs::new(simulator.clone())?;
|
|
self.inner.events.sim_connecting.emit_with(|| args.clone());
|
|
if args.cancel() {
|
|
self.simulators.remove(&simulator);
|
|
return Ok(None);
|
|
}
|
|
match simulator.native_connect(set_default) {
|
|
Ok(true) => {}
|
|
Ok(false) => {
|
|
self.simulators.remove(&simulator);
|
|
return Ok(None);
|
|
}
|
|
Err(error) => {
|
|
let _ = simulator.native_disconnect(false);
|
|
self.simulators.remove(&simulator);
|
|
return Err(error);
|
|
}
|
|
}
|
|
if set_default
|
|
&& let Err(error) = self.inner.set_current_sim(simulator.clone(), seed_caps)
|
|
{
|
|
if newly_created {
|
|
let _ = simulator.native_disconnect(false);
|
|
self.simulators.remove(&simulator);
|
|
}
|
|
return Err(error);
|
|
}
|
|
let args = SimConnectedEventArgs::new(simulator.clone())?;
|
|
self.inner.events.sim_connected.emit_with(|| args.clone());
|
|
} else if set_default {
|
|
simulator.native_use_circuit_code(true)?;
|
|
simulator.native_complete_agent_movement()?;
|
|
self.inner.set_current_sim(simulator.clone(), seed_caps)?;
|
|
}
|
|
Ok(Some(simulator))
|
|
}
|
|
|
|
pub async fn native_connect_async(
|
|
&self,
|
|
endpoint: std::net::SocketAddr,
|
|
handle: u64,
|
|
set_default: bool,
|
|
seed_caps: Option<Uri>,
|
|
size_x: u32,
|
|
size_y: u32,
|
|
) -> Result<Option<Simulator>, Error> {
|
|
let manager = Self {
|
|
login_response_data: Some(self.inner.login_response.clone()),
|
|
simulators: self.simulators.clone(),
|
|
inner: Arc::clone(&self.inner),
|
|
};
|
|
run_blocking_compat("libremetaverse-manager-connect", move || {
|
|
manager.native_connect(endpoint, handle, set_default, seed_caps, size_x, size_y)
|
|
})
|
|
.await?
|
|
}
|
|
|
|
pub fn native_disconnect_sim(
|
|
&self,
|
|
simulator: Simulator,
|
|
send_close_circuit: bool,
|
|
) -> Result<(), Error> {
|
|
self.inner
|
|
.disconnect_simulator(simulator, send_close_circuit)
|
|
}
|
|
|
|
pub fn native_enqueue_incoming(
|
|
&self,
|
|
incoming: NetworkManagerIncomingPacket,
|
|
) -> Result<(), Error> {
|
|
self.inner.enqueue_incoming(incoming)
|
|
}
|
|
|
|
pub fn native_enqueue_outgoing(
|
|
&self,
|
|
outgoing: NetworkManagerOutgoingPacket,
|
|
) -> Result<(), Error> {
|
|
self.inner.enqueue_outgoing(outgoing)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_find_endpoint(&self, endpoint: std::net::SocketAddr) -> Option<Simulator> {
|
|
self.simulators
|
|
.snapshot()
|
|
.into_iter()
|
|
.find(|simulator| simulator.ip_end_point() == endpoint)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_find_handle(&self, handle: u64) -> Option<Simulator> {
|
|
self.simulators
|
|
.snapshot()
|
|
.into_iter()
|
|
.find(|simulator| simulator.handle == handle)
|
|
}
|
|
|
|
pub fn native_send_packet(
|
|
&self,
|
|
packet: Packet,
|
|
simulator: Option<Simulator>,
|
|
) -> Result<(), Error> {
|
|
let simulator = simulator
|
|
.or_else(|| self.native_current_sim())
|
|
.or_else(|| self.simulators.snapshot().into_iter().next())
|
|
.ok_or(Error::InvalidOperation)?;
|
|
simulator.native_send_packet(packet)
|
|
}
|
|
|
|
fn send_logout_request_if_needed(&self) -> Result<(), Error> {
|
|
let Some(simulator) = self.native_current_sim() else {
|
|
return Ok(());
|
|
};
|
|
if !self.native_connected() {
|
|
return Ok(());
|
|
}
|
|
loop {
|
|
match self.inner.lifecycle_state() {
|
|
NetworkLifecycleState::Connected => {
|
|
if self
|
|
.inner
|
|
.lifecycle_state
|
|
.compare_exchange(
|
|
NetworkLifecycleState::Connected as u8,
|
|
NetworkLifecycleState::LoggingOut as u8,
|
|
Ordering::AcqRel,
|
|
Ordering::Acquire,
|
|
)
|
|
.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
NetworkLifecycleState::LoggingOut
|
|
| NetworkLifecycleState::Disconnected
|
|
| NetworkLifecycleState::Connecting
|
|
| NetworkLifecycleState::ShuttingDown => return Ok(()),
|
|
}
|
|
}
|
|
{
|
|
let mut cancellation = mutex(&self.inner.logout_cancellation);
|
|
if cancellation.token().is_cancellation_requested() {
|
|
*cancellation = CancellationTokenSource::new();
|
|
}
|
|
}
|
|
let result = (|| {
|
|
let mut packet = LogoutRequestPacket::new_with_constructor()?;
|
|
packet.agent_data.agent_id = *read(&self.inner.agent_id);
|
|
packet.agent_data.session_id = *read(&self.inner.session_id);
|
|
let data = packet.to_bytes_with_method()?;
|
|
simulator.native_send_packet_data(
|
|
data.clone(),
|
|
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
|
|
PacketType::LogoutRequest,
|
|
false,
|
|
)
|
|
})();
|
|
if result.is_err() {
|
|
let _ = self.inner.lifecycle_state.compare_exchange(
|
|
NetworkLifecycleState::LoggingOut as u8,
|
|
NetworkLifecycleState::Connected as u8,
|
|
Ordering::AcqRel,
|
|
Ordering::Acquire,
|
|
);
|
|
}
|
|
result
|
|
}
|
|
|
|
fn logout_signal(&self) -> (Receiver<()>, Subscription) {
|
|
let (sender, receiver) = sync_channel(1);
|
|
let subscription = self.native_subscribe_logged_out(Arc::new(move |_| {
|
|
let _ = sender.try_send(());
|
|
}));
|
|
(receiver, subscription)
|
|
}
|
|
|
|
fn finish_logout_wait(
|
|
&self,
|
|
outcome: LogoutWaitOutcome,
|
|
emit_timeout_logout: bool,
|
|
) -> Result<(), Error> {
|
|
match outcome {
|
|
LogoutWaitOutcome::Reply => {
|
|
self.native_shutdown(
|
|
crate::NetworkManagerDisconnectType::ClientInitiated,
|
|
"ClientInitiated".to_owned(),
|
|
)?;
|
|
let deadline = std::time::Instant::now()
|
|
+ positive_millisecond_duration(
|
|
self.inner.client.settings_ref().timing.logout_timeout,
|
|
);
|
|
while self.inner.lifecycle_state() != NetworkLifecycleState::Disconnected
|
|
&& std::time::Instant::now() < deadline
|
|
{
|
|
thread::sleep(Duration::from_millis(1));
|
|
}
|
|
Ok(())
|
|
}
|
|
LogoutWaitOutcome::Timeout => {
|
|
self.native_shutdown(
|
|
crate::NetworkManagerDisconnectType::NetworkTimeout,
|
|
"NetworkTimeout".to_owned(),
|
|
)?;
|
|
if emit_timeout_logout {
|
|
let args = LoggedOutEventArgs::new(Vec::new())?;
|
|
self.inner.events.logged_out.emit_with(|| args.clone());
|
|
}
|
|
Ok(())
|
|
}
|
|
LogoutWaitOutcome::Cancelled => {
|
|
let _ = self.inner.lifecycle_state.compare_exchange(
|
|
NetworkLifecycleState::LoggingOut as u8,
|
|
NetworkLifecycleState::Connected as u8,
|
|
Ordering::AcqRel,
|
|
Ordering::Acquire,
|
|
);
|
|
Err(Error::Cancelled)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn logout_blocking(&self, emit_timeout_logout: bool) -> Result<(), Error> {
|
|
if !self.native_connected() || self.native_current_sim().is_none() {
|
|
return Ok(());
|
|
}
|
|
let (receiver, _subscription) = self.logout_signal();
|
|
self.send_logout_request_if_needed()?;
|
|
let timeout =
|
|
positive_millisecond_duration(self.inner.client.settings_ref().timing.logout_timeout);
|
|
let lifecycle_cancellation = mutex(&self.inner.logout_cancellation).token();
|
|
self.finish_logout_wait(
|
|
wait_for_logout(&receiver, timeout, &[lifecycle_cancellation]),
|
|
emit_timeout_logout,
|
|
)
|
|
}
|
|
|
|
pub fn native_begin_logout(&self) -> Result<(), Error> {
|
|
if !self.native_connected() || self.native_current_sim().is_none() {
|
|
return Ok(());
|
|
}
|
|
*mutex(&self.inner.last_logout_error) = None;
|
|
let manager = Self {
|
|
login_response_data: Some(self.inner.login_response.clone()),
|
|
simulators: self.simulators.clone(),
|
|
inner: Arc::clone(&self.inner),
|
|
};
|
|
self.inner.logout_task_count.fetch_add(1, Ordering::AcqRel);
|
|
let result = thread::Builder::new()
|
|
.name("libremetaverse-logout".to_owned())
|
|
.spawn(move || {
|
|
if let Err(error) = manager.logout_blocking(true) {
|
|
*mutex(&manager.inner.last_logout_error) = Some(error);
|
|
}
|
|
manager
|
|
.inner
|
|
.logout_task_count
|
|
.fetch_sub(1, Ordering::AcqRel);
|
|
})
|
|
.map(|_| ())
|
|
.map_err(|_| Error::InvalidOperation);
|
|
if result.is_err() {
|
|
self.inner.logout_task_count.fetch_sub(1, Ordering::AcqRel);
|
|
*mutex(&self.inner.last_logout_error) = Some(Error::InvalidOperation);
|
|
}
|
|
result
|
|
}
|
|
|
|
pub fn native_logout(&self) -> Result<(), Error> {
|
|
self.logout_blocking(false)
|
|
}
|
|
|
|
pub async fn native_logout_async(
|
|
&self,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<(), Error> {
|
|
if !self.native_connected() || self.native_current_sim().is_none() {
|
|
return Ok(());
|
|
}
|
|
let (receiver, _subscription) = self.logout_signal();
|
|
self.send_logout_request_if_needed()?;
|
|
let timeout =
|
|
positive_millisecond_duration(self.inner.client.settings_ref().timing.logout_timeout);
|
|
let cancellations = vec![
|
|
cancellation_token.unwrap_or_default(),
|
|
mutex(&self.inner.logout_cancellation).token(),
|
|
];
|
|
let outcome = run_blocking_compat("libremetaverse-logout-wait", move || {
|
|
wait_for_logout(&receiver, timeout, &cancellations)
|
|
})
|
|
.await?;
|
|
self.finish_logout_wait(outcome, false)
|
|
}
|
|
|
|
pub fn native_request_logout(&self) -> Result<(), Error> {
|
|
self.send_logout_request_if_needed()
|
|
}
|
|
|
|
/// Number of nonblocking logout waiters still owned by this manager.
|
|
#[must_use]
|
|
pub fn pending_logout_tasks(&self) -> i32 {
|
|
self.inner.logout_task_count.load(Ordering::Acquire)
|
|
}
|
|
|
|
/// Last error produced by a detached [`Self::native_begin_logout`] waiter.
|
|
#[must_use]
|
|
pub fn last_logout_error(&self) -> Option<Error> {
|
|
*mutex(&self.inner.last_logout_error)
|
|
}
|
|
|
|
pub fn native_shutdown(
|
|
&self,
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
message: String,
|
|
) -> Result<(), Error> {
|
|
self.inner.shutdown(reason, message)
|
|
}
|
|
|
|
pub async fn native_shutdown_async(
|
|
&self,
|
|
reason: crate::NetworkManagerDisconnectType,
|
|
message: String,
|
|
) -> Result<(), Error> {
|
|
let inner = Arc::clone(&self.inner);
|
|
run_blocking_compat("libremetaverse-manager-shutdown", move || {
|
|
inner.shutdown(reason, message)
|
|
})
|
|
.await?
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_circuit_code(&self) -> u32 {
|
|
self.inner.circuit_code.load(Ordering::Acquire)
|
|
}
|
|
|
|
pub fn native_set_circuit_code(&self, value: u32) {
|
|
self.inner.circuit_code.store(value, Ordering::Release);
|
|
for simulator in self.simulators.snapshot() {
|
|
simulator.circuit_code.store(value, Ordering::Release);
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_connected(&self) -> bool {
|
|
self.inner.connected.load(Ordering::Acquire)
|
|
}
|
|
|
|
pub fn native_set_connected(&self, value: bool) {
|
|
self.inner.connected.store(value, Ordering::Release);
|
|
if value && self.inner.lifecycle_state() == NetworkLifecycleState::Disconnected {
|
|
self.inner
|
|
.set_lifecycle_state(NetworkLifecycleState::Connected);
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_current_sim(&self) -> Option<Simulator> {
|
|
read(&self.inner.current_sim).clone()
|
|
}
|
|
|
|
pub fn native_set_current_sim(&self, value: Option<Simulator>) {
|
|
*write(&self.inner.current_sim) = value;
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_inbox_count(&self) -> i32 {
|
|
self.inner.inbox_count.load(Ordering::Acquire)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn native_outbox_count(&self) -> i32 {
|
|
self.inner.outbox_count.load(Ordering::Acquire)
|
|
}
|
|
|
|
/// Executes one deterministic keepalive transition without sleeping.
|
|
pub fn poll_keepalive(&self) {
|
|
self.inner.keepalive_tick();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use libremetaverse_structured_data::{OSD, OSDMap};
|
|
use std::sync::mpsc;
|
|
|
|
#[test]
|
|
fn manager_workers_and_callback_registries_do_not_retain_the_client() {
|
|
let client = GridClient::new().expect("client");
|
|
let probe = client.retention_probe();
|
|
let manager = NetworkManager::new(client.clone()).expect("network manager");
|
|
manager.inner.ensure_workers().expect("manager workers");
|
|
let subscription = manager.subscribe_packet(PacketType::Default, Arc::new(|_| {}), true);
|
|
|
|
drop(subscription);
|
|
drop(manager);
|
|
drop(client);
|
|
|
|
assert!(probe.is_released());
|
|
}
|
|
|
|
#[test]
|
|
fn caps_packet_fallback_uses_the_bounded_udp_dispatch_pipeline() {
|
|
let client = GridClient::new().expect("client");
|
|
let manager = NetworkManager::new(client.clone()).expect("network manager");
|
|
manager.inner.ensure_workers().expect("workers");
|
|
let simulator =
|
|
Simulator::new(client, "127.0.0.1:14000".parse().unwrap(), 10, None, None).unwrap();
|
|
simulator.attach_manager(&manager.inner, 0);
|
|
manager.simulators.push_if_missing(simulator.clone());
|
|
let (sender, receiver) = mpsc::channel();
|
|
let _subscription = manager.subscribe_packet(
|
|
PacketType::PacketAck,
|
|
Arc::new(move |args| {
|
|
sender
|
|
.send((args.packet().type_, args.simulator().handle))
|
|
.unwrap();
|
|
}),
|
|
false,
|
|
);
|
|
let body = OSDMap::new_with_constructor().unwrap();
|
|
body.add_with_string_osd("Packets".to_owned(), OSD::Array(Vec::new()))
|
|
.unwrap();
|
|
let packet = Packet::build_packet_with_string_osd_map("PacketAck".to_owned(), body)
|
|
.unwrap()
|
|
.expect("packet fallback");
|
|
simulator.native_enqueue_caps_packet(packet).unwrap();
|
|
assert_eq!(
|
|
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
|
|
(PacketType::PacketAck, 10)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn event_queue_running_and_typed_caps_events_use_manager_registries() {
|
|
let client = GridClient::new().expect("client");
|
|
let manager = NetworkManager::new(client.clone()).expect("network manager");
|
|
let simulator =
|
|
Simulator::new(client, "127.0.0.1:14001".parse().unwrap(), 11, None, None).unwrap();
|
|
simulator.attach_manager(&manager.inner, 0);
|
|
|
|
let (running_sender, running_receiver) = mpsc::channel();
|
|
let _running = manager.native_subscribe_event_queue_running(Arc::new(move |args| {
|
|
running_sender.send(args.simulator().handle).unwrap();
|
|
}));
|
|
simulator.native_raise_event_queue_running();
|
|
assert_eq!(
|
|
running_receiver
|
|
.recv_timeout(Duration::from_secs(1))
|
|
.unwrap(),
|
|
11
|
|
);
|
|
|
|
let (caps_sender, caps_receiver) = mpsc::channel();
|
|
let _caps = manager.subscribe_caps(
|
|
"UpdateAgentLanguage".to_owned(),
|
|
CapsEventQueueCallback::from_handler(move |name, message, simulator| {
|
|
let typed = (message as &dyn std::any::Any)
|
|
.downcast_ref::<crate::messages::linden::UpdateAgentLanguageMessage>();
|
|
caps_sender
|
|
.send((name.to_owned(), typed.is_some(), simulator.handle))
|
|
.unwrap();
|
|
}),
|
|
);
|
|
let body = OSDMap::new_with_dictionary(HashMap::from([
|
|
("language".to_owned(), OSD::String("en".to_owned())),
|
|
("language_is_public".to_owned(), OSD::Boolean(true)),
|
|
]))
|
|
.unwrap();
|
|
let message = crate::message_decoder::decode_event("UpdateAgentLanguage", &body)
|
|
.unwrap()
|
|
.unwrap();
|
|
simulator.native_dispatch_caps_event("UpdateAgentLanguage", message.as_ref());
|
|
assert_eq!(
|
|
caps_receiver.recv_timeout(Duration::from_secs(1)).unwrap(),
|
|
("UpdateAgentLanguage".to_owned(), true, 11)
|
|
);
|
|
}
|
|
}
|