Complete milestone 09 world integration gate (#75)
This commit is contained in:
@@ -129,6 +129,7 @@ struct ClientRuntime {
|
||||
interest_list_manager: Mutex<Option<Arc<crate::interest_list::InterestListManagerInner>>>,
|
||||
friends_manager: Mutex<Option<Arc<crate::friends_manager::FriendsManagerInner>>>,
|
||||
group_manager: Mutex<Option<Arc<crate::group_manager::GroupManagerInner>>>,
|
||||
marketplace_manager: Mutex<Option<crate::marketplace_runtime::MarketplaceManager>>,
|
||||
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>>>,
|
||||
@@ -139,6 +140,12 @@ struct ClientRuntime {
|
||||
shutdown_wait: Mutex<()>,
|
||||
}
|
||||
|
||||
fn clear_cached<T>(slot: &Mutex<Option<T>>) {
|
||||
slot.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
}
|
||||
|
||||
impl ClientRuntime {
|
||||
fn new(
|
||||
services: Vec<Arc<dyn ClientService>>,
|
||||
@@ -174,6 +181,7 @@ impl ClientRuntime {
|
||||
interest_list_manager: Mutex::new(None),
|
||||
friends_manager: Mutex::new(None),
|
||||
group_manager: Mutex::new(None),
|
||||
marketplace_manager: Mutex::new(None),
|
||||
appearance_manager: Mutex::new(None),
|
||||
avatar_manager: Mutex::new(None),
|
||||
animesh_manager: Mutex::new(None),
|
||||
@@ -248,42 +256,25 @@ impl ClientRuntime {
|
||||
}
|
||||
}
|
||||
services.clear();
|
||||
self.agent_throttle_sender
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.inventory_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.inventory_ais_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.object_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.grid_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.directory_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.interest_list_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.friends_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.group_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
clear_cached(&self.agent_throttle_sender);
|
||||
clear_cached(&self.inventory_manager);
|
||||
clear_cached(&self.inventory_ais_client);
|
||||
clear_cached(&self.asset_manager);
|
||||
clear_cached(&self.appearance_manager);
|
||||
clear_cached(&self.avatar_manager);
|
||||
clear_cached(&self.animesh_manager);
|
||||
clear_cached(&self.object_manager);
|
||||
clear_cached(&self.environment_manager);
|
||||
clear_cached(&self.estate_tools);
|
||||
clear_cached(&self.terrain_manager);
|
||||
clear_cached(&self.sound_manager);
|
||||
clear_cached(&self.parcel_manager);
|
||||
clear_cached(&self.grid_manager);
|
||||
clear_cached(&self.directory_manager);
|
||||
clear_cached(&self.interest_list_manager);
|
||||
clear_cached(&self.friends_manager);
|
||||
clear_cached(&self.group_manager);
|
||||
clear_cached(&self.marketplace_manager);
|
||||
self.http_caps_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
@@ -1106,6 +1097,32 @@ impl GridClient {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
|
||||
}
|
||||
|
||||
pub(crate) fn native_marketplace(&self) -> crate::marketplace_runtime::MarketplaceManager {
|
||||
let mut cached = self
|
||||
.runtime
|
||||
.marketplace_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(manager) = cached.as_ref() {
|
||||
return manager.clone();
|
||||
}
|
||||
let manager = crate::marketplace_runtime::MarketplaceManager::native_new_client_owned(self);
|
||||
*cached = Some(manager.clone());
|
||||
manager
|
||||
}
|
||||
|
||||
pub(crate) fn native_set_marketplace(
|
||||
&self,
|
||||
value: crate::marketplace_runtime::MarketplaceManager,
|
||||
) {
|
||||
value.native_bind_to_client(self);
|
||||
*self
|
||||
.runtime
|
||||
.marketplace_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value);
|
||||
}
|
||||
|
||||
pub(crate) fn native_set_caps_rate_limiter(
|
||||
&mut self,
|
||||
value: crate::caps_http::CapsRateLimiter,
|
||||
|
||||
@@ -6730,11 +6730,13 @@ impl GridClient {
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.GridClient.Marketplace`.
|
||||
pub fn marketplace(&self) -> libremetaverse::marketplace::MarketplaceManager {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Marketplace")
|
||||
/* native client-core implementation */
|
||||
self.native_marketplace()
|
||||
}
|
||||
/// Setter for C# member: `P:LibreMetaverse.GridClient.Marketplace`.
|
||||
pub fn set_marketplace(&mut self, value: libremetaverse::marketplace::MarketplaceManager) {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Marketplace")
|
||||
/* native client-core implementation */
|
||||
self.native_set_marketplace(value)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.GridClient.Network`.
|
||||
pub fn network(&self) -> libremetaverse::NetworkManager {
|
||||
@@ -9930,9 +9932,14 @@ impl InventoryManager {
|
||||
>,
|
||||
>,
|
||||
) -> Result<Option<Option<libremetaverse_types::UUID>>, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryManager.UploadThumbnailAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})",
|
||||
self.native_upload_thumbnail(
|
||||
inventory_id,
|
||||
task_id,
|
||||
j2c_image_data,
|
||||
cancellation_token,
|
||||
progress,
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)`.
|
||||
pub async fn wait_for_next_inventory_offer(
|
||||
@@ -17640,6 +17647,7 @@ pub mod assets {
|
||||
pub const TERRAINS_PATH: &'static str = "terrains/";
|
||||
/// C# member: `M:LibreMetaverse.Assets.ArchiveConstants.#ctor`.
|
||||
pub fn new() -> Result<Self, crate::Error> {
|
||||
/* native zero-sized constants namespace */
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use libremetaverse_types::compat::{CancellationToken, HttpResponse, Uri};
|
||||
use libremetaverse_types::{AssetType, UUID};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const MAX_FOLDER_DEPTH_REQUEST: i32 = 50;
|
||||
const MAX_AIS_OBJECTS: usize = 100_000;
|
||||
@@ -110,6 +111,31 @@ impl InventoryAISClient {
|
||||
caps.capability_uri(name.to_owned())
|
||||
}
|
||||
|
||||
fn wait_for_capability(
|
||||
&self,
|
||||
name: &str,
|
||||
cancellation: &CancellationToken,
|
||||
) -> Result<Option<Uri>, Error> {
|
||||
let client = self.client()?;
|
||||
let Some(simulator) = client.native_network()?.current_sim() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(caps) = simulator.native_caps() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let deadline = Instant::now() + Duration::from_secs(2);
|
||||
loop {
|
||||
cancellation.throw_if_cancellation_requested()?;
|
||||
if let Some(uri) = caps.capability_uri(name.to_owned())? {
|
||||
return Ok(Some(uri));
|
||||
}
|
||||
if caps.seed_request_finished() || Instant::now() >= deadline {
|
||||
return Ok(None);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn native_is_available(&self) -> bool {
|
||||
self.capability(Self::INVENTORY_CAP_NAME)
|
||||
.ok()
|
||||
@@ -147,7 +173,7 @@ impl InventoryAISClient {
|
||||
) -> Result<Option<(HttpResponse, Vec<u8>)>, Error> {
|
||||
let token = Self::token(cancellation_token);
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let Some(capability) = self.capability(cap_name)? else {
|
||||
let Some(capability) = self.wait_for_capability(cap_name, &token)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let bytes = match payload {
|
||||
@@ -359,18 +385,27 @@ impl InventoryAISClient {
|
||||
new_inventory: OSD,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<Vec<InventoryItem>, Error> {
|
||||
let path = format!("category/{parent_uuid}?tid={}", Self::tid()?);
|
||||
let (_, _, _, links) = self
|
||||
.mutation(
|
||||
let Some((response, bytes)) = self
|
||||
.request(
|
||||
Self::INVENTORY_CAP_NAME,
|
||||
"POST",
|
||||
path,
|
||||
format!("category/{parent_uuid}"),
|
||||
BTreeMap::new(),
|
||||
Some(new_inventory),
|
||||
false,
|
||||
cancellation_token,
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
if !Self::status_ok(&response) {
|
||||
return Err(Error::HttpRequest);
|
||||
}
|
||||
let Some(map) = Self::decode_map(bytes)? else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let (folders, items, links) = self.parse_all_map(&map)?;
|
||||
self.reconcile(&map, &folders, &items, &links)?;
|
||||
Ok(links)
|
||||
}
|
||||
|
||||
|
||||
@@ -784,9 +784,10 @@ impl InventoryManager {
|
||||
|
||||
pub(crate) fn native_new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
|
||||
let client = client.ok_or(Error::ArgumentNull)?;
|
||||
let initial_store = Inventory::new_with_grid_client(Arc::clone(&client))?;
|
||||
let inner = Arc::new(InventoryManagerInner {
|
||||
client: client.native_weak_handle(),
|
||||
store: RwLock::new(None),
|
||||
store: RwLock::new(Some(initial_store)),
|
||||
disposed: AtomicBool::new(false),
|
||||
callback_position: AtomicU32::new(0),
|
||||
request_position: AtomicU64::new(0),
|
||||
@@ -1914,10 +1915,9 @@ impl InventoryManager {
|
||||
.await?
|
||||
.ok_or(Error::InvalidOperation)?;
|
||||
let uri = Uri(format!(
|
||||
"{}/category/{}?tid={}",
|
||||
"{}/category/{}",
|
||||
cap.0.trim_end_matches('/'),
|
||||
folder_id,
|
||||
UUID::random()?
|
||||
folder_id
|
||||
));
|
||||
let payload = OSD::Map(HashMap::from([(
|
||||
"links".to_owned(),
|
||||
@@ -4397,6 +4397,103 @@ fn parse_task_inventory_values(
|
||||
}
|
||||
|
||||
impl InventoryManager {
|
||||
pub(crate) async fn native_upload_thumbnail(
|
||||
&self,
|
||||
inventory_id: UUID,
|
||||
task_id: UUID,
|
||||
j2c_image_data: Vec<u8>,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
progress: Option<
|
||||
Box<dyn libremetaverse_types::compat::IProgress<crate::HttpCapsClientProgressReport>>,
|
||||
>,
|
||||
) -> Result<Option<Option<UUID>>, Error> {
|
||||
if inventory_id == UUID::zero()
|
||||
|| j2c_image_data.is_empty()
|
||||
|| j2c_image_data.len() > crate::asset_models::MAX_ASSET_BYTES
|
||||
{
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
|
||||
let token = cancellation_token.unwrap_or_default();
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let client = self.client()?;
|
||||
let Some(simulator) = client.native_network()?.current_sim() else {
|
||||
return Ok(Some(None));
|
||||
};
|
||||
let Some(uri) = self
|
||||
.wait_for_capability(&simulator, "InventoryThumbnailUpload", &token)
|
||||
.await?
|
||||
else {
|
||||
return Ok(Some(None));
|
||||
};
|
||||
|
||||
let inventory_store = self.native_store();
|
||||
let known_local_folder = task_id == UUID::zero()
|
||||
&& inventory_store
|
||||
.as_ref()
|
||||
.and_then(|store| {
|
||||
store
|
||||
.get_value_or_default_with_uuid_a7c63fbe::<InventoryFolder>(inventory_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
.is_some();
|
||||
let metadata = if known_local_folder {
|
||||
HashMap::from([("category_id".to_owned(), OSD::UUID(inventory_id))])
|
||||
} else {
|
||||
let mut metadata = HashMap::from([("item_id".to_owned(), OSD::UUID(inventory_id))]);
|
||||
if task_id != UUID::zero() {
|
||||
metadata.insert("task_id".to_owned(), OSD::UUID(task_id));
|
||||
}
|
||||
metadata
|
||||
};
|
||||
let payload = OSDParser::serialize_llsd_xml_bytes(OSD::Map(metadata))?;
|
||||
let (metadata_response, metadata_body) = client
|
||||
.native_http_caps_client()
|
||||
.post_with_uri_string_bytes_cancellation_token_i_progress(
|
||||
uri,
|
||||
"application/llsd+xml".into(),
|
||||
payload,
|
||||
token.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
if !(200..300).contains(&metadata_response.status_code) {
|
||||
return Ok(Some(None));
|
||||
}
|
||||
let metadata: serde_json::Value =
|
||||
serde_json::from_slice(&metadata_body).map_err(|_| Error::Argument)?;
|
||||
let Some(uploader) = metadata.get("uploader").and_then(serde_json::Value::as_str) else {
|
||||
return Ok(Some(None));
|
||||
};
|
||||
|
||||
let (upload_response, upload_body) = client
|
||||
.native_http_caps_client()
|
||||
.post_with_uri_string_bytes_cancellation_token_i_progress(
|
||||
Uri(uploader.to_owned()),
|
||||
"image/x-j2c".into(),
|
||||
j2c_image_data,
|
||||
token,
|
||||
progress,
|
||||
)
|
||||
.await?;
|
||||
if !(200..300).contains(&upload_response.status_code) {
|
||||
return Ok(Some(None));
|
||||
}
|
||||
let result: serde_json::Value =
|
||||
serde_json::from_slice(&upload_body).map_err(|_| Error::Argument)?;
|
||||
if result.get("state").and_then(serde_json::Value::as_str) != Some("complete") {
|
||||
return Ok(Some(None));
|
||||
}
|
||||
let asset_id = result
|
||||
.get("new_asset")
|
||||
.or_else(|| result.get("new_asset_id"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|id| UUID::new_with_string(id.to_owned()))
|
||||
.transpose()?;
|
||||
Ok(Some(asset_id))
|
||||
}
|
||||
|
||||
pub(crate) async fn native_update_material_inventory(
|
||||
&self,
|
||||
material: crate::assets::AssetMaterial,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#![allow(clippy::needless_pass_by_value)]
|
||||
|
||||
use crate::agent_manager::EventRegistry;
|
||||
use crate::client_core::ClientWeakHandle;
|
||||
use crate::marketplace::{
|
||||
MarketplaceFolderRole, MarketplaceListingStatus, MarketplaceValidationFlags,
|
||||
};
|
||||
@@ -291,8 +292,13 @@ impl MarketplaceListingsSyncedEventArgs {
|
||||
}
|
||||
}
|
||||
|
||||
enum MarketplaceClient {
|
||||
Standalone(GridClient),
|
||||
ClientOwned(ClientWeakHandle),
|
||||
}
|
||||
|
||||
struct MarketplaceState {
|
||||
client: GridClient,
|
||||
client: RwLock<MarketplaceClient>,
|
||||
by_id: RwLock<HashMap<i32, MarketplaceListing>>,
|
||||
by_folder: RwLock<HashMap<UUID, MarketplaceListing>>,
|
||||
errors: EventRegistry<MarketplaceErrorEventArgs>,
|
||||
@@ -306,15 +312,36 @@ pub struct MarketplaceManager(Arc<MarketplaceState>);
|
||||
|
||||
impl MarketplaceManager {
|
||||
pub fn new(client: GridClient) -> Result<Self, Error> {
|
||||
Ok(Self(Arc::new(MarketplaceState {
|
||||
client,
|
||||
Ok(Self::with_client(MarketplaceClient::Standalone(client)))
|
||||
}
|
||||
|
||||
fn with_client(client: MarketplaceClient) -> Self {
|
||||
Self(Arc::new(MarketplaceState {
|
||||
client: RwLock::new(client),
|
||||
by_id: RwLock::new(HashMap::new()),
|
||||
by_folder: RwLock::new(HashMap::new()),
|
||||
errors: EventRegistry::default(),
|
||||
changed: EventRegistry::default(),
|
||||
synced: EventRegistry::default(),
|
||||
live_mutations: AtomicBool::new(false),
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn native_new_client_owned(client: &GridClient) -> Self {
|
||||
Self::with_client(MarketplaceClient::ClientOwned(client.native_weak_handle()))
|
||||
}
|
||||
|
||||
pub(crate) fn native_bind_to_client(&self, client: &GridClient) {
|
||||
*write(&self.0.client) = MarketplaceClient::ClientOwned(client.native_weak_handle());
|
||||
}
|
||||
|
||||
fn client(&self) -> Result<GridClient, Error> {
|
||||
match &*read(&self.0.client) {
|
||||
MarketplaceClient::Standalone(client) => Ok(client.clone()),
|
||||
MarketplaceClient::ClientOwned(client) => {
|
||||
client.upgrade().ok_or(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicitly enables mutation requests to non-test capability hosts.
|
||||
@@ -366,8 +393,7 @@ impl MarketplaceManager {
|
||||
async fn base_uri(&self, token: &CancellationToken) -> Result<Uri, Error> {
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let simulator = self
|
||||
.0
|
||||
.client
|
||||
.client()?
|
||||
.native_network()?
|
||||
.native_current_sim()
|
||||
.ok_or(Error::InvalidOperation)?;
|
||||
@@ -412,7 +438,7 @@ impl MarketplaceManager {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
let uri = Uri(format!("{}/{}", base.0.trim_end_matches('/'), path));
|
||||
let client = self.0.client.native_http_caps_client();
|
||||
let client = self.client()?.native_http_caps_client();
|
||||
match method {
|
||||
"GET" => client.get(uri, token, None).await,
|
||||
"POST" => {
|
||||
|
||||
@@ -10,15 +10,15 @@ use libremetaverse::packets::{
|
||||
SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
|
||||
};
|
||||
use libremetaverse::{
|
||||
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
|
||||
LoginStatus, NetworkManager, NetworkManagerDisconnectType, NetworkManagerLoginResponseCallback,
|
||||
PacketEventDictionary, Simulator,
|
||||
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient,
|
||||
InventoryItem, LoginState, LoginStatus, NetworkManager, NetworkManagerDisconnectType,
|
||||
NetworkManagerLoginResponseCallback, PacketEventDictionary, Simulator,
|
||||
};
|
||||
use libremetaverse_structured_data::{OSD, OSDParser};
|
||||
use libremetaverse_types::compat::{
|
||||
CancellationTokenSource, EventHandler, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
|
||||
};
|
||||
use libremetaverse_types::{UUID, Utils};
|
||||
use libremetaverse_types::{AssetType, Error, UUID, Utils};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
@@ -1843,12 +1843,17 @@ async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task
|
||||
client.settings().agent_settings_mut().multiple_sims = true;
|
||||
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
||||
let manager = client.network();
|
||||
let manager_event_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let (queue_sender, queue_receiver) = channel();
|
||||
let queue_order = Arc::clone(&manager_event_order);
|
||||
let _queue_guard = manager.subscribe_event_queue_running(Arc::new(move |args| {
|
||||
queue_order.lock().unwrap().push("event_queue_running");
|
||||
queue_sender.send(args.simulator().handle).unwrap();
|
||||
}));
|
||||
let (logout_sender, logout_receiver) = channel();
|
||||
let logout_order = Arc::clone(&manager_event_order);
|
||||
let _logout_guard = manager.subscribe_logged_out(Arc::new(move |args| {
|
||||
logout_order.lock().unwrap().push("logged_out");
|
||||
logout_sender.send(args.inventory_items).unwrap();
|
||||
}));
|
||||
|
||||
@@ -1929,6 +1934,77 @@ async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task
|
||||
assert_eq!(asset, b"fake-grid-asset");
|
||||
assert_eq!(downloads.active_download_count(), 0);
|
||||
|
||||
// Cross-manager construction is deliberately performed only after the
|
||||
// authenticated simulator and seed capabilities are available. Every
|
||||
// manager below shares the same client runtime and must preserve its
|
||||
// local store when accessed again through GridClient.
|
||||
{
|
||||
let agent = client.self_();
|
||||
assert_eq!(
|
||||
agent.agent_id(),
|
||||
UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
agent.session_id(),
|
||||
UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap()
|
||||
);
|
||||
}
|
||||
let inventory_id =
|
||||
UUID::new_with_string("44444444-4444-4444-4444-444444444444".into()).unwrap();
|
||||
let inventory = client.inventory();
|
||||
let mut store = inventory.store().expect("authenticated inventory store");
|
||||
let mut item = InventoryItem::new_with_uuid(inventory_id).unwrap();
|
||||
item.base.set_name("fake-grid inventory marker".into());
|
||||
store.set_item(inventory_id, item.base);
|
||||
assert!(
|
||||
client
|
||||
.inventory()
|
||||
.store()
|
||||
.unwrap()
|
||||
.contains_with_uuid(inventory_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(!client.ais_client().is_available());
|
||||
assert_eq!(client.appearance().last_update_received_cof_version(), -1);
|
||||
assert!(
|
||||
client
|
||||
.assets()
|
||||
.create_asset_wrapper(AssetType::Texture)
|
||||
.is_ok()
|
||||
);
|
||||
let _avatars = client.avatars();
|
||||
assert!(client.animesh().get_player(UUID::zero()).unwrap().is_none());
|
||||
client.objects().request_object(first.clone(), 1).unwrap();
|
||||
assert!(client.parcels().current_parcel().is_none());
|
||||
assert!(client.grid().regions_read_only().is_empty());
|
||||
assert!(client.friends().friend_list().is_empty());
|
||||
assert_eq!(client.groups().current_group_count(), -1);
|
||||
assert!(client.marketplace().listings_by_id().is_empty());
|
||||
let _terrain = client.terrain();
|
||||
let _environment = client.environment();
|
||||
let _sound = client.sound();
|
||||
let _estate = client.estate();
|
||||
|
||||
let cancelled_search = CancellationTokenSource::new();
|
||||
cancelled_search.cancel();
|
||||
assert!(matches!(
|
||||
client
|
||||
.directory()
|
||||
.search_people("Ada Lovelace".into(), Some(cancelled_search.token()),),
|
||||
Err(Error::Cancelled)
|
||||
));
|
||||
|
||||
let (marketplace_error_sender, marketplace_error_receiver) = channel();
|
||||
let marketplace_order = Arc::clone(&manager_event_order);
|
||||
let _marketplace_error_guard = client.marketplace().subscribe_error(Arc::new(move |_| {
|
||||
marketplace_order.lock().unwrap().push("marketplace_error");
|
||||
marketplace_error_sender.send(()).unwrap();
|
||||
}));
|
||||
client.marketplace().fetch_listings(None).await.unwrap();
|
||||
marketplace_error_receiver
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap();
|
||||
|
||||
second_server.commands.send(ServerCommand::Handoff).unwrap();
|
||||
manager
|
||||
.connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
|
||||
@@ -2007,6 +2083,13 @@ async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task
|
||||
assert!(downloads.is_disposed());
|
||||
assert!(!downloads.dispatcher_running());
|
||||
assert_eq!(downloads.active_download_count(), 0);
|
||||
assert_eq!(
|
||||
*manager_event_order.lock().unwrap(),
|
||||
["event_queue_running", "marketplace_error", "logged_out"]
|
||||
);
|
||||
let client_cancellation = client.cancellation_token();
|
||||
client.dispose_with_method().unwrap();
|
||||
assert!(client_cancellation.is_cancellation_requested());
|
||||
let uris = request_uris.lock().unwrap();
|
||||
assert!(uris.iter().any(|uri| uri.contains("/login")));
|
||||
assert!(uris.iter().any(|uri| uri.contains("/seed")));
|
||||
|
||||
Reference in New Issue
Block a user