3480 lines
119 KiB
Rust
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();
|
|
}
|
|
}
|