//! Object update state, event correlation, and simulator action routing.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::new_ret_no_self)]
#![allow(clippy::fn_params_excessive_bools)]
#![allow(clippy::unused_self)]

use crate::agent_manager::EventRegistry;
use crate::network_manager::RawPacketReceivedEventArgs;
use crate::packet_catalog::GeneratedPacket;
use crate::{
    Animation, Avatar, Error, GridClient, NameValue, ObjectManager, Primitive,
    PrimitiveConstructionData, PrimitiveObjectProperties, PrimitiveParticleSystem,
    PrimitivePhysicsProperties, PrimitiveTextureAnimation, PrimitiveTextureEntry, Simulator,
};
use libremetaverse_types::compat::{EventHandler, Subscription};
use libremetaverse_types::{
    ClickAction, Color4, JointType, Material, ObjectCategory, PCode, PathCurve, PrimFlags,
    PrimType, ProfileCurve, Quaternion, SaleType, SoundFlags, UUID, Vector3, Vector4,
};
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, UNIX_EPOCH};

const MAX_TRACKED_OBJECTS: usize = 262_144;
const MAX_PACKET_BLOCKS: usize = 65_535;
const MAX_FIELD_BYTES: usize = 1024 * 1024;

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 decode_packet<T: GeneratedPacket>(bytes: &[u8]) -> Result<T, Error> {
    if bytes.len() > MAX_FIELD_BYTES * 16 {
        return Err(Error::Argument);
    }
    let mut packet = T::new_generated();
    let mut position = 0_i32;
    let mut packet_end = i32::try_from(bytes.len()).map_err(|_| Error::Argument)? - 1;
    let mut zero_buffer =
        vec![0_u8; crate::udp_transport::UdpTransportConfig::default().max_decoded_packet_size];
    packet.decode_from_bytes(
        bytes,
        &mut position,
        &mut packet_end,
        Some(zero_buffer.as_mut_slice()),
    )?;
    Ok(packet)
}

fn wire_string(bytes: &[u8]) -> Result<String, Error> {
    if bytes.len() > MAX_FIELD_BYTES {
        return Err(Error::Argument);
    }
    let end = bytes
        .iter()
        .position(|byte| *byte == 0)
        .unwrap_or(bytes.len());
    Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
}

fn name_values(bytes: &[u8]) -> Result<(Vec<NameValue>, bool), Error> {
    let text = wire_string(bytes)?;
    let mut values = Vec::new();
    let mut attachment = false;
    for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) {
        let mut fields = line.splitn(5, char::is_whitespace);
        let name = fields.next().unwrap_or_default().to_owned();
        let _wire_type = fields.next();
        let _wire_class = fields.next();
        let _wire_send_to = fields.next();
        let value = fields.next().unwrap_or_default().trim_start().to_owned();
        attachment |= name == "AttachItemID";
        values.push(NameValue {
            class_: crate::NameValueClassType::ReadOnly,
            name,
            sendto: crate::NameValueSendtoType::Sim,
            type_: crate::NameValueValueType::String,
            value: Some(value.into()),
        });
    }
    Ok((values, attachment))
}

fn f32_at(bytes: &[u8], offset: usize) -> Result<f32, Error> {
    let raw: [u8; 4] = bytes
        .get(offset..offset + 4)
        .ok_or(Error::IndexOutOfRange)?
        .try_into()
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(f32::from_le_bytes(raw))
}

fn u16_at(bytes: &[u8], offset: usize) -> Result<u16, Error> {
    let raw: [u8; 2] = bytes
        .get(offset..offset + 2)
        .ok_or(Error::IndexOutOfRange)?
        .try_into()
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(u16::from_le_bytes(raw))
}

fn u32_at(bytes: &[u8], offset: usize) -> Result<u32, Error> {
    let raw: [u8; 4] = bytes
        .get(offset..offset + 4)
        .ok_or(Error::IndexOutOfRange)?
        .try_into()
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(u32::from_le_bytes(raw))
}

fn vector3_at(bytes: &[u8], offset: usize) -> Result<Vector3, Error> {
    Ok(Vector3 {
        x: f32_at(bytes, offset)?,
        y: f32_at(bytes, offset + 4)?,
        z: f32_at(bytes, offset + 8)?,
    })
}

fn vector4_at(bytes: &[u8], offset: usize) -> Result<Vector4, Error> {
    Ok(Vector4 {
        x: f32_at(bytes, offset)?,
        y: f32_at(bytes, offset + 4)?,
        z: f32_at(bytes, offset + 8)?,
        w: f32_at(bytes, offset + 12)?,
    })
}

fn quaternion3_at(bytes: &[u8], offset: usize) -> Result<Quaternion, Error> {
    Quaternion::new_with_single_single_single(
        f32_at(bytes, offset)?,
        f32_at(bytes, offset + 4)?,
        f32_at(bytes, offset + 8)?,
    )
}

fn quantized_u16(bytes: &[u8], offset: usize, low: f32, high: f32) -> Result<f32, Error> {
    Ok(low + f32::from(u16_at(bytes, offset)?) * (high - low) / 65_535.0)
}

fn quantized_u8(value: u8, low: f32, high: f32) -> f32 {
    low + f32::from(value) * (high - low) / 255.0
}

fn decode_movement(bytes: &[u8]) -> Result<ObjectMovementUpdate, Error> {
    let mut value = ObjectMovementUpdate::default();
    let mut p = 0;
    if matches!(bytes.len(), 76 | 48) {
        value.collision_plane = vector4_at(bytes, 0)?;
        p = 16;
    }
    match bytes.len() {
        60 | 76 => {
            value.position = vector3_at(bytes, p)?;
            value.velocity = vector3_at(bytes, p + 12)?;
            value.acceleration = vector3_at(bytes, p + 24)?;
            value.rotation = quaternion3_at(bytes, p + 36)?;
            value.angular_velocity = vector3_at(bytes, p + 48)?;
        }
        32 | 48 => {
            value.position = Vector3 {
                x: quantized_u16(bytes, p, -128.0, 384.0)?,
                y: quantized_u16(bytes, p + 2, -128.0, 384.0)?,
                z: quantized_u16(bytes, p + 4, -256.0, 768.0)?,
            };
            p += 6;
            value.velocity = Vector3 {
                x: quantized_u16(bytes, p, -256.0, 256.0)?,
                y: quantized_u16(bytes, p + 2, -256.0, 256.0)?,
                z: quantized_u16(bytes, p + 4, -256.0, 256.0)?,
            };
            p += 6;
            value.acceleration = Vector3 {
                x: quantized_u16(bytes, p, -256.0, 256.0)?,
                y: quantized_u16(bytes, p + 2, -256.0, 256.0)?,
                z: quantized_u16(bytes, p + 4, -256.0, 256.0)?,
            };
            p += 6;
            value.rotation = Quaternion::new_with_single_single_single_single(
                quantized_u16(bytes, p, -1.0, 1.0)?,
                quantized_u16(bytes, p + 2, -1.0, 1.0)?,
                quantized_u16(bytes, p + 4, -1.0, 1.0)?,
                quantized_u16(bytes, p + 6, -1.0, 1.0)?,
            )?;
            p += 8;
            value.angular_velocity = Vector3 {
                x: quantized_u16(bytes, p, -256.0, 256.0)?,
                y: quantized_u16(bytes, p + 2, -256.0, 256.0)?,
                z: quantized_u16(bytes, p + 4, -256.0, 256.0)?,
            };
        }
        16 => {
            let q3 = |at: usize, low, high| -> Result<Vector3, Error> {
                let slice = bytes.get(at..at + 3).ok_or(Error::IndexOutOfRange)?;
                Ok(Vector3 {
                    x: quantized_u8(slice[0], low, high),
                    y: quantized_u8(slice[1], low, high),
                    z: quantized_u8(slice[2], low, high),
                })
            };
            value.position = q3(0, -256.0, 256.0)?;
            value.velocity = q3(3, -256.0, 256.0)?;
            value.acceleration = q3(6, -256.0, 256.0)?;
            value.rotation = Quaternion::new_with_single_single_single_single(
                quantized_u8(bytes[9], -1.0, 1.0),
                quantized_u8(bytes[10], -1.0, 1.0),
                quantized_u8(bytes[11], -1.0, 1.0),
                quantized_u8(bytes[12], -1.0, 1.0),
            )?;
            value.angular_velocity = q3(13, -256.0, 256.0)?;
        }
        _ => return Err(Error::Argument),
    }
    Ok(value)
}

fn click_action(value: u8) -> ClickAction {
    match value {
        1 => ClickAction::Sit,
        2 => ClickAction::Buy,
        3 => ClickAction::Pay,
        4 => ClickAction::OpenTask,
        5 => ClickAction::PlayMedia,
        6 => ClickAction::OpenMedia,
        _ => ClickAction::Touch,
    }
}

fn joint_type(value: u8) -> JointType {
    match value {
        1 => JointType::Hinge,
        2 => JointType::Point,
        _ => JointType::Invalid,
    }
}

fn object_category(value: u32) -> ObjectCategory {
    match value {
        1 => ObjectCategory::Owner,
        2 => ObjectCategory::Group,
        3 => ObjectCategory::Other,
        4 => ObjectCategory::Selected,
        5 => ObjectCategory::Temporary,
        _ => ObjectCategory::None,
    }
}

fn sale_type(value: u8) -> SaleType {
    match value {
        1 => SaleType::Original,
        2 => SaleType::Copy,
        3 => SaleType::Contents,
        _ => SaleType::Not,
    }
}

fn report_type(value: u32) -> crate::ReportType {
    match value {
        1 => crate::ReportType::Unknown,
        2 => crate::ReportType::Bug,
        3 => crate::ReportType::Complaint,
        4 => crate::ReportType::CustomerServiceRequest,
        _ => crate::ReportType::None,
    }
}

struct Cursor<'a> {
    bytes: &'a [u8],
    position: usize,
}

impl<'a> Cursor<'a> {
    fn new(bytes: &'a [u8]) -> Result<Self, Error> {
        if bytes.len() > MAX_FIELD_BYTES {
            return Err(Error::Argument);
        }
        Ok(Self { bytes, position: 0 })
    }
    fn remaining(&self) -> &'a [u8] {
        &self.bytes[self.position..]
    }
    fn take(&mut self, length: usize) -> Result<&'a [u8], Error> {
        let end = self.position.checked_add(length).ok_or(Error::Argument)?;
        let value = self
            .bytes
            .get(self.position..end)
            .ok_or(Error::IndexOutOfRange)?;
        self.position = end;
        Ok(value)
    }
    fn skip(&mut self, length: usize) -> Result<(), Error> {
        self.take(length).map(|_| ())
    }
    fn u8(&mut self) -> Result<u8, Error> {
        Ok(self.take(1)?[0])
    }
    fn i8(&mut self) -> Result<i8, Error> {
        Ok(self.u8()?.cast_signed())
    }
    fn u16(&mut self) -> Result<u16, Error> {
        u16_at(self.take(2)?, 0)
    }
    fn u32(&mut self) -> Result<u32, Error> {
        u32_at(self.take(4)?, 0)
    }
    fn f32(&mut self) -> Result<f32, Error> {
        f32_at(self.take(4)?, 0)
    }
    fn uuid(&mut self) -> Result<UUID, Error> {
        UUID::new_with_bytes_int32(self.take(16)?.to_vec(), 0)
    }
    fn vector3(&mut self) -> Result<Vector3, Error> {
        vector3_at(self.take(12)?, 0)
    }
    fn quaternion3(&mut self) -> Result<Quaternion, Error> {
        quaternion3_at(self.take(12)?, 0)
    }
    fn nul_string(&mut self) -> Result<String, Error> {
        let end = self
            .remaining()
            .iter()
            .position(|byte| *byte == 0)
            .ok_or(Error::Argument)?;
        let value = wire_string(self.take(end)?)?;
        self.skip(1)?;
        Ok(value)
    }
}

#[derive(Clone)]
pub struct PrimEventArgs {
    simulator: Simulator,
    prim: Primitive,
    time_dilation: u16,
    is_new: bool,
    is_attachment: bool,
}
impl PrimEventArgs {
    pub fn new(
        simulator: Simulator,
        prim: Primitive,
        time_dilation: u16,
        is_new: bool,
        is_attachment: bool,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            prim,
            time_dilation,
            is_new,
            is_attachment,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn prim(&self) -> Primitive {
        self.prim.clone()
    }
    pub const fn time_dilation(&self) -> u16 {
        self.time_dilation
    }
    pub const fn is_new(&self) -> bool {
        self.is_new
    }
    pub const fn is_attachment(&self) -> bool {
        self.is_attachment
    }
}

#[derive(Clone)]
pub struct AvatarUpdateEventArgs {
    simulator: Simulator,
    avatar: Avatar,
    time_dilation: u16,
    is_new: bool,
}
impl AvatarUpdateEventArgs {
    pub fn new(
        simulator: Simulator,
        avatar: Avatar,
        time_dilation: u16,
        is_new: bool,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            avatar,
            time_dilation,
            is_new,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn avatar(&self) -> Avatar {
        self.avatar.clone()
    }
    pub const fn time_dilation(&self) -> u16 {
        self.time_dilation
    }
    pub const fn is_new(&self) -> bool {
        self.is_new
    }
}

#[derive(Clone)]
pub struct KillObjectEventArgs {
    simulator: Simulator,
    object_local_id: u32,
}
impl KillObjectEventArgs {
    pub fn new(simulator: Simulator, object_local_id: u32) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            object_local_id,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub const fn object_local_id(&self) -> u32 {
        self.object_local_id
    }
}

#[derive(Clone)]
pub struct KillObjectsEventArgs {
    simulator: Simulator,
    object_local_i_ds: Vec<u32>,
}
impl KillObjectsEventArgs {
    pub fn new(simulator: Simulator, object_local_i_ds: Vec<u32>) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            object_local_i_ds,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn object_local_i_ds(&self) -> Vec<u32> {
        self.object_local_i_ds.clone()
    }
}

#[derive(Clone)]
pub struct ObjectAnimationEventArgs {
    simulator: Simulator,
    object_id: UUID,
    animations: Vec<Animation>,
}
impl ObjectAnimationEventArgs {
    pub fn new(
        simulator: Simulator,
        object_id: UUID,
        animations: Vec<Animation>,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            object_id,
            animations,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub const fn object_id(&self) -> UUID {
        self.object_id
    }
    pub fn animations(&self) -> Vec<Animation> {
        self.animations.clone()
    }
}

#[derive(Clone)]
pub struct ObjectPropertiesEventArgs {
    simulator: Simulator,
    properties: PrimitiveObjectProperties,
}
impl ObjectPropertiesEventArgs {
    pub fn new(simulator: Simulator, properties: PrimitiveObjectProperties) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            properties,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn properties(&self) -> PrimitiveObjectProperties {
        self.properties.clone()
    }
}

#[derive(Clone)]
pub struct ObjectPropertiesFamilyEventArgs {
    simulator: Simulator,
    properties: PrimitiveObjectProperties,
    type_: crate::ReportType,
}
impl ObjectPropertiesFamilyEventArgs {
    pub fn new(
        simulator: Simulator,
        properties: PrimitiveObjectProperties,
        type_: crate::ReportType,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            properties,
            type_,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn properties(&self) -> PrimitiveObjectProperties {
        self.properties.clone()
    }
    pub const fn type_(&self) -> crate::ReportType {
        self.type_
    }
}

#[derive(Clone)]
pub struct ObjectPropertiesUpdatedEventArgs {
    simulator: Simulator,
    prim: Primitive,
    properties: PrimitiveObjectProperties,
}
impl ObjectPropertiesUpdatedEventArgs {
    pub fn new(
        simulator: Simulator,
        prim: Primitive,
        properties: PrimitiveObjectProperties,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            prim,
            properties,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn prim(&self) -> Primitive {
        self.prim.clone()
    }
    pub fn properties(&self) -> PrimitiveObjectProperties {
        self.properties.clone()
    }
}

#[derive(Clone)]
pub struct ParticleUpdateEventArgs {
    simulator: Simulator,
    particle_system: PrimitiveParticleSystem,
    source: Primitive,
}
impl ParticleUpdateEventArgs {
    pub fn new(
        simulator: Simulator,
        particle_system: PrimitiveParticleSystem,
        source: Primitive,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            particle_system,
            source,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn particle_system(&self) -> PrimitiveParticleSystem {
        self.particle_system.clone()
    }
    pub fn source(&self) -> Primitive {
        self.source.clone()
    }
}

#[derive(Clone)]
pub struct PayPriceReplyEventArgs {
    simulator: Simulator,
    object_id: UUID,
    default_price: i32,
    button_prices: Vec<i32>,
}
impl PayPriceReplyEventArgs {
    pub fn new(
        simulator: Simulator,
        object_id: UUID,
        default_price: i32,
        button_prices: Vec<i32>,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            object_id,
            default_price,
            button_prices,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub const fn object_id(&self) -> UUID {
        self.object_id
    }
    pub const fn default_price(&self) -> i32 {
        self.default_price
    }
    pub fn button_prices(&self) -> Vec<i32> {
        self.button_prices.clone()
    }
}

#[derive(Clone)]
pub struct PhysicsPropertiesEventArgs {
    pub simulator: Simulator,
    pub physics_properties: PrimitivePhysicsProperties,
}
impl PhysicsPropertiesEventArgs {
    pub fn new(
        simulator: Simulator,
        physics_properties: PrimitivePhysicsProperties,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            physics_properties,
        })
    }
}

#[derive(Clone)]
pub struct AvatarSitChangedEventArgs {
    simulator: Simulator,
    avatar: Avatar,
    sitting_on: u32,
    old_seat: u32,
}
impl AvatarSitChangedEventArgs {
    pub fn new(
        simulator: Simulator,
        avatar: Avatar,
        sitting_on: u32,
        old_seat: u32,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            avatar,
            sitting_on,
            old_seat,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn avatar(&self) -> Avatar {
        self.avatar.clone()
    }
    pub const fn sitting_on(&self) -> u32 {
        self.sitting_on
    }
    pub const fn old_seat(&self) -> u32 {
        self.old_seat
    }
}

#[derive(Clone)]
pub struct ObjectMovementUpdate {
    pub acceleration: Vector3,
    pub angular_velocity: Vector3,
    pub avatar: bool,
    pub collision_plane: Vector4,
    pub local_id: u32,
    pub position: Vector3,
    pub rotation: Quaternion,
    pub state: u8,
    pub textures: PrimitiveTextureEntry,
    pub velocity: Vector3,
}
impl Default for ObjectMovementUpdate {
    fn default() -> Self {
        Self {
            acceleration: Vector3::zero(),
            angular_velocity: Vector3::zero(),
            avatar: false,
            collision_plane: Vector4::zero(),
            local_id: 0,
            position: Vector3::zero(),
            rotation: Quaternion::identity(),
            state: 0,
            textures: PrimitiveTextureEntry::new_with_uuid(UUID::zero())
                .expect("default texture entry"),
            velocity: Vector3::zero(),
        }
    }
}

#[derive(Clone)]
pub struct TerseObjectUpdateEventArgs {
    simulator: Simulator,
    prim: Primitive,
    update: ObjectMovementUpdate,
    time_dilation: u16,
}
impl TerseObjectUpdateEventArgs {
    pub fn new(
        simulator: Simulator,
        prim: Primitive,
        update: ObjectMovementUpdate,
        time_dilation: u16,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            prim,
            update,
            time_dilation,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn prim(&self) -> Primitive {
        self.prim.clone()
    }
    pub fn update(&self) -> ObjectMovementUpdate {
        self.update.clone()
    }
    pub const fn time_dilation(&self) -> u16 {
        self.time_dilation
    }
}

#[derive(Clone)]
pub struct ObjectDataBlockUpdateEventArgs {
    simulator: Simulator,
    prim: Primitive,
    construction_data: PrimitiveConstructionData,
    block: crate::packets::ObjectUpdatePacketObjectDataBlock,
    update: ObjectMovementUpdate,
    name_values: Vec<NameValue>,
}
impl ObjectDataBlockUpdateEventArgs {
    pub fn new(
        simulator: Simulator,
        prim: Primitive,
        construction_data: PrimitiveConstructionData,
        block: crate::packets::ObjectUpdatePacketObjectDataBlock,
        update: ObjectMovementUpdate,
        name_values: Vec<NameValue>,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            prim,
            construction_data,
            block,
            update,
            name_values,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn prim(&self) -> Primitive {
        self.prim.clone()
    }
    pub fn construction_data(&self) -> PrimitiveConstructionData {
        self.construction_data.clone()
    }
    pub fn block(&self) -> crate::packets::ObjectUpdatePacketObjectDataBlock {
        self.block.clone()
    }
    pub fn update(&self) -> ObjectMovementUpdate {
        self.update.clone()
    }
    pub fn name_values(&self) -> Vec<NameValue> {
        self.name_values.clone()
    }
}

#[derive(Default)]
pub(crate) struct ObjectEvents {
    avatar_sit_changed: EventRegistry<AvatarSitChangedEventArgs>,
    avatar_update: EventRegistry<AvatarUpdateEventArgs>,
    kill_object: EventRegistry<KillObjectEventArgs>,
    kill_objects: EventRegistry<KillObjectsEventArgs>,
    object_animation: EventRegistry<ObjectAnimationEventArgs>,
    object_data_block_update: EventRegistry<ObjectDataBlockUpdateEventArgs>,
    object_properties: EventRegistry<ObjectPropertiesEventArgs>,
    object_properties_family: EventRegistry<ObjectPropertiesFamilyEventArgs>,
    object_properties_updated: EventRegistry<ObjectPropertiesUpdatedEventArgs>,
    object_update: EventRegistry<PrimEventArgs>,
    particle_update: EventRegistry<ParticleUpdateEventArgs>,
    pay_price_reply: EventRegistry<PayPriceReplyEventArgs>,
    physics_properties: EventRegistry<PhysicsPropertiesEventArgs>,
    terse_object_update: EventRegistry<TerseObjectUpdateEventArgs>,
}
pub(crate) struct ObjectManagerInner {
    client: crate::client_core::ClientWeakHandle,
    events: ObjectEvents,
    tracked: RwLock<HashMap<UUID, Vec<Simulator>>>,
    avatar_parents: RwLock<HashMap<(u64, u32), u32>>,
    raw_subscription: Mutex<Option<Subscription>>,
    caps_subscription: Mutex<Option<Subscription>>,
    disposed: AtomicBool,
}

impl ObjectManagerInner {
    pub(crate) fn new(client: Option<Arc<GridClient>>) -> Result<ObjectManager, Error> {
        let client = client.ok_or(Error::ArgumentNull)?;
        if let Some(inner) = client.cached_object_manager_inner() {
            return Ok(ObjectManager { inner });
        }
        let inner = Arc::new(Self {
            client: client.native_weak_handle(),
            events: ObjectEvents::default(),
            tracked: RwLock::new(HashMap::new()),
            avatar_parents: RwLock::new(HashMap::new()),
            raw_subscription: Mutex::new(None),
            caps_subscription: Mutex::new(None),
            disposed: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        *mutex(&inner.raw_subscription) = Some(client.network().subscribe_raw_packet(Arc::new(
            move |event| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle_raw_packet(event);
                }
            },
        )));
        let weak = Arc::downgrade(&inner);
        *mutex(&inner.caps_subscription) = Some(client.network().subscribe_caps(
            "ObjectPhysicsProperties".into(),
            crate::CapsEventQueueCallback::from_handler(move |_name, message, simulator| {
                let Some(inner) = weak.upgrade() else {
                    return;
                };
                let Ok(map) = message.serialize() else {
                    return;
                };
                let Ok(mut decoded) =
                    crate::messages::linden::ObjectPhysicsPropertiesMessage::new()
                else {
                    return;
                };
                if decoded.deserialize(map).is_ok() {
                    inner.handle_physics_properties(decoded.object_physics_properties, simulator);
                }
            }),
        ));
        let inner = client.install_object_manager_inner(inner);
        Ok(ObjectManager { inner })
    }

    pub(crate) fn client(&self) -> Result<GridClient, Error> {
        self.client.upgrade().ok_or(Error::InvalidOperation)
    }

    fn handle_raw_packet(&self, event: RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire) {
            return;
        }
        let _ = self.try_handle_raw_packet(event);
    }

    fn try_handle_raw_packet(&self, event: RawPacketReceivedEventArgs) -> Result<(), Error> {
        match event.packet_type {
            crate::packets::PacketType::ObjectUpdate => {
                self.handle_object_update(&event.data, event.simulator)
            }
            crate::packets::PacketType::ImprovedTerseObjectUpdate => {
                self.handle_terse_update(&event.data, event.simulator)
            }
            crate::packets::PacketType::ObjectUpdateCompressed => {
                self.handle_compressed_update(&event.data, event.simulator)
            }
            crate::packets::PacketType::KillObject => {
                self.handle_kill_object(&event.data, event.simulator)
            }
            crate::packets::PacketType::ObjectProperties => {
                self.handle_object_properties(&event.data, event.simulator)
            }
            crate::packets::PacketType::ObjectPropertiesFamily => {
                self.handle_object_properties_family(&event.data, event.simulator)
            }
            crate::packets::PacketType::PayPriceReply => {
                self.handle_pay_price_reply(&event.data, event.simulator)
            }
            crate::packets::PacketType::ObjectAnimation => {
                self.handle_object_animation(&event.data, event.simulator)
            }
            _ => Ok(()),
        }
    }

    fn construction_data(
        block: &crate::packets::ObjectUpdatePacketObjectDataBlock,
    ) -> Result<PrimitiveConstructionData, Error> {
        Ok(PrimitiveConstructionData {
            state: block.state,
            material: crate::object_model::material_from_i32(i32::from(block.material)),
            path_curve: crate::object_model::path_curve_from_i32(i32::from(block.path_curve)),
            profile_curve_with_field: block.profile_curve,
            path_begin: Primitive::unpack_begin_cut(block.path_begin)?,
            path_end: Primitive::unpack_end_cut(block.path_end)?,
            path_scale_x: Primitive::unpack_path_scale(block.path_scale_x)?,
            path_scale_y: Primitive::unpack_path_scale(block.path_scale_y)?,
            path_shear_x: Primitive::unpack_path_shear(block.path_shear_x.cast_signed())?,
            path_shear_y: Primitive::unpack_path_shear(block.path_shear_y.cast_signed())?,
            path_twist: Primitive::unpack_path_twist(block.path_twist)?,
            path_twist_begin: Primitive::unpack_path_twist(block.path_twist_begin)?,
            path_radius_offset: Primitive::unpack_path_twist(block.path_radius_offset)?,
            path_taper_x: Primitive::unpack_path_taper(block.path_taper_x)?,
            path_taper_y: Primitive::unpack_path_taper(block.path_taper_y)?,
            path_revolutions: Primitive::unpack_path_revolutions(block.path_revolutions)?,
            path_skew: Primitive::unpack_path_twist(block.path_skew)?,
            profile_begin: Primitive::unpack_begin_cut(block.profile_begin)?,
            profile_end: Primitive::unpack_end_cut(block.profile_end)?,
            profile_hollow: Primitive::unpack_profile_hollow(block.profile_hollow)?,
            p_code: crate::object_model::pcode_from_i32(i32::from(block.p_code)),
        })
    }

    fn handle_object_update(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::ObjectUpdatePacket = decode_packet(bytes)?;
        if packet.object_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        for block in packet.object_data {
            let Ok(mut movement) = decode_movement(&block.object_data) else {
                continue;
            };
            let Ok(construction) = Self::construction_data(&block) else {
                continue;
            };
            let Ok((names, attachment_by_name)) = name_values(&block.name_value) else {
                continue;
            };
            movement.local_id = block.id;
            movement.avatar = construction.p_code == PCode::Avatar;
            movement.textures = if block.texture_entry.is_empty() {
                PrimitiveTextureEntry::new_with_uuid(UUID::zero())?
            } else {
                match PrimitiveTextureEntry::new_with_bytes_int32_int32(
                    block.texture_entry.clone(),
                    0,
                    i32::try_from(block.texture_entry.len()).map_err(|_| Error::Argument)?,
                ) {
                    Ok(value) => value,
                    Err(_) => continue,
                }
            };

            if construction.p_code == PCode::Avatar {
                let is_new = !read(&simulator.objects_avatars).contains_key(&block.id);
                let avatar = read(&simulator.objects_avatars)
                    .get(&block.id)
                    .cloned()
                    .unwrap_or(Avatar::new()?);
                let mut avatar_prim = Primitive::new_with_constructor()?;
                avatar_prim.id = block.full_id;
                avatar_prim.local_id = block.id;
                avatar_prim.parent_id = block.parent_id;
                avatar_prim.region_handle = packet.region_data.region_handle;
                avatar_prim.scale = block.scale;
                avatar_prim.prim_data = construction.clone();
                avatar_prim.position = movement.position;
                avatar_prim.velocity = movement.velocity;
                avatar_prim.acceleration = movement.acceleration;
                avatar_prim.rotation = movement.rotation;
                avatar_prim.angular_velocity = movement.angular_velocity;
                avatar_prim.collision_plane = movement.collision_plane;
                avatar_prim.textures = Some(movement.textures.clone());
                avatar_prim.name_values = Some(names.clone());
                self.events
                    .object_data_block_update
                    .emit(ObjectDataBlockUpdateEventArgs::new(
                        simulator.clone(),
                        avatar_prim,
                        construction,
                        block.clone(),
                        movement.clone(),
                        names,
                    )?);
                write(&simulator.objects_avatars).insert(block.id, avatar.clone());
                write(&simulator.global_to_local_id).insert(block.full_id, block.id);
                let old_parent = write(&self.avatar_parents)
                    .insert(
                        (packet.region_data.region_handle, block.id),
                        block.parent_id,
                    )
                    .unwrap_or(0);
                if old_parent != block.parent_id {
                    self.events
                        .avatar_sit_changed
                        .emit(AvatarSitChangedEventArgs::new(
                            simulator.clone(),
                            avatar.clone(),
                            block.parent_id,
                            old_parent,
                        )?);
                }
                self.events.avatar_update.emit(AvatarUpdateEventArgs::new(
                    simulator.clone(),
                    avatar,
                    packet.region_data.time_dilation,
                    is_new,
                )?);
                continue;
            }
            if !matches!(
                construction.p_code,
                PCode::Prim | PCode::Grass | PCode::Tree | PCode::NewTree
            ) {
                continue;
            }

            let (old, is_new) = {
                let objects = read(&simulator.objects_primitives);
                (
                    objects.get(&block.id).cloned(),
                    !objects.contains_key(&block.id),
                )
            };
            let old_id = old.as_ref().map(|value| value.id);
            let mut prim = old.unwrap_or(Primitive::new_with_constructor()?);
            self.events
                .object_data_block_update
                .emit(ObjectDataBlockUpdateEventArgs::new(
                    simulator.clone(),
                    prim.clone(),
                    construction.clone(),
                    block.clone(),
                    movement.clone(),
                    names.clone(),
                )?);

            prim.flags = PrimFlags(block.update_flags);
            if prim.flags.0 & PrimFlags::ZLIB_COMPRESSED.0 != 0 {
                continue;
            }
            prim.local_id = block.id;
            prim.id = block.full_id;
            prim.parent_id = block.parent_id;
            prim.region_handle = packet.region_data.region_handle;
            prim.scale = block.scale;
            prim.click_action = click_action(block.click_action);
            prim.owner_id = block.owner_id;
            prim.media_url = wire_string(&block.media_url)?;
            prim.text = wire_string(&block.text)?;
            if block.text_color.len() >= 4 {
                prim.text_color = Color4::new_with_byte_byte_byte_byte(
                    block.text_color[0],
                    block.text_color[1],
                    block.text_color[2],
                    block.text_color[3],
                )?;
            }
            prim.is_attachment = attachment_by_name && block.parent_id != 0;
            prim.sound = block.sound;
            prim.sound_flags = SoundFlags(block.flags);
            prim.sound_gain = block.gain;
            prim.sound_radius = block.radius;
            prim.joint = joint_type(block.joint_type);
            prim.joint_pivot = block.joint_pivot;
            prim.joint_axis_or_anchor = block.joint_axis_or_anchor;
            prim.prim_data = construction;
            prim.textures = Some(movement.textures.clone());
            prim.texture_anim =
                PrimitiveTextureAnimation::new(block.texture_anim.clone(), 0).unwrap_or_default();
            prim.particle_sys =
                PrimitiveParticleSystem::new(block.ps_block.clone(), 0).unwrap_or_default();
            if !block.extra_params.is_empty()
                && prim
                    .set_extra_params_from_bytes(block.extra_params.clone(), 0)
                    .is_err()
            {
                continue;
            }
            prim.name_values = Some(names);
            prim.scratch_pad.clear();
            prim.collision_plane = movement.collision_plane;
            prim.position = movement.position;
            prim.velocity = movement.velocity;
            prim.acceleration = movement.acceleration;
            prim.rotation = movement.rotation;
            prim.angular_velocity = movement.angular_velocity;

            {
                let mut objects = write(&simulator.objects_primitives);
                if is_new && objects.len() >= MAX_TRACKED_OBJECTS {
                    continue;
                }
                objects.insert(block.id, prim.clone());
            }
            write(&simulator.global_to_local_id).insert(block.full_id, block.id);
            if let Some(old_id) = old_id.filter(|old_id| *old_id != block.full_id) {
                write(&simulator.global_to_local_id).remove(&old_id);
            }
            self.track(block.full_id, simulator.clone());
            self.events.object_update.emit(PrimEventArgs::new(
                simulator.clone(),
                prim.clone(),
                packet.region_data.time_dilation,
                is_new,
                prim.is_attachment,
            )?);
            if prim.particle_sys.part_max_age != 0.0 {
                self.events
                    .particle_update
                    .emit(ParticleUpdateEventArgs::new(
                        simulator.clone(),
                        prim.particle_sys.clone(),
                        prim,
                    )?);
            }
        }
        Ok(())
    }

    fn track(&self, id: UUID, simulator: Simulator) {
        let mut tracked = write(&self.tracked);
        let simulators = tracked.entry(id).or_default();
        if !simulators.iter().any(|item| item == &simulator) {
            simulators.push(simulator);
        }
    }

    fn handle_terse_update(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::ImprovedTerseObjectUpdatePacket = decode_packet(bytes)?;
        if packet.object_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        for block in packet.object_data {
            let data = &block.data;
            let minimum = if data.get(5).copied().unwrap_or_default() == 0 {
                44
            } else {
                60
            };
            if data.len() < minimum {
                continue;
            }
            let local_id = u32_at(data, 0)?;
            let avatar = data[5] != 0;
            let mut p = 6;
            let collision_plane = if avatar {
                let value = vector4_at(data, p)?;
                p += 16;
                value
            } else {
                Vector4::zero()
            };
            let position = vector3_at(data, p)?;
            p += 12;
            let q3 = |at, low, high| -> Result<Vector3, Error> {
                Ok(Vector3 {
                    x: quantized_u16(data, at, low, high)?,
                    y: quantized_u16(data, at + 2, low, high)?,
                    z: quantized_u16(data, at + 4, low, high)?,
                })
            };
            let velocity = q3(p, -128.0, 128.0)?;
            p += 6;
            let acceleration = q3(p, -64.0, 64.0)?;
            p += 6;
            let rotation = Quaternion::new_with_single_single_single_single(
                quantized_u16(data, p, -1.0, 1.0)?,
                quantized_u16(data, p + 2, -1.0, 1.0)?,
                quantized_u16(data, p + 4, -1.0, 1.0)?,
                quantized_u16(data, p + 6, -1.0, 1.0)?,
            )?;
            p += 8;
            let angular_velocity = q3(p, -64.0, 64.0)?;
            let textures = if block.texture_entry.len() > 4 {
                PrimitiveTextureEntry::new_with_bytes_int32_int32(
                    block.texture_entry.clone(),
                    4,
                    i32::try_from(block.texture_entry.len() - 4).map_err(|_| Error::Argument)?,
                )?
            } else {
                PrimitiveTextureEntry::new_with_uuid(UUID::zero())?
            };
            let update = ObjectMovementUpdate {
                acceleration,
                angular_velocity,
                avatar,
                collision_plane,
                local_id,
                position,
                rotation,
                state: data[4],
                textures,
                velocity,
            };
            if avatar {
                continue;
            }
            let Some(mut prim) = read(&simulator.objects_primitives).get(&local_id).cloned() else {
                continue;
            };
            self.events
                .terse_object_update
                .emit(TerseObjectUpdateEventArgs::new(
                    simulator.clone(),
                    prim.clone(),
                    update.clone(),
                    packet.region_data.time_dilation,
                )?);
            prim.position = update.position;
            prim.rotation = update.rotation;
            prim.velocity = update.velocity;
            prim.collision_plane = update.collision_plane;
            prim.acceleration = update.acceleration;
            prim.angular_velocity = update.angular_velocity;
            prim.prim_data.state = update.state;
            if !block.texture_entry.is_empty() {
                prim.textures = Some(update.textures);
            }
            write(&simulator.objects_primitives).insert(local_id, prim);
        }
        Ok(())
    }

    fn handle_compressed_update(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::ObjectUpdateCompressedPacket = decode_packet(bytes)?;
        if packet.object_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        for block in packet.object_data {
            let encoded_local_id = block
                .data
                .get(16..20)
                .and_then(|bytes| <[u8; 4]>::try_from(bytes).ok().map(u32::from_le_bytes));
            let old_id = encoded_local_id.and_then(|local_id| {
                read(&simulator.objects_primitives)
                    .get(&local_id)
                    .map(|prim| prim.id)
            });
            let Ok((prim, is_new)) = Self::decode_compressed_prim(
                &block.data,
                block.update_flags,
                packet.region_data.region_handle,
                &simulator,
            ) else {
                continue;
            };
            {
                let mut objects = write(&simulator.objects_primitives);
                if is_new && objects.len() >= MAX_TRACKED_OBJECTS {
                    continue;
                }
                objects.insert(prim.local_id, prim.clone());
            }
            write(&simulator.global_to_local_id).insert(prim.id, prim.local_id);
            if let Some(old_id) = old_id.filter(|old_id| *old_id != prim.id) {
                write(&simulator.global_to_local_id).remove(&old_id);
            }
            self.track(prim.id, simulator.clone());
            self.events.object_update.emit(PrimEventArgs::new(
                simulator.clone(),
                prim.clone(),
                packet.region_data.time_dilation,
                is_new,
                prim.is_attachment,
            )?);
            if prim.particle_sys.part_max_age != 0.0 {
                self.events
                    .particle_update
                    .emit(ParticleUpdateEventArgs::new(
                        simulator.clone(),
                        prim.particle_sys.clone(),
                        prim,
                    )?);
            }
        }
        Ok(())
    }

    fn decode_compressed_prim(
        bytes: &[u8],
        update_flags: u32,
        region_handle: u64,
        simulator: &Simulator,
    ) -> Result<(Primitive, bool), Error> {
        let mut cursor = Cursor::new(bytes)?;
        let full_id = cursor.uuid()?;
        let local_id = cursor.u32()?;
        let p_code = crate::object_model::pcode_from_i32(i32::from(cursor.u8()?));
        if !matches!(
            p_code,
            PCode::Prim | PCode::Grass | PCode::Tree | PCode::NewTree
        ) {
            return Err(Error::Argument);
        }
        let (mut prim, is_new) = read(&simulator.objects_primitives)
            .get(&local_id)
            .cloned()
            .map_or_else(
                || Ok((Primitive::new_with_constructor()?, true)),
                |value| Ok::<_, Error>((value, false)),
            )?;
        prim.id = full_id;
        prim.local_id = local_id;
        prim.region_handle = region_handle;
        prim.flags = PrimFlags(update_flags);
        prim.prim_data.p_code = p_code;
        prim.prim_data.state = cursor.u8()?;
        prim.crc = cursor.u32()?;
        prim.prim_data.material = crate::object_model::material_from_i32(i32::from(cursor.u8()?));
        prim.click_action = click_action(cursor.u8()?);
        prim.scale = cursor.vector3()?;
        prim.position = cursor.vector3()?;
        prim.rotation = cursor.quaternion3()?;
        let flags = cursor.u32()?;
        prim.owner_id = cursor.uuid()?;
        if flags & crate::CompressedFlags::HAS_ANGULAR_VELOCITY.0 != 0 {
            prim.angular_velocity = cursor.vector3()?;
        } else {
            prim.angular_velocity = Vector3::zero();
        }
        prim.parent_id = if flags & crate::CompressedFlags::HAS_PARENT.0 != 0 {
            cursor.u32()?
        } else {
            0
        };
        if flags & crate::CompressedFlags::TREE.0 != 0 {
            let _species = cursor.u8()?;
        } else if flags & crate::CompressedFlags::SCRATCH_PAD.0 != 0 {
            let length = usize::from(cursor.u8()?);
            cursor.skip(length)?;
        }
        prim.scratch_pad.clear();
        if flags & crate::CompressedFlags::HAS_TEXT.0 != 0 {
            prim.text = cursor.nul_string()?;
            let color = cursor.take(4)?;
            prim.text_color =
                Color4::new_with_byte_byte_byte_byte(color[0], color[1], color[2], color[3])?;
        } else {
            prim.text.clear();
        }
        if flags & crate::CompressedFlags::MEDIA_URL.0 != 0 {
            prim.media_url = cursor.nul_string()?;
        } else {
            prim.media_url.clear();
        }
        if flags & crate::CompressedFlags::HAS_PARTICLES.0 != 0 {
            prim.particle_sys = PrimitiveParticleSystem::new(cursor.take(86)?.to_vec(), 0)?;
        } else if flags & crate::CompressedFlags::HAS_PARTICLES_NEW.0 != 0 {
            let length = usize::from(cursor.u8()?);
            if length > usize::from(PrimitiveParticleSystem::MAX_DATA_BLOCK_SIZE) {
                return Err(Error::Argument);
            }
            prim.particle_sys = PrimitiveParticleSystem::new(cursor.take(length)?.to_vec(), 0)?;
        } else {
            prim.particle_sys = PrimitiveParticleSystem::default();
        }
        let consumed = prim.set_extra_params_from_bytes(cursor.remaining().to_vec(), 0)?;
        cursor.skip(usize::try_from(consumed).map_err(|_| Error::Argument)?)?;
        if flags & crate::CompressedFlags::HAS_SOUND.0 != 0 {
            prim.sound = cursor.uuid()?;
            prim.sound_gain = cursor.f32()?;
            prim.sound_flags = SoundFlags(cursor.u8()?);
            prim.sound_radius = cursor.f32()?;
        }
        if flags & crate::CompressedFlags::HAS_NAME_VALUES.0 != 0 {
            let text = cursor.nul_string()?;
            let (values, _) = name_values(text.as_bytes())?;
            prim.name_values = Some(values);
        } else {
            prim.name_values = None;
        }
        prim.prim_data.path_curve =
            crate::object_model::path_curve_from_i32(i32::from(cursor.u8()?));
        prim.prim_data.path_begin = Primitive::unpack_begin_cut(cursor.u16()?)?;
        prim.prim_data.path_end = Primitive::unpack_end_cut(cursor.u16()?)?;
        prim.prim_data.path_scale_x = Primitive::unpack_path_scale(cursor.u8()?)?;
        prim.prim_data.path_scale_y = Primitive::unpack_path_scale(cursor.u8()?)?;
        prim.prim_data.path_shear_x = Primitive::unpack_path_shear(cursor.i8()?)?;
        prim.prim_data.path_shear_y = Primitive::unpack_path_shear(cursor.i8()?)?;
        prim.prim_data.path_twist = Primitive::unpack_path_twist(cursor.i8()?)?;
        prim.prim_data.path_twist_begin = Primitive::unpack_path_twist(cursor.i8()?)?;
        prim.prim_data.path_radius_offset = Primitive::unpack_path_twist(cursor.i8()?)?;
        prim.prim_data.path_taper_x = Primitive::unpack_path_taper(cursor.i8()?)?;
        prim.prim_data.path_taper_y = Primitive::unpack_path_taper(cursor.i8()?)?;
        prim.prim_data.path_revolutions = Primitive::unpack_path_revolutions(cursor.u8()?)?;
        prim.prim_data.path_skew = Primitive::unpack_path_twist(cursor.i8()?)?;
        prim.prim_data.profile_curve_with_field = cursor.u8()?;
        prim.prim_data.profile_begin = Primitive::unpack_begin_cut(cursor.u16()?)?;
        prim.prim_data.profile_end = Primitive::unpack_end_cut(cursor.u16()?)?;
        prim.prim_data.profile_hollow = Primitive::unpack_profile_hollow(cursor.u16()?)?;
        let texture_length = usize::try_from(cursor.u32()?).map_err(|_| Error::Argument)?;
        if texture_length > MAX_FIELD_BYTES {
            return Err(Error::Argument);
        }
        let textures = cursor.take(texture_length)?.to_vec();
        prim.textures = Some(PrimitiveTextureEntry::new_with_bytes_int32_int32(
            textures,
            0,
            i32::try_from(texture_length).map_err(|_| Error::Argument)?,
        )?);
        if flags & crate::CompressedFlags::TEXTURE_ANIMATION.0 != 0 {
            let length = usize::try_from(cursor.u32()?).map_err(|_| Error::Argument)?;
            if length != 16 {
                return Err(Error::Argument);
            }
            prim.texture_anim = PrimitiveTextureAnimation::new(cursor.take(length)?.to_vec(), 0)?;
        } else {
            prim.texture_anim = PrimitiveTextureAnimation::default();
        }
        if !cursor.remaining().is_empty() {
            return Err(Error::Argument);
        }
        prim.is_attachment =
            flags & crate::CompressedFlags::HAS_NAME_VALUES.0 != 0 && prim.parent_id != 0;
        Ok((prim, is_new))
    }

    fn handle_kill_object(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::KillObjectPacket = decode_packet(bytes)?;
        if packet.object_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        let requested = packet
            .object_data
            .into_iter()
            .map(|block| block.id)
            .collect::<Vec<_>>();
        for local_id in &requested {
            self.events
                .kill_object
                .emit(KillObjectEventArgs::new(simulator.clone(), *local_id)?);
        }
        self.events.kill_objects.emit(KillObjectsEventArgs::new(
            simulator.clone(),
            requested.clone(),
        )?);

        let objects = read(&simulator.objects_primitives);
        let mut children: HashMap<u32, Vec<u32>> = HashMap::new();
        for (local_id, prim) in objects.iter() {
            if prim.parent_id != 0 {
                children.entry(prim.parent_id).or_default().push(*local_id);
            }
        }
        drop(objects);
        let mut remove = HashSet::new();
        let mut stack = requested.clone();
        while let Some(local_id) = stack.pop() {
            if !remove.insert(local_id) {
                continue;
            }
            if let Some(descendants) = children.get(&local_id) {
                stack.extend(descendants.iter().copied());
            }
        }
        let removed_ids = {
            let mut objects = write(&simulator.objects_primitives);
            remove
                .iter()
                .filter_map(|local_id| objects.remove(local_id).map(|prim| prim.id))
                .collect::<Vec<_>>()
        };
        {
            let mut avatars = write(&simulator.objects_avatars);
            for local_id in &remove {
                avatars.remove(local_id);
            }
        }
        {
            let mut mappings = write(&simulator.global_to_local_id);
            mappings.retain(|_, local_id| !remove.contains(local_id));
        }
        let mut tracked = write(&self.tracked);
        for id in removed_ids {
            if let Some(simulators) = tracked.get_mut(&id) {
                simulators.retain(|item| item != &simulator);
                if simulators.is_empty() {
                    tracked.remove(&id);
                }
            }
        }
        Ok(())
    }

    fn properties_from_block(
        block: &crate::packets::ObjectPropertiesPacketObjectDataBlock,
    ) -> Result<PrimitiveObjectProperties, Error> {
        if block.texture_id.len() > MAX_FIELD_BYTES || !block.texture_id.len().is_multiple_of(16) {
            return Err(Error::Argument);
        }
        let mut texture_i_ds = Vec::with_capacity(block.texture_id.len() / 16);
        for bytes in block.texture_id.chunks_exact(16) {
            texture_i_ds.push(UUID::new_with_bytes_int32(bytes.to_vec(), 0)?);
        }
        Ok(PrimitiveObjectProperties {
            aggregate_perm_textures: block.aggregate_perm_textures,
            aggregate_perm_textures_owner: block.aggregate_perm_textures_owner,
            aggregate_perms: block.aggregate_perms,
            category: object_category(block.category),
            creation_date: UNIX_EPOCH
                .checked_add(Duration::from_secs(block.creation_date))
                .ok_or(Error::Argument)?,
            creator_id: block.creator_id,
            description: wire_string(&block.description)?,
            folder_id: block.folder_id,
            from_task_id: block.from_task_id,
            group_id: block.group_id,
            inventory_serial: block.inventory_serial,
            item_id: block.item_id,
            last_owner_id: block.last_owner_id,
            name: wire_string(&block.name)?,
            object_id: block.object_id,
            owner_id: block.owner_id,
            ownership_cost: block.ownership_cost,
            permissions: crate::Permissions::new(
                block.base_mask,
                block.everyone_mask,
                block.group_mask,
                block.next_owner_mask,
                block.owner_mask,
            )?,
            sale_price: block.sale_price,
            sale_type: sale_type(block.sale_type),
            sit_name: wire_string(&block.sit_name)?,
            texture_i_ds,
            touch_name: wire_string(&block.touch_name)?,
        })
    }

    fn handle_object_properties(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::ObjectPropertiesPacket = decode_packet(bytes)?;
        if packet.object_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        for block in packet.object_data {
            let Ok(properties) = Self::properties_from_block(&block) else {
                continue;
            };
            let local_id = {
                read(&simulator.global_to_local_id)
                    .get(&properties.object_id)
                    .copied()
            };
            let primitive = local_id
                .and_then(|local_id| read(&simulator.objects_primitives).get(&local_id).cloned());
            if let Some(mut prim) = primitive {
                let local_id = prim.local_id;
                self.events
                    .object_properties_updated
                    .emit(ObjectPropertiesUpdatedEventArgs::new(
                        simulator.clone(),
                        prim.clone(),
                        properties.clone(),
                    )?);
                prim.properties = Some(properties.clone());
                write(&simulator.objects_primitives).insert(local_id, prim);
            }
            self.events
                .object_properties
                .emit(ObjectPropertiesEventArgs::new(
                    simulator.clone(),
                    properties,
                )?);
        }
        Ok(())
    }

    fn handle_object_properties_family(
        &self,
        bytes: &[u8],
        simulator: Simulator,
    ) -> Result<(), Error> {
        let packet: crate::packets::ObjectPropertiesFamilyPacket = decode_packet(bytes)?;
        let block = packet.object_data;
        let properties = PrimitiveObjectProperties {
            category: object_category(block.category),
            description: wire_string(&block.description)?,
            group_id: block.group_id,
            last_owner_id: block.last_owner_id,
            name: wire_string(&block.name)?,
            object_id: block.object_id,
            owner_id: block.owner_id,
            ownership_cost: block.ownership_cost,
            permissions: crate::Permissions::new(
                block.base_mask,
                block.everyone_mask,
                block.group_mask,
                block.next_owner_mask,
                block.owner_mask,
            )?,
            sale_price: block.sale_price,
            sale_type: sale_type(block.sale_type),
            ..PrimitiveObjectProperties::default()
        };
        let local_id = {
            read(&simulator.global_to_local_id)
                .get(&properties.object_id)
                .copied()
        };
        let primitive = local_id
            .and_then(|local_id| read(&simulator.objects_primitives).get(&local_id).cloned());
        if let Some(mut prim) = primitive {
            let local_id = prim.local_id;
            let mut current = prim.properties.unwrap_or_default();
            current.set_family_properties(properties.clone())?;
            prim.properties = Some(current);
            write(&simulator.objects_primitives).insert(local_id, prim);
        }
        self.events
            .object_properties_family
            .emit(ObjectPropertiesFamilyEventArgs::new(
                simulator,
                properties,
                report_type(block.request_flags),
            )?);
        Ok(())
    }

    fn handle_pay_price_reply(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::PayPriceReplyPacket = decode_packet(bytes)?;
        if packet.button_data.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        self.events
            .pay_price_reply
            .emit(PayPriceReplyEventArgs::new(
                simulator,
                packet.object_data.object_id,
                packet.object_data.default_pay_price,
                packet
                    .button_data
                    .into_iter()
                    .map(|block| block.pay_button)
                    .collect(),
            )?);
        Ok(())
    }

    fn handle_object_animation(&self, bytes: &[u8], simulator: Simulator) -> Result<(), Error> {
        let packet: crate::packets::ObjectAnimationPacket = decode_packet(bytes)?;
        if packet.animation_list.len() > MAX_PACKET_BLOCKS {
            return Err(Error::Argument);
        }
        let object_id = packet.sender.id;
        let animations = packet
            .animation_list
            .into_iter()
            .map(|block| Animation {
                animation_id: block.anim_id,
                animation_sequence: block.anim_sequence_id,
                animation_source_object_id: object_id,
            })
            .collect::<Vec<_>>();
        let local_id = { read(&simulator.global_to_local_id).get(&object_id).copied() };
        let primitive = local_id
            .and_then(|local_id| read(&simulator.objects_primitives).get(&local_id).cloned());
        if let Some(mut prim) = primitive {
            let local_id = prim.local_id;
            prim.signaled_animations = Some(animations.clone());
            write(&simulator.objects_primitives).insert(local_id, prim);
        }
        self.events
            .object_animation
            .emit(ObjectAnimationEventArgs::new(
                simulator, object_id, animations,
            )?);
        Ok(())
    }

    fn handle_physics_properties(
        &self,
        properties: Vec<PrimitivePhysicsProperties>,
        simulator: Simulator,
    ) {
        if properties.len() > MAX_PACKET_BLOCKS {
            return;
        }
        for property in properties {
            let primitive = {
                read(&simulator.objects_primitives)
                    .get(&property.local_id)
                    .cloned()
            };
            if let Some(mut prim) = primitive {
                prim.physics_props = Some(property.clone());
                write(&simulator.objects_primitives).insert(property.local_id, prim);
            }
            self.events
                .physics_properties
                .emit(PhysicsPropertiesEventArgs {
                    simulator: simulator.clone(),
                    physics_properties: property,
                });
        }
    }
}

impl ObjectManager {
    pub(crate) fn native_subscribe_avatar_sit_changed(
        &self,
        h: EventHandler<AvatarSitChangedEventArgs>,
    ) -> Subscription {
        self.inner.events.avatar_sit_changed.subscribe(h)
    }
    pub(crate) fn native_subscribe_avatar_update(
        &self,
        h: EventHandler<AvatarUpdateEventArgs>,
    ) -> Subscription {
        self.inner.events.avatar_update.subscribe(h)
    }
    pub(crate) fn native_subscribe_kill_object(
        &self,
        h: EventHandler<KillObjectEventArgs>,
    ) -> Subscription {
        self.inner.events.kill_object.subscribe(h)
    }
    pub(crate) fn native_subscribe_kill_objects(
        &self,
        h: EventHandler<KillObjectsEventArgs>,
    ) -> Subscription {
        self.inner.events.kill_objects.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_animation(
        &self,
        h: EventHandler<ObjectAnimationEventArgs>,
    ) -> Subscription {
        self.inner.events.object_animation.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_data_block_update(
        &self,
        h: EventHandler<ObjectDataBlockUpdateEventArgs>,
    ) -> Subscription {
        self.inner.events.object_data_block_update.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_properties(
        &self,
        h: EventHandler<ObjectPropertiesEventArgs>,
    ) -> Subscription {
        self.inner.events.object_properties.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_properties_family(
        &self,
        h: EventHandler<ObjectPropertiesFamilyEventArgs>,
    ) -> Subscription {
        self.inner.events.object_properties_family.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_properties_updated(
        &self,
        h: EventHandler<ObjectPropertiesUpdatedEventArgs>,
    ) -> Subscription {
        self.inner.events.object_properties_updated.subscribe(h)
    }
    pub(crate) fn native_subscribe_object_update(
        &self,
        h: EventHandler<PrimEventArgs>,
    ) -> Subscription {
        self.inner.events.object_update.subscribe(h)
    }
    pub(crate) fn native_subscribe_particle_update(
        &self,
        h: EventHandler<ParticleUpdateEventArgs>,
    ) -> Subscription {
        self.inner.events.particle_update.subscribe(h)
    }
    pub(crate) fn native_subscribe_pay_price_reply(
        &self,
        h: EventHandler<PayPriceReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.pay_price_reply.subscribe(h)
    }
    pub(crate) fn native_subscribe_physics_properties(
        &self,
        h: EventHandler<PhysicsPropertiesEventArgs>,
    ) -> Subscription {
        self.inner.events.physics_properties.subscribe(h)
    }
    pub(crate) fn native_subscribe_terse_object_update(
        &self,
        h: EventHandler<TerseObjectUpdateEventArgs>,
    ) -> Subscription {
        self.inner.events.terse_object_update.subscribe(h)
    }

    fn auth(&self) -> Result<(UUID, UUID), Error> {
        let network = self.inner.client()?.native_network()?;
        Ok((network.native_agent_id(), network.native_session_id()))
    }

    fn send<P: GeneratedPacket>(
        &self,
        simulator: &Simulator,
        packet_type: crate::packets::PacketType,
        packet: &P,
    ) -> Result<(), Error> {
        let data = packet.encode_packet()?;
        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 validate_ids(ids: &[u32]) -> Result<(), Error> {
        if ids.is_empty() || ids.len() > 255 {
            return Err(Error::Argument);
        }
        Ok(())
    }

    pub(crate) fn native_request_objects(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::RequestMultipleObjectsPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block = crate::packets::RequestMultipleObjectsPacketObjectDataBlock::new_with_constructor()?;
                block.id = id;
                block.cache_miss_type = 0;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::RequestMultipleObjects,
            &packet,
        )
    }

    pub(crate) fn native_select_objects(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        automatic_deselect: bool,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectSelectPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectSelectPacketObjectDataBlock::new_with_constructor()?;
                block.object_local_id = *id;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectSelect,
            &packet,
        )?;
        if automatic_deselect {
            self.native_deselect_objects(simulator, local_ids)?;
        }
        Ok(())
    }

    pub(crate) fn native_deselect_objects(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectDeselectPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectDeselectPacketObjectDataBlock::new_with_constructor()?;
                block.object_local_id = id;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectDeselect,
            &packet,
        )
    }

    pub(crate) fn native_link_objects(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        link: bool,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        if link {
            let mut packet = crate::packets::ObjectLinkPacket::new_with_constructor()?;
            packet.agent_data.agent_id = agent_id;
            packet.agent_data.session_id = session_id;
            packet.object_data = local_ids
                .into_iter()
                .map(|id| {
                    let mut block =
                        crate::packets::ObjectLinkPacketObjectDataBlock::new_with_constructor()?;
                    block.object_local_id = id;
                    Ok(block)
                })
                .collect::<Result<Vec<_>, Error>>()?;
            self.send(&simulator, crate::packets::PacketType::ObjectLink, &packet)
        } else {
            let mut packet = crate::packets::ObjectDelinkPacket::new_with_constructor()?;
            packet.agent_data.agent_id = agent_id;
            packet.agent_data.session_id = session_id;
            packet.object_data = local_ids
                .into_iter()
                .map(|id| {
                    let mut block =
                        crate::packets::ObjectDelinkPacketObjectDataBlock::new_with_constructor()?;
                    block.object_local_id = id;
                    Ok(block)
                })
                .collect::<Result<Vec<_>, Error>>()?;
            self.send(
                &simulator,
                crate::packets::PacketType::ObjectDelink,
                &packet,
            )
        }
    }

    pub(crate) fn native_request_pay_price(
        &self,
        simulator: Simulator,
        object_id: UUID,
    ) -> Result<(), Error> {
        let mut packet = crate::packets::RequestPayPricePacket::new_with_constructor()?;
        packet.object_data.object_id = object_id;
        self.send(
            &simulator,
            crate::packets::PacketType::RequestPayPrice,
            &packet,
        )
    }

    pub(crate) fn native_request_properties_family(
        &self,
        simulator: Simulator,
        object_id: UUID,
        reliable: bool,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet =
            crate::packets::RequestObjectPropertiesFamilyPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data.object_id = object_id;
        packet.object_data.request_flags = 0;
        let _ = reliable; // This packet is reliable in the protocol template.
        self.send(
            &simulator,
            crate::packets::PacketType::RequestObjectPropertiesFamily,
            &packet,
        )
    }

    pub(crate) fn native_set_names(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        names: Vec<String>,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        if local_ids.len() != names.len() || names.iter().any(|name| name.len() > 255) {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectNamePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .into_iter()
            .zip(names)
            .map(|(id, name)| {
                let mut block =
                    crate::packets::ObjectNamePacketObjectDataBlock::new_with_constructor()?;
                block.local_id = id;
                block.name = name.into_bytes();
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(&simulator, crate::packets::PacketType::ObjectName, &packet)
    }

    pub(crate) fn native_set_descriptions(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        descriptions: Vec<String>,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        if local_ids.len() != descriptions.len()
            || descriptions
                .iter()
                .any(|description| description.len() > 255)
        {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectDescriptionPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .into_iter()
            .zip(descriptions)
            .map(|(id, description)| {
                let mut block =
                    crate::packets::ObjectDescriptionPacketObjectDataBlock::new_with_constructor()?;
                block.local_id = id;
                block.description = description.into_bytes();
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectDescription,
            &packet,
        )
    }

    pub(crate) fn native_set_flags(
        &self,
        simulator: Simulator,
        local_id: u32,
        physical: bool,
        temporary: bool,
        phantom: bool,
        casts_shadow: bool,
        physics_type: libremetaverse_types::PhysicsShapeType,
        density: f32,
        friction: f32,
        restitution: f32,
        gravity_multiplier: f32,
    ) -> Result<(), Error> {
        if ![density, friction, restitution, gravity_multiplier]
            .iter()
            .all(|value| value.is_finite())
        {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectFlagUpdatePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.object_local_id = local_id;
        packet.agent_data.use_physics = physical;
        packet.agent_data.is_temporary = temporary;
        packet.agent_data.is_phantom = phantom;
        packet.agent_data.casts_shadows = casts_shadow;
        let mut block =
            crate::packets::ObjectFlagUpdatePacketExtraPhysicsBlock::new_with_constructor()?;
        block.physics_shape_type = physics_type as u8;
        block.density = density;
        block.friction = friction;
        block.restitution = restitution;
        block.gravity_multiplier = gravity_multiplier;
        packet.extra_physics = vec![block];
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectFlagUpdate,
            &packet,
        )
    }

    pub(crate) fn native_set_sale_info(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        sale_type: SaleType,
        price: i32,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        if price < 0 {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectSaleInfoPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectSaleInfoPacketObjectDataBlock::new_with_constructor()?;
                block.local_id = id;
                block.sale_type = sale_type as u8;
                block.sale_price = price;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectSaleInfo,
            &packet,
        )
    }

    pub(crate) fn native_attach(
        &self,
        simulator: Simulator,
        local_id: u32,
        point: libremetaverse_types::AttachmentPoint,
        rotation: Quaternion,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectAttachPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.attachment_point = point as u8;
        let mut block = crate::packets::ObjectAttachPacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.rotation = rotation;
        packet.object_data = vec![block];
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectAttach,
            &packet,
        )
    }

    pub(crate) fn native_attachment_action(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        drop: bool,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        if drop {
            let mut packet = crate::packets::ObjectDropPacket::new_with_constructor()?;
            packet.agent_data.agent_id = agent_id;
            packet.agent_data.session_id = session_id;
            packet.object_data = local_ids
                .into_iter()
                .map(|id| {
                    let mut block =
                        crate::packets::ObjectDropPacketObjectDataBlock::new_with_constructor()?;
                    block.object_local_id = id;
                    Ok(block)
                })
                .collect::<Result<Vec<_>, Error>>()?;
            self.send(&simulator, crate::packets::PacketType::ObjectDrop, &packet)
        } else {
            let mut packet = crate::packets::ObjectDetachPacket::new_with_constructor()?;
            packet.agent_data.agent_id = agent_id;
            packet.agent_data.session_id = session_id;
            packet.object_data = local_ids
                .into_iter()
                .map(|id| {
                    let mut block =
                        crate::packets::ObjectDetachPacketObjectDataBlock::new_with_constructor()?;
                    block.object_local_id = id;
                    Ok(block)
                })
                .collect::<Result<Vec<_>, Error>>()?;
            self.send(
                &simulator,
                crate::packets::PacketType::ObjectDetach,
                &packet,
            )
        }
    }

    pub(crate) fn native_buy_object(
        &self,
        simulator: Simulator,
        local_id: u32,
        sale_type: SaleType,
        price: i32,
        group_id: UUID,
        category_id: UUID,
    ) -> Result<(), Error> {
        if price < 0 {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectBuyPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.group_id = group_id;
        packet.agent_data.category_id = category_id;
        let mut block = crate::packets::ObjectBuyPacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.sale_type = sale_type as u8;
        block.sale_price = price;
        packet.object_data = vec![block];
        self.send(&simulator, crate::packets::PacketType::ObjectBuy, &packet)
    }

    pub(crate) fn native_set_group(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        group_id: UUID,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectGroupPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.group_id = group_id;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectGroupPacketObjectDataBlock::new_with_constructor()?;
                block.object_local_id = id;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(&simulator, crate::packets::PacketType::ObjectGroup, &packet)
    }

    pub(crate) fn native_set_permissions(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        who: crate::PermissionWho,
        permissions: crate::PermissionMask,
        set: bool,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectPermissionsPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.header_data.override_ = false;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectPermissionsPacketObjectDataBlock::new_with_constructor()?;
                block.object_local_id = id;
                block.field = who.0;
                block.mask = permissions.0;
                block.set = u8::from(set);
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectPermissions,
            &packet,
        )
    }

    pub(crate) fn native_deed_objects(
        &self,
        simulator: Simulator,
        local_ids: Vec<u32>,
        group_id: UUID,
    ) -> Result<(), Error> {
        Self::validate_ids(&local_ids)?;
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectOwnerPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.header_data.override_ = false;
        packet.header_data.owner_id = UUID::zero();
        packet.header_data.group_id = group_id;
        packet.object_data = local_ids
            .into_iter()
            .map(|id| {
                let mut block =
                    crate::packets::ObjectOwnerPacketObjectDataBlock::new_with_constructor()?;
                block.object_local_id = id;
                Ok(block)
            })
            .collect::<Result<Vec<_>, Error>>()?;
        self.send(&simulator, crate::packets::PacketType::ObjectOwner, &packet)
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) async fn native_click_object(
        &self,
        simulator: Simulator,
        local_id: u32,
        uv_coord: Vector3,
        st_coord: Vector3,
        face_index: i32,
        position: Vector3,
        normal: Vector3,
        binormal: Vector3,
        cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut grab = crate::packets::ObjectGrabPacket::new_with_constructor()?;
        grab.agent_data.agent_id = agent_id;
        grab.agent_data.session_id = session_id;
        grab.object_data.local_id = local_id;
        grab.object_data.grab_offset = Vector3::zero();
        let mut surface = crate::packets::ObjectGrabPacketSurfaceInfoBlock::new_with_constructor()?;
        surface.uv_coord = uv_coord;
        surface.st_coord = st_coord;
        surface.face_index = face_index;
        surface.position = position;
        surface.normal = normal;
        surface.binormal = binormal;
        grab.surface_info = vec![surface];
        self.send(&simulator, crate::packets::PacketType::ObjectGrab, &grab)?;

        if let Some(token) = cancellation_token {
            tokio::select! {
                () = tokio::time::sleep(Duration::from_millis(50)) => {}
                () = token.cancelled() => {}
            }
        } else {
            tokio::time::sleep(Duration::from_millis(50)).await;
        }

        let mut degrab = crate::packets::ObjectDeGrabPacket::new_with_constructor()?;
        degrab.agent_data.agent_id = agent_id;
        degrab.agent_data.session_id = session_id;
        degrab.object_data.local_id = local_id;
        let mut surface =
            crate::packets::ObjectDeGrabPacketSurfaceInfoBlock::new_with_constructor()?;
        surface.uv_coord = uv_coord;
        surface.st_coord = st_coord;
        surface.face_index = face_index;
        surface.position = position;
        surface.normal = normal;
        surface.binormal = binormal;
        degrab.surface_info = vec![surface];
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectDeGrab,
            &degrab,
        )
    }

    pub(crate) fn native_add_prim(
        &self,
        simulator: Simulator,
        prim: PrimitiveConstructionData,
        group_id: UUID,
        position: Vector3,
        scale: Vector3,
        rotation: Quaternion,
        create_flags: PrimFlags,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectAddPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.group_id = group_id;
        let data = &mut packet.object_data;
        data.state = prim.state;
        data.add_flags = create_flags.0;
        data.p_code = PCode::Prim as u8;
        data.material = prim.material as u8;
        data.scale = scale;
        data.rotation = rotation;
        data.path_curve = prim.path_curve as u8;
        data.path_begin = Primitive::pack_begin_cut(prim.path_begin)?;
        data.path_end = Primitive::pack_end_cut(prim.path_end)?;
        data.path_radius_offset = Primitive::pack_path_twist(prim.path_radius_offset)?;
        data.path_revolutions = Primitive::pack_path_revolutions(prim.path_revolutions)?;
        data.path_scale_x = Primitive::pack_path_scale(prim.path_scale_x)?;
        data.path_scale_y = Primitive::pack_path_scale(prim.path_scale_y)?;
        data.path_shear_x = Primitive::pack_path_shear(prim.path_shear_x)?.cast_unsigned();
        data.path_shear_y = Primitive::pack_path_shear(prim.path_shear_y)?.cast_unsigned();
        data.path_skew = Primitive::pack_path_twist(prim.path_skew)?;
        data.path_taper_x = Primitive::pack_path_taper(prim.path_taper_x)?;
        data.path_taper_y = Primitive::pack_path_taper(prim.path_taper_y)?;
        data.path_twist = Primitive::pack_path_twist(prim.path_twist)?;
        data.path_twist_begin = Primitive::pack_path_twist(prim.path_twist_begin)?;
        data.profile_curve = prim.profile_curve_with_field;
        data.profile_begin = Primitive::pack_begin_cut(prim.profile_begin)?;
        data.profile_end = Primitive::pack_end_cut(prim.profile_end)?;
        data.profile_hollow = Primitive::pack_profile_hollow(prim.profile_hollow)?;
        data.ray_start = position;
        data.ray_end = position;
        data.ray_end_is_intersection = 0;
        data.ray_target_id = UUID::zero();
        data.bypass_raycast = 1;
        self.send(&simulator, crate::packets::PacketType::ObjectAdd, &packet)
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn native_add_foliage(
        &self,
        simulator: Simulator,
        scale: Vector3,
        rotation: Quaternion,
        position: Vector3,
        state: u8,
        group_id: UUID,
        p_code: PCode,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectAddPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.group_id = group_id;
        packet.object_data.bypass_raycast = 1;
        packet.object_data.material = Material::Wood as u8;
        packet.object_data.path_curve = PathCurve::Line as u8;
        packet.object_data.p_code = p_code as u8;
        packet.object_data.ray_end = position;
        packet.object_data.ray_start = position;
        packet.object_data.ray_target_id = UUID::zero();
        packet.object_data.rotation = rotation;
        packet.object_data.scale = scale;
        packet.object_data.state = state;
        self.send(&simulator, crate::packets::PacketType::ObjectAdd, &packet)
    }

    pub(crate) fn native_set_textures(
        &self,
        simulator: Simulator,
        local_id: u32,
        textures: PrimitiveTextureEntry,
        media_url: String,
    ) -> Result<(), Error> {
        if media_url.len() > 255 {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectImagePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        let mut block = crate::packets::ObjectImagePacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.texture_entry = textures.get_bytes()?;
        block.media_url = media_url.into_bytes();
        packet.object_data = vec![block];
        self.send(&simulator, crate::packets::PacketType::ObjectImage, &packet)
    }

    pub(crate) fn native_set_extra_param(
        &self,
        simulator: Simulator,
        local_id: u32,
        type_: libremetaverse_types::ExtraParamType,
        in_use: bool,
        data: Vec<u8>,
    ) -> Result<(), Error> {
        if data.len() > MAX_FIELD_BYTES {
            return Err(Error::Argument);
        }
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectExtraParamsPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        let mut block =
            crate::packets::ObjectExtraParamsPacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.param_type = type_.0;
        block.param_in_use = in_use;
        block.param_size = u32::try_from(data.len()).map_err(|_| Error::Argument)?;
        block.param_data = data;
        packet.object_data = vec![block];
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectExtraParams,
            &packet,
        )
    }

    pub(crate) fn native_set_sculpt(
        &self,
        simulator: Simulator,
        local_id: u32,
        sculpt: crate::PrimitiveSculptData,
    ) -> Result<(), Error> {
        self.native_set_extra_param(
            simulator.clone(),
            local_id,
            libremetaverse_types::ExtraParamType::SCULPT,
            true,
            sculpt.get_bytes()?,
        )?;
        let shape = PrimitiveConstructionData {
            path_curve: PathCurve::Circle,
            path_scale_x: 1.0,
            path_scale_y: 0.5,
            ..PrimitiveConstructionData::default()
        };
        self.native_set_shape(simulator, local_id, shape)
    }

    pub(crate) fn native_set_shape(
        &self,
        simulator: Simulator,
        local_id: u32,
        prim: PrimitiveConstructionData,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectShapePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        let mut data = crate::packets::ObjectShapePacketObjectDataBlock::new_with_constructor()?;
        data.object_local_id = local_id;
        data.path_curve = prim.path_curve as u8;
        data.path_begin = Primitive::pack_begin_cut(prim.path_begin)?;
        data.path_end = Primitive::pack_end_cut(prim.path_end)?;
        data.path_scale_x = Primitive::pack_path_scale(prim.path_scale_x)?;
        data.path_scale_y = Primitive::pack_path_scale(prim.path_scale_y)?;
        data.path_shear_x = Primitive::pack_path_shear(prim.path_shear_x)?.cast_unsigned();
        data.path_shear_y = Primitive::pack_path_shear(prim.path_shear_y)?.cast_unsigned();
        data.path_twist = Primitive::pack_path_twist(prim.path_twist)?;
        data.path_twist_begin = Primitive::pack_path_twist(prim.path_twist_begin)?;
        data.path_radius_offset = Primitive::pack_path_twist(prim.path_radius_offset)?;
        data.path_taper_x = Primitive::pack_path_taper(prim.path_taper_x)?;
        data.path_taper_y = Primitive::pack_path_taper(prim.path_taper_y)?;
        data.path_revolutions = Primitive::pack_path_revolutions(prim.path_revolutions)?;
        data.path_skew = Primitive::pack_path_twist(prim.path_skew)?;
        data.profile_curve = prim.profile_curve_with_field;
        data.profile_begin = Primitive::pack_begin_cut(prim.profile_begin)?;
        data.profile_end = Primitive::pack_end_cut(prim.profile_end)?;
        data.profile_hollow = Primitive::pack_profile_hollow(prim.profile_hollow)?;
        packet.object_data = vec![data];
        self.send(&simulator, crate::packets::PacketType::ObjectShape, &packet)
    }

    pub(crate) fn native_set_material(
        &self,
        simulator: Simulator,
        local_id: u32,
        material: Material,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::ObjectMaterialPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        let mut block =
            crate::packets::ObjectMaterialPacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.material = material as u8;
        packet.object_data = vec![block];
        self.send(
            &simulator,
            crate::packets::PacketType::ObjectMaterial,
            &packet,
        )
    }

    pub(crate) fn native_basic_shape(type_: PrimType) -> Result<PrimitiveConstructionData, Error> {
        let mut prim = PrimitiveConstructionData {
            p_code: PCode::Prim,
            material: Material::Wood,
            ..PrimitiveConstructionData::default()
        };
        let (profile, path, scale_y) = match type_ {
            PrimType::Box => (ProfileCurve::Square, PathCurve::Line, 1.0),
            PrimType::Cylinder => (ProfileCurve::Circle, PathCurve::Line, 1.0),
            PrimType::Prism => (ProfileCurve::EqualTriangle, PathCurve::Line, 0.0),
            PrimType::Ring => (ProfileCurve::EqualTriangle, PathCurve::Circle, 0.25),
            PrimType::Sphere => (ProfileCurve::HalfCircle, PathCurve::Circle, 1.0),
            PrimType::Torus => (ProfileCurve::Circle, PathCurve::Circle, 0.25),
            PrimType::Tube => (ProfileCurve::Square, PathCurve::Circle, 0.25),
            PrimType::Sculpt => (ProfileCurve::Circle, PathCurve::Circle, 0.5),
            PrimType::Mesh | PrimType::Unknown => return Err(Error::Argument),
        };
        prim.profile_curve_with_field = profile as u8;
        prim.path_curve = path;
        prim.path_scale_y = scale_y;
        if type_ == PrimType::Prism {
            prim.path_scale_x = 0.0;
        }
        Ok(prim)
    }

    pub(crate) fn native_update_vector(
        &self,
        simulator: Simulator,
        local_id: u32,
        data: Vector3,
        type_: crate::UpdateType,
    ) -> Result<(), Error> {
        self.native_update_bytes(
            simulator,
            local_id,
            data.get_bytes()?,
            u8::try_from(type_.0).map_err(|_| Error::Argument)?,
        )
    }

    pub(crate) fn native_update_rotation(
        &self,
        simulator: Simulator,
        local_id: u32,
        rotation: Quaternion,
        child_only: bool,
    ) -> Result<(), Error> {
        let mut type_ = crate::UpdateType::ROTATION.0;
        if !child_only {
            type_ |= crate::UpdateType::LINKED.0;
        }
        self.native_update_bytes(
            simulator,
            local_id,
            rotation.get_bytes()?,
            u8::try_from(type_).map_err(|_| Error::Argument)?,
        )
    }

    fn native_update_bytes(
        &self,
        simulator: Simulator,
        local_id: u32,
        data: Vec<u8>,
        type_: u8,
    ) -> Result<(), Error> {
        let (agent_id, session_id) = self.auth()?;
        let mut packet = crate::packets::MultipleObjectUpdatePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        let mut block =
            crate::packets::MultipleObjectUpdatePacketObjectDataBlock::new_with_constructor()?;
        block.object_local_id = local_id;
        block.type_ = type_;
        block.data = data;
        packet.object_data = vec![block];
        self.send(
            &simulator,
            crate::packets::PacketType::MultipleObjectUpdate,
            &packet,
        )
    }

    pub(crate) fn native_get_primitive(
        &self,
        simulator: Simulator,
        local_id: u32,
        full_id: UUID,
        create_if_missing: bool,
    ) -> Result<Primitive, Error> {
        if let Some(value) = read(&simulator.objects_primitives).get(&local_id).cloned()
            && (full_id == UUID::zero() || value.id == full_id)
        {
            return Ok(value);
        }
        if !create_if_missing {
            return Err(Error::InvalidOperation);
        }
        if read(&simulator.objects_primitives).len() >= MAX_TRACKED_OBJECTS {
            return Err(Error::InvalidOperation);
        }
        let mut prim = Primitive::new_with_constructor()?;
        prim.local_id = local_id;
        prim.id = full_id;
        write(&simulator.objects_primitives).insert(local_id, prim.clone());
        if full_id != UUID::zero() {
            write(&simulator.global_to_local_id).insert(full_id, local_id);
            self.inner.track(full_id, simulator);
        }
        Ok(prim)
    }

    pub(crate) fn native_track(&self, object_id: UUID, simulator: Simulator) {
        self.inner.track(object_id, simulator);
    }

    pub(crate) fn native_untrack(&self, object_id: UUID, simulator: &Simulator) {
        let mut tracked = write(&self.inner.tracked);
        if let Some(simulators) = tracked.get_mut(&object_id) {
            simulators.retain(|item| item != simulator);
            if simulators.is_empty() {
                tracked.remove(&object_id);
            }
        }
    }

    pub(crate) fn native_simulators_for(&self, object_id: UUID) -> Vec<Simulator> {
        read(&self.inner.tracked)
            .get(&object_id)
            .cloned()
            .unwrap_or_default()
    }

    pub(crate) fn native_cleanup_tracking(&self) {
        write(&self.inner.tracked).retain(|_, simulators| {
            simulators.retain(Simulator::connected);
            !simulators.is_empty()
        });
    }

    pub(crate) fn native_is_near_border(
        position: Vector3,
        region_size_x: u32,
        region_size_y: u32,
        threshold: Option<f32>,
    ) -> Result<bool, Error> {
        let threshold = threshold.unwrap_or(32.0);
        if !threshold.is_finite() || threshold < 0.0 {
            return Err(Error::Argument);
        }
        Ok(position.x < threshold
            || f64::from(position.x) > f64::from(region_size_x) - f64::from(threshold)
            || position.y < threshold
            || f64::from(position.y) > f64::from(region_size_y) - f64::from(threshold))
    }

    pub(crate) fn native_dispose(&self) {
        if !self.inner.disposed.swap(true, Ordering::AcqRel) {
            mutex(&self.inner.raw_subscription).take();
            mutex(&self.inner.caps_subscription).take();
            write(&self.inner.tracked).clear();
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::AtomicBool;

    fn context() -> (ObjectManager, Simulator) {
        let client = GridClient::new().expect("client");
        let manager = ObjectManager::new(Some(Arc::new(client.clone()))).expect("object manager");
        let simulator = Simulator::new(
            client,
            "127.0.0.1:14069".parse().expect("endpoint"),
            69,
            None,
            None,
        )
        .expect("simulator");
        (manager, simulator)
    }

    fn transport_wire(data: &[u8]) -> Vec<u8> {
        if data
            .first()
            .is_none_or(|flags| flags & crate::Helpers::MSG_ZEROCODED == 0)
        {
            return data.to_vec();
        }
        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
    }

    #[test]
    fn movement_decoding_accepts_all_reference_encodings_and_rejects_other_lengths() {
        for length in [16, 32, 48, 60, 76] {
            let update = decode_movement(&vec![0; length]).expect("valid movement size");
            assert!(update.position.x.is_finite());
            assert!(update.rotation.w.is_finite());
        }
        for length in [0, 15, 17, 31, 33, 59, 61, 75, 77] {
            assert!(decode_movement(&vec![0; length]).is_err());
        }
    }

    #[test]
    fn name_values_preserve_values_and_detect_attachments() {
        let (values, attachment) = name_values(
            b"AttachItemID STRING R S 01234567-89ab-cdef-0123-456789abcdef\nTitle STRING R S Hello world\0",
        )
        .expect("name values");
        assert!(attachment);
        assert_eq!(values.len(), 2);
        assert_eq!(values[1].name, "Title");
        assert_eq!(values[1].value, Some("Hello world".into()));
    }

    #[test]
    fn malformed_terse_block_never_mutates_tracked_state() {
        let (manager, simulator) = context();
        let id = UUID::random().expect("uuid");
        let mut prim = manager
            .native_get_primitive(simulator.clone(), 7, id, true)
            .expect("tracked primitive");
        prim.position = Vector3 {
            x: 1.0,
            y: 2.0,
            z: 3.0,
        };
        write(&simulator.objects_primitives).insert(7, prim.clone());

        let mut packet = crate::packets::ImprovedTerseObjectUpdatePacket::new_with_constructor()
            .expect("packet");
        let mut block =
            crate::packets::ImprovedTerseObjectUpdatePacketObjectDataBlock::new_with_constructor()
                .expect("block");
        block.data = vec![7, 0, 0];
        packet.object_data = vec![block];
        let bytes = packet.to_bytes_with_method().expect("wire packet");
        manager
            .inner
            .handle_terse_update(&bytes, simulator.clone())
            .expect("malformed block is isolated");
        assert_eq!(
            read(&simulator.objects_primitives)
                .get(&7)
                .unwrap()
                .position,
            prim.position
        );
    }

    #[test]
    fn full_update_decodes_then_emits_preapply_and_postapply_events() {
        let (manager, simulator) = context();
        let object_id = UUID::random().expect("uuid");
        let preapply_saw_absent = Arc::new(AtomicBool::new(false));
        let preapply = Arc::clone(&preapply_saw_absent);
        let preapply_simulator = simulator.clone();
        let _pre_subscription =
            manager.subscribe_object_data_block_update(Arc::new(move |event| {
                if event.block().id == 21 {
                    preapply.store(
                        !read(&preapply_simulator.objects_primitives).contains_key(&21),
                        Ordering::Release,
                    );
                }
            }));
        let postapply_saw_present = Arc::new(AtomicBool::new(false));
        let postapply = Arc::clone(&postapply_saw_present);
        let postapply_simulator = simulator.clone();
        let _post_subscription = manager.subscribe_object_update(Arc::new(move |event| {
            if event.prim().local_id == 21 {
                postapply.store(
                    read(&postapply_simulator.objects_primitives).contains_key(&21),
                    Ordering::Release,
                );
            }
        }));

        let mut packet = crate::packets::ObjectUpdatePacket::new_with_constructor().unwrap();
        packet.region_data.region_handle = 0x1000;
        packet.region_data.time_dilation = 32_768;
        let mut block =
            crate::packets::ObjectUpdatePacketObjectDataBlock::new_with_constructor().unwrap();
        block.id = 21;
        block.full_id = object_id;
        block.p_code = PCode::Prim as u8;
        block.object_data = vec![0; 60];
        block.scale = Vector3::one();
        packet.object_data = vec![block];
        let wire = transport_wire(&packet.to_bytes_with_method().expect("wire packet"));
        manager
            .inner
            .handle_object_update(&wire, simulator.clone())
            .expect("object update");

        assert!(preapply_saw_absent.load(Ordering::Acquire));
        assert!(postapply_saw_present.load(Ordering::Acquire));
        let prim = read(&simulator.objects_primitives)
            .get(&21)
            .cloned()
            .unwrap();
        assert_eq!(prim.id, object_id);
        assert_eq!(prim.region_handle, 0x1000);
        assert_eq!(
            read(&simulator.global_to_local_id).get(&object_id),
            Some(&21)
        );
    }

    #[test]
    fn property_reply_correlates_uuid_and_updates_tracked_primitive() {
        let (manager, simulator) = context();
        let object_id = UUID::random().expect("uuid");
        manager
            .native_get_primitive(simulator.clone(), 22, object_id, true)
            .expect("primitive");
        let observed = Arc::new(AtomicBool::new(false));
        let captured = Arc::clone(&observed);
        let _subscription = manager.subscribe_object_properties_updated(Arc::new(move |event| {
            captured.store(
                event.prim().local_id == 22 && event.properties().name == "Native object",
                Ordering::Release,
            );
        }));
        let mut packet = crate::packets::ObjectPropertiesPacket::new_with_constructor().unwrap();
        let mut block =
            crate::packets::ObjectPropertiesPacketObjectDataBlock::new_with_constructor().unwrap();
        block.object_id = object_id;
        block.name = b"Native object".to_vec();
        block.description = b"decoded".to_vec();
        packet.object_data = vec![block];
        let wire = transport_wire(&packet.to_bytes_with_method().expect("wire packet"));
        manager
            .inner
            .handle_object_properties(&wire, simulator.clone())
            .expect("properties reply");
        assert!(observed.load(Ordering::Acquire));
        assert_eq!(
            read(&simulator.objects_primitives)
                .get(&22)
                .and_then(|prim| prim.properties.as_ref())
                .map(|properties| properties.description.as_str()),
            Some("decoded")
        );
    }

    #[test]
    fn kill_events_run_before_recursive_collection_removal() {
        let (manager, simulator) = context();
        let root_id = UUID::random().expect("root uuid");
        let child_id = UUID::random().expect("child uuid");
        manager
            .native_get_primitive(simulator.clone(), 10, root_id, true)
            .expect("root");
        let mut child = manager
            .native_get_primitive(simulator.clone(), 11, child_id, true)
            .expect("child");
        child.parent_id = 10;
        write(&simulator.objects_primitives).insert(11, child);
        let observed_before_removal = Arc::new(AtomicBool::new(false));
        let observed = Arc::clone(&observed_before_removal);
        let event_simulator = simulator.clone();
        let _subscription = manager.subscribe_kill_object(Arc::new(move |event| {
            if event.object_local_id() == 10 {
                observed.store(
                    read(&event_simulator.objects_primitives).contains_key(&10),
                    Ordering::Release,
                );
            }
        }));
        let mut packet = crate::packets::KillObjectPacket::new_with_constructor().expect("packet");
        let mut block =
            crate::packets::KillObjectPacketObjectDataBlock::new_with_constructor().expect("block");
        block.id = 10;
        packet.object_data = vec![block];
        manager
            .inner
            .handle_kill_object(
                &packet.to_bytes_with_method().expect("wire packet"),
                simulator.clone(),
            )
            .expect("kill handling");
        assert!(observed_before_removal.load(Ordering::Acquire));
        assert!(read(&simulator.objects_primitives).is_empty());
        assert!(!read(&simulator.global_to_local_id).contains_key(&root_id));
        assert!(!read(&simulator.global_to_local_id).contains_key(&child_id));
    }

    #[test]
    fn border_detection_uses_both_region_dimensions() {
        assert!(
            ObjectManager::native_is_near_border(
                Vector3 {
                    x: 128.0,
                    y: 250.0,
                    z: 0.0
                },
                256,
                256,
                Some(8.0),
            )
            .unwrap()
        );
        assert!(
            !ObjectManager::native_is_near_border(
                Vector3 {
                    x: 128.0,
                    y: 128.0,
                    z: 0.0
                },
                256,
                256,
                Some(8.0),
            )
            .unwrap()
        );
        assert!(
            ObjectManager::native_is_near_border(Vector3::zero(), 256, 256, Some(-1.0)).is_err()
        );
    }

    #[test]
    fn grid_client_returns_one_owned_object_manager() {
        let client = GridClient::new().expect("client");
        let first = client.objects();
        let second = client.objects();
        assert!(Arc::ptr_eq(&first.inner, &second.inner));
    }

    #[test]
    fn concurrent_first_access_returns_one_owned_object_manager() {
        let client = GridClient::new().expect("client");
        let barrier = Arc::new(std::sync::Barrier::new(9));
        let handles = (0..8)
            .map(|_| {
                let client = client.clone();
                let barrier = Arc::clone(&barrier);
                std::thread::spawn(move || {
                    barrier.wait();
                    ObjectManager::new(Some(Arc::new(client)))
                        .expect("object manager")
                        .inner
                })
            })
            .collect::<Vec<_>>();
        barrier.wait();
        let managers = handles
            .into_iter()
            .map(|handle| handle.join().expect("manager thread"))
            .collect::<Vec<_>>();

        assert!(
            managers
                .iter()
                .all(|inner| Arc::ptr_eq(inner, &managers[0]))
        );
        assert!(Arc::ptr_eq(&client.objects().inner, &managers[0]));
    }
}
