Implement grid and directory discovery (#71)
All checks were successful
Native code generation / deterministic (push) Successful in 15m16s
Imaging and meshing gate / native (push) Successful in 5m9s
JPEG 2000 feature / linux (push) Successful in 2m47s
Native Rust workspace compile / compile (push) Successful in 5m14s
Skia feature / linux (push) Successful in 31m28s
All checks were successful
Native code generation / deterministic (push) Successful in 15m16s
Imaging and meshing gate / native (push) Successful in 5m9s
JPEG 2000 feature / linux (push) Successful in 2m47s
Native Rust workspace compile / compile (push) Successful in 5m14s
Skia feature / linux (push) Successful in 31m28s
This commit is contained in:
237
crates/libremetaverse/src/interest_list.rs
Normal file
237
crates/libremetaverse/src/interest_list.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
//! Capability-backed interest-list mode control.
|
||||
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::must_use_candidate)]
|
||||
#![allow(clippy::needless_pass_by_value)]
|
||||
#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructor is fallible.
|
||||
|
||||
use crate::{Error, GridClient, Simulator};
|
||||
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
|
||||
use libremetaverse_types::compat::CancellationToken;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InterestListMessage {
|
||||
mode: String,
|
||||
interest_list_mode: crate::messages::linden::InterestListMode,
|
||||
}
|
||||
impl InterestListMessage {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
mode: "default".into(),
|
||||
interest_list_mode: crate::messages::linden::InterestListMode::Default,
|
||||
})
|
||||
}
|
||||
pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
|
||||
let mode = map
|
||||
.get("mode")
|
||||
.map(|value| value.as_string())
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| "default".into());
|
||||
self.set_mode(mode);
|
||||
Ok(())
|
||||
}
|
||||
pub fn serialize(&self) -> Result<OSDMap, Error> {
|
||||
OSDMap::new_with_dictionary(std::collections::HashMap::from([(
|
||||
"mode".into(),
|
||||
OSD::String(self.mode.clone()),
|
||||
)]))
|
||||
}
|
||||
pub fn interest_list_mode(&self) -> crate::messages::linden::InterestListMode {
|
||||
self.interest_list_mode
|
||||
}
|
||||
pub fn set_interest_list_mode(&mut self, value: crate::messages::linden::InterestListMode) {
|
||||
self.interest_list_mode = value;
|
||||
self.mode = mode_name(value).into();
|
||||
}
|
||||
pub fn mode(&self) -> String {
|
||||
self.mode.clone()
|
||||
}
|
||||
pub fn set_mode(&mut self, value: String) {
|
||||
self.interest_list_mode = if matches!(
|
||||
value.to_ascii_lowercase().as_str(),
|
||||
"360" | "panoramic360" | "panoramic_360"
|
||||
) {
|
||||
crate::messages::linden::InterestListMode::Panoramic360
|
||||
} else {
|
||||
crate::messages::linden::InterestListMode::Default
|
||||
};
|
||||
self.mode = mode_name(self.interest_list_mode).into();
|
||||
}
|
||||
}
|
||||
impl crate::interfaces::IMessage for InterestListMessage {
|
||||
fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
|
||||
InterestListMessage::deserialize(self, map)
|
||||
}
|
||||
fn serialize(&self) -> Result<OSDMap, Error> {
|
||||
InterestListMessage::serialize(self)
|
||||
}
|
||||
}
|
||||
fn mode_name(mode: crate::messages::linden::InterestListMode) -> &'static str {
|
||||
match mode {
|
||||
crate::messages::linden::InterestListMode::Default => "default",
|
||||
crate::messages::linden::InterestListMode::Panoramic360 => "360",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InterestListManagerInner {
|
||||
client: Arc<GridClient>,
|
||||
current_mode: RwLock<crate::messages::linden::InterestListMode>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct InterestListManager {
|
||||
pub(crate) inner: Arc<InterestListManagerInner>,
|
||||
}
|
||||
impl InterestListManager {
|
||||
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_interest_list_manager_inner() {
|
||||
return Ok(Self { inner });
|
||||
}
|
||||
let inner = Arc::new(InterestListManagerInner {
|
||||
client: Arc::clone(&client),
|
||||
current_mode: RwLock::new(crate::messages::linden::InterestListMode::Default),
|
||||
});
|
||||
Ok(Self {
|
||||
inner: client.install_interest_list_manager_inner(inner),
|
||||
})
|
||||
}
|
||||
pub fn dispose(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
pub fn current_mode(&self) -> crate::messages::linden::InterestListMode {
|
||||
*self
|
||||
.inner
|
||||
.current_mode
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
pub async fn reset(&self, cancellation_token: Option<CancellationToken>) -> Result<(), Error> {
|
||||
self.set_mode(
|
||||
crate::messages::linden::InterestListMode::Default,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
pub async fn set_mode(
|
||||
&self,
|
||||
mode: crate::messages::linden::InterestListMode,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<(), Error> {
|
||||
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let simulations = self.inner.client.network().simulators.snapshot();
|
||||
let mut accepted = false;
|
||||
for simulator in simulations {
|
||||
if self
|
||||
.set_mode_on_sim(simulator, mode, Some(token.clone()))
|
||||
.await?
|
||||
{
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
if accepted || self.inner.client.network().simulators.is_empty() {
|
||||
*self
|
||||
.inner
|
||||
.current_mode
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn reset_on_sim(
|
||||
&self,
|
||||
simulator: Simulator,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<bool, Error> {
|
||||
self.set_mode_on_sim(
|
||||
simulator,
|
||||
crate::messages::linden::InterestListMode::Default,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
pub async fn set_mode_on_sim(
|
||||
&self,
|
||||
simulator: Simulator,
|
||||
mode: crate::messages::linden::InterestListMode,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<bool, Error> {
|
||||
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let Some(caps) = simulator.native_caps() else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(uri) = caps.capability_uri("InterestList".into())? else {
|
||||
return Ok(false);
|
||||
};
|
||||
let mut message = InterestListMessage::new()?;
|
||||
message.set_interest_list_mode(mode);
|
||||
let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(message.serialize()?.snapshot()))?;
|
||||
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.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
token.throw_if_cancellation_requested()?;
|
||||
if bytes.len() > 1024 * 1024 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let ok = (200..300).contains(&response.status_code);
|
||||
if ok {
|
||||
*self
|
||||
.inner
|
||||
.current_mode
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
|
||||
}
|
||||
Ok(ok)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libremetaverse_types::compat::CancellationTokenSource;
|
||||
#[test]
|
||||
fn message_round_trip() {
|
||||
let mut value = InterestListMessage::new().unwrap();
|
||||
value.set_interest_list_mode(crate::messages::linden::InterestListMode::Panoramic360);
|
||||
let mut copy = InterestListMessage::new().unwrap();
|
||||
copy.deserialize(value.serialize().unwrap()).unwrap();
|
||||
assert_eq!(copy.mode(), "360");
|
||||
assert_eq!(
|
||||
copy.interest_list_mode(),
|
||||
crate::messages::linden::InterestListMode::Panoramic360
|
||||
);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn cancelled_mode_change_does_not_commit() {
|
||||
let client = GridClient::new().unwrap();
|
||||
let manager = InterestListManager::new(client).unwrap();
|
||||
let source = CancellationTokenSource::new();
|
||||
source.cancel();
|
||||
assert_eq!(
|
||||
manager
|
||||
.set_mode(
|
||||
crate::messages::linden::InterestListMode::Panoramic360,
|
||||
Some(source.token())
|
||||
)
|
||||
.await,
|
||||
Err(Error::Cancelled)
|
||||
);
|
||||
assert_eq!(
|
||||
manager.current_mode(),
|
||||
crate::messages::linden::InterestListMode::Default
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user