//! `ModifyMaterialParams` capability integration. #![allow( clippy::missing_errors_doc, clippy::must_use_candidate, clippy::option_option )] use crate::assets::AssetMaterial; use crate::{Error, GridClient, Simulator}; use libremetaverse_structured_data::{OSD, OSDParser}; use libremetaverse_types::{UUID, compat::CancellationToken}; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; #[derive(Clone, Debug)] pub struct GLTFMaterialUpdate { asset_id: Option>, object_id: UUID, override_: Option, side: i32, } impl GLTFMaterialUpdate { pub fn new() -> Result { Ok(Self { asset_id: None, object_id: UUID::zero(), override_: None, side: 0, }) } pub fn asset_id(&self) -> Option> { self.asset_id } pub fn set_asset_id(&mut self, value: Option>) { self.asset_id = value; } pub const fn object_id(&self) -> UUID { self.object_id } pub fn set_object_id(&mut self, value: UUID) { self.object_id = value; } pub fn override_(&self) -> Option { self.override_.clone() } pub fn set_override_(&mut self, value: Option) { self.override_ = value; } pub const fn side(&self) -> i32 { self.side } pub fn set_side(&mut self, value: i32) { self.side = value; } } pub struct ObjectManager { client: crate::client_core::ClientWeakHandle, } impl ObjectManager { pub(crate) fn native_new(client: Option>) -> Result { Ok(Self { client: client.ok_or(Error::ArgumentNull)?.native_weak_handle(), }) } fn client(&self) -> Result { self.client.upgrade().ok_or(Error::InvalidOperation) } pub(crate) async fn native_send_material_updates( &self, sim: Simulator, updates: Box>, cancellation_token: Option, ) -> Result { let Some(caps) = sim.native_caps() else { return Ok(false); }; let deadline = Instant::now() + Duration::from_secs(2); let uri = loop { if let Some(uri) = caps.capability_uri("ModifyMaterialParams".into())? { break uri; } if caps.seed_request_finished() || Instant::now() >= deadline { return Ok(false); } std::thread::sleep(Duration::from_millis(1)); }; let mut body = Vec::new(); for update in updates { if update.side < 0 { return Err(Error::Argument); } let mut entry = HashMap::from([ ("object_id".into(), OSD::UUID(update.object_id)), ("side".into(), OSD::Integer(update.side)), ]); if let Some(Some(asset_id)) = update.asset_id { entry.insert("asset_id".into(), OSD::UUID(asset_id)); } if let Some(material) = update.override_ { entry.insert("gltf_json".into(), OSD::String(material.to_json()?)); } else if update.asset_id.flatten().is_none() { entry.insert("gltf_json".into(), OSD::String(String::new())); } body.push(OSD::Map(entry)); } if body.is_empty() { return Err(Error::Argument); } let payload = OSDParser::serialize_llsd_xml_bytes(OSD::Array(body))?; let client = self.client()?; let (response, bytes) = client .native_http_caps_client() .post_with_uri_string_bytes_cancellation_token_i_progress( uri, "application/llsd+xml".into(), payload, cancellation_token.unwrap_or_default(), None, ) .await?; if !(200..300).contains(&response.status_code) { return Ok(false); } let response: serde_json::Value = serde_json::from_slice(&bytes).map_err(|_| Error::Argument)?; Ok(response .get("success") .and_then(serde_json::Value::as_bool) .unwrap_or(false)) } pub(crate) async fn native_set_material_override( &self, sim: Simulator, object_id: UUID, side: i32, override_material: AssetMaterial, cancellation_token: Option, ) -> Result { let mut update = GLTFMaterialUpdate::new()?; update.object_id = object_id; update.side = side; update.override_ = Some(override_material); self.native_send_material_updates( sim, Box::new(std::iter::once(update)), cancellation_token, ) .await } pub(crate) async fn native_clear_material_override( &self, sim: Simulator, object_id: UUID, side: i32, cancellation_token: Option, ) -> Result { let mut update = GLTFMaterialUpdate::new()?; update.object_id = object_id; update.side = side; self.native_send_material_updates( sim, Box::new(std::iter::once(update)), cancellation_token, ) .await } pub(crate) async fn native_apply_material( &self, sim: Simulator, object_id: UUID, side: i32, material_asset_id: UUID, override_material: Option, cancellation_token: Option, ) -> Result { let mut update = GLTFMaterialUpdate::new()?; update.object_id = object_id; update.side = side; update.asset_id = Some(Some(material_asset_id)); update.override_ = override_material; self.native_send_material_updates( sim, Box::new(std::iter::once(update)), cancellation_token, ) .await } }