Implement land, terrain, environment, and sound managers (#70)
All checks were successful
Native code generation / deterministic (push) Successful in 15m1s
Imaging and meshing gate / native (push) Successful in 5m22s
Native Rust workspace compile / compile (push) Successful in 5m10s

This commit is contained in:
2026-08-10 16:53:35 +00:00
parent 13f85bfa34
commit d537a37172
14 changed files with 4890 additions and 2028 deletions

View File

@@ -119,6 +119,10 @@ struct ClientRuntime {
network_manager: Mutex<std::sync::Weak<crate::network_manager::NetworkManagerInner>>,
agent_manager: Mutex<std::sync::Weak<crate::agent_manager::AgentManagerInner>>,
object_manager: Mutex<Option<Arc<crate::object_manager::ObjectManagerInner>>>,
environment_manager: Mutex<Option<Arc<crate::environment_manager::EnvironmentManagerInner>>>,
terrain_manager: Mutex<Option<Arc<crate::terrain_manager::TerrainManagerInner>>>,
sound_manager: Mutex<Option<Arc<crate::sound_manager::SoundManagerInner>>>,
parcel_manager: Mutex<Option<Arc<crate::parcel_manager::ParcelManagerInner>>>,
appearance_manager: Mutex<Option<Arc<crate::appearance_manager::AppearanceManagerInner>>>,
avatar_manager: Mutex<Option<Arc<crate::avatar_manager::AvatarManagerInner>>>,
animesh_manager: Mutex<Option<Arc<crate::animesh_runtime::AnimeshManagerInner>>>,
@@ -154,6 +158,10 @@ impl ClientRuntime {
network_manager: Mutex::new(std::sync::Weak::new()),
agent_manager: Mutex::new(std::sync::Weak::new()),
object_manager: Mutex::new(None),
environment_manager: Mutex::new(None),
terrain_manager: Mutex::new(None),
sound_manager: Mutex::new(None),
parcel_manager: Mutex::new(None),
appearance_manager: Mutex::new(None),
avatar_manager: Mutex::new(None),
animesh_manager: Mutex::new(None),
@@ -684,6 +692,159 @@ impl GridClient {
self.cache_object_manager_inner(&value.inner);
}
pub(crate) fn install_environment_manager_inner(
&self,
candidate: Arc<crate::environment_manager::EnvironmentManagerInner>,
) -> Arc<crate::environment_manager::EnvironmentManagerInner> {
let mut cached = self
.runtime
.environment_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_environment(&self) -> Result<crate::EnvironmentManager, crate::Error> {
if let Some(inner) = self
.runtime
.environment_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
{
return Ok(crate::EnvironmentManager { inner });
}
crate::environment_manager::EnvironmentManager::native_new(Arc::new(self.clone()))
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn native_set_environment(&self, value: crate::EnvironmentManager) {
*self
.runtime
.environment_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn cached_terrain_manager_inner(
&self,
) -> Option<Arc<crate::terrain_manager::TerrainManagerInner>> {
self.runtime
.terrain_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn install_terrain_manager_inner(
&self,
candidate: Arc<crate::terrain_manager::TerrainManagerInner>,
) -> Arc<crate::terrain_manager::TerrainManagerInner> {
let mut cached = self
.runtime
.terrain_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_terrain(&self) -> Result<crate::TerrainManager, crate::Error> {
if let Some(inner) = self.cached_terrain_manager_inner() {
Ok(crate::TerrainManager { inner })
} else {
crate::terrain_manager::TerrainManager::native_new(Arc::new(self.clone()))
}
}
pub(crate) fn native_set_terrain(&self, value: crate::TerrainManager) {
*self
.runtime
.terrain_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn cached_sound_manager_inner(
&self,
) -> Option<Arc<crate::sound_manager::SoundManagerInner>> {
self.runtime
.sound_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn install_sound_manager_inner(
&self,
candidate: Arc<crate::sound_manager::SoundManagerInner>,
) -> Arc<crate::sound_manager::SoundManagerInner> {
let mut cached = self
.runtime
.sound_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_sound(&self) -> Result<crate::SoundManager, crate::Error> {
if let Some(inner) = self.cached_sound_manager_inner() {
Ok(crate::SoundManager { inner })
} else {
crate::sound_manager::SoundManager::native_new(Arc::new(self.clone()))
}
}
pub(crate) fn native_set_sound(&self, value: crate::SoundManager) {
*self
.runtime
.sound_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn cached_parcel_manager_inner(
&self,
) -> Option<Arc<crate::parcel_manager::ParcelManagerInner>> {
self.runtime
.parcel_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn install_parcel_manager_inner(
&self,
candidate: Arc<crate::parcel_manager::ParcelManagerInner>,
) -> Arc<crate::parcel_manager::ParcelManagerInner> {
let mut cached = self
.runtime
.parcel_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_parcels(&self) -> Result<crate::ParcelManager, crate::Error> {
if let Some(inner) = self.cached_parcel_manager_inner() {
Ok(crate::ParcelManager { inner })
} else {
crate::parcel_manager::ParcelManager::native_new(Arc::new(self.clone()))
}
}
pub(crate) fn native_set_parcels(&self, value: crate::ParcelManager) {
*self
.runtime
.parcel_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn native_set_caps_rate_limiter(
&mut self,
value: crate::caps_http::CapsRateLimiter,

View File

@@ -0,0 +1,651 @@
//! Extended and legacy environment capability support.
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
use crate::agent_manager::EventRegistry;
use crate::{Error, GridClient};
use futures_channel::oneshot;
use futures_util::future::{Either, select};
use futures_util::pin_mut;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
const MAX_ENVIRONMENT_BYTES: usize = 16 * 1024 * 1024;
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 integer(map: &OSDMap, key: &str, default: i32) -> i32 {
map.get(key)
.and_then(|value| value.as_integer().ok())
.unwrap_or(default)
}
fn boolean(map: &OSDMap, key: &str, default: bool) -> bool {
map.get(key)
.and_then(|value| value.as_boolean().ok())
.unwrap_or(default)
}
fn string(map: &OSDMap, key: &str) -> String {
map.get(key)
.and_then(|value| value.as_string().ok())
.unwrap_or_default()
}
#[derive(Clone, Debug, PartialEq)]
pub struct EnvironmentData {
day_cycle: Option<OSD>,
day_length: i32,
day_offset: i32,
flags: crate::messages::linden::EnvironmentFlags,
is_default: bool,
}
impl EnvironmentData {
pub fn new() -> Result<Self, Error> {
Ok(Self {
day_cycle: None,
day_length: 14_400,
day_offset: 57_600,
flags: crate::messages::linden::EnvironmentFlags::NONE,
is_default: false,
})
}
pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.day_length = integer(&map, "day_length", 14_400);
self.day_offset = integer(&map, "day_offset", 57_600);
self.flags = crate::messages::linden::EnvironmentFlags(
u32::try_from(integer(&map, "flags", 0)).unwrap_or_default(),
);
self.is_default = boolean(&map, "is_default", false);
self.day_cycle = map
.get("day_cycle")
.filter(|value| !matches!(value, OSD::Undefined));
Ok(())
}
pub fn serialize(&self) -> Result<OSDMap, Error> {
let mut values = HashMap::from([
("day_length".into(), OSD::Integer(self.day_length)),
("day_offset".into(), OSD::Integer(self.day_offset)),
(
"flags".into(),
OSD::Integer(i32::try_from(self.flags.0).unwrap_or(i32::MAX)),
),
]);
if let Some(day_cycle) = self.day_cycle.clone() {
values.insert("day_cycle".into(), day_cycle);
}
OSDMap::new_with_dictionary(values)
}
pub fn day_cycle(&self) -> Option<OSD> {
self.day_cycle.clone()
}
pub fn set_day_cycle(&mut self, value: Option<OSD>) {
self.day_cycle = value;
}
pub fn day_length(&self) -> i32 {
self.day_length
}
pub fn set_day_length(&mut self, value: i32) {
self.day_length = value;
}
pub fn day_offset(&self) -> i32 {
self.day_offset
}
pub fn set_day_offset(&mut self, value: i32) {
self.day_offset = value;
}
pub fn flags(&self) -> crate::messages::linden::EnvironmentFlags {
self.flags
}
pub fn set_flags(&mut self, value: crate::messages::linden::EnvironmentFlags) {
self.flags = value;
}
pub fn is_default(&self) -> bool {
self.is_default
}
pub fn set_is_default(&mut self, value: bool) {
self.is_default = value;
}
}
impl crate::interfaces::IMessage for EnvironmentData {
fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.deserialize(map)
}
fn serialize(&self) -> Result<OSDMap, Error> {
self.serialize()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ExtEnvironmentMessage {
environment: Option<EnvironmentData>,
message: String,
parcel_id: i32,
success: bool,
version: i32,
}
impl ExtEnvironmentMessage {
pub fn new() -> Result<Self, Error> {
Ok(Self {
environment: None,
message: String::new(),
parcel_id: -1,
success: false,
version: 0,
})
}
pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.success = boolean(&map, "success", false);
self.parcel_id = integer(&map, "parcel_id", -1);
self.version = integer(&map, "version", 0);
self.message = string(&map, "message");
self.environment = match map.get("environment") {
Some(OSD::Map(values)) => {
let mut value = EnvironmentData::new()?;
value.deserialize(OSDMap::new_with_dictionary(values)?)?;
Some(value)
}
_ => None,
};
Ok(())
}
pub fn serialize(&self) -> Result<OSDMap, Error> {
let mut values = HashMap::new();
values.insert("parcel_id".into(), OSD::Integer(self.parcel_id));
if let Some(environment) = &self.environment {
values.insert(
"environment".into(),
OSD::Map(environment.serialize()?.snapshot()),
);
}
if self.version != 0 {
values.insert("version".into(), OSD::Integer(self.version));
}
OSDMap::new_with_dictionary(values)
}
pub fn environment(&self) -> Option<EnvironmentData> {
self.environment.clone()
}
pub fn set_environment(&mut self, value: Option<EnvironmentData>) {
self.environment = value;
}
pub fn message(&self) -> String {
self.message.clone()
}
pub fn set_message(&mut self, value: String) {
self.message = value;
}
pub fn parcel_id(&self) -> i32 {
self.parcel_id
}
pub fn set_parcel_id(&mut self, value: i32) {
self.parcel_id = value;
}
pub fn success(&self) -> bool {
self.success
}
pub fn set_success(&mut self, value: bool) {
self.success = value;
}
pub fn version(&self) -> i32 {
self.version
}
pub fn set_version(&mut self, value: i32) {
self.version = value;
}
}
impl crate::interfaces::IMessage for ExtEnvironmentMessage {
fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.deserialize(map)
}
fn serialize(&self) -> Result<OSDMap, Error> {
self.serialize()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LegacyEnvironmentMessage {
settings: Option<OSD>,
}
impl LegacyEnvironmentMessage {
pub fn new() -> Result<Self, Error> {
Ok(Self { settings: None })
}
pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.settings = Some(OSD::Map(map.snapshot()));
Ok(())
}
pub fn serialize(&self) -> Result<OSDMap, Error> {
match &self.settings {
Some(OSD::Map(values)) => OSDMap::new_with_dictionary(values.clone()),
_ => OSDMap::new_with_constructor(),
}
}
pub fn settings(&self) -> Option<OSD> {
self.settings.clone()
}
pub fn set_settings(&mut self, value: Option<OSD>) {
self.settings = value;
}
}
impl crate::interfaces::IMessage for LegacyEnvironmentMessage {
fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
self.deserialize(map)
}
fn serialize(&self) -> Result<OSDMap, Error> {
self.serialize()
}
}
#[derive(Clone)]
pub struct RegionEnvironmentEventArgs {
environment: ExtEnvironmentMessage,
}
impl RegionEnvironmentEventArgs {
pub fn new(environment: ExtEnvironmentMessage) -> Result<Self, Error> {
Ok(Self { environment })
}
pub fn environment(&self) -> ExtEnvironmentMessage {
self.environment.clone()
}
}
#[derive(Clone)]
pub struct ParcelEnvironmentEventArgs {
parcel_id: i32,
environment: ExtEnvironmentMessage,
}
impl ParcelEnvironmentEventArgs {
pub fn new(parcel_id: i32, environment: ExtEnvironmentMessage) -> Result<Self, Error> {
Ok(Self {
parcel_id,
environment,
})
}
pub fn parcel_id(&self) -> i32 {
self.parcel_id
}
pub fn environment(&self) -> ExtEnvironmentMessage {
self.environment.clone()
}
}
#[derive(Clone)]
pub struct LegacyEnvironmentEventArgs {
environment: LegacyEnvironmentMessage,
}
impl LegacyEnvironmentEventArgs {
pub fn new(environment: LegacyEnvironmentMessage) -> Result<Self, Error> {
Ok(Self { environment })
}
pub fn environment(&self) -> LegacyEnvironmentMessage {
self.environment.clone()
}
}
pub(crate) struct EnvironmentManagerInner {
client: Arc<GridClient>,
disposed: AtomicBool,
region: RwLock<Option<ExtEnvironmentMessage>>,
legacy: RwLock<Option<LegacyEnvironmentMessage>>,
region_events: EventRegistry<RegionEnvironmentEventArgs>,
parcel_events: EventRegistry<ParcelEnvironmentEventArgs>,
legacy_events: EventRegistry<LegacyEnvironmentEventArgs>,
}
#[derive(Clone)]
pub struct EnvironmentManager {
pub(crate) inner: Arc<EnvironmentManagerInner>,
}
impl EnvironmentManager {
pub fn new(client: GridClient) -> Result<Self, Error> {
Self::native_new(Arc::new(client))
}
#[allow(clippy::unnecessary_wraps)] // The mapped public constructor has a fixed fallible shape.
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
let candidate = Arc::new(EnvironmentManagerInner {
client: Arc::clone(&client),
disposed: AtomicBool::new(false),
region: RwLock::new(None),
legacy: RwLock::new(None),
region_events: EventRegistry::default(),
parcel_events: EventRegistry::default(),
legacy_events: EventRegistry::default(),
});
Ok(Self {
inner: client.install_environment_manager_inner(candidate),
})
}
async fn capability(
&self,
name: &str,
token: &CancellationToken,
) -> Result<Option<Uri>, Error> {
if self.inner.disposed.load(Ordering::Acquire) {
return Err(Error::InvalidOperation);
}
let Some(caps) = self
.inner
.client
.network()
.current_sim()
.and_then(|sim| sim.native_caps())
else {
return Ok(None);
};
if let Some(uri) = caps.capability_uri(name.to_owned())? {
return Ok(Some(uri));
}
if caps.seed_request_finished() {
return Ok(None);
}
let (sender, receiver) = oneshot::channel();
let sender = Arc::new(std::sync::Mutex::new(Some(sender)));
let wake = Arc::clone(&sender);
let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| {
if let Some(sender) = wake
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
{
let _ = sender.send(());
}
})));
if let Some(uri) = caps.capability_uri(name.to_owned())? {
return Ok(Some(uri));
}
let cancellation = token.cancelled();
pin_mut!(receiver, cancellation);
match select(receiver, cancellation).await {
Either::Left(_) => caps.capability_uri(name.to_owned()),
Either::Right(_) => Err(Error::Cancelled),
}
}
async fn request(
&self,
method: &str,
uri: Uri,
body: Vec<u8>,
token: Option<CancellationToken>,
) -> Result<Option<Vec<u8>>, Error> {
if body.len() > MAX_ENVIRONMENT_BYTES {
return Err(Error::Argument);
}
let http = self.inner.client.native_http_caps_client();
let token = token.unwrap_or_default();
let (response, bytes) = match method {
"GET" => http.get(uri, token, None).await?,
"POST" => {
http.post_with_uri_string_bytes_cancellation_token_i_progress(
uri,
"application/llsd+xml".into(),
body,
token,
None,
)
.await?
}
"DELETE" => {
http.delete_with_uri_string_bytes_cancellation_token_i_progress(
uri,
"application/llsd+xml".into(),
body,
token,
None,
)
.await?
}
_ => return Err(Error::Argument),
};
if bytes.len() > MAX_ENVIRONMENT_BYTES {
return Err(Error::Argument);
}
Ok((200..300).contains(&response.status_code).then_some(bytes))
}
fn parcel_uri(uri: Uri, parcel_id: i32) -> Uri {
let separator = if uri.0.contains('?') { '&' } else { '?' };
Uri(format!("{}{separator}parcelid={parcel_id}", uri.0))
}
fn decode_ext(bytes: Vec<u8>) -> Result<ExtEnvironmentMessage, Error> {
let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
return Err(Error::Argument);
};
let mut message = ExtEnvironmentMessage::new()?;
message.deserialize(OSDMap::new_with_dictionary(values)?)?;
Ok(message)
}
pub async fn get_region_environment(
&self,
token: Option<CancellationToken>,
) -> Result<Option<ExtEnvironmentMessage>, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else {
return Ok(None);
};
let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else {
return Ok(None);
};
let message = Self::decode_ext(bytes)?;
*write(&self.inner.region) = Some(message.clone());
self.inner
.region_events
.emit(RegionEnvironmentEventArgs::new(message.clone())?);
Ok(Some(message))
}
pub async fn get_parcel_environment(
&self,
parcel_id: i32,
token: Option<CancellationToken>,
) -> Result<Option<ExtEnvironmentMessage>, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else {
return Ok(None);
};
let Some(bytes) = self
.request("GET", Self::parcel_uri(uri, parcel_id), Vec::new(), token)
.await?
else {
return Ok(None);
};
let message = Self::decode_ext(bytes)?;
self.inner
.parcel_events
.emit(ParcelEnvironmentEventArgs::new(parcel_id, message.clone())?);
Ok(Some(message))
}
async fn set_environment(
&self,
parcel_id: Option<i32>,
environment: EnvironmentData,
token: Option<CancellationToken>,
) -> Result<Option<ExtEnvironmentMessage>, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else {
return Ok(None);
};
if let Some(parcel_id) = parcel_id {
uri = Self::parcel_uri(uri, parcel_id);
}
let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([(
"environment".into(),
OSD::Map(environment.serialize()?.snapshot()),
)])))?;
let Some(bytes) = self.request("POST", uri, body, token).await? else {
return Ok(None);
};
Self::decode_ext(bytes).map(Some)
}
pub async fn set_parcel_environment(
&self,
parcel_id: i32,
environment: EnvironmentData,
token: Option<CancellationToken>,
) -> Result<Option<ExtEnvironmentMessage>, Error> {
self.set_environment(Some(parcel_id), environment, token)
.await
}
pub async fn set_region_environment_with_environment_data_cancellation_token(
&self,
environment: EnvironmentData,
token: Option<CancellationToken>,
) -> Result<Option<ExtEnvironmentMessage>, Error> {
self.set_environment(None, environment, token).await
}
async fn reset_environment(
&self,
parcel_id: Option<i32>,
token: Option<CancellationToken>,
) -> Result<bool, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else {
return Ok(false);
};
if let Some(parcel_id) = parcel_id {
uri = Self::parcel_uri(uri, parcel_id);
}
let Some(bytes) = self.request("DELETE", uri, Vec::new(), token).await? else {
return Ok(false);
};
Ok(Self::decode_ext(bytes)?.success())
}
pub async fn reset_parcel_environment(
&self,
parcel_id: i32,
token: Option<CancellationToken>,
) -> Result<bool, Error> {
self.reset_environment(Some(parcel_id), token).await
}
pub async fn reset_region_environment(
&self,
token: Option<CancellationToken>,
) -> Result<bool, Error> {
self.reset_environment(None, token).await
}
pub async fn get_legacy_environment(
&self,
token: Option<CancellationToken>,
) -> Result<Option<LegacyEnvironmentMessage>, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else {
return Ok(None);
};
let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else {
return Ok(None);
};
let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
return Err(Error::Argument);
};
let mut message = LegacyEnvironmentMessage::new()?;
message.deserialize(OSDMap::new_with_dictionary(values)?)?;
*write(&self.inner.legacy) = Some(message.clone());
self.inner
.legacy_events
.emit(LegacyEnvironmentEventArgs::new(message.clone())?);
Ok(Some(message))
}
pub async fn set_legacy_environment_with_osd_cancellation_token(
&self,
settings: OSD,
token: Option<CancellationToken>,
) -> Result<bool, Error> {
let resolved = token.clone().unwrap_or_default();
let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else {
return Ok(false);
};
let body = OSDParser::serialize_llsd_xml_bytes(settings)?;
let Some(bytes) = self.request("POST", uri, body, token).await? else {
return Ok(false);
};
let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
return Ok(false);
};
Ok(values
.get("success")
.and_then(|value| value.as_boolean().ok())
.unwrap_or(true))
}
pub fn region_environment(&self) -> Option<ExtEnvironmentMessage> {
read(&self.inner.region).clone()
}
pub fn set_region_environment(&mut self, value: Option<ExtEnvironmentMessage>) {
*write(&self.inner.region) = value;
}
pub fn legacy_environment(&self) -> Option<LegacyEnvironmentMessage> {
read(&self.inner.legacy).clone()
}
pub fn set_legacy_environment(&mut self, value: Option<LegacyEnvironmentMessage>) {
*write(&self.inner.legacy) = value;
}
pub fn subscribe_region_environment_updated(
&self,
handler: EventHandler<RegionEnvironmentEventArgs>,
) -> Subscription {
self.inner.region_events.subscribe(handler)
}
pub fn subscribe_parcel_environment_updated(
&self,
handler: EventHandler<ParcelEnvironmentEventArgs>,
) -> Subscription {
self.inner.parcel_events.subscribe(handler)
}
pub fn subscribe_legacy_environment_updated(
&self,
handler: EventHandler<LegacyEnvironmentEventArgs>,
) -> Subscription {
self.inner.legacy_events.subscribe(handler)
}
pub fn dispose(&self) -> Result<(), Error> {
self.inner.disposed.store(true, Ordering::Release);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn environment_defaults_and_wire_shape_match_eep() {
let mut value = EnvironmentData::new().unwrap();
assert_eq!(value.day_length(), 14_400);
assert_eq!(value.day_offset(), 57_600);
value.set_is_default(true);
let map = value.serialize().unwrap();
assert!(!map.contains_key("is_default".into()).unwrap());
assert!(!map.contains_key("day_cycle".into()).unwrap());
}
#[test]
fn ext_environment_round_trip_preserves_correlation_fields() {
let mut value = ExtEnvironmentMessage::new().unwrap();
value.set_parcel_id(17);
value.set_version(9);
value.set_environment(Some(EnvironmentData::new().unwrap()));
let mut decoded = ExtEnvironmentMessage::new().unwrap();
decoded.deserialize(value.serialize().unwrap()).unwrap();
assert_eq!(decoded.parcel_id(), 17);
assert_eq!(decoded.version(), 9);
assert!(decoded.environment().is_some());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ mod caps_http;
mod client_core;
mod current_outfit;
mod download_manager;
mod environment_manager;
mod event_queue;
mod gesture;
mod initial_outfit;
@@ -48,11 +49,15 @@ mod object_model;
#[rustfmt::skip]
pub mod packet_catalog;
mod packet_wire;
mod parcel_manager;
mod sim_stats;
mod skeleton;
mod sound_manager;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod skeleton_catalog;
mod targa;
mod terrain_codec;
mod terrain_manager;
mod transfers;
mod udp_transport;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.

View File

@@ -22,8 +22,8 @@ use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransp
use crate::{
AccountLevelBenefits, Avatar, Caps, Error, GenericStreamingMethod, GridClient, Helpers,
LoginCredential, LoginParams, LoginProgressEventArgs, LoginResponseData, LoginStatus,
NetworkManagerLoginResponseCallback, Primitive, RegionFlags, RegionProtocols, SimAccess,
SimulatorDataPool, SimulatorFeatures, SimulatorSimStats, TerrainPatch,
NetworkManagerLoginResponseCallback, Parcel, Primitive, RegionFlags, RegionProtocols,
SimAccess, SimulatorDataPool, SimulatorFeatures, SimulatorSimStats, TerrainPatch,
};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
use libremetaverse_types::compat::{
@@ -978,8 +978,11 @@ pub struct SimulatorData {
pub name: String,
pub objects_avatars: RwLock<HashMap<u32, Avatar>>,
pub objects_primitives: RwLock<HashMap<u32, Primitive>>,
pub parcel_overlay: Vec<u8>,
pub parcel_overlays_received: i32,
pub parcel_overlay: RwLock<Vec<u8>>,
pub parcel_overlays_received: AtomicI32,
parcel_overlay_segments: AtomicU8,
pub parcels: RwLock<HashMap<i32, Parcel>>,
pub parcel_map: RwLock<Vec<i32>>,
pub product_name: String,
pub product_sku: String,
pub protocols: RegionProtocols,
@@ -990,7 +993,7 @@ pub struct SimulatorData {
pub size_x: u32,
pub size_y: u32,
pub stats: SimulatorSimStats,
pub terrain: Vec<TerrainPatch>,
pub terrain: RwLock<Vec<TerrainPatch>>,
pub terrain_base0: UUID,
pub terrain_base1: UUID,
pub terrain_base2: UUID,
@@ -1008,7 +1011,7 @@ pub struct SimulatorData {
pub terrain_start_height10: f32,
pub terrain_start_height11: f32,
pub water_height: f32,
pub wind_speeds: Option<Vec<Vector2>>,
pub wind_speeds: RwLock<Option<Vec<Vector2>>>,
endpoint: std::net::SocketAddr,
connected: AtomicBool,
movement_complete: AtomicBool,
@@ -1141,8 +1144,11 @@ impl Simulator {
name: String::new(),
objects_avatars: RwLock::new(HashMap::new()),
objects_primitives: RwLock::new(HashMap::new()),
parcel_overlay: vec![0; 4096],
parcel_overlays_received: 0,
parcel_overlay: RwLock::new(vec![0; 4096]),
parcel_overlays_received: AtomicI32::new(0),
parcel_overlay_segments: AtomicU8::new(0),
parcels: RwLock::new(HashMap::new()),
parcel_map: RwLock::new(vec![0; 4096]),
product_name: String::new(),
product_sku: String::new(),
protocols: RegionProtocols(0),
@@ -1153,7 +1159,7 @@ impl Simulator {
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
stats: SimulatorSimStats::default(),
terrain: Vec::new(),
terrain: RwLock::new(Vec::new()),
terrain_base0: UUID::zero(),
terrain_base1: UUID::zero(),
terrain_base2: UUID::zero(),
@@ -1171,7 +1177,7 @@ impl Simulator {
terrain_start_height10: 0.0,
terrain_start_height11: 0.0,
water_height: 0.0,
wind_speeds: None,
wind_speeds: RwLock::new(None),
endpoint: address,
connected: AtomicBool::new(false),
movement_complete: AtomicBool::new(false),
@@ -1220,6 +1226,187 @@ impl Simulator {
self.data.id
}
pub(crate) fn native_store_terrain_patch(
&self,
patch: TerrainPatch,
large_region: bool,
) -> Result<(), Error> {
let (width, height) = if large_region {
(
usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?,
usize::try_from(self.size_y.max(16) / 16).map_err(|_| Error::Argument)?,
)
} else {
(16, 16)
};
let x = usize::try_from(patch.x).map_err(|_| Error::Argument)?;
let y = usize::try_from(patch.y).map_err(|_| Error::Argument)?;
if x >= width || y >= height || width > 256 || height > 256 {
return Err(Error::Argument);
}
let index = y
.checked_mul(width)
.and_then(|value| value.checked_add(x))
.ok_or(Error::Argument)?;
let patch_count = width.checked_mul(height).ok_or(Error::Argument)?;
let mut terrain = write(&self.terrain);
terrain.resize_with(patch_count, || TerrainPatch {
data: Vec::new(),
x: 0,
y: 0,
});
terrain[index] = patch;
Ok(())
}
pub(crate) fn native_store_wind(&self, values: Vec<Vector2>) {
*write(&self.wind_speeds) = Some(values);
}
pub(crate) fn native_apply_parcel_overlay(
&self,
sequence_id: i32,
data: Vec<u8>,
) -> Result<bool, Error> {
if !(0..4).contains(&sequence_id) {
return Err(Error::Argument);
}
let mut overlay = write(&self.parcel_overlay);
let segment_size = overlay.len().checked_div(4).ok_or(Error::Argument)?;
if data.len() != segment_size {
return Err(Error::Argument);
}
let sequence = usize::try_from(sequence_id).map_err(|_| Error::Argument)?;
let offset = sequence.checked_mul(segment_size).ok_or(Error::Argument)?;
let end = offset.checked_add(segment_size).ok_or(Error::Argument)?;
overlay[offset..end].copy_from_slice(&data);
// The overlay may be retransmitted or arrive out of order. Count unique
// sequence IDs so duplicates cannot publish an incomplete map.
let bit = 1_u8 << sequence;
let old_segments = self.parcel_overlay_segments.load(Ordering::Acquire);
let segments = old_segments | bit;
self.parcel_overlay_segments
.store(segments, Ordering::Release);
let received = i32::try_from(segments.count_ones()).map_err(|_| Error::Argument)?;
self.parcel_overlays_received
.store(received, Ordering::Release);
if segments == 0b1111 {
self.parcel_overlay_segments.store(0, Ordering::Release);
self.parcel_overlays_received.store(0, Ordering::Release);
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn native_store_parcel(&self, parcel: Parcel) {
if parcel.bitmap.len() == 512 {
let mut map = write(&self.parcel_map);
for cell in 0..4096 {
if parcel.bitmap[cell / 8] & (1 << (cell % 8)) != 0 {
map[cell] = parcel.local_id;
}
}
}
write(&self.parcels).insert(parcel.local_id, parcel);
}
pub(crate) fn native_parcel(&self, local_id: i32) -> Option<Parcel> {
read(&self.parcels).get(&local_id).cloned()
}
pub(crate) fn native_parcel_map_at(&self, x: usize, y: usize) -> Option<i32> {
if x >= 64 || y >= 64 {
return None;
}
read(&self.parcel_map).get(y * 64 + x).copied()
}
pub(crate) fn native_parcels_snapshot(&self) -> HashMap<i32, Parcel> {
read(&self.parcels).clone()
}
pub(crate) fn native_parcel_map_snapshot(&self) -> Vec<i32> {
read(&self.parcel_map).clone()
}
pub(crate) fn native_is_parcel_map_full(&self) -> bool {
read(&self.parcel_map).iter().all(|value| *value > 0)
}
pub(crate) fn native_terrain_height_at_point(
&self,
x: i32,
y: i32,
height: &mut f32,
) -> Result<bool, Error> {
let (Ok(x), Ok(y)) = (u32::try_from(x), u32::try_from(y)) else {
return Ok(false);
};
if x >= self.size_x || y >= self.size_y {
return Ok(false);
}
let per_edge = usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?;
let patch_x = usize::try_from(x / 16).map_err(|_| Error::Argument)?;
let patch_y = usize::try_from(y / 16).map_err(|_| Error::Argument)?;
let point = usize::try_from((y % 16) * 16 + (x % 16)).map_err(|_| Error::Argument)?;
let terrain = read(&self.terrain);
let Some(patch) = terrain.get(patch_y * per_edge + patch_x) else {
return Ok(false);
};
let Some(value) = patch.data.get(point) else {
return Ok(false);
};
*height = *value;
Ok(true)
}
pub(crate) fn native_clear_parcels(&self) {
write(&self.parcels).clear();
write(&self.parcel_map).fill(0);
}
pub(crate) fn native_update_parcel_dwell(&self, local_id: i32, dwell: f32) {
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
parcel.dwell = dwell;
}
}
pub(crate) fn native_update_parcel_access(
&self,
local_id: i32,
flags: u32,
entries: Vec<crate::ParcelManagerParcelAccessEntry>,
) {
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
if flags & crate::AccessList::BAN.0 != 0 {
parcel.access_black_list = entries;
} else {
parcel.access_white_list = entries;
}
}
}
pub(crate) fn native_send_parcel_clean_time(
&self,
local_id: i32,
clean_time: i32,
) -> Result<(), Error> {
let mut packet = crate::packets::ParcelSetOtherCleanTimePacket::new_with_constructor()?;
packet.agent_data.agent_id = *read(&self.data.agent_id);
packet.agent_data.session_id = *read(&self.data.session_id);
packet.parcel_data.local_id = local_id;
packet.parcel_data.other_clean_time = clean_time;
let data = packet.to_bytes_with_method()?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::ParcelSetOtherCleanTime,
false,
)
}
pub(crate) fn native_from_weak(data: &Weak<SimulatorData>) -> Option<Self> {
data.upgrade().map(|data| Self { caps: None, data })
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,497 @@
//! Sound trigger routing and simulator sound event decoding.
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::collapsible_if)] // Packet decoding and payload bounds remain distinct checks.
#![allow(clippy::unnecessary_wraps)] // Mapped constructors have fixed fallible signatures.
use crate::agent_manager::EventRegistry;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, GridClient, Simulator};
use libremetaverse_types::compat::{EventHandler, Subscription};
use libremetaverse_types::{SoundFlags, UUID, Vector3};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
const MAX_SOUND_PACKET_BYTES: usize = 1024 * 1024;
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[derive(Clone)]
pub struct AttachedSoundEventArgs {
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
gain: f32,
flags: SoundFlags,
}
impl AttachedSoundEventArgs {
pub fn new(
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
gain: f32,
flags: SoundFlags,
) -> Result<Self, Error> {
Ok(Self {
sim,
sound_id,
owner_id,
object_id,
gain,
flags,
})
}
pub fn simulator(&self) -> Simulator {
self.sim.clone()
}
pub fn sound_id(&self) -> UUID {
self.sound_id
}
pub fn owner_id(&self) -> UUID {
self.owner_id
}
pub fn object_id(&self) -> UUID {
self.object_id
}
pub fn gain(&self) -> f32 {
self.gain
}
pub fn flags(&self) -> SoundFlags {
self.flags
}
}
#[derive(Clone)]
pub struct AttachedSoundGainChangeEventArgs {
sim: Simulator,
object_id: UUID,
gain: f32,
}
impl AttachedSoundGainChangeEventArgs {
pub fn new(sim: Simulator, object_id: UUID, gain: f32) -> Result<Self, Error> {
Ok(Self {
sim,
object_id,
gain,
})
}
pub fn simulator(&self) -> Simulator {
self.sim.clone()
}
pub fn object_id(&self) -> UUID {
self.object_id
}
pub fn gain(&self) -> f32 {
self.gain
}
}
#[derive(Clone)]
pub struct PreloadSoundEventArgs {
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
}
impl PreloadSoundEventArgs {
pub fn new(
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
) -> Result<Self, Error> {
Ok(Self {
sim,
sound_id,
owner_id,
object_id,
})
}
pub fn simulator(&self) -> Simulator {
self.sim.clone()
}
pub fn sound_id(&self) -> UUID {
self.sound_id
}
pub fn owner_id(&self) -> UUID {
self.owner_id
}
pub fn object_id(&self) -> UUID {
self.object_id
}
}
#[derive(Clone)]
pub struct SoundTriggerEventArgs {
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
parent_id: UUID,
gain: f32,
region_handle: u64,
position: Vector3,
}
impl SoundTriggerEventArgs {
pub fn new(
sim: Simulator,
sound_id: UUID,
owner_id: UUID,
object_id: UUID,
parent_id: UUID,
gain: f32,
region_handle: u64,
position: Vector3,
) -> Result<Self, Error> {
Ok(Self {
sim,
sound_id,
owner_id,
object_id,
parent_id,
gain,
region_handle,
position,
})
}
pub fn simulator(&self) -> Simulator {
self.sim.clone()
}
pub fn sound_id(&self) -> UUID {
self.sound_id
}
pub fn owner_id(&self) -> UUID {
self.owner_id
}
pub fn object_id(&self) -> UUID {
self.object_id
}
pub fn parent_id(&self) -> UUID {
self.parent_id
}
pub fn gain(&self) -> f32 {
self.gain
}
pub fn region_handle(&self) -> u64 {
self.region_handle
}
pub fn position(&self) -> Vector3 {
self.position
}
}
#[derive(Default)]
struct SoundEvents {
attached: EventRegistry<AttachedSoundEventArgs>,
gain: EventRegistry<AttachedSoundGainChangeEventArgs>,
preload: EventRegistry<PreloadSoundEventArgs>,
trigger: EventRegistry<SoundTriggerEventArgs>,
}
pub(crate) struct SoundManagerInner {
client: Arc<GridClient>,
events: SoundEvents,
raw_subscription: Mutex<Option<Subscription>>,
disposed: AtomicBool,
}
#[derive(Clone)]
pub struct SoundManager {
pub(crate) inner: Arc<SoundManagerInner>,
}
impl SoundManager {
pub fn new(client: GridClient) -> Result<Self, Error> {
Self::native_new(Arc::new(client))
}
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
if let Some(inner) = client.cached_sound_manager_inner() {
return Ok(Self { inner });
}
let inner = Arc::new(SoundManagerInner {
client: Arc::clone(&client),
events: SoundEvents::default(),
raw_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(event);
}
},
)));
Ok(Self {
inner: client.install_sound_manager_inner(inner),
})
}
pub fn subscribe_attached_sound(
&self,
handler: EventHandler<AttachedSoundEventArgs>,
) -> Subscription {
self.inner.events.attached.subscribe(handler)
}
pub fn subscribe_attached_sound_gain_change(
&self,
handler: EventHandler<AttachedSoundGainChangeEventArgs>,
) -> Subscription {
self.inner.events.gain.subscribe(handler)
}
pub fn subscribe_preload_sound(
&self,
handler: EventHandler<PreloadSoundEventArgs>,
) -> Subscription {
self.inner.events.preload.subscribe(handler)
}
pub fn subscribe_sound_trigger(
&self,
handler: EventHandler<SoundTriggerEventArgs>,
) -> Subscription {
self.inner.events.trigger.subscribe(handler)
}
pub fn dispose(&self) -> Result<(), Error> {
self.inner.disposed.store(true, Ordering::Release);
mutex(&self.inner.raw_subscription).take();
Ok(())
}
pub fn play_sound(&self, sound_id: UUID) -> Result<(), Error> {
let mut client = (*self.inner.client).clone();
let position = client.self_().sim_position();
self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0)
}
pub fn send_sound_trigger_with_uuid_vector3(
&self,
sound_id: UUID,
position: Vector3,
) -> Result<(), Error> {
self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0)
}
pub fn send_sound_trigger_with_uuid_vector3_single(
&self,
sound_id: UUID,
position: Vector3,
gain: f32,
) -> Result<(), Error> {
let sim = self
.inner
.client
.network()
.current_sim()
.ok_or(Error::InvalidOperation)?;
self.send_to_sim(sound_id, &sim, sim.handle, position, gain)
}
pub fn send_sound_trigger_with_uuid_simulator_vector3_single(
&self,
sound_id: UUID,
sim: Simulator,
position: Vector3,
gain: f32,
) -> Result<(), Error> {
self.send_to_sim(sound_id, &sim, sim.handle, position, gain)
}
pub fn send_sound_trigger_with_uuid_u_int64_vector3_single(
&self,
sound_id: UUID,
handle: u64,
position: Vector3,
gain: f32,
) -> Result<(), Error> {
let sim = self
.inner
.client
.network()
.current_sim()
.ok_or(Error::InvalidOperation)?;
self.send_to_sim(sound_id, &sim, handle, position, gain)
}
fn send_to_sim(
&self,
sound_id: UUID,
sim: &Simulator,
handle: u64,
position: Vector3,
gain: f32,
) -> Result<(), Error> {
if self.inner.disposed.load(Ordering::Acquire) {
return Err(Error::InvalidOperation);
}
if sound_id == UUID::zero()
|| !gain.is_finite()
|| !position.x.is_finite()
|| !position.y.is_finite()
|| !position.z.is_finite()
{
return Err(Error::Argument);
}
let mut packet = crate::packets::SoundTriggerPacket::new_with_constructor()?;
packet.sound_data.sound_id = sound_id;
packet.sound_data.object_id = UUID::zero();
packet.sound_data.owner_id = UUID::zero();
packet.sound_data.parent_id = UUID::zero();
packet.sound_data.handle = handle;
packet.sound_data.position = position;
packet.sound_data.gain = gain.clamp(0.0, 1.0);
let bytes = packet.to_bytes_with_method()?;
sim.native_send_packet_data(
bytes.clone(),
i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
PacketType::SoundTrigger,
false,
)
}
}
impl SoundManagerInner {
fn handle(&self, event: crate::network_manager::RawPacketReceivedEventArgs) {
if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_SOUND_PACKET_BYTES {
return;
}
let mut position = 0;
match event.packet_type {
PacketType::AttachedSound => {
if let Ok(packet) =
crate::packets::AttachedSoundPacket::new_from_bytes(&event.data, &mut position)
{
let value = packet.data_block;
self.events.attached.emit(AttachedSoundEventArgs {
sim: event.simulator,
sound_id: value.sound_id,
owner_id: value.owner_id,
object_id: value.object_id,
gain: value.gain,
flags: SoundFlags(value.flags),
});
}
}
PacketType::AttachedSoundGainChange => {
if let Ok(packet) = crate::packets::AttachedSoundGainChangePacket::new_from_bytes(
&event.data,
&mut position,
) {
self.events.gain.emit(AttachedSoundGainChangeEventArgs {
sim: event.simulator,
object_id: packet.data_block.object_id,
gain: packet.data_block.gain,
});
}
}
PacketType::PreloadSound => {
if let Ok(packet) =
crate::packets::PreloadSoundPacket::new_from_bytes(&event.data, &mut position)
{
if packet.data_block.len() <= 65_535 {
for value in packet.data_block {
self.events.preload.emit(PreloadSoundEventArgs {
sim: event.simulator.clone(),
sound_id: value.sound_id,
owner_id: value.owner_id,
object_id: value.object_id,
});
}
}
}
}
PacketType::SoundTrigger => {
if let Ok(packet) =
crate::packets::SoundTriggerPacket::new_from_bytes(&event.data, &mut position)
{
let value = packet.sound_data;
self.events.trigger.emit(SoundTriggerEventArgs {
sim: event.simulator,
sound_id: value.sound_id,
owner_id: value.owner_id,
object_id: value.object_id,
parent_id: value.parent_id,
gain: value.gain,
region_handle: value.handle,
position: value.position,
});
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sound_trigger_rejects_zero_ids_and_non_finite_gain() {
let client = GridClient::new().unwrap();
let manager = SoundManager::new(client).unwrap();
assert!(
manager
.send_sound_trigger_with_uuid_u_int64_vector3_single(
UUID::zero(),
0,
Vector3::default(),
1.0
)
.is_err()
);
}
#[test]
fn event_args_preserve_gain_and_queue_identity() {
let client = GridClient::new().unwrap();
let sim = Simulator::new(client, "127.0.0.1:13".parse().unwrap(), 0, None, None).unwrap();
let id = UUID::random().unwrap();
let value =
AttachedSoundEventArgs::new(sim, id, UUID::zero(), UUID::zero(), 0.25, SoundFlags(2))
.unwrap();
assert_eq!(value.sound_id(), id);
assert_eq!(value.gain(), 0.25);
assert_eq!(value.flags(), SoundFlags(2));
}
#[test]
fn attached_sound_packet_preserves_ids_gain_and_queue_flag() {
let client = GridClient::new().unwrap();
let sim = Simulator::new(
client.clone(),
"127.0.0.1:13".parse().unwrap(),
0,
None,
None,
)
.unwrap();
let manager = SoundManager::new(client).unwrap();
let observed = Arc::new(Mutex::new(None));
let captured = Arc::clone(&observed);
let _subscription = manager.subscribe_attached_sound(Arc::new(move |event| {
*mutex(&captured) = Some((
event.sound_id(),
event.owner_id(),
event.object_id(),
event.gain(),
event.flags(),
));
}));
let sound_id = UUID::random().unwrap();
let owner_id = UUID::random().unwrap();
let object_id = UUID::random().unwrap();
let mut packet = crate::packets::AttachedSoundPacket::new_with_constructor().unwrap();
packet.data_block.sound_id = sound_id;
packet.data_block.owner_id = owner_id;
packet.data_block.object_id = object_id;
packet.data_block.gain = 0.375;
packet.data_block.flags = 2;
manager
.inner
.handle(crate::network_manager::RawPacketReceivedEventArgs {
packet_type: PacketType::AttachedSound,
data: packet.to_bytes_with_method().unwrap(),
simulator: sim,
});
assert_eq!(
mutex(&observed).clone(),
Some((sound_id, owner_id, object_id, 0.375, SoundFlags(2)))
);
}
}

View File

@@ -0,0 +1,578 @@
//! Second Life terrain patch codec and layer packet construction.
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_wrap)] // Bit widths and bounded patch coordinates are range-checked.
#![allow(clippy::cast_sign_loss)] // Patch coordinates are validated before indexing.
use crate::{BitPack, Error, TerrainPatchLayerType};
const PATCH_SIZE: usize = 16;
const PATCH_VALUES: usize = PATCH_SIZE * PATCH_SIZE;
const STRIDE: i32 = 264;
const OO_SQRT2: f32 = std::f32::consts::FRAC_1_SQRT_2;
#[derive(Clone, Debug, PartialEq)]
pub struct TerrainPatch {
pub data: Vec<f32>,
pub x: i32,
pub y: i32,
}
impl TerrainPatch {
pub fn new() -> Result<Self, Error> {
Ok(Self {
data: Vec::new(),
x: 0,
y: 0,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TerrainPatchGroupHeader {
pub stride: i32,
pub patch_size: i32,
pub type_: TerrainPatchLayerType,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TerrainPatchHeader {
pub dc_offset: f32,
pub range: i32,
pub quant_w_bits: i32,
pub patch_i_ds: i32,
pub word_bits: u32,
large_region: bool,
}
impl Default for TerrainPatchHeader {
fn default() -> Self {
Self {
dc_offset: 0.0,
range: 0,
quant_w_bits: 0,
patch_i_ds: 0,
word_bits: 0,
large_region: false,
}
}
}
impl TerrainPatchHeader {
pub fn set_patch_i_ds(&mut self, xx: i32, yy: i32) -> Result<i32, Error> {
let valid = if self.large_region {
(0..=65_535).contains(&xx) && (0..=65_535).contains(&yy)
} else {
(0..=31).contains(&xx) && (0..=31).contains(&yy)
};
if !valid {
return Err(Error::Argument);
}
self.patch_i_ds = if self.large_region {
(xx << 16) | (yy & 0xffff)
} else {
(xx << 5) | (yy & 0x1f)
};
Ok(self.patch_i_ds)
}
pub fn large_region(&self) -> bool {
self.large_region
}
pub fn set_large_region(&mut self, value: bool) {
self.large_region = value;
}
pub fn x(&self) -> i32 {
self.patch_i_ds >> if self.large_region { 16 } else { 5 }
}
pub fn set_x(&mut self, value: i32) {
let _ = self.set_patch_i_ds(value, self.y());
}
pub fn y(&self) -> i32 {
self.patch_i_ds & if self.large_region { 0xffff } else { 0x1f }
}
pub fn set_y(&mut self, value: i32) {
let _ = self.set_patch_i_ds(self.x(), value);
}
}
pub struct TerrainCompressor;
impl TerrainCompressor {
pub const PATCHES_PER_EDGE: i32 = 16;
pub const END_OF_PATCHES: i32 = 97;
fn copy_matrix() -> [usize; PATCH_VALUES] {
let mut result = [0; PATCH_VALUES];
let (mut diag, mut right, mut i, mut j, mut count) = (false, true, 0usize, 0usize, 0usize);
while i < PATCH_SIZE && j < PATCH_SIZE {
result[j * PATCH_SIZE + i] = count;
count += 1;
if !diag {
if right {
if i < PATCH_SIZE - 1 {
i += 1;
} else {
j += 1;
}
} else if j < PATCH_SIZE - 1 {
j += 1;
} else {
i += 1;
}
right = !right;
diag = true;
} else if right {
i += 1;
j -= 1;
if i == PATCH_SIZE - 1 || j == 0 {
diag = false;
}
} else {
i -= 1;
j += 1;
if j == PATCH_SIZE - 1 || i == 0 {
diag = false;
}
}
}
result
}
fn cosine(u: usize, n: usize) -> f32 {
(((2 * n + 1) as f32) * (u as f32) * std::f32::consts::PI * 0.5 / PATCH_SIZE as f32).cos()
}
fn prescan(data: &[f32]) -> Result<TerrainPatchHeader, Error> {
if data.len() != PATCH_VALUES || data.iter().any(|value| !value.is_finite()) {
return Err(Error::Argument);
}
let mut min = f32::INFINITY;
let mut max = f32::NEG_INFINITY;
for value in data {
min = min.min(*value);
max = max.max(*value);
}
Ok(TerrainPatchHeader {
dc_offset: min,
range: ((max - min) + 1.0) as i32,
..TerrainPatchHeader::default()
})
}
fn compress(data: &[f32], header: &TerrainPatchHeader) -> [i32; PATCH_VALUES] {
let premult = 1024.0 / header.range.max(1) as f32;
let sub = 512.0 + header.dc_offset * premult;
let mut block = [0.0; PATCH_VALUES];
for (target, value) in block.iter_mut().zip(data) {
*target = *value * premult - sub;
}
let mut rows = [0.0; PATCH_VALUES];
for line in 0..PATCH_SIZE {
let base = line * PATCH_SIZE;
let total: f32 = block[base..base + PATCH_SIZE].iter().sum();
rows[base] = OO_SQRT2 * total;
for u in 1..PATCH_SIZE {
rows[base + u] = (0..PATCH_SIZE)
.map(|n| block[base + n] * Self::cosine(u, n))
.sum();
}
}
let copy = Self::copy_matrix();
let mut output = [0; PATCH_VALUES];
for column in 0..PATCH_SIZE {
let total: f32 = (0..PATCH_SIZE).map(|n| rows[n * PATCH_SIZE + column]).sum();
output[copy[column]] = (OO_SQRT2 * total * 0.125 / (1.0 + 2.0 * column as f32)) as i32;
for u in 1..PATCH_SIZE {
let total: f32 = (0..PATCH_SIZE)
.map(|n| rows[n * PATCH_SIZE + column] * Self::cosine(u, n))
.sum();
output[copy[u * PATCH_SIZE + column]] =
(total * 0.125 / (1.0 + 2.0 * (u + column) as f32)) as i32;
}
}
output
}
fn encode_header(
output: &mut BitPack,
mut header: TerrainPatchHeader,
patch: &[i32],
) -> Result<i32, Error> {
let base = (header.quant_w_bits & 0x0f) + 2;
let mut word_bits = base / 2;
for value in patch.iter().copied().filter(|value| *value != 0) {
let magnitude = value.unsigned_abs();
let bits = (u32::BITS - magnitude.leading_zeros()) as i32;
word_bits = word_bits.max(bits);
}
word_bits = (word_bits + 1).clamp(2, 17);
header.quant_w_bits = (header.quant_w_bits & 0xf0) | (word_bits - 2);
output.pack_bits_with_int32_int32(header.quant_w_bits, 8)?;
output.pack_float(header.dc_offset)?;
output.pack_bits_with_int32_int32(header.range, 16)?;
output.pack_bits_with_int32_int32(
header.patch_i_ds,
if header.large_region { 32 } else { 10 },
)?;
Ok(word_bits)
}
fn encode_patch_values(
output: &mut BitPack,
patch: &[i32],
word_bits: i32,
) -> Result<(), Error> {
for (index, value) in patch.iter().copied().enumerate() {
if value == 0 {
if patch[index..].iter().all(|candidate| *candidate == 0) {
output.pack_bits_with_int32_int32(2, 2)?;
return Ok(());
}
output.pack_bits_with_int32_int32(0, 1)?;
} else {
output.pack_bits_with_int32_int32(if value < 0 { 7 } else { 6 }, 3)?;
let maximum = (1_i32 << word_bits) - 1;
output.pack_bits_with_int32_int32(
i32::try_from(value.unsigned_abs())
.unwrap_or(maximum)
.min(maximum),
word_bits,
)?;
}
}
Ok(())
}
fn encode_one(
output: &mut BitPack,
data: &[f32],
x: i32,
y: i32,
large_region: bool,
) -> Result<(), Error> {
let mut header = Self::prescan(data)?;
header.large_region = large_region;
header.quant_w_bits = 136;
header.set_patch_i_ds(x, y)?;
let patch = Self::compress(data, &header);
let word_bits = Self::encode_header(output, header, &patch)?;
Self::encode_patch_values(output, &patch, word_bits)
}
pub fn create_layer_data_packet(
patches: Vec<TerrainPatch>,
type_: TerrainPatchLayerType,
) -> Result<crate::packets::LayerDataPacket, Error> {
if patches.len() > 4096 {
return Err(Error::Argument);
}
let capacity = patches
.len()
.checked_mul(1024)
.and_then(|value| value.checked_add(32))
.ok_or(Error::Argument)?;
let mut bits = BitPack::new(vec![0; capacity], 0)?;
bits.pack_bits_with_int32_int32(STRIDE, 16)?;
bits.pack_bits_with_int32_int32(PATCH_SIZE as i32, 8)?;
bits.pack_bits_with_int32_int32(i32::from(type_.0), 8)?;
let large = matches!(type_.0, 57 | 58 | 77);
for patch in patches {
Self::encode_one(&mut bits, &patch.data, patch.x, patch.y, large)?;
}
bits.pack_bits_with_int32_int32(Self::END_OF_PATCHES, 8)?;
let used = usize::try_from(bits.byte_pos())
.map_err(|_| Error::Argument)?
.checked_add(1)
.ok_or(Error::Argument)?;
let mut packet = crate::packets::LayerDataPacket::new_with_constructor()?;
packet.layer_id.type_ = type_.0;
packet.layer_data.data = bits.data[..used.min(bits.data.len())].to_vec();
Ok(packet)
}
pub fn create_land_packet_with_single_array_int32_int32(
patch_data: Vec<f32>,
x: i32,
y: i32,
) -> Result<crate::packets::LayerDataPacket, Error> {
Self::create_layer_data_packet(
vec![TerrainPatch {
data: patch_data,
x,
y,
}],
TerrainPatchLayerType::LAND,
)
}
pub fn create_land_packet_with_single_array_int32_int32_80090bdf(
patch_data: Vec<f32>,
x: i32,
y: i32,
) -> Result<crate::packets::LayerDataPacket, Error> {
Self::create_land_packet_with_single_array_int32_int32(patch_data, x, y)
}
pub fn create_land_packet_with_single_array_int32_array(
heightmap: Vec<f32>,
patches: Vec<i32>,
) -> Result<crate::packets::LayerDataPacket, Error> {
let edge = (heightmap.len() as f64).sqrt() as usize;
if edge * edge != heightmap.len()
|| edge < PATCH_SIZE
|| !edge.is_multiple_of(PATCH_SIZE)
|| patches.len() > 4096
{
return Err(Error::Argument);
}
let per_edge = edge / PATCH_SIZE;
let mut values = Vec::with_capacity(patches.len());
for id in patches {
let id = usize::try_from(id).map_err(|_| Error::Argument)?;
let (x, y) = (id % per_edge, id / per_edge);
if y >= per_edge {
return Err(Error::Argument);
}
let mut data = Vec::with_capacity(PATCH_VALUES);
for row in 0..PATCH_SIZE {
let start = (y * PATCH_SIZE + row) * edge + x * PATCH_SIZE;
data.extend_from_slice(&heightmap[start..start + PATCH_SIZE]);
}
values.push(TerrainPatch {
data,
x: x as i32,
y: y as i32,
});
}
let layer = if edge > 256 {
TerrainPatchLayerType::LAND_EXTENDED
} else {
TerrainPatchLayerType::LAND
};
Self::create_layer_data_packet(values, layer)
}
pub fn create_patch_with_bit_pack_single_array_int32_int32(
mut output: BitPack,
patch_data: Vec<f32>,
x: i32,
y: i32,
) -> Result<(), Error> {
Self::encode_one(&mut output, &patch_data, x, y, false)
}
pub fn create_patch_with_bit_pack_single_array_int32_int32_boolean(
mut output: BitPack,
patch_data: Vec<f32>,
x: i32,
y: i32,
large_region: bool,
) -> Result<(), Error> {
Self::encode_one(&mut output, &patch_data, x, y, large_region)
}
pub fn create_patch_from_heightmap_with_bit_pack_single_array_int32_int32(
output: BitPack,
heightmap: Vec<f32>,
x: i32,
y: i32,
) -> Result<(), Error> {
Self::create_patch_from_heightmap_with_bit_pack_single_array_int32_int32_boolean(
output, heightmap, x, y, false,
)
}
pub fn create_patch_from_heightmap_with_bit_pack_single_array_int32_int32_boolean(
mut output: BitPack,
heightmap: Vec<f32>,
x: i32,
y: i32,
large_region: bool,
) -> Result<(), Error> {
if heightmap.len() != 65_536 || !(0..16).contains(&x) || !(0..16).contains(&y) {
return Err(Error::Argument);
}
let mut data = Vec::with_capacity(PATCH_VALUES);
for row in 0..PATCH_SIZE {
let start = (y as usize * PATCH_SIZE + row) * 256 + x as usize * PATCH_SIZE;
data.extend_from_slice(&heightmap[start..start + PATCH_SIZE]);
}
Self::encode_one(&mut output, &data, x, y, large_region)
}
pub fn decode_patch_header_with_bit_pack(
bitpack: BitPack,
) -> Result<TerrainPatchHeader, Error> {
Self::decode_patch_header_with_bit_pack_boolean(bitpack, false)
}
pub fn decode_patch_header_with_bit_pack_boolean(
mut bitpack: BitPack,
large_region: bool,
) -> Result<TerrainPatchHeader, Error> {
Self::decode_header(&mut bitpack, large_region)
}
pub(crate) fn decode_header(
bitpack: &mut BitPack,
large_region: bool,
) -> Result<TerrainPatchHeader, Error> {
let quant_w_bits = bitpack.unpack_bits(8)?;
if quant_w_bits == Self::END_OF_PATCHES {
return Ok(TerrainPatchHeader {
quant_w_bits,
large_region,
..TerrainPatchHeader::default()
});
}
let dc_offset = bitpack.unpack_float()?;
let range = bitpack.unpack_bits(16)?;
let patch_i_ds = bitpack.unpack_bits(if large_region { 32 } else { 10 })?;
Ok(TerrainPatchHeader {
dc_offset,
range,
quant_w_bits,
patch_i_ds,
word_bits: ((quant_w_bits & 0x0f) + 2) as u32,
large_region,
})
}
pub fn decode_patch(
mut patches: Vec<i32>,
mut bitpack: BitPack,
header: TerrainPatchHeader,
size: i32,
) -> Result<(), Error> {
Self::decode_values(&mut patches, &mut bitpack, header, size)
}
pub(crate) fn decode_values(
patches: &mut [i32],
bitpack: &mut BitPack,
header: TerrainPatchHeader,
size: i32,
) -> Result<(), Error> {
let count = usize::try_from(size.checked_mul(size).ok_or(Error::Argument)?)
.map_err(|_| Error::Argument)?;
if patches.len() < count
|| !(1..=32).contains(&size)
|| !(2..=17).contains(&header.word_bits)
{
return Err(Error::Argument);
}
for index in 0..count {
if bitpack.unpack_bits(1)? == 0 {
patches[index] = 0;
continue;
}
if bitpack.unpack_bits(1)? == 0 {
patches[index..count].fill(0);
break;
}
let negative = bitpack.unpack_bits(1)? != 0;
let value = bitpack.unpack_bits(header.word_bits as i32)?;
patches[index] = if negative { -value } else { value };
}
Ok(())
}
pub fn decompress_patch(
patches: Vec<i32>,
header: TerrainPatchHeader,
group: TerrainPatchGroupHeader,
) -> Result<Vec<f32>, Error> {
if group.patch_size != 16 || patches.len() < PATCH_VALUES {
return Err(Error::Argument);
}
let copy = Self::copy_matrix();
let mut block = [0.0; PATCH_VALUES];
for index in 0..PATCH_VALUES {
block[index] = patches[copy[index]] as f32
* (1.0 + 2.0 * ((index / PATCH_SIZE) + (index % PATCH_SIZE)) as f32);
}
let mut columns = [0.0; PATCH_VALUES];
for column in 0..PATCH_SIZE {
for n in 0..PATCH_SIZE {
columns[n * PATCH_SIZE + column] = OO_SQRT2 * block[column]
+ (1..PATCH_SIZE)
.map(|u| block[u * PATCH_SIZE + column] * Self::cosine(u, n))
.sum::<f32>();
}
}
let mut transformed = [0.0; PATCH_VALUES];
for line in 0..PATCH_SIZE {
let base = line * PATCH_SIZE;
for n in 0..PATCH_SIZE {
transformed[base + n] = (OO_SQRT2 * columns[base]
+ (1..PATCH_SIZE)
.map(|u| columns[base + u] * Self::cosine(u, n))
.sum::<f32>())
* 0.125;
}
}
let prequant = (header.quant_w_bits >> 4) + 2;
if !(1..31).contains(&prequant) {
return Err(Error::Argument);
}
let mult = header.range as f32 / (1_i32 << prequant) as f32;
let add = mult * (1_i32 << (prequant - 1)) as f32 + header.dc_offset;
Ok(transformed
.into_iter()
.map(|value| value * mult + add)
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn terrain_codec_round_trip_stays_within_quantization_error() {
let source: Vec<f32> = (0..PATCH_VALUES)
.map(|index| 20.0 + index as f32 * 0.03125)
.collect();
let packet = TerrainCompressor::create_land_packet_with_single_array_int32_int32(
source.clone(),
3,
7,
)
.unwrap();
let mut bits = BitPack::new(packet.layer_data.data, 0).unwrap();
assert_eq!(bits.unpack_bits(16).unwrap(), STRIDE);
let size = bits.unpack_bits(8).unwrap();
assert_eq!(
bits.unpack_bits(8).unwrap(),
i32::from(TerrainPatchLayerType::LAND.0)
);
let header = TerrainCompressor::decode_header(&mut bits, false).unwrap();
let mut coefficients = vec![0; PATCH_VALUES];
TerrainCompressor::decode_values(&mut coefficients, &mut bits, header, size).unwrap();
let decoded = TerrainCompressor::decompress_patch(
coefficients,
header,
TerrainPatchGroupHeader {
stride: STRIDE,
patch_size: size,
type_: TerrainPatchLayerType::LAND,
},
)
.unwrap();
assert_eq!((header.x(), header.y()), (3, 7));
assert!(
source
.iter()
.zip(decoded)
.all(|(expected, actual)| (expected - actual).abs() < 0.2)
);
}
#[test]
fn terrain_payloads_are_bounded_and_coordinates_validated() {
assert!(
TerrainCompressor::create_land_packet_with_single_array_int32_int32(
vec![0.0; 255],
0,
0
)
.is_err()
);
assert!(
TerrainCompressor::create_land_packet_with_single_array_int32_int32(
vec![0.0; 256],
32,
0
)
.is_err()
);
}
}

View File

@@ -0,0 +1,464 @@
//! Terrain layer reception, wind state, and PBR terrain material capabilities.
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::unnecessary_wraps)] // Mapped constructors have fixed fallible signatures.
use crate::agent_manager::EventRegistry;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::terrain_codec::{TerrainCompressor, TerrainPatch, TerrainPatchGroupHeader};
use crate::{BitPack, Error, GridClient, Simulator, TerrainPatchLayerType};
use futures_channel::oneshot;
use futures_util::future::{Either, select};
use futures_util::pin_mut;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::Vector2;
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
const MAX_LAYER_BYTES: usize = 16 * 1024 * 1024;
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[derive(Clone)]
pub struct LandPatchReceivedEventArgs {
simulator: Simulator,
x: i32,
y: i32,
patch_size: i32,
height_map: Vec<f32>,
}
impl LandPatchReceivedEventArgs {
pub fn new(
simulator: Simulator,
x: i32,
y: i32,
patch_size: i32,
height_map: Vec<f32>,
) -> Result<Self, Error> {
if height_map.len()
!= usize::try_from(patch_size.checked_mul(patch_size).ok_or(Error::Argument)?)
.map_err(|_| Error::Argument)?
{
return Err(Error::Argument);
}
Ok(Self {
simulator,
x,
y,
patch_size,
height_map,
})
}
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
pub fn x(&self) -> i32 {
self.x
}
pub fn y(&self) -> i32 {
self.y
}
pub fn patch_size(&self) -> i32 {
self.patch_size
}
pub fn height_map(&self) -> Vec<f32> {
self.height_map.clone()
}
}
pub(crate) struct TerrainManagerInner {
client: Arc<GridClient>,
events: EventRegistry<LandPatchReceivedEventArgs>,
raw_subscription: Mutex<Option<Subscription>>,
disposed: AtomicBool,
}
#[derive(Clone)]
pub struct TerrainManager {
pub(crate) inner: Arc<TerrainManagerInner>,
}
impl TerrainManager {
pub const TERRAIN_MATERIAL_SLOT_COUNT: i32 = 4;
pub fn new(client: GridClient) -> Result<Self, Error> {
Self::native_new(Arc::new(client))
}
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
if let Some(inner) = client.cached_terrain_manager_inner() {
return Ok(Self { inner });
}
let inner = Arc::new(TerrainManagerInner {
client: Arc::clone(&client),
events: EventRegistry::default(),
raw_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_layer(event);
}
},
)));
Ok(Self {
inner: client.install_terrain_manager_inner(inner),
})
}
pub fn subscribe_land_patch_received(
&self,
handler: EventHandler<LandPatchReceivedEventArgs>,
) -> Subscription {
self.inner.events.subscribe(handler)
}
pub fn dispose(&self) -> Result<(), Error> {
self.inner.disposed.store(true, Ordering::Release);
mutex(&self.inner.raw_subscription).take();
Ok(())
}
async fn capability(
&self,
name: &str,
token: &CancellationToken,
) -> Result<Option<Uri>, Error> {
if self.inner.disposed.load(Ordering::Acquire) {
return Err(Error::InvalidOperation);
}
let Some(caps) = self
.inner
.client
.network()
.current_sim()
.and_then(|sim| sim.native_caps())
else {
return Ok(None);
};
if let Some(uri) = caps.capability_uri(name.into())? {
return Ok(Some(uri));
}
if caps.seed_request_finished() {
return Ok(None);
}
let (sender, receiver) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(sender)));
let wake = Arc::clone(&sender);
let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| {
if let Some(sender) = mutex(&wake).take() {
let _ = sender.send(());
}
})));
if let Some(uri) = caps.capability_uri(name.into())? {
return Ok(Some(uri));
}
let cancellation = token.cancelled();
pin_mut!(receiver, cancellation);
match select(receiver, cancellation).await {
Either::Left(_) => caps.capability_uri(name.into()),
Either::Right(_) => Err(Error::Cancelled),
}
}
pub async fn get_terrain_material_overrides(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<Vec<Option<crate::assets::AssetMaterial>>>, Error> {
let token = cancellation_token.unwrap_or_default();
let Some(uri) = self.capability("ModifyRegion", &token).await? else {
return Ok(None);
};
let (response, bytes) = self
.inner
.client
.native_http_caps_client()
.get(uri, token, None)
.await?;
if !(200..300).contains(&response.status_code) || bytes.len() > MAX_LAYER_BYTES {
return Ok(None);
}
let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
return Ok(None);
};
if !map
.get("success")
.and_then(|value| value.as_boolean().ok())
.unwrap_or(false)
{
return Ok(None);
}
let Some(OSD::Array(overrides)) = map.get("overrides") else {
return Ok(None);
};
if overrides.len() < 4 || overrides.len() > 1024 {
return Ok(None);
}
let mut result = Vec::with_capacity(4);
for value in overrides.iter().take(4) {
match value {
OSD::Map(values) if !values.is_empty() => {
result.push(Some(crate::assets::AssetMaterial::from_override_osd(
OSDMap::new_with_dictionary(values.clone())?,
)?));
}
_ => result.push(None),
}
}
Ok(Some(result))
}
pub async fn set_terrain_material_overrides(
&self,
overrides: Vec<Option<crate::assets::AssetMaterial>>,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
if overrides.len() != 4 {
return Err(Error::Argument);
}
let token = cancellation_token.unwrap_or_default();
let Some(uri) = self.capability("ModifyRegion", &token).await? else {
return Ok(false);
};
let mut wire = Vec::with_capacity(4);
for material in overrides {
wire.push(match material {
Some(value) => OSD::Map(value.to_override_osd()?.snapshot()),
None => OSD::Map(HashMap::new()),
});
}
let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([(
"overrides".into(),
OSD::Array(wire),
)])))?;
if body.len() > MAX_LAYER_BYTES {
return Err(Error::Argument);
}
let (response, bytes) = self
.inner
.client
.native_http_caps_client()
.post_with_uri_string_bytes_cancellation_token_i_progress(
uri,
"application/llsd+xml".into(),
body,
token,
None,
)
.await?;
if !(200..300).contains(&response.status_code) || bytes.len() > MAX_LAYER_BYTES {
return Ok(false);
}
let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
return Ok(false);
};
Ok(map
.get("success")
.and_then(|value| value.as_boolean().ok())
.unwrap_or(false))
}
}
impl TerrainManagerInner {
fn handle_layer(&self, event: crate::network_manager::RawPacketReceivedEventArgs) {
if self.disposed.load(Ordering::Acquire)
|| event.packet_type != PacketType::LayerData
|| event.data.len() > MAX_LAYER_BYTES
{
return;
}
let mut position = 0;
let Ok(packet) =
crate::packets::LayerDataPacket::new_from_bytes(&event.data, &mut position)
else {
return;
};
let _ = self.decode_layer(packet, event.simulator);
}
fn decode_layer(
&self,
packet: crate::packets::LayerDataPacket,
simulator: Simulator,
) -> Result<(), Error> {
if packet.layer_data.data.len() > MAX_LAYER_BYTES {
return Err(Error::Argument);
}
let mut bits = BitPack::new(packet.layer_data.data, 0)?;
let group = TerrainPatchGroupHeader {
stride: bits.unpack_bits(16)?,
patch_size: bits.unpack_bits(8)?,
type_: TerrainPatchLayerType(
u8::try_from(bits.unpack_bits(8)?).map_err(|_| Error::Argument)?,
),
};
if group.patch_size != 16 {
return Err(Error::Argument);
}
match packet.layer_id.type_ {
76 | 77 => self.decode_land(&mut bits, group, simulator, packet.layer_id.type_ == 77),
55 | 57 => Self::decode_wind(&mut bits, group, simulator, packet.layer_id.type_ == 57),
56 | 58 | 87 => Ok(()),
_ => Err(Error::Argument),
}
}
fn decode_land(
&self,
bits: &mut BitPack,
group: TerrainPatchGroupHeader,
simulator: Simulator,
large: bool,
) -> Result<(), Error> {
let mut count = 0usize;
loop {
let header = TerrainCompressor::decode_header(bits, large)?;
if header.quant_w_bits == TerrainCompressor::END_OF_PATCHES {
break;
}
if !large && (!(0..16).contains(&header.x()) || !(0..16).contains(&header.y())) {
return Err(Error::Argument);
}
count += 1;
if count > 4096 {
return Err(Error::Argument);
}
let mut coefficients = vec![0; 1024];
TerrainCompressor::decode_values(&mut coefficients, bits, header, group.patch_size)?;
let heights = TerrainCompressor::decompress_patch(coefficients, header, group)?;
simulator.native_store_terrain_patch(
TerrainPatch {
data: heights.clone(),
x: header.x(),
y: header.y(),
},
large,
)?;
self.events.emit(LandPatchReceivedEventArgs::new(
simulator.clone(),
header.x(),
header.y(),
group.patch_size,
heights,
)?);
}
Ok(())
}
fn decode_wind(
bits: &mut BitPack,
mut group: TerrainPatchGroupHeader,
simulator: Simulator,
large: bool,
) -> Result<(), Error> {
group.stride = group.patch_size;
let mut axes = Vec::with_capacity(2);
for _ in 0..2 {
let header = TerrainCompressor::decode_header(bits, large)?;
if header.quant_w_bits == TerrainCompressor::END_OF_PATCHES {
return Err(Error::Argument);
}
let mut coefficients = vec![0; 1024];
TerrainCompressor::decode_values(&mut coefficients, bits, header, group.patch_size)?;
axes.push(TerrainCompressor::decompress_patch(
coefficients,
header,
group,
)?);
}
simulator.native_store_wind(
axes[0]
.iter()
.zip(&axes[1])
.take(256)
.map(|(x, y)| Vector2 { x: *x, y: *y })
.collect(),
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn land_event_validates_patch_dimensions() {
let client = GridClient::new().unwrap();
let sim = Simulator::new(client, "127.0.0.1:13".parse().unwrap(), 0, None, None).unwrap();
assert!(LandPatchReceivedEventArgs::new(sim, 0, 0, 16, vec![0.0; 255]).is_err());
}
#[test]
fn layer_packet_updates_terrain_before_emitting_the_correlated_patch() {
let client = GridClient::new().unwrap();
let sim = Simulator::new(
client.clone(),
"127.0.0.1:13".parse().unwrap(),
0,
None,
None,
)
.unwrap();
let manager = TerrainManager::new(client).unwrap();
let observed = Arc::new(Mutex::new(None));
let captured = Arc::clone(&observed);
let _subscription = manager.subscribe_land_patch_received(Arc::new(move |event| {
let mut height = 0.0;
let stored = event
.simulator()
.terrain_height_at_point(event.x() * 16, event.y() * 16, &mut height)
.unwrap();
*mutex(&captured) = Some((event.x(), event.y(), stored, height));
}));
let source: Vec<f32> = (0..256).map(|index| 32.0 + index as f32 / 64.0).collect();
let packet = TerrainCompressor::create_land_packet_with_single_array_int32_int32(
source.clone(),
5,
9,
)
.unwrap();
manager.inner.decode_layer(packet, sim).unwrap();
let observed = mutex(&observed).expect("land event");
assert_eq!((observed.0, observed.1, observed.2), (5, 9, true));
assert!((observed.3 - source[0]).abs() < 0.2);
}
#[test]
fn rectangular_large_region_uses_independent_patch_dimensions() {
let client = GridClient::new().unwrap();
let sim = Simulator::new(
client,
"127.0.0.1:13".parse().unwrap(),
0,
Some(512),
Some(256),
)
.unwrap();
sim.native_store_terrain_patch(
TerrainPatch {
data: vec![41.5; 256],
x: 31,
y: 15,
},
true,
)
.unwrap();
let mut height = 0.0;
assert!(sim.terrain_height_at_point(511, 255, &mut height).unwrap());
assert_eq!(height, 41.5);
assert!(
sim.native_store_terrain_patch(
TerrainPatch {
data: vec![0.0; 256],
x: 0,
y: 16,
},
true,
)
.is_err()
);
}
}