Files
MetaCrate/crates/libremetaverse/src/environment_manager.rs
Chili Palmer d537a37172
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
Implement land, terrain, environment, and sound managers (#70)
2026-08-10 16:53:35 +00:00

652 lines
21 KiB
Rust

//! 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());
}
}