652 lines
21 KiB
Rust
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());
|
|
}
|
|
}
|