//! Native agent movement, camera, teleport, and region-crossing behavior. #![allow(clippy::cast_possible_truncation)] // C# camera math narrows doubles to singles. #![allow(clippy::cast_precision_loss)] // C# compares region dimensions in single precision. #![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API. #![allow(clippy::needless_pass_by_value)] // Owned parameters mirror mapped C# signatures. #![allow(clippy::too_many_arguments)] // AgentUpdate is a protocol-shaped method. use crate::agent_manager::{AgentManager, AgentManagerInner, EventRegistry}; use crate::interfaces::IMessage; use crate::packet_catalog::GeneratedPacket; use crate::packets::{ AgentFOVPacket, AgentMovementCompletePacket, AgentRequestSitPacket, AgentSitPacket, AgentUpdatePacket, CameraConstraintPacket, CrossedRegionPacket, GenericMessagePacket, GenericMessagePacketParamListBlock, PacketType, SetAlwaysRunPacket, StartLurePacket, StartLurePacketTargetDataBlock, TeleportCancelPacket, TeleportFailedPacket, TeleportFinishPacket, TeleportLandmarkRequestPacket, TeleportLocalPacket, TeleportLocationRequestPacket, TeleportLureRequestPacket, TeleportProgressPacket, TeleportStartPacket, }; use crate::{ AgentFlags, AgentManagerControlFlags, AgentManagerCrossingFailureReason, AgentManagerCrossingState, AgentState, BorderCrossingDirection, Error, GridClient, InstantMessageDialog, InstantMessageOnline, NetworkManager, Simulator, TeleportFlags, TeleportStatus, }; use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; use libremetaverse_types::{Quaternion, UUID, Vector3, Vector3d, Vector4}; use std::collections::HashMap; use std::fmt; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Condvar, Mutex, RwLock, Weak}; use std::thread::{self, JoinHandle}; use std::time::{Duration, Instant, SystemTime}; fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { value .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { value .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { value .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn wire_string(mut bytes: Vec) -> String { while bytes.last() == Some(&0) { bytes.pop(); } String::from_utf8_lossy(&bytes).into_owned() } fn decode_packet(data: &[u8]) -> Result { let mut packet = T::new_generated(); let mut position = 0_i32; let mut packet_end = i32::try_from(data.len()).map_err(|_| Error::Argument)? - 1; let mut zero_buffer = vec![0_u8; crate::UdpTransportConfig::default().max_decoded_packet_size]; packet.decode_from_bytes( data, &mut position, &mut packet_end, Some(zero_buffer.as_mut_slice()), )?; Ok(packet) } fn send_encoded( simulator: &Simulator, packet_type: PacketType, mut data: Vec, reliable: Option, ) -> Result<(), Error> { if let Some(reliable) = reliable { let flags = data.first_mut().ok_or(Error::Argument)?; if reliable { *flags |= crate::Helpers::MSG_RELIABLE; } else { *flags &= !crate::Helpers::MSG_RELIABLE; } } let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?; let zerocoded = data .first() .is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0); simulator.native_send_packet_data(data, length, packet_type, zerocoded) } fn normalize(value: Vector3) -> Vector3 { let length_squared = value.x * value.x + value.y * value.y + value.z * value.z; if length_squared > 0.000_000_1 { let inverse = 1.0 / length_squared.sqrt(); Vector3 { x: value.x * inverse, y: value.y * inverse, z: value.z * inverse, } } else { Vector3::zero() } } fn cross(lhs: Vector3, rhs: Vector3) -> Vector3 { Vector3 { x: lhs.y * rhs.z - lhs.z * rhs.y, y: lhs.z * rhs.x - lhs.x * rhs.z, z: lhs.x * rhs.y - lhs.y * rhs.x, } } fn dot(lhs: Vector3, rhs: Vector3) -> f32 { lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z } fn subtract(lhs: Vector3, rhs: Vector3) -> Vector3 { Vector3 { x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z, } } fn add(lhs: Vector3, rhs: Vector3) -> Vector3 { Vector3 { x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z, } } fn scale(value: Vector3, factor: f32) -> Vector3 { Vector3 { x: value.x * factor, y: value.y * factor, z: value.z * factor, } } fn rotate_axis(value: Vector3, axis: Vector3, angle: f32) -> Vector3 { let axis = normalize(axis); let cosine = angle.cos(); let sine = angle.sin(); add( add(scale(value, cosine), scale(cross(axis, value), sine)), scale(axis, dot(axis, value) * (1.0 - cosine)), ) } #[derive(Clone, Debug, PartialEq)] pub struct TeleportEventArgs { message: String, status: TeleportStatus, flags: TeleportFlags, } impl TeleportEventArgs { pub fn new( message: String, status: TeleportStatus, flags: TeleportFlags, ) -> Result { Ok(Self { message, status, flags, }) } #[must_use] pub const fn flags(&self) -> TeleportFlags { self.flags } #[must_use] pub fn message(&self) -> String { self.message.clone() } #[must_use] pub const fn status(&self) -> TeleportStatus { self.status } } #[derive(Clone, Copy, Debug, PartialEq)] pub struct CameraConstraintEventArgs { collide_plane: Vector4, } impl CameraConstraintEventArgs { pub const fn new(collide_plane: Vector4) -> Result { Ok(Self { collide_plane }) } #[must_use] pub const fn collide_plane(&self) -> Vector4 { self.collide_plane } } #[derive(Clone, Copy, Debug, PartialEq)] pub struct AvatarSitResponseEventArgs { object_id: UUID, auto_pilot: bool, camera_at_offset: Vector3, camera_eye_offset: Vector3, force_mouselook: bool, sit_position: Vector3, sit_rotation: Quaternion, } #[derive(Clone, Debug)] pub struct RegionCrossedEventArgs { old_simulator: Option, new_simulator: Option, } #[derive(Clone, Debug)] pub struct RegionCrossingPredictionEventArgs { current_simulator: Simulator, direction: BorderCrossingDirection, time_until_crossing: f32, } impl RegionCrossingPredictionEventArgs { pub const fn new( current_simulator: Simulator, direction: BorderCrossingDirection, time_until_crossing: f32, ) -> Result { Ok(Self { current_simulator, direction, time_until_crossing, }) } #[must_use] pub fn current_simulator(&self) -> Simulator { self.current_simulator.clone() } #[must_use] pub const fn direction(&self) -> BorderCrossingDirection { self.direction } #[must_use] pub const fn time_until_crossing(&self) -> f32 { self.time_until_crossing } } impl RegionCrossedEventArgs { pub const fn new( old_simulator: Option, new_simulator: Option, ) -> Result { Ok(Self { old_simulator, new_simulator, }) } #[must_use] pub fn new_simulator(&self) -> Option { self.new_simulator.clone() } #[must_use] pub fn old_simulator(&self) -> Option { self.old_simulator.clone() } } impl AvatarSitResponseEventArgs { pub const fn new( object_id: UUID, auto_pilot: bool, camera_at_offset: Vector3, camera_eye_offset: Vector3, force_mouselook: bool, sit_position: Vector3, sit_rotation: Quaternion, ) -> Result { Ok(Self { object_id, auto_pilot, camera_at_offset, camera_eye_offset, force_mouselook, sit_position, sit_rotation, }) } #[must_use] pub const fn autopilot(&self) -> bool { self.auto_pilot } #[must_use] pub const fn camera_at_offset(&self) -> Vector3 { self.camera_at_offset } #[must_use] pub const fn camera_eye_offset(&self) -> Vector3 { self.camera_eye_offset } #[must_use] pub const fn force_mouselook(&self) -> bool { self.force_mouselook } #[must_use] pub const fn object_id(&self) -> UUID { self.object_id } #[must_use] pub const fn sit_position(&self) -> Vector3 { self.sit_position } #[must_use] pub const fn sit_rotation(&self) -> Quaternion { self.sit_rotation } } #[derive(Clone, Copy, Debug, PartialEq)] struct CameraFrame { origin: Vector3, x_axis: Vector3, y_axis: Vector3, z_axis: Vector3, } impl Default for CameraFrame { fn default() -> Self { Self { origin: Vector3 { x: 128.0, y: 128.0, z: 20.0, }, x_axis: Vector3::unit_x(), y_axis: Vector3::unit_y(), z_axis: Vector3::unit_z(), } } } impl CameraFrame { fn finite(value: Vector3) -> bool { value.x.is_finite() && value.y.is_finite() && value.z.is_finite() } fn orthonormalize(&mut self) { self.x_axis = normalize(self.x_axis); self.y_axis = normalize(subtract( self.y_axis, scale(self.x_axis, dot(self.x_axis, self.y_axis)), )); self.z_axis = cross(self.x_axis, self.y_axis); } fn rotate(&mut self, angle: f32, axis: Vector3) { self.x_axis = rotate_axis(self.x_axis, axis, angle); self.y_axis = rotate_axis(self.y_axis, axis, angle); self.orthonormalize(); } fn look_direction(&mut self, mut at: Vector3, up: Vector3) { let mut left = cross(up, at); if left == Vector3::zero() { at.x += 0.01; at = normalize(at); left = cross(up, at); } self.x_axis = at; self.y_axis = normalize(left); self.z_axis = cross(at, self.y_axis); } } /// Shared-reference camera wrapper with the same axis mapping as the C# frame. #[derive(Clone)] pub struct AgentManagerAgentMovementAgentCamera { pub far: f32, frame: Arc>, } impl fmt::Debug for AgentManagerAgentMovementAgentCamera { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("AgentCamera") .field("far", &self.far) .field("frame", &*mutex(&self.frame)) .finish() } } impl AgentManagerAgentMovementAgentCamera { pub fn new() -> Result { Ok(Self { far: 128.0, frame: Arc::new(Mutex::new(CameraFrame::default())), }) } fn from_frame(frame: Arc>, far: f32) -> Self { Self { far, frame } } pub fn look_at_with_vector3_vector3( &self, position: Vector3, target: Vector3, ) -> Result<(), Error> { self.look_at_with_vector3_vector3_vector3(position, target, Vector3::unit_z()) } pub fn look_at_with_vector3_vector3_vector3( &self, position: Vector3, target: Vector3, up_direction: Vector3, ) -> Result<(), Error> { if !CameraFrame::finite(position) || !CameraFrame::finite(target) || !CameraFrame::finite(up_direction) { return Err(Error::Argument); } let mut frame = mutex(&self.frame); frame.origin = position; frame.look_direction(normalize(subtract(target, position)), up_direction); Ok(()) } pub fn look_direction_with_vector3(&self, target: Vector3) -> Result<(), Error> { self.look_direction_with_vector3_vector3(target, Vector3::unit_z()) } pub fn look_direction_with_vector3_vector3( &self, target: Vector3, up_direction: Vector3, ) -> Result<(), Error> { if !CameraFrame::finite(target) || !CameraFrame::finite(up_direction) { return Err(Error::Argument); } mutex(&self.frame).look_direction(target, up_direction); Ok(()) } pub fn look_direction_with_double(&self, heading: f64) -> Result<(), Error> { if !heading.is_finite() { return Err(Error::Argument); } let mut frame = mutex(&self.frame); frame.y_axis = Vector3 { x: heading.cos() as f32, y: heading.sin() as f32, z: frame.y_axis.z, }; frame.x_axis = Vector3 { x: -heading.sin() as f32, y: heading.cos() as f32, z: frame.x_axis.z, }; Ok(()) } pub fn pitch(&self, angle: f32) -> Result<(), Error> { if !angle.is_finite() { return Err(Error::Argument); } let mut frame = mutex(&self.frame); let axis = frame.y_axis; frame.rotate(angle, axis); Ok(()) } pub fn roll(&self, angle: f32) -> Result<(), Error> { if !angle.is_finite() { return Err(Error::Argument); } let mut frame = mutex(&self.frame); let axis = frame.x_axis; frame.rotate(angle, axis); Ok(()) } pub fn set_position_orientation( &self, position: Vector3, roll: f32, pitch: f32, yaw: f32, ) -> Result<(), Error> { if !CameraFrame::finite(position) || !roll.is_finite() || !pitch.is_finite() || !yaw.is_finite() { return Err(Error::Argument); } let mut frame = mutex(&self.frame); frame.origin = position; frame.x_axis = Vector3::unit_x(); frame.y_axis = Vector3::unit_y(); frame.z_axis = Vector3::unit_z(); let axis = frame.x_axis; frame.rotate(roll, axis); let axis = frame.y_axis; frame.rotate(pitch, axis); let axis = frame.z_axis; frame.rotate(yaw, axis); Ok(()) } pub fn yaw(&self, angle: f32) -> Result<(), Error> { if !angle.is_finite() { return Err(Error::Argument); } let mut frame = mutex(&self.frame); let axis = frame.z_axis; frame.rotate(angle, axis); Ok(()) } #[must_use] pub fn at_axis(&self) -> Vector3 { mutex(&self.frame).y_axis } pub fn set_at_axis(&mut self, value: Vector3) { if CameraFrame::finite(value) { mutex(&self.frame).y_axis = value; } } #[must_use] pub fn left_axis(&self) -> Vector3 { mutex(&self.frame).x_axis } pub fn set_left_axis(&mut self, value: Vector3) { if CameraFrame::finite(value) { mutex(&self.frame).x_axis = value; } } #[must_use] pub fn position(&self) -> Vector3 { mutex(&self.frame).origin } pub fn set_position(&mut self, value: Vector3) { if CameraFrame::finite(value) { mutex(&self.frame).origin = value; } } #[must_use] pub fn up_axis(&self) -> Vector3 { mutex(&self.frame).z_axis } pub fn set_up_axis(&mut self, value: Vector3) { if CameraFrame::finite(value) { mutex(&self.frame).z_axis = value; } } } #[derive(Clone, Copy, Debug, PartialEq)] struct WireState { body_rotation: Quaternion, head_rotation: Quaternion, camera_center: Vector3, camera_x_axis: Vector3, camera_y_axis: Vector3, camera_z_axis: Vector3, far: f32, flags: AgentFlags, state: AgentState, } impl Default for WireState { fn default() -> Self { Self { body_rotation: Quaternion::identity(), head_rotation: Quaternion::identity(), camera_center: CameraFrame::default().origin, camera_x_axis: Vector3::unit_x(), camera_y_axis: Vector3::unit_y(), camera_z_axis: Vector3::unit_z(), far: 128.0, flags: AgentFlags::NONE, state: AgentState::NONE, } } } #[derive(Clone, Copy, Default)] struct LastUpdate { wire: WireState, duplicate_count: u8, } #[derive(Clone, Copy, Debug)] struct Kinematics { relative_position: Vector3, relative_rotation: Quaternion, velocity: Vector3, acceleration: Vector3, angular_velocity: Vector3, sitting_on: u32, last_position_update: SystemTime, home_position: Vector3, } impl Default for Kinematics { fn default() -> Self { Self { relative_position: Vector3::zero(), relative_rotation: Quaternion::identity(), velocity: Vector3::zero(), acceleration: Vector3::zero(), angular_velocity: Vector3::zero(), sitting_on: 0, last_position_update: SystemTime::UNIX_EPOCH, home_position: Vector3::zero(), } } } struct TeleportState { status: TeleportStatus, message: String, generation: u64, waiter: Option<(u64, tokio::sync::oneshot::Sender)>, } impl Default for TeleportState { fn default() -> Self { Self { status: TeleportStatus::None, message: String::new(), generation: 0, waiter: None, } } } #[derive(Default)] struct TimerState { connected: bool, started: bool, shutdown: bool, revision: u64, } #[derive(Default)] struct TimerControl { state: Mutex, wake: Condvar, } struct CrossingInfo { state: AgentManagerCrossingState, started: Instant, old_simulator: Option, new_simulator: Option, region_handle: u64, endpoint: SocketAddr, seed: Uri, position: Vector3, look_at: Vector3, size_x: u32, size_y: u32, retry_count: u8, failure_reason: AgentManagerCrossingFailureReason, failure_message: String, generation: u64, cancel_requested: bool, restored_old_simulator: bool, } #[allow(dead_code)] // Mirrors the reference's per-simulator cache for later packet updates. #[derive(Clone, Copy)] struct SimulatorAgentState { position: Vector3, rotation: Quaternion, local_id: u32, is_present: bool, last_update: SystemTime, } #[allow(dead_code)] // Tracks proactive child establishment exactly as the reference does. #[derive(Clone, Copy)] struct ChildAgentStatus { request_time: SystemTime, established: bool, direction: BorderCrossingDirection, } #[derive(Default)] struct CrossingControl { current: Mutex>, wake: Condvar, } pub(crate) struct AgentMovementRuntime { client: Arc, network: NetworkManager, owner: Weak, controls: AtomicU32, always_run: AtomicBool, auto_reset_controls: AtomicBool, update_interval: AtomicI32, camera_frame: Arc>, last_update: Mutex, kinematics: RwLock, teleport: Mutex, teleport_progress: EventRegistry, camera_constraint: EventRegistry, avatar_sit_response: EventRegistry, region_crossed: EventRegistry, region_crossing_predicted: EventRegistry, timer: Arc, timer_thread: Mutex>>, lifecycle_subscriptions: Mutex>, crossing_generation: AtomicU64, crossing: Arc, crossing_thread: Mutex>>, simulator_states: RwLock>, child_agent_status: RwLock>, object_simulators: RwLock>>, } impl fmt::Debug for AgentMovementRuntime { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("AgentMovementRuntime") .field("controls", &self.controls.load(Ordering::Acquire)) .field( "update_interval", &self.update_interval.load(Ordering::Acquire), ) .field("kinematics", &*read(&self.kinematics)) .finish_non_exhaustive() } } impl Drop for AgentMovementRuntime { fn drop(&mut self) { { let mut state = mutex(&self.timer.state); state.shutdown = true; state.revision = state.revision.wrapping_add(1); } self.timer.wake.notify_all(); self.crossing_generation.fetch_add(1, Ordering::AcqRel); self.crossing.wake.notify_all(); if let Some(handle) = mutex(&self.timer_thread).take() && handle.thread().id() != thread::current().id() { let _ = handle.join(); } if let Some(handle) = mutex(&self.crossing_thread).take() && handle.thread().id() != thread::current().id() { let _ = handle.join(); } } } impl AgentMovementRuntime { pub(crate) fn new( client: Arc, network: NetworkManager, owner: Weak, ) -> Arc { let interval = client.settings_ref().timing.agent_update_interval; let runtime = Arc::new(Self { client, network, owner, controls: AtomicU32::new(0), always_run: AtomicBool::new(false), auto_reset_controls: AtomicBool::new(false), update_interval: AtomicI32::new(interval), camera_frame: Arc::new(Mutex::new(CameraFrame::default())), last_update: Mutex::new(LastUpdate::default()), kinematics: RwLock::new(Kinematics::default()), teleport: Mutex::new(TeleportState::default()), teleport_progress: EventRegistry::default(), camera_constraint: EventRegistry::default(), avatar_sit_response: EventRegistry::default(), region_crossed: EventRegistry::default(), region_crossing_predicted: EventRegistry::default(), timer: Arc::new(TimerControl { state: Mutex::new(TimerState { connected: false, started: false, shutdown: false, revision: 0, }), wake: Condvar::new(), }), timer_thread: Mutex::new(None), lifecycle_subscriptions: Mutex::new(Vec::new()), crossing_generation: AtomicU64::new(0), crossing: Arc::new(CrossingControl::default()), crossing_thread: Mutex::new(None), simulator_states: RwLock::new(HashMap::new()), child_agent_status: RwLock::new(HashMap::new()), object_simulators: RwLock::new(HashMap::new()), }); runtime.install_lifecycle(); runtime.start_timer_worker(); runtime } fn install_lifecycle(self: &Arc) { let weak = Arc::downgrade(self); let login = self .network .native_subscribe_login_progress(Arc::new(move |event| { if let Some(runtime) = weak.upgrade() && event.status() == crate::LoginStatus::Success { runtime.set_timer_connection(true, true); } })); let weak = Arc::downgrade(self); let disconnected = self .network .native_subscribe_disconnected(Arc::new(move |_| { if let Some(runtime) = weak.upgrade() { runtime.set_timer_connection(false, false); } })); mutex(&self.lifecycle_subscriptions).extend([login, disconnected]); } fn set_timer_connection(&self, connected: bool, login_succeeded: bool) { let mut state = mutex(&self.timer.state); state.connected = connected; if login_succeeded { state.started = self.client.settings_ref().agent.send_updates_regularly; } state.revision = state.revision.wrapping_add(1); drop(state); self.timer.wake.notify_all(); } fn start_timer_worker(self: &Arc) { let weak = Arc::downgrade(self); let timer = Arc::clone(&self.timer); let handle = thread::Builder::new() .name("libremetaverse-agent-update".into()) .spawn(move || { loop { let Some(runtime) = weak.upgrade() else { break; }; let interval = runtime.update_interval.load(Ordering::Acquire); drop(runtime); let state = mutex(&timer.state); if state.shutdown { break; } if !state.connected || !state.started || interval <= 0 { drop( timer .wake .wait(state) .unwrap_or_else(std::sync::PoisonError::into_inner), ); continue; } let revision = state.revision; let (next_state, timed_out) = timer .wake .wait_timeout( state, Duration::from_millis(u64::try_from(interval).unwrap_or(1)), ) .unwrap_or_else(std::sync::PoisonError::into_inner); let should_send = timed_out.timed_out() && !next_state.shutdown && next_state.connected && next_state.started && next_state.revision == revision; drop(next_state); if should_send && let Some(runtime) = weak.upgrade() && runtime.client.settings_ref().agent.send_updates && runtime.network.native_connected() { let _ = runtime.send_last_update(false, None); } } }); if let Ok(handle) = handle { *mutex(&self.timer_thread) = Some(handle); } } pub(crate) fn stop(&self) { { let mut state = mutex(&self.timer.state); state.connected = false; state.started = false; state.shutdown = true; state.revision = state.revision.wrapping_add(1); } self.timer.wake.notify_all(); if let Some(handle) = mutex(&self.timer_thread).take() && handle.thread().id() != thread::current().id() { let _ = handle.join(); } let mut teleport = mutex(&self.teleport); if let Some((_, waiter)) = teleport.waiter.take() { let _ = waiter.send(false); } drop(teleport); self.crossing_generation.fetch_add(1, Ordering::AcqRel); if let Some(crossing) = mutex(&self.crossing.current).as_mut() { crossing.cancel_requested = true; } self.crossing.wake.notify_all(); if let Some(handle) = mutex(&self.crossing_thread).take() && handle.thread().id() != thread::current().id() { let _ = handle.join(); } } fn ids(&self) -> (UUID, UUID) { self.owner.upgrade().map_or_else( || { ( self.network.native_agent_id(), self.network.native_session_id(), ) }, |owner| owner.native_ids(&self.network), ) } fn current_sim(&self) -> Result { self.network .native_current_sim() .ok_or(Error::InvalidOperation) } fn send_current( &self, packet_type: PacketType, data: Vec, reliable: Option, ) -> Result<(), Error> { send_encoded(&self.current_sim()?, packet_type, data, reliable) } fn snapshot(&self) -> WireState { mutex(&self.last_update).wire } fn camera(&self, far: f32) -> AgentManagerAgentMovementAgentCamera { AgentManagerAgentMovementAgentCamera::from_frame(Arc::clone(&self.camera_frame), far) } fn send_update( &self, wire: WireState, reliable: bool, simulator: Option, ) -> Result<(), Error> { let simulator = simulator.map_or_else(|| self.current_sim(), Ok)?; if !simulator.native_agent_movement_complete() { return Ok(()); } let controls = self.controls.load(Ordering::Acquire); let should_send = { let mut last = mutex(&self.last_update); if controls == 0 && wire == last.wire { last.duplicate_count = last.duplicate_count.saturating_add(1); } else { last.duplicate_count = 0; } let should_send = self .client .settings_ref() .agent .disable_update_duplicate_check || last.duplicate_count < 10; if should_send { last.wire = wire; } should_send }; if !should_send { return Ok(()); } let (agent_id, session_id) = self.ids(); let mut packet = AgentUpdatePacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.head_rotation = wire.head_rotation; packet.agent_data.body_rotation = wire.body_rotation; // Preserve the reference's deliberate Camera wrapper/frame swap. packet.agent_data.camera_at_axis = wire.camera_x_axis; packet.agent_data.camera_center = wire.camera_center; packet.agent_data.camera_left_axis = wire.camera_y_axis; packet.agent_data.camera_up_axis = wire.camera_z_axis; packet.agent_data.far = wire.far; packet.agent_data.state = wire.state.0; packet.agent_data.control_flags = controls; packet.agent_data.flags = wire.flags.0; send_encoded( &simulator, PacketType::AgentUpdate, packet.to_bytes_with_method()?, Some(reliable), )?; if self.auto_reset_controls.load(Ordering::Acquire) { self.reset_control_flags(); } Ok(()) } fn send_last_update(&self, reliable: bool, simulator: Option) -> Result<(), Error> { self.send_update(self.snapshot(), reliable, simulator) } fn reset_control_flags(&self) { const PERSISTENT: u32 = (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5); self.controls.fetch_and(PERSISTENT, Ordering::AcqRel); } fn set_flag(&self, flag: u32, value: bool) { if value { self.controls.fetch_or(flag, Ordering::AcqRel); } else { self.controls.fetch_and(!flag, Ordering::AcqRel); } } fn flag(&self, flag: u32) -> bool { self.controls.load(Ordering::Acquire) & flag != 0 } fn set_update_interval(&self, value: i32) { self.update_interval.store(value.max(0), Ordering::Release); let mut state = mutex(&self.timer.state); state.revision = state.revision.wrapping_add(1); drop(state); self.timer.wake.notify_all(); } fn update_position(&self, position: Vector3, look_at: Vector3) -> Result<(), Error> { { let mut kinematics = write(&self.kinematics); kinematics.relative_position = position; kinematics.last_position_update = SystemTime::now(); } self.camera(128.0).look_direction_with_vector3(look_at) } fn teleport_event(&self, message: String, status: TeleportStatus, flags: TeleportFlags) { let terminal = matches!( status, TeleportStatus::Failed | TeleportStatus::Finished | TeleportStatus::Cancelled ); let waiter = { let mut state = mutex(&self.teleport); state.message.clone_from(&message); state.status = status; if terminal { state.waiter.take().map(|(_, waiter)| waiter) } else { None } }; self.teleport_progress.emit(TeleportEventArgs { message, status, flags, }); if let Some(waiter) = waiter { let _ = waiter.send(status == TeleportStatus::Finished); } } fn begin_teleport(&self) -> (u64, tokio::sync::oneshot::Receiver) { let (sender, receiver) = tokio::sync::oneshot::channel(); let old_waiter = { let mut state = mutex(&self.teleport); state.generation = state.generation.wrapping_add(1); state.status = TeleportStatus::None; state.message.clear(); let generation = state.generation; let old = state.waiter.replace((generation, sender)); (generation, old) }; if let Some((_, old)) = old_waiter.1 { let _ = old.send(false); } (old_waiter.0, receiver) } async fn wait_for_teleport( &self, generation: u64, receiver: tokio::sync::oneshot::Receiver, cancellation_token: Option, ) -> Result { let cancellation = cancellation_token.unwrap_or_default(); if let Err(error) = cancellation.throw_if_cancellation_requested() { let mut state = mutex(&self.teleport); if state .waiter .as_ref() .is_some_and(|(candidate, _)| *candidate == generation) { state.waiter.take(); } return Err(error); } let timeout = Duration::from_millis( u64::try_from(self.client.settings_ref().timing.teleport_timeout.max(1)).unwrap_or(1), ); tokio::select! { result = receiver => Ok(result.unwrap_or(false)), () = cancellation.cancelled() => { let mut state = mutex(&self.teleport); if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) { state.waiter.take(); } Err(Error::Cancelled) } () = tokio::time::sleep(timeout) => { let mut state = mutex(&self.teleport); if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) { state.waiter.take(); state.message = "Teleport timed out.".into(); state.status = TeleportStatus::Failed; } Ok(false) } } } async fn wait_for_event_queue( &self, cancellation_token: Option, ) -> Result<(), Error> { let simulator = self.current_sim()?; if simulator.native_is_event_queue_running(None)? { return Ok(()); } simulator.native_start_event_queue(); let cancellation = cancellation_token.unwrap_or_default(); cancellation.throw_if_cancellation_requested()?; let (sender, receiver) = tokio::sync::oneshot::channel(); let sender = Mutex::new(Some(sender)); let expected = simulator.clone(); let subscription = self.network .native_subscribe_event_queue_running(Arc::new(move |event| { if event.simulator() == expected && let Some(sender) = mutex(&sender).take() { let _ = sender.send(()); } })); tokio::select! { _ = receiver => {} () = cancellation.cancelled() => { drop(subscription); return Err(Error::Cancelled); } () = tokio::time::sleep(Duration::from_secs(10)) => {} } drop(subscription); Ok(()) } fn request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = TeleportLandmarkRequestPacket::new_with_constructor()?; packet.info.agent_id = agent_id; packet.info.session_id = session_id; packet.info.landmark_id = landmark; self.send_current( PacketType::TeleportLandmarkRequest, packet.to_bytes_with_method()?, None, ) } fn request_teleport_location( &self, region_handle: u64, position: Vector3, look_at: Vector3, ignore_caps_status: bool, ) -> Result<(), Error> { let sim = self.current_sim()?; if !ignore_caps_status && !sim.native_is_event_queue_running(None)? { self.teleport_event( "CAPS event queue is not running".into(), TeleportStatus::Failed, TeleportFlags::DEFAULT, ); return Ok(()); } let (agent_id, session_id) = self.ids(); let mut packet = TeleportLocationRequestPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.info.look_at = look_at; packet.info.position = position; packet.info.region_handle = region_handle; send_encoded( &sim, PacketType::TeleportLocationRequest, packet.to_bytes_with_method()?, None, ) } fn complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = crate::packets::CompleteAgentMovementPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.circuit_code = self.network.native_circuit_code(); send_encoded( &simulator, PacketType::CompleteAgentMovement, packet.to_bytes_with_method()?, None, ) } fn connect_destination( &self, endpoint: SocketAddr, region_handle: u64, seed: Option, size_x: u32, size_y: u32, ) -> bool { if let Some(old) = self.network.native_current_sim() { old.native_set_agent_movement_complete(false); } self.network .native_connect(endpoint, region_handle, true, seed, size_x, size_y) .ok() .flatten() .is_some() } #[allow(clippy::too_many_lines)] // Packet dispatch mirrors the protocol state machine. pub(crate) fn handle_raw_packet( self: &Arc, packet_type: PacketType, data: &[u8], simulator: Simulator, ) -> Result { match packet_type { PacketType::AgentMovementComplete => { let packet = decode_packet::(data)?; self.update_position(packet.data.position, packet.data.look_at)?; simulator.native_set_agent_movement_complete(true); self.notify_movement_complete(&simulator); self.update_multi_simulator_state(&simulator); Ok(true) } PacketType::CameraConstraint => { let packet = decode_packet::(data)?; self.camera_constraint.emit(CameraConstraintEventArgs { collide_plane: packet.camera_collide_plane.plane, }); Ok(true) } PacketType::AvatarSitResponse => { let packet = decode_packet::(data)?; let transform = packet.sit_transform; self.avatar_sit_response.emit(AvatarSitResponseEventArgs { object_id: packet.sit_object.id, auto_pilot: transform.auto_pilot, camera_at_offset: transform.camera_at_offset, camera_eye_offset: transform.camera_eye_offset, force_mouselook: transform.force_mouselook, sit_position: transform.sit_position, sit_rotation: transform.sit_rotation, }); Ok(true) } PacketType::TeleportStart => { let packet = decode_packet::(data)?; self.teleport_event( "Teleport started".into(), TeleportStatus::Start, TeleportFlags(packet.info.teleport_flags), ); Ok(true) } PacketType::TeleportProgress => { let packet = decode_packet::(data)?; self.teleport_event( wire_string(packet.info.message), TeleportStatus::Progress, TeleportFlags(packet.info.teleport_flags), ); Ok(true) } PacketType::TeleportFailed => { let packet = decode_packet::(data)?; let status = mutex(&self.teleport).status; if matches!(status, TeleportStatus::Finished | TeleportStatus::None) { return Ok(true); } self.teleport_event( wire_string(packet.info.reason), TeleportStatus::Failed, TeleportFlags::DEFAULT, ); Ok(true) } PacketType::TeleportCancel => { let _ = decode_packet::(data)?; self.teleport_event( "Cancelled".into(), TeleportStatus::Cancelled, TeleportFlags::DEFAULT, ); Ok(true) } PacketType::TeleportLocal => { let packet = decode_packet::(data)?; self.update_position(packet.info.position, packet.info.look_at)?; self.teleport_event( "Teleport finished".into(), TeleportStatus::Finished, TeleportFlags(packet.info.teleport_flags), ); Ok(true) } PacketType::TeleportFinish => { let packet = decode_packet::(data)?; let endpoint = SocketAddr::new( IpAddr::V4(Ipv4Addr::from(packet.info.sim_ip.to_le_bytes())), packet.info.sim_port, ); let seed = Uri(wire_string(packet.info.seed_capability)); let success = self.connect_destination( endpoint, packet.info.region_handle, Some(seed), 256, 256, ); self.teleport_event( if success { "Teleport finished".into() } else { "Failed to connect to simulator after teleport".into() }, if success { TeleportStatus::Finished } else { TeleportStatus::Failed }, TeleportFlags(packet.info.teleport_flags), ); Ok(true) } PacketType::CrossedRegion => { let packet = decode_packet::(data)?; self.begin_crossing( simulator, SocketAddr::new( IpAddr::V4(Ipv4Addr::from(packet.region_data.sim_ip.to_le_bytes())), packet.region_data.sim_port, ), packet.region_data.region_handle, Uri(wire_string(packet.region_data.seed_capability)), packet.info.position, packet.info.look_at, 256, 256, ); Ok(true) } _ => Ok(false), } } #[allow(clippy::too_many_arguments)] fn begin_crossing( self: &Arc, old_simulator: Simulator, endpoint: SocketAddr, region_handle: u64, seed: Uri, position: Vector3, look_at: Vector3, size_x: u32, size_y: u32, ) -> bool { { let current = mutex(&self.crossing.current); if current.as_ref().is_some_and(|crossing| { !matches!( crossing.state, AgentManagerCrossingState::Idle | AgentManagerCrossingState::Completed | AgentManagerCrossingState::Failed ) }) { return false; } } let generation = self.crossing_generation.fetch_add(1, Ordering::AcqRel) + 1; *mutex(&self.crossing.current) = Some(CrossingInfo { state: AgentManagerCrossingState::PreparingCross, started: Instant::now(), old_simulator: Some(old_simulator), new_simulator: None, region_handle, endpoint, seed, position, look_at, size_x, size_y, retry_count: 0, failure_reason: AgentManagerCrossingFailureReason::Unknown, failure_message: String::new(), generation, cancel_requested: false, restored_old_simulator: false, }); let weak = Arc::downgrade(self); let crossing = Arc::clone(&self.crossing); let spawned = thread::Builder::new() .name("libremetaverse-region-crossing".into()) .spawn(move || Self::run_crossing(weak, crossing, generation)); let Ok(handle) = spawned else { if let Some(info) = mutex(&self.crossing.current).as_mut() { info.state = AgentManagerCrossingState::Failed; info.failure_reason = AgentManagerCrossingFailureReason::NetworkError; info.failure_message = "Unable to start region crossing worker".into(); } return false; }; if let Some(previous) = mutex(&self.crossing_thread).replace(handle) && previous.thread().id() != thread::current().id() { let _ = previous.join(); } true } #[allow(clippy::too_many_lines)] // Keeping transitions together makes the state audit explicit. fn run_crossing(weak: Weak, crossing: Arc, generation: u64) { loop { let (endpoint, region_handle, seed, size_x, size_y) = { let mut current = mutex(&crossing.current); let Some(info) = current .as_mut() .filter(|info| info.generation == generation) else { return; }; if info.cancel_requested { drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } info.state = AgentManagerCrossingState::Connecting; ( info.endpoint, info.region_handle, info.seed.clone(), info.size_x, info.size_y, ) }; let Some(runtime) = weak.upgrade() else { return; }; if runtime.crossing_generation.load(Ordering::Acquire) != generation { return; } let connection = runtime.network.native_connect( endpoint, region_handle, true, Some(seed), size_x, size_y, ); drop(runtime); match connection { Ok(Some(new_simulator)) => { let (old_simulator, position, look_at) = { let mut current = mutex(&crossing.current); let Some(info) = current .as_mut() .filter(|info| info.generation == generation) else { return; }; if info.cancel_requested { info.new_simulator = Some(new_simulator); drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } info.new_simulator = Some(new_simulator.clone()); info.state = AgentManagerCrossingState::WaitingForComplete; (info.old_simulator.clone(), info.position, info.look_at) }; crossing.wake.notify_all(); if let Some(old) = &old_simulator && *old != new_simulator { old.native_set_agent_movement_complete(false); } if let Some(runtime) = weak.upgrade() { let _ = runtime.update_position(position, look_at); } let current = mutex(&crossing.current); let (mut current, timeout) = crossing .wake .wait_timeout_while(current, Duration::from_secs(30), |current| { current.as_ref().is_some_and(|info| { info.generation == generation && info.state == AgentManagerCrossingState::WaitingForComplete && !info.cancel_requested }) }) .unwrap_or_else(std::sync::PoisonError::into_inner); let Some(info) = current .as_mut() .filter(|info| info.generation == generation) else { return; }; if info.state == AgentManagerCrossingState::Completed { let event = RegionCrossedEventArgs { old_simulator: info.old_simulator.clone(), new_simulator: info.new_simulator.clone(), }; info.state = AgentManagerCrossingState::Idle; drop(current); if let Some(runtime) = weak.upgrade() { runtime.region_crossed.emit(event); } return; } if timeout.timed_out() { info.failure_reason = AgentManagerCrossingFailureReason::Timeout; info.failure_message = "Timeout in state WaitingForComplete".into(); } drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } Ok(None) => { let mut current = mutex(&crossing.current); let Some(info) = current .as_mut() .filter(|info| info.generation == generation) else { return; }; info.failure_reason = AgentManagerCrossingFailureReason::ConnectionFailed; info.failure_message = format!("Failed to connect to {}", info.endpoint); if info.retry_count >= 3 { info.failure_reason = AgentManagerCrossingFailureReason::MaxRetriesExceeded; info.failure_message.push_str(" after 3 retries"); drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } info.retry_count += 1; let delay = Duration::from_secs(u64::from(info.retry_count)); let (current, _) = crossing .wake .wait_timeout(current, delay) .unwrap_or_else(std::sync::PoisonError::into_inner); if current .as_ref() .is_none_or(|info| info.generation != generation || info.cancel_requested) { drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } } Err(error) => { let mut current = mutex(&crossing.current); if let Some(info) = current .as_mut() .filter(|info| info.generation == generation) { info.failure_reason = AgentManagerCrossingFailureReason::NetworkError; info.failure_message = format!("Exception during connection: {error}"); } drop(current); Self::recover_crossing(&weak, &crossing, generation); return; } } } } fn recover_crossing(weak: &Weak, crossing: &CrossingControl, generation: u64) { let (old_simulator, new_simulator) = { let mut current = mutex(&crossing.current); let Some(info) = current .as_mut() .filter(|info| info.generation == generation) else { return; }; info.state = AgentManagerCrossingState::Recovering; info.restored_old_simulator = info.old_simulator.is_some(); (info.old_simulator.clone(), info.new_simulator.clone()) }; if let Some(runtime) = weak.upgrade() { if runtime.crossing_generation.load(Ordering::Acquire) != generation { return; } if let Some(old) = &old_simulator { old.native_set_agent_movement_complete(true); runtime.network.native_set_current_sim(Some(old.clone())); let _ = runtime.complete_agent_movement(old.clone()); } if let Some(new_simulator) = new_simulator { let _ = runtime.network.native_disconnect_sim(new_simulator, false); } let event = RegionCrossedEventArgs { old_simulator, new_simulator: None, }; if let Some(info) = mutex(&crossing.current) .as_mut() .filter(|info| info.generation == generation) { info.state = AgentManagerCrossingState::Failed; info.state = AgentManagerCrossingState::Idle; } runtime.region_crossed.emit(event); } } fn notify_movement_complete(&self, simulator: &Simulator) { let mut current = mutex(&self.crossing.current); let Some(info) = current.as_mut() else { return; }; if info.state != AgentManagerCrossingState::WaitingForComplete { return; } if info.new_simulator.as_ref() == Some(simulator) { info.state = AgentManagerCrossingState::Completed; } else if info.old_simulator.as_ref() == Some(simulator) { info.failure_reason = AgentManagerCrossingFailureReason::SimulatorRejected; info.failure_message = "Received MovementComplete from old simulator instead of new one".into(); info.cancel_requested = true; } else { return; } drop(current); self.crossing.wake.notify_all(); } fn update_multi_simulator_state(&self, simulator: &Simulator) { let state = *read(&self.kinematics); write(&self.simulator_states).insert( simulator.native_ip_end_point(), SimulatorAgentState { position: state.relative_position, rotation: state.relative_rotation, local_id: 0, is_present: simulator.native_agent_movement_complete(), last_update: SystemTime::now(), }, ); if !self.client.settings_ref().agent.multiple_sims { return; } self.predict_crossing(simulator, state.relative_position, state.velocity); self.establish_neighbor_children(simulator, state.relative_position, state.velocity); self.cleanup_object_tracking(); let now = SystemTime::now(); write(&self.child_agent_status).retain(|_, status| { now.duration_since(status.request_time).unwrap_or_default() <= Duration::from_mins(2) }); } #[allow(clippy::float_cmp)] // Golden code selects the direction by exact minimum equality. fn predict_crossing(&self, simulator: &Simulator, position: Vector3, velocity: Vector3) { if velocity == Vector3::zero() { return; } let west = if velocity.x < -0.1 { -position.x / velocity.x } else { f32::MAX }; let east = if velocity.x > 0.1 { (simulator.size_x as f32 - position.x) / velocity.x } else { f32::MAX }; let south = if velocity.y < -0.1 { -position.y / velocity.y } else { f32::MAX }; let north = if velocity.y > 0.1 { (simulator.size_y as f32 - position.y) / velocity.y } else { f32::MAX }; let time = west.min(east).min(south).min(north); if !(0.0 < time && time < 3.0) { return; } let direction = if time == west { BorderCrossingDirection::West } else if time == east { BorderCrossingDirection::East } else if time == south { BorderCrossingDirection::South } else if time == north { BorderCrossingDirection::North } else { BorderCrossingDirection::Unknown }; self.region_crossing_predicted .emit(RegionCrossingPredictionEventArgs { current_simulator: simulator.clone(), direction, time_until_crossing: time, }); } fn establish_neighbor_children( &self, simulator: &Simulator, position: Vector3, velocity: Vector3, ) { if !simulator.native_agent_movement_complete() { return; } let x = u32::try_from(simulator.handle >> 32).unwrap_or_default(); let y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default(); let size_x = simulator.size_x as f32; let size_y = simulator.size_y as f32; if velocity.x < -0.5 && position.x < 128.0 { self.establish_child(x.wrapping_sub(256), y, BorderCrossingDirection::West); } else if velocity.x > 0.5 && position.x > size_x - 128.0 { self.establish_child(x.wrapping_add(256), y, BorderCrossingDirection::East); } if velocity.y < -0.5 && position.y < 128.0 { self.establish_child(x, y.wrapping_sub(256), BorderCrossingDirection::South); } else if velocity.y > 0.5 && position.y > size_y - 128.0 { self.establish_child(x, y.wrapping_add(256), BorderCrossingDirection::North); } if Self::is_near_corner(position, size_x, size_y, 64.0) { for (child_x, child_y, direction) in [ (x.wrapping_sub(256), y, BorderCrossingDirection::West), (x.wrapping_add(256), y, BorderCrossingDirection::East), (x, y.wrapping_sub(256), BorderCrossingDirection::South), (x, y.wrapping_add(256), BorderCrossingDirection::North), ] { self.establish_child(child_x, child_y, direction); } } } fn establish_child(&self, x: u32, y: u32, direction: BorderCrossingDirection) { let handle = (u64::from(x) << 32) | u64::from(y); let now = SystemTime::now(); let mut children = write(&self.child_agent_status); if children.get(&handle).is_some_and(|status| { now.duration_since(status.request_time).unwrap_or_default() < Duration::from_secs(30) }) { return; } let established = self .network .simulators .snapshot() .iter() .any(|simulator| simulator.handle == handle); children.insert( handle, ChildAgentStatus { request_time: now, established, direction, }, ); } const fn is_near_corner(position: Vector3, size_x: f32, size_y: f32, threshold: f32) -> bool { (position.x < threshold || position.x > size_x - threshold) && (position.y < threshold || position.y > size_y - threshold) } fn cleanup_object_tracking(&self) { write(&self.object_simulators).retain(|_, simulators| { simulators.retain(Simulator::native_is_connected); !simulators.is_empty() }); } fn track_object(&self, object_id: UUID, simulator: Simulator) { if object_id == UUID::zero() { return; } let mut objects = write(&self.object_simulators); let simulators = objects.entry(object_id).or_default(); if !simulators.contains(&simulator) { simulators.push(simulator); } } fn untrack_object(&self, object_id: UUID, simulator: &Simulator) { let mut objects = write(&self.object_simulators); if let Some(simulators) = objects.get_mut(&object_id) { simulators.retain(|candidate| candidate != simulator); if simulators.is_empty() { objects.remove(&object_id); } } } fn object_simulators(&self, object_id: UUID) -> Vec { read(&self.object_simulators) .get(&object_id) .cloned() .unwrap_or_default() } pub(crate) fn handle_caps_event( self: &Arc, name: &str, message: &dyn IMessage, simulator: Simulator, ) -> Result { match name { "TeleportFinish" => { let mut finish = crate::messages::linden::TeleportFinishMessage::new()?; finish.deserialize(message.serialize()?)?; let port = u16::try_from(finish.port).map_err(|_| Error::Argument)?; let success = self.connect_destination( SocketAddr::new(finish.ip, port), finish.region_handle, Some(finish.seed_capability), finish.region_size_x, finish.region_size_y, ); self.teleport_event( if success { "Teleport finished".into() } else { "Failed to connect to simulator after teleport".into() }, if success { TeleportStatus::Finished } else { TeleportStatus::Failed }, finish.flags, ); Ok(true) } "TeleportFailed" => { let mut failed = crate::messages::linden::TeleportFailedMessage::new()?; failed.deserialize(message.serialize()?)?; let status = mutex(&self.teleport).status; if !matches!(status, TeleportStatus::Finished | TeleportStatus::None) { self.teleport_event( failed.reason, TeleportStatus::Failed, TeleportFlags::DEFAULT, ); } Ok(true) } "CrossedRegion" => { let mut crossed = crate::messages::linden::CrossedRegionMessage::new()?; crossed.deserialize(message.serialize()?)?; let port = u16::try_from(crossed.port).map_err(|_| Error::Argument)?; self.begin_crossing( simulator, SocketAddr::new(crossed.ip, port), crossed.region_handle, crossed.seed_capability, crossed.position, crossed.look_at, crossed.region_size_x, crossed.region_size_y, ); Ok(true) } _ => Ok(false), } } pub(crate) fn sim_position(&self) -> Vector3 { let state = *read(&self.kinematics); if state.sitting_on == 0 { return state.relative_position; } let Some(simulator) = self.network.native_current_sim() else { return state.relative_position; }; let primitives = read(&simulator.objects_primitives); let Some(parent) = primitives.get(&state.sitting_on) else { return state.relative_position; }; add( parent.position, Vector3::mul_with_vector3_quaternion(state.relative_position, parent.rotation), ) } pub(crate) fn sim_rotation(&self) -> Quaternion { let state = *read(&self.kinematics); if state.sitting_on == 0 { return state.relative_rotation; } self.network .native_current_sim() .and_then(|simulator| { read(&simulator.objects_primitives) .get(&state.sitting_on) .map(|parent| { Quaternion::mul_with_quaternion_quaternion( state.relative_rotation, parent.rotation, ) }) }) .unwrap_or(state.relative_rotation) } } /// Public movement handle. Control and camera state are shared with packet callbacks. pub struct AgentManagerAgentMovement { pub body_rotation: Quaternion, pub camera: AgentManagerAgentMovementAgentCamera, pub flags: AgentFlags, pub head_rotation: Quaternion, pub state: AgentState, pub(crate) runtime: Arc, } impl fmt::Debug for AgentManagerAgentMovement { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("AgentMovement") .field("body_rotation", &self.body_rotation) .field("camera", &self.camera) .field("flags", &self.flags) .field("head_rotation", &self.head_rotation) .field("state", &self.state) .field("controls", &self.agent_controls()) .finish_non_exhaustive() } } impl AgentManagerAgentMovement { pub(crate) fn from_runtime(runtime: Arc) -> Self { let wire = runtime.snapshot(); Self { body_rotation: wire.body_rotation, camera: runtime.camera(wire.far), flags: wire.flags, head_rotation: wire.head_rotation, state: wire.state, runtime, } } pub fn new(client: GridClient) -> Result { Ok(AgentManager::native_new(Some(Arc::new(client)))?.movement) } fn wire(&self) -> WireState { WireState { body_rotation: self.body_rotation, head_rotation: self.head_rotation, camera_center: self.camera.position(), camera_x_axis: self.camera.left_axis(), camera_y_axis: self.camera.at_axis(), camera_z_axis: self.camera.up_axis(), far: self.camera.far, flags: self.flags, state: self.state, } } pub fn reset_control_flags(&self) -> Result<(), Error> { self.runtime.reset_control_flags(); Ok(()) } pub fn send_manual_update( &self, control_flags: AgentManagerControlFlags, position: Vector3, forward_axis: Vector3, left_axis: Vector3, up_axis: Vector3, body_rotation: Quaternion, head_rotation: Quaternion, far_clip: f32, flags: AgentFlags, state: AgentState, reliable: bool, ) -> Result<(), Error> { let simulator = self.runtime.current_sim()?; if !simulator.native_handshake_complete() { return Ok(()); } let (agent_id, session_id) = self.runtime.ids(); let mut packet = AgentUpdatePacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.body_rotation = body_rotation; packet.agent_data.head_rotation = head_rotation; packet.agent_data.camera_center = position; packet.agent_data.camera_at_axis = forward_axis; packet.agent_data.camera_left_axis = left_axis; packet.agent_data.camera_up_axis = up_axis; packet.agent_data.far = far_clip; packet.agent_data.control_flags = control_flags.0.cast_unsigned(); packet.agent_data.flags = flags.0; packet.agent_data.state = state.0; send_encoded( &simulator, PacketType::AgentUpdate, packet.to_bytes_with_method()?, Some(reliable), ) } pub fn send_update_with_boolean(&self, reliable: Option) -> Result<(), Error> { self.runtime .send_update(self.wire(), reliable.unwrap_or(false), None) } pub fn send_update_with_boolean_simulator( &self, reliable: bool, simulator: Simulator, ) -> Result<(), Error> { self.runtime .send_update(self.wire(), reliable, Some(simulator)) } pub fn set_fov_vertical_angle(&self, angle: f32) -> Result<(), Error> { let (agent_id, session_id) = self.runtime.ids(); let mut packet = AgentFOVPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.circuit_code = self.runtime.network.native_circuit_code(); packet.fov_block.gen_counter = 0; packet.fov_block.vertical_angle = angle; self.runtime .send_current(PacketType::AgentFOV, packet.to_bytes_with_method()?, None) } pub fn turn_toward(&self, target: Vector3, send_update: Option) -> Result { if !self.runtime.client.settings_ref().agent.send_updates { return Ok(false); } let position = self.runtime.sim_position(); let direction = normalize(subtract(target, position)); if direction == Vector3::zero() { return Ok(false); } let rotation = Vector3::rotation_between(Vector3::unit_x(), direction)?; self.camera.look_at_with_vector3_vector3(position, target)?; let mut wire = self.wire(); wire.body_rotation = rotation; wire.head_rotation = rotation; if send_update.unwrap_or(true) { self.runtime.send_update(wire, false, None)?; } else { mutex(&self.runtime.last_update).wire = wire; } Ok(true) } pub fn update_from_heading(&self, heading: f64, reliable: bool) -> Result<(), Error> { let position = self.runtime.sim_position(); { let mut camera = self.camera.clone(); camera.set_position(position); camera.look_direction_with_double(heading)?; } let rotation = Quaternion { x: self.body_rotation.x, y: self.body_rotation.y, z: (heading / 2.0).sin() as f32, w: (heading / 2.0).cos() as f32, }; let mut wire = self.wire(); wire.body_rotation = rotation; wire.head_rotation = rotation; self.runtime.send_update(wire, reliable, None) } #[must_use] pub fn agent_controls(&self) -> u32 { self.runtime.controls.load(Ordering::Acquire) } pub fn set_agent_controls(&mut self, value: u32) { self.runtime.controls.store(value, Ordering::Release); } #[must_use] pub fn always_run(&self) -> bool { self.runtime.always_run.load(Ordering::Acquire) } pub fn set_always_run(&mut self, value: bool) { self.runtime.always_run.store(value, Ordering::Release); let result = (|| { let (agent_id, session_id) = self.runtime.ids(); let mut packet = SetAlwaysRunPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.always_run = value; self.runtime.send_current( PacketType::SetAlwaysRun, packet.to_bytes_with_method()?, None, ) })(); let _: Result<(), Error> = result; } #[must_use] pub fn auto_reset_controls(&self) -> bool { self.runtime.auto_reset_controls.load(Ordering::Acquire) } pub fn set_auto_reset_controls(&mut self, value: bool) { self.runtime .auto_reset_controls .store(value, Ordering::Release); } #[must_use] pub fn update_enabled(&self) -> bool { self.update_interval() != 0 } #[must_use] pub fn update_interval(&self) -> i32 { self.runtime.update_interval.load(Ordering::Acquire) } pub fn set_update_interval(&mut self, value: i32) { self.runtime.set_update_interval(value); } } macro_rules! control_accessors { ($(($getter:ident, $setter:ident, $flag:expr)),+ $(,)?) => { impl AgentManagerAgentMovement { $( #[must_use] pub fn $getter(&self) -> bool { self.runtime.flag($flag) } pub fn $setter(&mut self, value: bool) { self.runtime.set_flag($flag, value); } )+ } }; } control_accessors!( (at_pos, set_at_pos, 1 << 0), (at_neg, set_at_neg, 1 << 1), (left_pos, set_left_pos, 1 << 2), (left_neg, set_left_neg, 1 << 3), (up_pos, set_up_pos, 1 << 4), (up_neg, set_up_neg, 1 << 5), (pitch_pos, set_pitch_pos, 1 << 6), (pitch_neg, set_pitch_neg, 1 << 7), (yaw_pos, set_yaw_pos, 1 << 8), (yaw_neg, set_yaw_neg, 1 << 9), (fast_at, set_fast_at, 1 << 10), (fast_left, set_fast_left, 1 << 11), (fast_up, set_fast_up, 1 << 12), (fly, set_fly, 1 << 13), (stop, set_stop, 1 << 14), (finish_anim, set_finish_anim, 1 << 15), (stand_up, set_stand_up, 1 << 16), (sit_on_ground, set_sit_on_ground, 1 << 17), (mouselook, set_mouselook, 1 << 18), (nudge_at_pos, set_nudge_at_pos, 1 << 19), (nudge_at_neg, set_nudge_at_neg, 1 << 20), (nudge_left_pos, set_nudge_left_pos, 1 << 21), (nudge_left_neg, set_nudge_left_neg, 1 << 22), (nudge_up_pos, set_nudge_up_pos, 1 << 23), (nudge_up_neg, set_nudge_up_neg, 1 << 24), (turn_left, set_turn_left, 1 << 25), (turn_right, set_turn_right, 1 << 26), (away, set_away, 1 << 27), (l_button_down, set_l_button_down, 1 << 28), (l_button_up, set_l_button_up, 1 << 29), (ml_button_down, set_ml_button_down, 1 << 30), (ml_button_up, set_ml_button_up, 1 << 31), ); impl AgentMovementRuntime { pub(crate) fn subscribe_teleport_progress( &self, handler: EventHandler, ) -> Subscription { self.teleport_progress.subscribe(handler) } pub(crate) fn subscribe_camera_constraint( &self, handler: EventHandler, ) -> Subscription { self.camera_constraint.subscribe(handler) } pub(crate) fn subscribe_avatar_sit_response( &self, handler: EventHandler, ) -> Subscription { self.avatar_sit_response.subscribe(handler) } pub(crate) fn subscribe_region_crossed( &self, handler: EventHandler, ) -> Subscription { self.region_crossed.subscribe(handler) } pub(crate) fn subscribe_region_crossing_predicted( &self, handler: EventHandler, ) -> Subscription { self.region_crossing_predicted.subscribe(handler) } pub(crate) fn crossing_state(&self) -> AgentManagerCrossingState { mutex(&self.crossing.current) .as_ref() .map_or(AgentManagerCrossingState::Idle, |crossing| crossing.state) } pub(crate) fn crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason { mutex(&self.crossing.current) .as_ref() .map_or(AgentManagerCrossingFailureReason::Unknown, |crossing| { crossing.failure_reason }) } pub(crate) fn crossing_details(&self) -> String { let current = mutex(&self.crossing.current); let Some(crossing) = current.as_ref() else { return "No active crossing".into(); }; format!( "State: {:?}, Duration: {:.2}s, Retries: {}/3, Old Sim: {}, New Sim: {}, Target: {}, Failure: {:?} - {}", crossing.state, crossing.started.elapsed().as_secs_f64(), crossing.retry_count, crossing .old_simulator .as_ref() .map_or("null", |simulator| simulator.name.as_str()), crossing .new_simulator .as_ref() .map_or("null", |simulator| simulator.name.as_str()), crossing.endpoint, crossing.failure_reason, crossing.failure_message, ) } pub(crate) fn is_crossing(&self) -> bool { !matches!( self.crossing_state(), AgentManagerCrossingState::Idle | AgentManagerCrossingState::Completed | AgentManagerCrossingState::Failed ) } pub(crate) fn cancel_crossing(&self) { let mut current = mutex(&self.crossing.current); let Some(crossing) = current.as_mut() else { return; }; if matches!( crossing.state, AgentManagerCrossingState::Idle | AgentManagerCrossingState::Completed | AgentManagerCrossingState::Failed ) { return; } crossing.failure_message = "Crossing was manually cancelled".into(); crossing.cancel_requested = true; drop(current); self.crossing.wake.notify_all(); } pub(crate) fn acceleration(&self) -> Vector3 { read(&self.kinematics).acceleration } pub(crate) fn angular_velocity(&self) -> Vector3 { read(&self.kinematics).angular_velocity } pub(crate) fn global_position(&self) -> Vector3d { let Some(simulator) = self.network.native_current_sim() else { return Vector3d::zero(); }; let position = self.sim_position(); let global_x = u32::try_from(simulator.handle >> 32).unwrap_or_default(); let global_y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default(); Vector3d { x: f64::from(global_x) + f64::from(position.x), y: f64::from(global_y) + f64::from(position.y), z: f64::from(position.z), } } pub(crate) fn home_position(&self) -> Vector3 { read(&self.kinematics).home_position } pub(crate) fn last_position_update(&self) -> SystemTime { read(&self.kinematics).last_position_update } pub(crate) fn set_last_position_update(&self, value: SystemTime) { write(&self.kinematics).last_position_update = value; } pub(crate) fn relative_position(&self) -> Vector3 { read(&self.kinematics).relative_position } pub(crate) fn set_relative_position(&self, value: Vector3) { write(&self.kinematics).relative_position = value; } pub(crate) fn relative_position_estimate(&self) -> Vector3 { let state = *read(&self.kinematics); let elapsed = SystemTime::now() .duration_since(state.last_position_update) .unwrap_or_default() .as_secs_f32(); add(state.relative_position, scale(state.velocity, elapsed)) } pub(crate) fn relative_rotation(&self) -> Quaternion { read(&self.kinematics).relative_rotation } pub(crate) fn set_relative_rotation(&self, value: Quaternion) { write(&self.kinematics).relative_rotation = value; } pub(crate) fn sitting_on(&self) -> u32 { read(&self.kinematics).sitting_on } pub(crate) fn teleport_message(&self) -> String { mutex(&self.teleport).message.clone() } pub(crate) fn set_teleport_message(&self, value: String) { mutex(&self.teleport).message = value; } pub(crate) fn velocity(&self) -> Vector3 { read(&self.kinematics).velocity } pub(crate) fn request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = AgentRequestSitPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.target_object.target_id = target_id; packet.target_object.offset = offset; self.send_current( PacketType::AgentRequestSit, packet.to_bytes_with_method()?, None, ) } pub(crate) fn sit(&self) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = AgentSitPacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; self.send_current(PacketType::AgentSit, packet.to_bytes_with_method()?, None) } pub(crate) fn autopilot(&self, x: String, y: String, z: String) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = GenericMessagePacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.agent_data.transaction_id = UUID::zero(); packet.method_data.invoice = UUID::zero(); packet.method_data.method = b"autopilot".to_vec(); packet.param_list = [x, y, z] .into_iter() .map(|parameter| GenericMessagePacketParamListBlock { parameter: parameter.into_bytes(), }) .collect(); self.send_current( PacketType::GenericMessage, packet.to_bytes_with_method()?, None, ) } pub(crate) fn send_lure(&self, target_id: UUID, message: String) -> Result<(), Error> { let (agent_id, session_id) = self.ids(); let mut packet = StartLurePacket::new_with_constructor()?; packet.agent_data.agent_id = agent_id; packet.agent_data.session_id = session_id; packet.info.lure_type = 0; packet.info.message = message.into_bytes(); packet.target_data = vec![StartLurePacketTargetDataBlock { target_id }]; self.send_current(PacketType::StartLure, packet.to_bytes_with_method()?, None) } pub(crate) fn accept_lure(&self, session_id: UUID) -> Result<(), Error> { let (agent_id, own_session_id) = self.ids(); let mut packet = TeleportLureRequestPacket::new_with_constructor()?; packet.info.agent_id = agent_id; packet.info.session_id = own_session_id; packet.info.lure_id = session_id; packet.info.teleport_flags = TeleportFlags::VIA_LURE.0; self.send_current( PacketType::TeleportLureRequest, packet.to_bytes_with_method()?, None, ) } } impl AgentManager { pub(crate) fn native_movement_runtime(&self) -> &Arc { &self.movement.runtime } pub(crate) fn native_subscribe_teleport_progress( &self, handler: EventHandler, ) -> Subscription { self.movement.runtime.subscribe_teleport_progress(handler) } pub(crate) fn native_subscribe_camera_constraint( &self, handler: EventHandler, ) -> Subscription { self.movement.runtime.subscribe_camera_constraint(handler) } pub(crate) fn native_subscribe_avatar_sit_response( &self, handler: EventHandler, ) -> Subscription { self.movement.runtime.subscribe_avatar_sit_response(handler) } pub(crate) fn native_subscribe_region_crossed( &self, handler: EventHandler, ) -> Subscription { self.movement.runtime.subscribe_region_crossed(handler) } pub(crate) fn native_subscribe_region_crossing_predicted( &self, handler: EventHandler, ) -> Subscription { self.movement .runtime .subscribe_region_crossing_predicted(handler) } pub(crate) fn native_track_object_in_simulator(&self, object_id: UUID, simulator: Simulator) { self.movement.runtime.track_object(object_id, simulator); } pub(crate) fn native_untrack_object_in_simulator( &self, object_id: UUID, simulator: &Simulator, ) { self.movement.runtime.untrack_object(object_id, simulator); } pub(crate) fn native_get_simulators_for_object(&self, object_id: UUID) -> Vec { self.movement.runtime.object_simulators(object_id) } #[allow(clippy::unused_self)] // Public C# method is instance-shaped but uses only its inputs. pub(crate) fn native_is_near_border( &self, position: Vector3, region_size_x: u32, region_size_y: u32, threshold: f32, ) -> bool { position.x < threshold || position.x > region_size_x as f32 - threshold || position.y < threshold || position.y > region_size_y as f32 - threshold } pub(crate) fn native_cancel_crossing(&self) { self.movement.runtime.cancel_crossing(); } pub(crate) fn native_crossing_state(&self) -> AgentManagerCrossingState { self.movement.runtime.crossing_state() } pub(crate) fn native_crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason { self.movement.runtime.crossing_failure_reason() } pub(crate) fn native_crossing_details(&self) -> String { self.movement.runtime.crossing_details() } pub(crate) fn native_is_crossing(&self) -> bool { self.movement.runtime.is_crossing() } pub(crate) fn native_complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> { self.movement.runtime.complete_agent_movement(simulator) } pub(crate) fn native_request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> { self.movement.runtime.request_sit(target_id, offset) } pub(crate) fn native_sit(&self) -> Result<(), Error> { self.movement.runtime.sit() } pub(crate) fn native_stand(&self) -> Result { if !self.client.settings_ref().agent.send_updates { return Ok(false); } self.movement.runtime.set_flag(1 << 17, false); self.movement.runtime.set_flag(1 << 16, true); self.movement.runtime.send_last_update(false, None)?; self.movement.runtime.set_flag(1 << 16, false); self.movement.runtime.send_last_update(false, None)?; Ok(true) } pub(crate) fn native_sit_on_ground(&self) -> Result<(), Error> { self.movement.runtime.set_flag(1 << 17, true); self.movement.runtime.send_last_update(true, None) } pub(crate) fn native_fly(&self, start: bool) -> Result<(), Error> { self.movement.runtime.set_flag(1 << 13, start); self.movement.runtime.send_last_update(true, None) } pub(crate) fn native_crouch(&self, crouching: bool) -> Result<(), Error> { self.movement.runtime.set_flag(1 << 5, crouching); self.movement.runtime.send_last_update(true, None) } pub(crate) fn native_jump(&self, jumping: bool) -> Result<(), Error> { self.movement.runtime.set_flag(1 << 4, jumping); self.movement.runtime.set_flag(1 << 12, jumping); self.movement.runtime.send_last_update(true, None) } pub(crate) fn native_autopilot_f64(&self, x: f64, y: f64, z: f64) -> Result<(), Error> { self.movement .runtime .autopilot(x.to_string(), y.to_string(), z.to_string()) } pub(crate) fn native_autopilot_u64(&self, x: u64, y: u64, z: f32) -> Result<(), Error> { self.movement .runtime .autopilot(x.to_string(), y.to_string(), z.to_string()) } pub(crate) fn native_autopilot_local(&self, x: i32, y: i32, z: f32) -> Result<(), Error> { let Some(simulator) = self.client.network().native_current_sim() else { return Ok(()); }; let global_x = i64::from(u32::try_from(simulator.handle >> 32).unwrap_or_default()) .checked_add(i64::from(x)) .ok_or(Error::Argument)?; let global_y = i64::from(u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default()) .checked_add(i64::from(y)) .ok_or(Error::Argument)?; self.movement.runtime.autopilot( u64::try_from(global_x) .map_err(|_| Error::Argument)? .to_string(), u64::try_from(global_y) .map_err(|_| Error::Argument)? .to_string(), z.to_string(), ) } pub(crate) fn native_autopilot_cancel(&self) -> Result { if !self.client.settings_ref().agent.send_updates { return Ok(false); } self.movement.runtime.set_flag(1, true); self.movement.runtime.send_last_update(false, None)?; self.movement.runtime.set_flag(1, false); self.movement.runtime.send_last_update(false, None)?; Ok(true) } pub(crate) fn native_request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> { self.movement.runtime.request_teleport_landmark(landmark) } pub(crate) fn native_request_teleport_location( &self, region_handle: u64, position: Vector3, look_at: Vector3, ignore_caps_status: Option, ) -> Result<(), Error> { self.movement.runtime.request_teleport_location( region_handle, position, look_at, ignore_caps_status.unwrap_or(false), ) } pub(crate) async fn native_teleport_landmark( &self, landmark: UUID, cancellation_token: Option, ) -> Result { if mutex(&self.movement.runtime.teleport).status == TeleportStatus::Progress { return Ok(false); } let (generation, receiver) = self.movement.runtime.begin_teleport(); self.movement.runtime.request_teleport_landmark(landmark)?; self.movement .runtime .wait_for_teleport(generation, receiver, cancellation_token) .await } pub(crate) async fn native_teleport_location( &self, region_handle: u64, position: Vector3, look_at: Vector3, cancellation_token: Option, ) -> Result { self.movement .runtime .wait_for_event_queue(cancellation_token.clone()) .await?; let (generation, receiver) = self.movement.runtime.begin_teleport(); self.movement .runtime .request_teleport_location(region_handle, position, look_at, true)?; self.movement .runtime .wait_for_teleport(generation, receiver, cancellation_token) .await } pub(crate) async fn native_teleport_name( &self, sim_name: String, position: Vector3, look_at: Vector3, cancellation_token: Option, ) -> Result { if sim_name.is_empty() { self.movement.runtime.teleport_event( "Invalid simulator name".into(), TeleportStatus::Failed, TeleportFlags::DEFAULT, ); return Ok(false); } let Some(current) = self.client.network().native_current_sim() else { self.movement.runtime.teleport_event( format!("Not in a current simulator, cannot teleport to {sim_name}"), TeleportStatus::Failed, TeleportFlags::DEFAULT, ); return Ok(false); }; let handle = if current.name.eq_ignore_ascii_case(&sim_name) { Some(current.handle) } else { self.client .network() .simulators .snapshot() .into_iter() .find(|simulator| simulator.name.eq_ignore_ascii_case(&sim_name)) .map(|simulator| simulator.handle) }; let Some(handle) = handle else { self.movement.runtime.teleport_event( format!("Unable to resolve simulator named: {sim_name}"), TeleportStatus::Failed, TeleportFlags::DEFAULT, ); return Ok(false); }; self.native_teleport_location(handle, position, look_at, cancellation_token) .await } pub(crate) fn native_send_teleport_lure( &self, target_id: UUID, message: Option, ) -> Result<(), Error> { let message = message.unwrap_or_else(|| { let region = self .client .network() .native_current_sim() .map_or_else(|| "unknown".into(), |simulator| simulator.name.clone()); format!("Join me in {region}!") }); self.movement.runtime.send_lure(target_id, message) } pub(crate) fn native_teleport_lure_respond( &self, requester_id: UUID, session_id: UUID, accept: bool, ) -> Result<(), Error> { if accept { self.movement.runtime.accept_lure(session_id) } else { self.native_instant_message_full( self.native_name(), requester_id, String::new(), session_id, InstantMessageDialog::DenyTeleport, InstantMessageOnline::Offline, self.movement.runtime.sim_position(), UUID::zero(), Vec::new(), ) } } pub(crate) fn native_send_teleport_lure_request( &self, target_id: UUID, session_id: UUID, message: String, ) -> Result<(), Error> { if target_id == self.native_agent_id() { return Ok(()); } self.native_instant_message_full( self.native_name(), target_id, message, session_id, InstantMessageDialog::RequestLure, InstantMessageOnline::Offline, self.movement.runtime.sim_position(), UUID::zero(), Vec::new(), ) } } #[cfg(test)] mod tests { use super::*; use crate::packets::{ Packet, PacketAckPacket, PacketAckPacketPacketsBlock, RegionHandshakePacket, }; use libremetaverse_types::compat::CancellationTokenSource; use std::sync::mpsc::{self, Receiver, Sender}; fn approx(actual: Vector3, expected: Vector3) { assert!((actual.x - expected.x).abs() < 0.000_1, "x: {actual:?}"); assert!((actual.y - expected.y).abs() < 0.000_1, "y: {actual:?}"); assert!((actual.z - expected.z).abs() < 0.000_1, "z: {actual:?}"); } fn packet_type(bytes: &[u8]) -> Option { let mut packet_end = i32::try_from(bytes.len()).ok()?.checked_sub(1)?; Packet::build_packet_with_bytes_int32_bytes(bytes.to_vec(), &mut packet_end, vec![0; 8192]) .ok() .map(|packet| packet.type_) } fn ack(sequence: u32) -> Vec { let mut packet = PacketAckPacket::new_with_constructor().unwrap(); packet.packets = vec![PacketAckPacketPacketsBlock { id: sequence }]; let mut bytes = packet.to_bytes_with_method().unwrap(); bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED); bytes } fn transport_wire(data: Vec) -> Vec { if data .first() .is_none_or(|flags| flags & crate::Helpers::MSG_ZEROCODED == 0) { return data; } let mut encoded = vec![0_u8; data.len().saturating_mul(2).saturating_add(2)]; let length = crate::packet_wire::zero_encode( Some(&data), i32::try_from(data.len()).unwrap(), Some(&mut encoded), ) .unwrap(); encoded.truncate(usize::try_from(length).unwrap()); encoded } struct FakeGrid { endpoint: SocketAddr, packets: Receiver<(PacketType, Vec)>, shutdown: Sender<()>, thread: Option>, } impl FakeGrid { fn start() -> Self { use std::io::ErrorKind; use std::net::UdpSocket; use std::sync::mpsc::TryRecvError; let socket = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(); socket .set_read_timeout(Some(Duration::from_millis(100))) .unwrap(); let endpoint = socket.local_addr().unwrap(); let (packet_sender, packets) = mpsc::channel(); let (shutdown, shutdown_receiver) = mpsc::channel(); let thread = thread::spawn(move || { let mut buffer = [0_u8; 8192]; let (length, client_endpoint) = socket.recv_from(&mut buffer).unwrap(); assert_eq!( packet_type(&buffer[..length]), Some(PacketType::UseCircuitCode) ); let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap()); socket.send_to(&ack(sequence), client_endpoint).unwrap(); let mut handshake = RegionHandshakePacket::new_with_constructor().unwrap(); handshake.region_info.sim_name = b"Movement Test Region\0".to_vec(); let mut bytes = handshake.to_bytes_with_method().unwrap(); bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED); socket.send_to(&bytes, client_endpoint).unwrap(); loop { match shutdown_receiver.try_recv() { Ok(()) | Err(TryRecvError::Disconnected) => break, Err(TryRecvError::Empty) => {} } let (length, _) = match socket.recv_from(&mut buffer) { Ok(value) => value, Err(error) if matches!( error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut ) => { continue; } Err(_) => break, }; if buffer[0] & crate::Helpers::MSG_RELIABLE != 0 { let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap()); socket.send_to(&ack(sequence), client_endpoint).unwrap(); } let Some(kind) = packet_type(&buffer[..length]) else { continue; }; if !matches!( kind, PacketType::PacketAck | PacketType::RegionHandshakeReply ) { packet_sender .send((kind, buffer[..length].to_vec())) .unwrap(); } } }); Self { endpoint, packets, shutdown, thread: Some(thread), } } fn receive(&self, expected: PacketType) -> Vec { loop { let (kind, bytes) = self .packets .recv_timeout(Duration::from_secs(3)) .expect("fake grid packet"); if kind == expected { return bytes; } } } } impl Drop for FakeGrid { fn drop(&mut self) { let _ = self.shutdown.send(()); if let Some(handle) = self.thread.take() { handle.join().unwrap(); } } } #[test] fn camera_math_preserves_the_reference_frame_axis_mapping() { let camera = AgentManagerAgentMovementAgentCamera::new().unwrap(); approx( camera.position(), Vector3 { x: 128.0, y: 128.0, z: 20.0, }, ); camera .look_direction_with_vector3(Vector3::unit_x()) .unwrap(); approx(camera.left_axis(), Vector3::unit_x()); approx(camera.at_axis(), Vector3::unit_y()); approx(camera.up_axis(), Vector3::unit_z()); camera .look_at_with_vector3_vector3( Vector3::zero(), Vector3 { x: 0.0, y: 10.0, z: 0.0, }, ) .unwrap(); approx(camera.left_axis(), Vector3::unit_y()); approx( camera.at_axis(), Vector3 { x: -1.0, y: 0.0, z: 0.0, }, ); assert!(camera.look_direction_with_double(f64::NAN).is_err()); } #[test] fn control_bits_and_reset_rules_match_agent_movement() { let client = Arc::new(GridClient::new().unwrap()); let manager = AgentManager::native_new(Some(client)).unwrap(); let mut movement = manager.movement; movement.set_agent_controls(u32::MAX); movement.reset_control_flags().unwrap(); assert_eq!( movement.agent_controls(), (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5) ); movement.set_at_pos(true); movement.set_turn_right(true); assert!(movement.at_pos()); assert!(movement.turn_right()); movement.set_update_interval(-5); assert_eq!(movement.update_interval(), 0); assert!(!movement.update_enabled()); } #[test] fn fake_grid_decodes_locomotion_camera_and_autopilot_packets() { let grid = FakeGrid::start(); let mut client = GridClient::new().unwrap(); client.settings().timing().login_timeout = 2_000; let client = Arc::new(client); let mut manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); manager.native_set_agent_id(UUID::random().unwrap()); manager.native_set_session_id(UUID::random().unwrap()); let mut network = client.network(); network.set_circuit_code(0x1234_5678); let simulator = network .native_connect(grid.endpoint, 0x1000, true, None, 256, 256) .unwrap() .unwrap(); let _ = grid.receive(PacketType::CompleteAgentMovement); let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap(); complete.data.position = Vector3 { x: 10.0, y: 20.0, z: 30.0, }; complete.data.look_at = Vector3::unit_x(); manager .native_movement_runtime() .handle_raw_packet( PacketType::AgentMovementComplete, &transport_wire(complete.to_bytes_with_method().unwrap()), simulator.clone(), ) .unwrap(); assert_eq!(manager.sim_position(), complete.data.position); manager.native_fly(true).unwrap(); let update = decode_packet::(&grid.receive(PacketType::AgentUpdate)).unwrap(); assert_eq!(update.agent_data.control_flags, 1 << 13); assert_eq!(update.agent_data.agent_id, manager.native_agent_id()); assert_eq!(update.agent_data.session_id, manager.native_session_id()); manager.movement.set_always_run(true); let run = decode_packet::(&grid.receive(PacketType::SetAlwaysRun)).unwrap(); assert!(run.agent_data.always_run); manager.movement.set_fov_vertical_angle(1.25).unwrap(); let fov = decode_packet::(&grid.receive(PacketType::AgentFOV)).unwrap(); assert_eq!(fov.fov_block.vertical_angle, 1.25); manager.native_autopilot_u64(1000, 2000, 30.5).unwrap(); let autopilot = decode_packet::(&grid.receive(PacketType::GenericMessage)) .unwrap(); assert_eq!(wire_string(autopilot.method_data.method), "autopilot"); assert_eq!(autopilot.param_list.len(), 3); assert_eq!( wire_string(autopilot.param_list[0].parameter.clone()), "1000" ); assert_eq!( wire_string(autopilot.param_list[1].parameter.clone()), "2000" ); assert_eq!( wire_string(autopilot.param_list[2].parameter.clone()), "30.5" ); manager.movement.set_auto_reset_controls(true); manager.movement.set_at_pos(true); manager .movement .send_update_with_boolean(Some(false)) .unwrap(); let _ = grid.receive(PacketType::AgentUpdate); assert_eq!(manager.movement.agent_controls(), 1 << 13); manager.dispose().unwrap(); simulator.native_disconnect(false).unwrap(); } #[tokio::test(flavor = "current_thread", start_paused = true)] async fn teleport_completion_timeout_and_cancellation_are_deterministic() { let mut client = GridClient::new().unwrap(); client.settings().timing().teleport_timeout = 5_000; let client = Arc::new(client); let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); let runtime = Arc::clone(manager.native_movement_runtime()); let simulator = Simulator::native_new( (*client).clone(), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13001), 0x1000, Some(256), Some(256), ) .unwrap(); runtime .network .native_set_current_sim(Some(simulator.clone())); let queue_source = CancellationTokenSource::new(); let queue_token = queue_source.token(); let queue_runtime = Arc::clone(&runtime); let queue_wait = tokio::spawn( async move { queue_runtime.wait_for_event_queue(Some(queue_token)).await }, ); tokio::task::yield_now().await; queue_source.cancel(); assert!(matches!(queue_wait.await.unwrap(), Err(Error::Cancelled))); let events = Arc::new(Mutex::new(Vec::new())); let recorded = Arc::clone(&events); let _subscription = runtime.subscribe_teleport_progress(Arc::new(move |event| { mutex(&recorded).push((event.status(), event.message())); })); let (generation, receiver) = runtime.begin_teleport(); let mut local = TeleportLocalPacket::new_with_constructor().unwrap(); local.info.position = Vector3 { x: 40.0, y: 50.0, z: 60.0, }; local.info.look_at = Vector3::unit_y(); runtime .handle_raw_packet( PacketType::TeleportLocal, &transport_wire(local.to_bytes_with_method().unwrap()), simulator.clone(), ) .unwrap(); assert!( runtime .wait_for_teleport(generation, receiver, None) .await .unwrap() ); assert_eq!(runtime.sim_position(), local.info.position); assert_eq!( mutex(&events).as_slice(), &[(TeleportStatus::Finished, "Teleport finished".into())] ); let (generation, receiver) = runtime.begin_teleport(); let timeout_runtime = Arc::clone(&runtime); let timeout = tokio::spawn(async move { timeout_runtime .wait_for_teleport(generation, receiver, None) .await }); tokio::task::yield_now().await; tokio::time::advance(Duration::from_secs(5)).await; assert!(!timeout.await.unwrap().unwrap()); assert_eq!(mutex(&runtime.teleport).status, TeleportStatus::Failed); let (generation, receiver) = runtime.begin_teleport(); let source = CancellationTokenSource::new(); let token = source.token(); let cancelled_runtime = Arc::clone(&runtime); let cancelled = tokio::spawn(async move { cancelled_runtime .wait_for_teleport(generation, receiver, Some(token)) .await }); tokio::task::yield_now().await; source.cancel(); assert!(matches!(cancelled.await.unwrap(), Err(Error::Cancelled))); assert!(mutex(&runtime.teleport).waiter.is_none()); manager.dispose().unwrap(); } #[test] fn crossing_diagnostics_and_manual_cancellation_are_consistent() { let client = Arc::new(GridClient::new().unwrap()); let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); let runtime = manager.native_movement_runtime(); let simulator = Simulator::native_new( (*client).clone(), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13002), 0x1000, Some(256), Some(256), ) .unwrap(); *mutex(&runtime.crossing.current) = Some(CrossingInfo { state: AgentManagerCrossingState::WaitingForComplete, started: Instant::now(), old_simulator: Some(simulator), new_simulator: None, region_handle: 0x2000, endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13003), seed: Uri("https://caps.test/seed".into()), position: Vector3::zero(), look_at: Vector3::unit_x(), size_x: 256, size_y: 256, retry_count: 2, failure_reason: AgentManagerCrossingFailureReason::ConnectionFailed, failure_message: "connection failed".into(), generation: 1, cancel_requested: false, restored_old_simulator: false, }); assert!(manager.is_crossing().unwrap()); assert_eq!( manager.get_crossing_state().unwrap(), AgentManagerCrossingState::WaitingForComplete ); assert_eq!( manager.get_crossing_failure_reason().unwrap(), AgentManagerCrossingFailureReason::ConnectionFailed ); let details = manager.get_crossing_details().unwrap(); assert!(details.contains("State: WaitingForComplete")); assert!(details.contains("Retries: 2/3")); manager.cancel_crossing().unwrap(); assert!( mutex(&runtime.crossing.current) .as_ref() .unwrap() .cancel_requested ); assert!( manager .get_crossing_details() .unwrap() .contains("Crossing was manually cancelled") ); manager.dispose().unwrap(); } #[test] fn fake_grids_complete_a_region_crossing_and_raise_the_final_event() { let old_grid = FakeGrid::start(); let new_grid = FakeGrid::start(); let mut client = GridClient::new().unwrap(); client.settings().timing().login_timeout = 2_000; let client = Arc::new(client); let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); let mut network = client.network(); network.set_circuit_code(0x1234_5678); let old_simulator = network .native_connect(old_grid.endpoint, 0x1000, true, None, 256, 256) .unwrap() .unwrap(); let _ = old_grid.receive(PacketType::CompleteAgentMovement); old_simulator.native_set_agent_movement_complete(true); let (event_sender, event_receiver) = mpsc::channel(); let _subscription = manager.native_subscribe_region_crossed(Arc::new(move |event| { event_sender.send(event).unwrap(); })); let runtime = manager.native_movement_runtime(); assert!(runtime.begin_crossing( old_simulator.clone(), new_grid.endpoint, 0x2000, Uri("http://127.0.0.1:1/seed".into()), Vector3 { x: 12.0, y: 24.0, z: 36.0, }, Vector3::unit_y(), 256, 256, )); let _ = new_grid.receive(PacketType::CompleteAgentMovement); let current = mutex(&runtime.crossing.current); let (current, timeout) = runtime .crossing .wake .wait_timeout_while(current, Duration::from_secs(3), |current| { current.as_ref().is_none_or(|crossing| { crossing.state != AgentManagerCrossingState::WaitingForComplete }) }) .unwrap_or_else(std::sync::PoisonError::into_inner); assert!(!timeout.timed_out(), "crossing did not reach movement wait"); let new_simulator = current .as_ref() .and_then(|crossing| crossing.new_simulator.clone()) .expect("destination simulator"); drop(current); let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap(); complete.data.position = Vector3 { x: 12.0, y: 24.0, z: 36.0, }; complete.data.look_at = Vector3::unit_y(); runtime .handle_raw_packet( PacketType::AgentMovementComplete, &transport_wire(complete.to_bytes_with_method().unwrap()), new_simulator.clone(), ) .unwrap(); let event = event_receiver .recv_timeout(Duration::from_secs(3)) .expect("RegionCrossed event"); assert_eq!(event.old_simulator(), Some(old_simulator.clone())); assert_eq!(event.new_simulator(), Some(new_simulator.clone())); assert_eq!( manager.get_crossing_state().unwrap(), AgentManagerCrossingState::Idle ); assert_eq!(network.native_current_sim(), Some(new_simulator.clone())); assert!(!old_simulator.native_agent_movement_complete()); manager.dispose().unwrap(); old_simulator.native_disconnect(false).unwrap(); new_simulator.native_disconnect(false).unwrap(); } #[test] fn multi_simulator_prediction_and_object_visibility_match_reference_rules() { let mut client = GridClient::new().unwrap(); client.settings().agent_settings_mut().multiple_sims = true; let client = Arc::new(client); let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); let simulator = Simulator::native_new( (*client).clone(), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13010), (u64::from(1024_u32) << 32) | u64::from(2048_u32), Some(256), Some(256), ) .unwrap(); let other = Simulator::native_new( (*client).clone(), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13011), (u64::from(1280_u32) << 32) | u64::from(2048_u32), Some(256), Some(256), ) .unwrap(); simulator.native_set_agent_movement_complete(true); let runtime = manager.native_movement_runtime(); { let mut state = write(&runtime.kinematics); state.relative_position = Vector3 { x: 255.0, y: 128.0, z: 20.0, }; state.velocity = Vector3::unit_x(); } let (prediction_sender, prediction_receiver) = mpsc::channel(); let _prediction = manager.native_subscribe_region_crossing_predicted(Arc::new(move |event| { prediction_sender.send(event).unwrap() })); runtime.update_multi_simulator_state(&simulator); let prediction = prediction_receiver.try_recv().unwrap(); assert_eq!(prediction.current_simulator(), simulator); assert_eq!(prediction.direction(), BorderCrossingDirection::East); assert_eq!(prediction.time_until_crossing(), 1.0); let state = read(&runtime.simulator_states)[&simulator.native_ip_end_point()]; assert_eq!(state.position.x, 255.0); assert!(state.is_present); let object_id = UUID::random().unwrap(); manager .track_object_in_simulator(object_id, simulator.clone()) .unwrap(); manager .track_object_in_simulator(object_id, other.clone()) .unwrap(); manager .track_object_in_simulator(object_id, simulator.clone()) .unwrap(); assert_eq!( manager.get_simulators_for_object(object_id).unwrap().len(), 2 ); manager .untrack_object_in_simulator(object_id, simulator) .unwrap(); assert_eq!( manager.get_simulators_for_object(object_id).unwrap(), [other] ); assert!( manager .is_near_border( Vector3 { x: 31.0, y: 128.0, z: 0.0, }, 256, 256, None, ) .unwrap() ); assert!( !manager .is_near_border( Vector3 { x: 128.0, y: 128.0, z: 0.0, }, 256, 256, None, ) .unwrap() ); manager.dispose().unwrap(); } }