Files
MetaCrate/crates/libremetaverse/src/agent_movement.rs
Chili Palmer 0791e0a69e
All checks were successful
Native code generation / deterministic (push) Successful in 12m6s
Imaging and meshing gate / native (push) Successful in 4m6s
Native Rust workspace compile / compile (push) Successful in 4m10s
Implement native agent movement and teleportation (#60)
2026-08-09 23:07:30 +00:00

3480 lines
119 KiB
Rust

//! 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<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 wire_string(mut bytes: Vec<u8>) -> String {
while bytes.last() == Some(&0) {
bytes.pop();
}
String::from_utf8_lossy(&bytes).into_owned()
}
fn decode_packet<T: GeneratedPacket>(data: &[u8]) -> Result<T, Error> {
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<u8>,
reliable: Option<bool>,
) -> 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<Self, Error> {
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<Self, Error> {
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<Simulator>,
new_simulator: Option<Simulator>,
}
#[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<Self, Error> {
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<Simulator>,
new_simulator: Option<Simulator>,
) -> Result<Self, Error> {
Ok(Self {
old_simulator,
new_simulator,
})
}
#[must_use]
pub fn new_simulator(&self) -> Option<Simulator> {
self.new_simulator.clone()
}
#[must_use]
pub fn old_simulator(&self) -> Option<Simulator> {
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<Self, Error> {
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<Mutex<CameraFrame>>,
}
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<Self, Error> {
Ok(Self {
far: 128.0,
frame: Arc::new(Mutex::new(CameraFrame::default())),
})
}
fn from_frame(frame: Arc<Mutex<CameraFrame>>, 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<bool>)>,
}
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<TimerState>,
wake: Condvar,
}
struct CrossingInfo {
state: AgentManagerCrossingState,
started: Instant,
old_simulator: Option<Simulator>,
new_simulator: Option<Simulator>,
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<Option<CrossingInfo>>,
wake: Condvar,
}
pub(crate) struct AgentMovementRuntime {
client: Arc<GridClient>,
network: NetworkManager,
owner: Weak<AgentManagerInner>,
controls: AtomicU32,
always_run: AtomicBool,
auto_reset_controls: AtomicBool,
update_interval: AtomicI32,
camera_frame: Arc<Mutex<CameraFrame>>,
last_update: Mutex<LastUpdate>,
kinematics: RwLock<Kinematics>,
teleport: Mutex<TeleportState>,
teleport_progress: EventRegistry<TeleportEventArgs>,
camera_constraint: EventRegistry<CameraConstraintEventArgs>,
avatar_sit_response: EventRegistry<AvatarSitResponseEventArgs>,
region_crossed: EventRegistry<RegionCrossedEventArgs>,
region_crossing_predicted: EventRegistry<RegionCrossingPredictionEventArgs>,
timer: Arc<TimerControl>,
timer_thread: Mutex<Option<JoinHandle<()>>>,
lifecycle_subscriptions: Mutex<Vec<Subscription>>,
crossing_generation: AtomicU64,
crossing: Arc<CrossingControl>,
crossing_thread: Mutex<Option<JoinHandle<()>>>,
simulator_states: RwLock<HashMap<SocketAddr, SimulatorAgentState>>,
child_agent_status: RwLock<HashMap<u64, ChildAgentStatus>>,
object_simulators: RwLock<HashMap<UUID, Vec<Simulator>>>,
}
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<GridClient>,
network: NetworkManager,
owner: Weak<AgentManagerInner>,
) -> Arc<Self> {
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<Self>) {
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<Self>) {
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<Simulator, Error> {
self.network
.native_current_sim()
.ok_or(Error::InvalidOperation)
}
fn send_current(
&self,
packet_type: PacketType,
data: Vec<u8>,
reliable: Option<bool>,
) -> 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<Simulator>,
) -> 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<Simulator>) -> 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<bool>) {
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<bool>,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
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<CancellationToken>,
) -> 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<Uri>,
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<Self>,
packet_type: PacketType,
data: &[u8],
simulator: Simulator,
) -> Result<bool, Error> {
match packet_type {
PacketType::AgentMovementComplete => {
let packet = decode_packet::<AgentMovementCompletePacket>(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::<CameraConstraintPacket>(data)?;
self.camera_constraint.emit(CameraConstraintEventArgs {
collide_plane: packet.camera_collide_plane.plane,
});
Ok(true)
}
PacketType::AvatarSitResponse => {
let packet = decode_packet::<crate::packets::AvatarSitResponsePacket>(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::<TeleportStartPacket>(data)?;
self.teleport_event(
"Teleport started".into(),
TeleportStatus::Start,
TeleportFlags(packet.info.teleport_flags),
);
Ok(true)
}
PacketType::TeleportProgress => {
let packet = decode_packet::<TeleportProgressPacket>(data)?;
self.teleport_event(
wire_string(packet.info.message),
TeleportStatus::Progress,
TeleportFlags(packet.info.teleport_flags),
);
Ok(true)
}
PacketType::TeleportFailed => {
let packet = decode_packet::<TeleportFailedPacket>(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::<TeleportCancelPacket>(data)?;
self.teleport_event(
"Cancelled".into(),
TeleportStatus::Cancelled,
TeleportFlags::DEFAULT,
);
Ok(true)
}
PacketType::TeleportLocal => {
let packet = decode_packet::<TeleportLocalPacket>(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::<TeleportFinishPacket>(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::<CrossedRegionPacket>(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<Self>,
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<Self>, crossing: Arc<CrossingControl>, 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<Self>, 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<Simulator> {
read(&self.object_simulators)
.get(&object_id)
.cloned()
.unwrap_or_default()
}
pub(crate) fn handle_caps_event(
self: &Arc<Self>,
name: &str,
message: &dyn IMessage,
simulator: Simulator,
) -> Result<bool, Error> {
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<AgentMovementRuntime>,
}
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<AgentMovementRuntime>) -> 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<Self, Error> {
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<bool>) -> 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<bool>) -> Result<bool, Error> {
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<TeleportEventArgs>,
) -> Subscription {
self.teleport_progress.subscribe(handler)
}
pub(crate) fn subscribe_camera_constraint(
&self,
handler: EventHandler<CameraConstraintEventArgs>,
) -> Subscription {
self.camera_constraint.subscribe(handler)
}
pub(crate) fn subscribe_avatar_sit_response(
&self,
handler: EventHandler<AvatarSitResponseEventArgs>,
) -> Subscription {
self.avatar_sit_response.subscribe(handler)
}
pub(crate) fn subscribe_region_crossed(
&self,
handler: EventHandler<RegionCrossedEventArgs>,
) -> Subscription {
self.region_crossed.subscribe(handler)
}
pub(crate) fn subscribe_region_crossing_predicted(
&self,
handler: EventHandler<RegionCrossingPredictionEventArgs>,
) -> 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<AgentMovementRuntime> {
&self.movement.runtime
}
pub(crate) fn native_subscribe_teleport_progress(
&self,
handler: EventHandler<TeleportEventArgs>,
) -> Subscription {
self.movement.runtime.subscribe_teleport_progress(handler)
}
pub(crate) fn native_subscribe_camera_constraint(
&self,
handler: EventHandler<CameraConstraintEventArgs>,
) -> Subscription {
self.movement.runtime.subscribe_camera_constraint(handler)
}
pub(crate) fn native_subscribe_avatar_sit_response(
&self,
handler: EventHandler<AvatarSitResponseEventArgs>,
) -> Subscription {
self.movement.runtime.subscribe_avatar_sit_response(handler)
}
pub(crate) fn native_subscribe_region_crossed(
&self,
handler: EventHandler<RegionCrossedEventArgs>,
) -> Subscription {
self.movement.runtime.subscribe_region_crossed(handler)
}
pub(crate) fn native_subscribe_region_crossing_predicted(
&self,
handler: EventHandler<RegionCrossingPredictionEventArgs>,
) -> 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<Simulator> {
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<bool, Error> {
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<bool, Error> {
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<bool>,
) -> 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<CancellationToken>,
) -> Result<bool, Error> {
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<CancellationToken>,
) -> Result<bool, Error> {
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<CancellationToken>,
) -> Result<bool, Error> {
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<String>,
) -> 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<PacketType> {
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<u8> {
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<u8>) -> Vec<u8> {
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<u8>)>,
shutdown: Sender<()>,
thread: Option<JoinHandle<()>>,
}
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<u8> {
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::<AgentUpdatePacket>(&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::<SetAlwaysRunPacket>(&grid.receive(PacketType::SetAlwaysRun)).unwrap();
assert!(run.agent_data.always_run);
manager.movement.set_fov_vertical_angle(1.25).unwrap();
let fov = decode_packet::<AgentFOVPacket>(&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::<GenericMessagePacket>(&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();
}
}