Complete milestone 09 world integration gate (#75)
This commit is contained in:
@@ -19,6 +19,11 @@ on:
|
|||||||
- "tools/check_milestone_09_issue_61.py"
|
- "tools/check_milestone_09_issue_61.py"
|
||||||
- "tools/check_milestone_09_issue_62.py"
|
- "tools/check_milestone_09_issue_62.py"
|
||||||
- "tools/check_milestone_09_issue_63.py"
|
- "tools/check_milestone_09_issue_63.py"
|
||||||
|
- "tools/check_milestone_09_issue_64.py"
|
||||||
|
- "tools/check_milestone_09_issue_65.py"
|
||||||
|
- "tools/check_milestone_09_issue_66.py"
|
||||||
|
- "tools/check_milestone_09.py"
|
||||||
|
- "tools/test_milestone_09.py"
|
||||||
- "crates/libremetaverse/src/agent_movement.rs"
|
- "crates/libremetaverse/src/agent_movement.rs"
|
||||||
- "crates/libremetaverse/src/inventory.rs"
|
- "crates/libremetaverse/src/inventory.rs"
|
||||||
- "crates/libremetaverse/src/inventory_manager.rs"
|
- "crates/libremetaverse/src/inventory_manager.rs"
|
||||||
@@ -61,6 +66,11 @@ on:
|
|||||||
- "tools/check_milestone_09_issue_61.py"
|
- "tools/check_milestone_09_issue_61.py"
|
||||||
- "tools/check_milestone_09_issue_62.py"
|
- "tools/check_milestone_09_issue_62.py"
|
||||||
- "tools/check_milestone_09_issue_63.py"
|
- "tools/check_milestone_09_issue_63.py"
|
||||||
|
- "tools/check_milestone_09_issue_64.py"
|
||||||
|
- "tools/check_milestone_09_issue_65.py"
|
||||||
|
- "tools/check_milestone_09_issue_66.py"
|
||||||
|
- "tools/check_milestone_09.py"
|
||||||
|
- "tools/test_milestone_09.py"
|
||||||
- "crates/libremetaverse/src/agent_movement.rs"
|
- "crates/libremetaverse/src/agent_movement.rs"
|
||||||
- "crates/libremetaverse/src/inventory.rs"
|
- "crates/libremetaverse/src/inventory.rs"
|
||||||
- "crates/libremetaverse/src/inventory_manager.rs"
|
- "crates/libremetaverse/src/inventory_manager.rs"
|
||||||
@@ -119,7 +129,10 @@ jobs:
|
|||||||
python3 tools/check_milestone_09_issue_61.py
|
python3 tools/check_milestone_09_issue_61.py
|
||||||
python3 tools/check_milestone_09_issue_62.py
|
python3 tools/check_milestone_09_issue_62.py
|
||||||
python3 tools/check_milestone_09_issue_63.py
|
python3 tools/check_milestone_09_issue_63.py
|
||||||
|
python3 tools/check_milestone_09_issue_64.py
|
||||||
|
python3 tools/check_milestone_09_issue_65.py
|
||||||
python3 tools/check_milestone_09_issue_66.py
|
python3 tools/check_milestone_09_issue_66.py
|
||||||
|
python3 tools/check_milestone_09.py
|
||||||
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
|
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
|
||||||
- name: Test generated wire codecs and translated parity cases
|
- name: Test generated wire codecs and translated parity cases
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".gitea/workflows/rust-workspace.yml"
|
- ".gitea/workflows/rust-workspace.yml"
|
||||||
- "tools/install_openjpeg_2_5_4.sh"
|
- "tools/install_openjpeg_2_5_4.sh"
|
||||||
|
- "tools/check_milestone_09.py"
|
||||||
|
- "tools/test_milestone_09.py"
|
||||||
- "**/*.rs"
|
- "**/*.rs"
|
||||||
- "**/Cargo.toml"
|
- "**/Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
@@ -12,6 +14,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".gitea/workflows/rust-workspace.yml"
|
- ".gitea/workflows/rust-workspace.yml"
|
||||||
- "tools/install_openjpeg_2_5_4.sh"
|
- "tools/install_openjpeg_2_5_4.sh"
|
||||||
|
- "tools/check_milestone_09.py"
|
||||||
|
- "tools/test_milestone_09.py"
|
||||||
- "**/*.rs"
|
- "**/*.rs"
|
||||||
- "**/Cargo.toml"
|
- "**/Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
@@ -43,5 +47,11 @@ jobs:
|
|||||||
python3 tools/check_milestone_09_issue_61.py
|
python3 tools/check_milestone_09_issue_61.py
|
||||||
python3 tools/check_milestone_09_issue_62.py
|
python3 tools/check_milestone_09_issue_62.py
|
||||||
python3 tools/check_milestone_09_issue_63.py
|
python3 tools/check_milestone_09_issue_63.py
|
||||||
|
python3 tools/check_milestone_09_issue_64.py
|
||||||
|
python3 tools/check_milestone_09_issue_65.py
|
||||||
|
python3 tools/check_milestone_09_issue_66.py
|
||||||
|
python3 tools/check_milestone_09.py
|
||||||
|
- name: Test the complete native world milestone
|
||||||
|
run: python3 tools/test_milestone_09.py
|
||||||
- name: Compile every workspace target with bounded memory
|
- name: Compile every workspace target with bounded memory
|
||||||
run: cargo check --workspace --all-targets --locked -j 1
|
run: cargo check --workspace --all-targets --locked -j 1
|
||||||
|
|||||||
@@ -388,6 +388,15 @@ one atomic store transaction; cancellation and malformed responses cannot
|
|||||||
partially mutate the cache. Focused compatibility and injected-HTTP tests cover
|
partially mutate the cache. Focused compatibility and injected-HTTP tests cover
|
||||||
link correction, endpoint recording, error isolation, and metadata updates.
|
link correction, endpoint recording, error isolation, and metadata updates.
|
||||||
|
|
||||||
|
The milestone closes with one deterministic cross-manager fake-grid scenario
|
||||||
|
and a reproducible owned-test runner. The scenario covers authenticated agent
|
||||||
|
state, movement and handoff, shared inventory/AIS state, assets and appearance,
|
||||||
|
avatar/animesh and object entry points, land and discovery caches, social,
|
||||||
|
estate, experience and marketplace construction, ordered events, redacted
|
||||||
|
diagnostics, clean logout, and root cancellation. The complete audit and test
|
||||||
|
boundary is documented in
|
||||||
|
[`docs/world-milestone-gate.md`](docs/world-milestone-gate.md).
|
||||||
|
|
||||||
Native appearance state now tracks layered wearables and attachment points,
|
Native appearance state now tracks layered wearables and attachment points,
|
||||||
sends the inventory-backed wearable and attachment LLUDP messages, and exposes
|
sends the inventory-backed wearable and attachment LLUDP messages, and exposes
|
||||||
worn queries by slot, item, and point. The client-owned Current Outfit Folder
|
worn queries by slot, item, and point. The client-owned Current Outfit Folder
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
|
|||||||
|
|
||||||
| Assembly | Types | Members | Status |
|
| Assembly | Types | Members | Status |
|
||||||
|---|---:|---:|---|
|
|---|---:|---:|---|
|
||||||
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 401 types / 17,023 members; remaining surface is callable failure-only shims |
|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 401 types / 17,025 members; remaining surface is callable failure-only shims |
|
||||||
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
|
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
|
||||||
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
|
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
|
||||||
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
|
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ struct ClientRuntime {
|
|||||||
interest_list_manager: Mutex<Option<Arc<crate::interest_list::InterestListManagerInner>>>,
|
interest_list_manager: Mutex<Option<Arc<crate::interest_list::InterestListManagerInner>>>,
|
||||||
friends_manager: Mutex<Option<Arc<crate::friends_manager::FriendsManagerInner>>>,
|
friends_manager: Mutex<Option<Arc<crate::friends_manager::FriendsManagerInner>>>,
|
||||||
group_manager: Mutex<Option<Arc<crate::group_manager::GroupManagerInner>>>,
|
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>>>,
|
appearance_manager: Mutex<Option<Arc<crate::appearance_manager::AppearanceManagerInner>>>,
|
||||||
avatar_manager: Mutex<Option<Arc<crate::avatar_manager::AvatarManagerInner>>>,
|
avatar_manager: Mutex<Option<Arc<crate::avatar_manager::AvatarManagerInner>>>,
|
||||||
animesh_manager: Mutex<Option<Arc<crate::animesh_runtime::AnimeshManagerInner>>>,
|
animesh_manager: Mutex<Option<Arc<crate::animesh_runtime::AnimeshManagerInner>>>,
|
||||||
@@ -139,6 +140,12 @@ struct ClientRuntime {
|
|||||||
shutdown_wait: Mutex<()>,
|
shutdown_wait: Mutex<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_cached<T>(slot: &Mutex<Option<T>>) {
|
||||||
|
slot.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.take();
|
||||||
|
}
|
||||||
|
|
||||||
impl ClientRuntime {
|
impl ClientRuntime {
|
||||||
fn new(
|
fn new(
|
||||||
services: Vec<Arc<dyn ClientService>>,
|
services: Vec<Arc<dyn ClientService>>,
|
||||||
@@ -174,6 +181,7 @@ impl ClientRuntime {
|
|||||||
interest_list_manager: Mutex::new(None),
|
interest_list_manager: Mutex::new(None),
|
||||||
friends_manager: Mutex::new(None),
|
friends_manager: Mutex::new(None),
|
||||||
group_manager: Mutex::new(None),
|
group_manager: Mutex::new(None),
|
||||||
|
marketplace_manager: Mutex::new(None),
|
||||||
appearance_manager: Mutex::new(None),
|
appearance_manager: Mutex::new(None),
|
||||||
avatar_manager: Mutex::new(None),
|
avatar_manager: Mutex::new(None),
|
||||||
animesh_manager: Mutex::new(None),
|
animesh_manager: Mutex::new(None),
|
||||||
@@ -248,42 +256,25 @@ impl ClientRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
services.clear();
|
services.clear();
|
||||||
self.agent_throttle_sender
|
clear_cached(&self.agent_throttle_sender);
|
||||||
.lock()
|
clear_cached(&self.inventory_manager);
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
clear_cached(&self.inventory_ais_client);
|
||||||
.take();
|
clear_cached(&self.asset_manager);
|
||||||
self.inventory_manager
|
clear_cached(&self.appearance_manager);
|
||||||
.lock()
|
clear_cached(&self.avatar_manager);
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
clear_cached(&self.animesh_manager);
|
||||||
.take();
|
clear_cached(&self.object_manager);
|
||||||
self.inventory_ais_client
|
clear_cached(&self.environment_manager);
|
||||||
.lock()
|
clear_cached(&self.estate_tools);
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
clear_cached(&self.terrain_manager);
|
||||||
.take();
|
clear_cached(&self.sound_manager);
|
||||||
self.object_manager
|
clear_cached(&self.parcel_manager);
|
||||||
.lock()
|
clear_cached(&self.grid_manager);
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
clear_cached(&self.directory_manager);
|
||||||
.take();
|
clear_cached(&self.interest_list_manager);
|
||||||
self.grid_manager
|
clear_cached(&self.friends_manager);
|
||||||
.lock()
|
clear_cached(&self.group_manager);
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
clear_cached(&self.marketplace_manager);
|
||||||
.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();
|
|
||||||
self.http_caps_client
|
self.http_caps_client
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.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);
|
.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(
|
pub(crate) fn native_set_caps_rate_limiter(
|
||||||
&mut self,
|
&mut self,
|
||||||
value: crate::caps_http::CapsRateLimiter,
|
value: crate::caps_http::CapsRateLimiter,
|
||||||
|
|||||||
@@ -6730,11 +6730,13 @@ impl GridClient {
|
|||||||
}
|
}
|
||||||
/// C# member: `P:LibreMetaverse.GridClient.Marketplace`.
|
/// C# member: `P:LibreMetaverse.GridClient.Marketplace`.
|
||||||
pub fn marketplace(&self) -> libremetaverse::marketplace::MarketplaceManager {
|
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`.
|
/// Setter for C# member: `P:LibreMetaverse.GridClient.Marketplace`.
|
||||||
pub fn set_marketplace(&mut self, value: libremetaverse::marketplace::MarketplaceManager) {
|
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`.
|
/// C# member: `P:LibreMetaverse.GridClient.Network`.
|
||||||
pub fn network(&self) -> libremetaverse::NetworkManager {
|
pub fn network(&self) -> libremetaverse::NetworkManager {
|
||||||
@@ -9930,9 +9932,14 @@ impl InventoryManager {
|
|||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
) -> Result<Option<Option<libremetaverse_types::UUID>>, crate::Error> {
|
) -> Result<Option<Option<libremetaverse_types::UUID>>, crate::Error> {
|
||||||
libremetaverse_types::not_implemented(
|
self.native_upload_thumbnail(
|
||||||
"M:LibreMetaverse.InventoryManager.UploadThumbnailAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})",
|
inventory_id,
|
||||||
|
task_id,
|
||||||
|
j2c_image_data,
|
||||||
|
cancellation_token,
|
||||||
|
progress,
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
/// C# member: `M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)`.
|
/// C# member: `M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)`.
|
||||||
pub async fn wait_for_next_inventory_offer(
|
pub async fn wait_for_next_inventory_offer(
|
||||||
@@ -17640,6 +17647,7 @@ pub mod assets {
|
|||||||
pub const TERRAINS_PATH: &'static str = "terrains/";
|
pub const TERRAINS_PATH: &'static str = "terrains/";
|
||||||
/// C# member: `M:LibreMetaverse.Assets.ArchiveConstants.#ctor`.
|
/// C# member: `M:LibreMetaverse.Assets.ArchiveConstants.#ctor`.
|
||||||
pub fn new() -> Result<Self, crate::Error> {
|
pub fn new() -> Result<Self, crate::Error> {
|
||||||
|
/* native zero-sized constants namespace */
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use libremetaverse_types::compat::{CancellationToken, HttpResponse, Uri};
|
|||||||
use libremetaverse_types::{AssetType, UUID};
|
use libremetaverse_types::{AssetType, UUID};
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
const MAX_FOLDER_DEPTH_REQUEST: i32 = 50;
|
const MAX_FOLDER_DEPTH_REQUEST: i32 = 50;
|
||||||
const MAX_AIS_OBJECTS: usize = 100_000;
|
const MAX_AIS_OBJECTS: usize = 100_000;
|
||||||
@@ -110,6 +111,31 @@ impl InventoryAISClient {
|
|||||||
caps.capability_uri(name.to_owned())
|
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 {
|
pub(crate) fn native_is_available(&self) -> bool {
|
||||||
self.capability(Self::INVENTORY_CAP_NAME)
|
self.capability(Self::INVENTORY_CAP_NAME)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -147,7 +173,7 @@ impl InventoryAISClient {
|
|||||||
) -> Result<Option<(HttpResponse, Vec<u8>)>, Error> {
|
) -> Result<Option<(HttpResponse, Vec<u8>)>, Error> {
|
||||||
let token = Self::token(cancellation_token);
|
let token = Self::token(cancellation_token);
|
||||||
token.throw_if_cancellation_requested()?;
|
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);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let bytes = match payload {
|
let bytes = match payload {
|
||||||
@@ -359,18 +385,27 @@ impl InventoryAISClient {
|
|||||||
new_inventory: OSD,
|
new_inventory: OSD,
|
||||||
cancellation_token: Option<CancellationToken>,
|
cancellation_token: Option<CancellationToken>,
|
||||||
) -> Result<Vec<InventoryItem>, Error> {
|
) -> Result<Vec<InventoryItem>, Error> {
|
||||||
let path = format!("category/{parent_uuid}?tid={}", Self::tid()?);
|
let Some((response, bytes)) = self
|
||||||
let (_, _, _, links) = self
|
.request(
|
||||||
.mutation(
|
|
||||||
Self::INVENTORY_CAP_NAME,
|
Self::INVENTORY_CAP_NAME,
|
||||||
"POST",
|
"POST",
|
||||||
path,
|
format!("category/{parent_uuid}"),
|
||||||
BTreeMap::new(),
|
BTreeMap::new(),
|
||||||
Some(new_inventory),
|
Some(new_inventory),
|
||||||
false,
|
|
||||||
cancellation_token,
|
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)
|
Ok(links)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -784,9 +784,10 @@ impl InventoryManager {
|
|||||||
|
|
||||||
pub(crate) fn native_new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
|
pub(crate) fn native_new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
|
||||||
let client = client.ok_or(Error::ArgumentNull)?;
|
let client = client.ok_or(Error::ArgumentNull)?;
|
||||||
|
let initial_store = Inventory::new_with_grid_client(Arc::clone(&client))?;
|
||||||
let inner = Arc::new(InventoryManagerInner {
|
let inner = Arc::new(InventoryManagerInner {
|
||||||
client: client.native_weak_handle(),
|
client: client.native_weak_handle(),
|
||||||
store: RwLock::new(None),
|
store: RwLock::new(Some(initial_store)),
|
||||||
disposed: AtomicBool::new(false),
|
disposed: AtomicBool::new(false),
|
||||||
callback_position: AtomicU32::new(0),
|
callback_position: AtomicU32::new(0),
|
||||||
request_position: AtomicU64::new(0),
|
request_position: AtomicU64::new(0),
|
||||||
@@ -1914,10 +1915,9 @@ impl InventoryManager {
|
|||||||
.await?
|
.await?
|
||||||
.ok_or(Error::InvalidOperation)?;
|
.ok_or(Error::InvalidOperation)?;
|
||||||
let uri = Uri(format!(
|
let uri = Uri(format!(
|
||||||
"{}/category/{}?tid={}",
|
"{}/category/{}",
|
||||||
cap.0.trim_end_matches('/'),
|
cap.0.trim_end_matches('/'),
|
||||||
folder_id,
|
folder_id
|
||||||
UUID::random()?
|
|
||||||
));
|
));
|
||||||
let payload = OSD::Map(HashMap::from([(
|
let payload = OSD::Map(HashMap::from([(
|
||||||
"links".to_owned(),
|
"links".to_owned(),
|
||||||
@@ -4397,6 +4397,103 @@ fn parse_task_inventory_values(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InventoryManager {
|
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(
|
pub(crate) async fn native_update_material_inventory(
|
||||||
&self,
|
&self,
|
||||||
material: crate::assets::AssetMaterial,
|
material: crate::assets::AssetMaterial,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#![allow(clippy::needless_pass_by_value)]
|
#![allow(clippy::needless_pass_by_value)]
|
||||||
|
|
||||||
use crate::agent_manager::EventRegistry;
|
use crate::agent_manager::EventRegistry;
|
||||||
|
use crate::client_core::ClientWeakHandle;
|
||||||
use crate::marketplace::{
|
use crate::marketplace::{
|
||||||
MarketplaceFolderRole, MarketplaceListingStatus, MarketplaceValidationFlags,
|
MarketplaceFolderRole, MarketplaceListingStatus, MarketplaceValidationFlags,
|
||||||
};
|
};
|
||||||
@@ -291,8 +292,13 @@ impl MarketplaceListingsSyncedEventArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MarketplaceClient {
|
||||||
|
Standalone(GridClient),
|
||||||
|
ClientOwned(ClientWeakHandle),
|
||||||
|
}
|
||||||
|
|
||||||
struct MarketplaceState {
|
struct MarketplaceState {
|
||||||
client: GridClient,
|
client: RwLock<MarketplaceClient>,
|
||||||
by_id: RwLock<HashMap<i32, MarketplaceListing>>,
|
by_id: RwLock<HashMap<i32, MarketplaceListing>>,
|
||||||
by_folder: RwLock<HashMap<UUID, MarketplaceListing>>,
|
by_folder: RwLock<HashMap<UUID, MarketplaceListing>>,
|
||||||
errors: EventRegistry<MarketplaceErrorEventArgs>,
|
errors: EventRegistry<MarketplaceErrorEventArgs>,
|
||||||
@@ -306,15 +312,36 @@ pub struct MarketplaceManager(Arc<MarketplaceState>);
|
|||||||
|
|
||||||
impl MarketplaceManager {
|
impl MarketplaceManager {
|
||||||
pub fn new(client: GridClient) -> Result<Self, Error> {
|
pub fn new(client: GridClient) -> Result<Self, Error> {
|
||||||
Ok(Self(Arc::new(MarketplaceState {
|
Ok(Self::with_client(MarketplaceClient::Standalone(client)))
|
||||||
client,
|
}
|
||||||
|
|
||||||
|
fn with_client(client: MarketplaceClient) -> Self {
|
||||||
|
Self(Arc::new(MarketplaceState {
|
||||||
|
client: RwLock::new(client),
|
||||||
by_id: RwLock::new(HashMap::new()),
|
by_id: RwLock::new(HashMap::new()),
|
||||||
by_folder: RwLock::new(HashMap::new()),
|
by_folder: RwLock::new(HashMap::new()),
|
||||||
errors: EventRegistry::default(),
|
errors: EventRegistry::default(),
|
||||||
changed: EventRegistry::default(),
|
changed: EventRegistry::default(),
|
||||||
synced: EventRegistry::default(),
|
synced: EventRegistry::default(),
|
||||||
live_mutations: AtomicBool::new(false),
|
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.
|
/// 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> {
|
async fn base_uri(&self, token: &CancellationToken) -> Result<Uri, Error> {
|
||||||
token.throw_if_cancellation_requested()?;
|
token.throw_if_cancellation_requested()?;
|
||||||
let simulator = self
|
let simulator = self
|
||||||
.0
|
.client()?
|
||||||
.client
|
|
||||||
.native_network()?
|
.native_network()?
|
||||||
.native_current_sim()
|
.native_current_sim()
|
||||||
.ok_or(Error::InvalidOperation)?;
|
.ok_or(Error::InvalidOperation)?;
|
||||||
@@ -412,7 +438,7 @@ impl MarketplaceManager {
|
|||||||
return Err(Error::InvalidOperation);
|
return Err(Error::InvalidOperation);
|
||||||
}
|
}
|
||||||
let uri = Uri(format!("{}/{}", base.0.trim_end_matches('/'), path));
|
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 {
|
match method {
|
||||||
"GET" => client.get(uri, token, None).await,
|
"GET" => client.get(uri, token, None).await,
|
||||||
"POST" => {
|
"POST" => {
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ use libremetaverse::packets::{
|
|||||||
SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
|
SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
|
||||||
};
|
};
|
||||||
use libremetaverse::{
|
use libremetaverse::{
|
||||||
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
|
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient,
|
||||||
LoginStatus, NetworkManager, NetworkManagerDisconnectType, NetworkManagerLoginResponseCallback,
|
InventoryItem, LoginState, LoginStatus, NetworkManager, NetworkManagerDisconnectType,
|
||||||
PacketEventDictionary, Simulator,
|
NetworkManagerLoginResponseCallback, PacketEventDictionary, Simulator,
|
||||||
};
|
};
|
||||||
use libremetaverse_structured_data::{OSD, OSDParser};
|
use libremetaverse_structured_data::{OSD, OSDParser};
|
||||||
use libremetaverse_types::compat::{
|
use libremetaverse_types::compat::{
|
||||||
CancellationTokenSource, EventHandler, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
|
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::collections::{BTreeMap, HashMap};
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
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.settings().agent_settings_mut().multiple_sims = true;
|
||||||
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
||||||
let manager = client.network();
|
let manager = client.network();
|
||||||
|
let manager_event_order = Arc::new(Mutex::new(Vec::new()));
|
||||||
let (queue_sender, queue_receiver) = channel();
|
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| {
|
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();
|
queue_sender.send(args.simulator().handle).unwrap();
|
||||||
}));
|
}));
|
||||||
let (logout_sender, logout_receiver) = channel();
|
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| {
|
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();
|
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!(asset, b"fake-grid-asset");
|
||||||
assert_eq!(downloads.active_download_count(), 0);
|
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();
|
second_server.commands.send(ServerCommand::Handoff).unwrap();
|
||||||
manager
|
manager
|
||||||
.connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
|
.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.is_disposed());
|
||||||
assert!(!downloads.dispatcher_running());
|
assert!(!downloads.dispatcher_running());
|
||||||
assert_eq!(downloads.active_download_count(), 0);
|
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();
|
let uris = request_uris.lock().unwrap();
|
||||||
assert!(uris.iter().any(|uri| uri.contains("/login")));
|
assert!(uris.iter().any(|uri| uri.contains("/login")));
|
||||||
assert!(uris.iter().any(|uri| uri.contains("/seed")));
|
assert!(uris.iter().any(|uri| uri.contains("/seed")));
|
||||||
|
|||||||
48
docs/world-milestone-gate.md
Normal file
48
docs/world-milestone-gate.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# World milestone integration gate
|
||||||
|
|
||||||
|
Milestone 09 closes on native Rust behavior, not on catalog coverage alone. The
|
||||||
|
reproducible focused suite is `python3 tools/test_milestone_09.py`; its target
|
||||||
|
list contains the world-owned library tests, the complete fake-grid lifecycle,
|
||||||
|
and the translated compatibility targets for agent, inventory, assets,
|
||||||
|
appearance, avatars, objects, land, discovery, social, estate, experience, and
|
||||||
|
marketplace behavior. Live tests remain catalogued and reviewed but skip unless
|
||||||
|
their existing credentials and the explicit `RUN_LIVE_TESTS=1` opt-in are
|
||||||
|
supplied. The offline milestone runner always clears that opt-in.
|
||||||
|
|
||||||
|
## Cross-manager fake grid
|
||||||
|
|
||||||
|
The loopback scenario logs in through injected HTTP and UDP endpoints, waits
|
||||||
|
for seed capabilities and the event queue, then performs a simulator handoff.
|
||||||
|
While authenticated it constructs every client-owned world manager, verifies
|
||||||
|
agent/session identity, reconciles an inventory marker through the shared
|
||||||
|
store, checks AIS availability, creates an asset wrapper, observes appearance
|
||||||
|
and animesh state, requests an object, and inspects parcel, grid, friend, group,
|
||||||
|
and marketplace caches. A cancelled directory query proves that caller
|
||||||
|
cancellation wins before wire work. A marketplace capability error emitted
|
||||||
|
through a second `GridClient.Marketplace` lookup proves that the manager cache
|
||||||
|
and event registry are shared.
|
||||||
|
|
||||||
|
Manager events are asserted in deterministic order: event-queue readiness,
|
||||||
|
the marketplace error, then logout. Capability downloads are bounded by the
|
||||||
|
existing HTTP and download-manager policies. Diagnostic formatting is checked
|
||||||
|
against login and capability secrets.
|
||||||
|
|
||||||
|
## Cancellation and teardown
|
||||||
|
|
||||||
|
Logout cancels the event queue, disconnects every simulator, leaves no pending
|
||||||
|
logout task, and stops the download dispatcher. Client disposal then cancels
|
||||||
|
the root token and releases every cached manager, including assets,
|
||||||
|
appearance, avatars, animesh, land, discovery, social, estate, and marketplace
|
||||||
|
state. Inventory has an empty shared store before login so offline fixtures and
|
||||||
|
programs can stage deterministic data; a successful login atomically replaces
|
||||||
|
it with the authenticated hierarchy.
|
||||||
|
|
||||||
|
## Audit and program boundary
|
||||||
|
|
||||||
|
`python3 tools/check_milestone_09.py` rejects stubs in all milestone-owned
|
||||||
|
sources, missing native type/member mappings, unreviewed related parity cases,
|
||||||
|
missing fake-grid evidence, and drift in the nine catalogued Rust program
|
||||||
|
targets. The program ports remain a later milestone, but their required world
|
||||||
|
APIs and target inventory are now auditable. Live actions remain opt-in:
|
||||||
|
uploads, marketplace changes, estate/parcel mutations, currency spending, and
|
||||||
|
operations affecting other users are never part of this offline gate.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
const KEYS: [&str; 3] = ["GRID_USER", "GRID_PASSWORD", "GRID_LOGIN_URL"];
|
const KEYS: [&str; 3] = ["GRID_USER", "GRID_PASSWORD", "GRID_LOGIN_URL"];
|
||||||
|
const LIVE_OPT_IN: &str = "RUN_LIVE_TESTS";
|
||||||
|
|
||||||
fn dotenv_has(path: &Path, key: &str) -> bool {
|
fn dotenv_has(path: &Path, key: &str) -> bool {
|
||||||
std::fs::read_to_string(path).is_ok_and(|contents| {
|
std::fs::read_to_string(path).is_ok_and(|contents| {
|
||||||
@@ -22,9 +23,19 @@ fn main() {
|
|||||||
for key in KEYS {
|
for key in KEYS {
|
||||||
println!("cargo:rerun-if-env-changed={key}");
|
println!("cargo:rerun-if-env-changed={key}");
|
||||||
}
|
}
|
||||||
if KEYS.iter().all(|key| {
|
println!("cargo:rerun-if-env-changed={LIVE_OPT_IN}");
|
||||||
std::env::var(key).is_ok_and(|value| !value.trim().is_empty()) || dotenv_has(&dotenv, key)
|
let opted_in = std::env::var(LIVE_OPT_IN).is_ok_and(|value| {
|
||||||
}) {
|
matches!(
|
||||||
|
value.trim().to_ascii_lowercase().as_str(),
|
||||||
|
"1" | "true" | "yes"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if opted_in
|
||||||
|
&& KEYS.iter().all(|key| {
|
||||||
|
std::env::var(key).is_ok_and(|value| !value.trim().is_empty())
|
||||||
|
|| dotenv_has(&dotenv, key)
|
||||||
|
})
|
||||||
|
{
|
||||||
println!("cargo:rustc-cfg=live_grid_credentials");
|
println!("cargo:rustc-cfg=live_grid_credentials");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -734,8 +734,10 @@ fn thumbnail_known_local_folder_sends_category_id() {
|
|||||||
let mut folder = InventoryFolder::new(folder_id).expect("InventoryFolder constructor");
|
let mut folder = InventoryFolder::new(folder_id).expect("InventoryFolder constructor");
|
||||||
folder.base.set_name("Test Folder".into());
|
folder.base.set_name("Test Folder".into());
|
||||||
folder.base.set_parent_uuid(UUID::zero());
|
folder.base.set_parent_uuid(UUID::zero());
|
||||||
let mut store = client.inventory().store().expect("fixture inventory store");
|
let store = client.inventory().store().expect("fixture inventory store");
|
||||||
store.set_item(folder_id, folder.base);
|
store
|
||||||
|
.update_node_for(&folder)
|
||||||
|
.expect("install fixture inventory folder");
|
||||||
let result = block_on(client.inventory().upload_thumbnail(
|
let result = block_on(client.inventory().upload_thumbnail(
|
||||||
folder_id,
|
folder_id,
|
||||||
UUID::zero(),
|
UUID::zero(),
|
||||||
|
|||||||
@@ -95,19 +95,22 @@ fn avatar_facing_flows_have_typed_callable_signatures() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn avatar_facing_constructors_fail_with_catalog_ids() {
|
fn avatar_facing_constructors_preserve_native_defaults_and_catalog_errors() {
|
||||||
assert_eq!(
|
let download = AssetDownload::new().expect("AssetDownload constructor");
|
||||||
member_id(AssetDownload::new()),
|
assert_eq!(download.asset_id, UUID::zero());
|
||||||
"M:LibreMetaverse.AssetDownload.#ctor"
|
assert_eq!(download.next_packet, 0);
|
||||||
);
|
assert!(download.out_of_order_packets.read().unwrap().is_empty());
|
||||||
assert_eq!(
|
|
||||||
member_id(AssetUpload::new()),
|
let upload = AssetUpload::new().expect("AssetUpload constructor");
|
||||||
"M:LibreMetaverse.AssetUpload.#ctor"
|
assert_eq!(upload.asset_id, UUID::zero());
|
||||||
);
|
assert_eq!(upload.packet_num, 0);
|
||||||
assert_eq!(
|
assert_eq!(upload.xfer_id, 0);
|
||||||
member_id(AppearanceManagerWearableData::new()),
|
|
||||||
"M:LibreMetaverse.AppearanceManager.WearableData.#ctor"
|
let wearable = AppearanceManagerWearableData::new().expect("WearableData constructor");
|
||||||
);
|
assert_eq!(wearable.asset_id, UUID::zero());
|
||||||
|
assert_eq!(wearable.item_id, UUID::zero());
|
||||||
|
assert!(wearable.asset.is_none());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
member_id(InventoryException::new_with_constructor()),
|
member_id(InventoryException::new_with_constructor()),
|
||||||
"M:LibreMetaverse.InventoryException.#ctor"
|
"M:LibreMetaverse.InventoryException.#ctor"
|
||||||
|
|||||||
245
tools/check_milestone_09.py
Normal file
245
tools/check_milestone_09.py
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit the complete native world-services milestone boundary."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
import generate_api_shims
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SOURCE_ROOT = ROOT / "crates" / "libremetaverse" / "src"
|
||||||
|
OWNED_SOURCES = (
|
||||||
|
"agent_manager.rs",
|
||||||
|
"agent_messages.rs",
|
||||||
|
"agent_movement.rs",
|
||||||
|
"animation.rs",
|
||||||
|
"animesh.rs",
|
||||||
|
"animesh_skinning.rs",
|
||||||
|
"appearance_baker.rs",
|
||||||
|
"appearance_manager.rs",
|
||||||
|
"asset_archive.rs",
|
||||||
|
"asset_cache.rs",
|
||||||
|
"asset_manager.rs",
|
||||||
|
"asset_material.rs",
|
||||||
|
"asset_models.rs",
|
||||||
|
"avatar_manager.rs",
|
||||||
|
"avatar_rig.rs",
|
||||||
|
"baking_texture_provider.rs",
|
||||||
|
"current_outfit.rs",
|
||||||
|
"directory_manager.rs",
|
||||||
|
"environment_manager.rs",
|
||||||
|
"estate_tools.rs",
|
||||||
|
"friends_manager.rs",
|
||||||
|
"gesture.rs",
|
||||||
|
"gltf.rs",
|
||||||
|
"grid_manager.rs",
|
||||||
|
"group_manager.rs",
|
||||||
|
"initial_outfit.rs",
|
||||||
|
"interest_list.rs",
|
||||||
|
"inventory.rs",
|
||||||
|
"inventory_ais.rs",
|
||||||
|
"inventory_manager.rs",
|
||||||
|
"marketplace.rs",
|
||||||
|
"model_workflow.rs",
|
||||||
|
"object_manager.rs",
|
||||||
|
"object_material.rs",
|
||||||
|
"object_model.rs",
|
||||||
|
"parcel_manager.rs",
|
||||||
|
"slurl.rs",
|
||||||
|
"sound_manager.rs",
|
||||||
|
"terrain_codec.rs",
|
||||||
|
"terrain_manager.rs",
|
||||||
|
"transfers.rs",
|
||||||
|
"user_report.rs",
|
||||||
|
)
|
||||||
|
REQUIRED_NATIVE_TYPES = {
|
||||||
|
"T:LibreMetaverse.AgentManager",
|
||||||
|
"T:LibreMetaverse.AgentManager.AgentMovement",
|
||||||
|
"T:LibreMetaverse.Animesh.AnimeshManager",
|
||||||
|
"T:LibreMetaverse.AppearanceManager",
|
||||||
|
"T:LibreMetaverse.AssetManager",
|
||||||
|
"T:LibreMetaverse.AvatarManager",
|
||||||
|
"T:LibreMetaverse.DirectoryManager",
|
||||||
|
"T:LibreMetaverse.EnvironmentManager",
|
||||||
|
"T:LibreMetaverse.EstateTools",
|
||||||
|
"T:LibreMetaverse.FriendsManager",
|
||||||
|
"T:LibreMetaverse.GridManager",
|
||||||
|
"T:LibreMetaverse.GroupManager",
|
||||||
|
"T:LibreMetaverse.InventoryAISClient",
|
||||||
|
"T:LibreMetaverse.InventoryManager",
|
||||||
|
"T:LibreMetaverse.Marketplace.MarketplaceManager",
|
||||||
|
"T:LibreMetaverse.ObjectManager",
|
||||||
|
"T:LibreMetaverse.ParcelManager",
|
||||||
|
"T:LibreMetaverse.SoundManager",
|
||||||
|
"T:LibreMetaverse.TerrainManager",
|
||||||
|
}
|
||||||
|
REQUIRED_NATIVE_MEMBERS = {
|
||||||
|
"M:LibreMetaverse.InventoryManager.UploadThumbnailAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})",
|
||||||
|
"P:LibreMetaverse.GridClient.Marketplace",
|
||||||
|
"P:LibreMetaverse.GridClient.Marketplace#set",
|
||||||
|
}
|
||||||
|
RELATED_UPSTREAM_SOURCES = {
|
||||||
|
"LibreMetaverse.Tests/AnimationTrackTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AnimeshPlayerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AnimeshSkinningTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AppearanceLiveTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AppearanceManagerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AppearanceTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AssetMaterialOverrideTests.cs",
|
||||||
|
"LibreMetaverse.Tests/AvatarManagerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/BakerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/BatchLinkTests.cs",
|
||||||
|
"LibreMetaverse.Tests/CurrentOutfitFolderTests.cs",
|
||||||
|
"LibreMetaverse.Tests/EnvironmentManagerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/EstateExperienceReplyTests.cs",
|
||||||
|
"LibreMetaverse.Tests/EstateToolsTests.cs",
|
||||||
|
"LibreMetaverse.Tests/ExperienceListAndRegionMessageTests.cs",
|
||||||
|
"LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs",
|
||||||
|
"LibreMetaverse.Tests/GLTFMaterialOverrideCapTests.cs",
|
||||||
|
"LibreMetaverse.Tests/GLTFMaterialUploadTests.cs",
|
||||||
|
"LibreMetaverse.Tests/GltfDocumentTests.cs",
|
||||||
|
"LibreMetaverse.Tests/InventoryAISClientTests.cs",
|
||||||
|
"LibreMetaverse.Tests/InventoryFetchTests.cs",
|
||||||
|
"LibreMetaverse.Tests/InventoryManagerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/InventoryTests.cs",
|
||||||
|
"LibreMetaverse.Tests/LibraryInventoryTests.cs",
|
||||||
|
"LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs",
|
||||||
|
"LibreMetaverse.Tests/MarketplaceManagerTests.cs",
|
||||||
|
"LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs",
|
||||||
|
"LibreMetaverse.Tests/OarFileTerrainTests.cs",
|
||||||
|
"LibreMetaverse.Tests/OfflineMessagesTests.cs",
|
||||||
|
"LibreMetaverse.Tests/PrimObjectTests.cs",
|
||||||
|
"LibreMetaverse.Tests/PrimitiveTests.cs",
|
||||||
|
"LibreMetaverse.Tests/ProductInfoRequestMessageTests.cs",
|
||||||
|
"LibreMetaverse.Tests/QueryExperiencesOnParcelTests.cs",
|
||||||
|
"LibreMetaverse.Tests/RegionScheduleTests.cs",
|
||||||
|
"LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs",
|
||||||
|
"LibreMetaverse.Tests/SendPostcardTests.cs",
|
||||||
|
"LibreMetaverse.Tests/SimConsoleTests.cs",
|
||||||
|
"LibreMetaverse.Tests/SlurlParserTests.cs",
|
||||||
|
"LibreMetaverse.Tests/StartAuctionTests.cs",
|
||||||
|
"LibreMetaverse.Tests/TerrainMaterialOverrideTests.cs",
|
||||||
|
"LibreMetaverse.Tests/TextureEntryTests.cs",
|
||||||
|
"LibreMetaverse.Tests/UploadThumbnailTests.cs",
|
||||||
|
}
|
||||||
|
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
|
||||||
|
|
||||||
|
|
||||||
|
def require_markers(path: Path, markers: tuple[str, ...]) -> None:
|
||||||
|
text = path.read_text()
|
||||||
|
missing = [marker for marker in markers if marker not in text]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"{path}: audit evidence missing: " + ", ".join(missing))
|
||||||
|
|
||||||
|
|
||||||
|
def check_owned_sources() -> None:
|
||||||
|
offenders = [
|
||||||
|
name for name in OWNED_SOURCES if STUB_RE.search((SOURCE_ROOT / name).read_text())
|
||||||
|
]
|
||||||
|
if offenders:
|
||||||
|
raise SystemExit("milestone 09 owned Rust stubs remain: " + ", ".join(offenders))
|
||||||
|
|
||||||
|
|
||||||
|
def check_native_mappings() -> None:
|
||||||
|
native = set(generate_api_shims.NATIVE_TYPES) | set(generate_api_shims.NATIVE_DECLARATIONS)
|
||||||
|
missing_types = sorted(REQUIRED_NATIVE_TYPES - native)
|
||||||
|
if missing_types:
|
||||||
|
raise SystemExit("milestone 09 native types missing: " + ", ".join(missing_types))
|
||||||
|
missing_members = sorted(
|
||||||
|
REQUIRED_NATIVE_MEMBERS - set(generate_api_shims.NATIVE_MEMBER_BODIES)
|
||||||
|
)
|
||||||
|
if missing_members:
|
||||||
|
raise SystemExit("milestone 09 native members missing: " + ", ".join(missing_members))
|
||||||
|
|
||||||
|
|
||||||
|
def check_parity_catalog() -> None:
|
||||||
|
catalog = json.loads((ROOT / "tests" / "upstream-tests.json").read_text())
|
||||||
|
related = [test for test in catalog["tests"] if test["source"] in RELATED_UPSTREAM_SOURCES]
|
||||||
|
present_sources = {test["source"] for test in related}
|
||||||
|
missing_sources = sorted(RELATED_UPSTREAM_SOURCES - present_sources)
|
||||||
|
if missing_sources:
|
||||||
|
raise SystemExit("milestone 09 parity sources missing: " + ", ".join(missing_sources))
|
||||||
|
bad = [
|
||||||
|
test["id"]
|
||||||
|
for test in related
|
||||||
|
if test["status"] not in {"translated", "ignored-live"}
|
||||||
|
or test.get("semantic_review") != "reviewed"
|
||||||
|
]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit("milestone 09 parity cases are not reviewed: " + ", ".join(bad))
|
||||||
|
missing_rust = sorted(
|
||||||
|
{
|
||||||
|
test["rust_file"]
|
||||||
|
for test in related
|
||||||
|
if test["status"] == "translated"
|
||||||
|
and not (ROOT / test["rust_file"]).is_file()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if missing_rust:
|
||||||
|
raise SystemExit("milestone 09 translated files missing: " + ", ".join(missing_rust))
|
||||||
|
|
||||||
|
|
||||||
|
def check_fake_grid_gate() -> None:
|
||||||
|
require_markers(
|
||||||
|
ROOT / "crates" / "libremetaverse" / "tests" / "network_manager.rs",
|
||||||
|
(
|
||||||
|
"offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks",
|
||||||
|
"event_queue_running",
|
||||||
|
"fake-grid inventory marker",
|
||||||
|
"create_asset_wrapper",
|
||||||
|
"get_player",
|
||||||
|
"request_object",
|
||||||
|
"current_parcel",
|
||||||
|
"friend_list",
|
||||||
|
"current_group_count",
|
||||||
|
"marketplace_error",
|
||||||
|
"search_people",
|
||||||
|
"pending_logout_tasks",
|
||||||
|
"dispatcher_running",
|
||||||
|
"is_cancellation_requested",
|
||||||
|
"seed-secret",
|
||||||
|
"queue-secret",
|
||||||
|
"asset-secret",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_docs_and_program_prerequisites() -> None:
|
||||||
|
require_markers(
|
||||||
|
ROOT / "docs" / "world-milestone-gate.md",
|
||||||
|
(
|
||||||
|
"Cross-manager fake grid",
|
||||||
|
"Cancellation and teardown",
|
||||||
|
"Live actions remain opt-in",
|
||||||
|
"tools/test_milestone_09.py",
|
||||||
|
"RUN_LIVE_TESTS=1",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
projects = json.loads((ROOT / "programs" / "upstream-programs.json").read_text())[
|
||||||
|
"projects"
|
||||||
|
]
|
||||||
|
if len(projects) != 9:
|
||||||
|
raise SystemExit("milestone 09 expected all nine program prerequisite inventories")
|
||||||
|
manifest = (ROOT / "programs" / "Cargo.toml").read_text()
|
||||||
|
if manifest.count("[[bin]]") != 9:
|
||||||
|
raise SystemExit("milestone 09 expected all nine Rust program targets")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
check_owned_sources()
|
||||||
|
check_native_mappings()
|
||||||
|
check_parity_catalog()
|
||||||
|
check_fake_grid_gate()
|
||||||
|
check_docs_and_program_prerequisites()
|
||||||
|
print(
|
||||||
|
"milestone 09 audit: owned world sources contain no stubs; native mappings, "
|
||||||
|
"reviewed parity cases, fake-grid integration, docs, and program prerequisites are present"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -510,7 +510,8 @@ NATIVE_MEMBER_BODIES = {
|
|||||||
"crate::asset_archive::asset_type_to_extension()",
|
"crate::asset_archive::asset_type_to_extension()",
|
||||||
"F:LibreMetaverse.Assets.ArchiveConstants.EXTENSION_TO_ASSET_TYPE":
|
"F:LibreMetaverse.Assets.ArchiveConstants.EXTENSION_TO_ASSET_TYPE":
|
||||||
"crate::asset_archive::extension_to_asset_type()",
|
"crate::asset_archive::extension_to_asset_type()",
|
||||||
"M:LibreMetaverse.Assets.ArchiveConstants.#ctor": "Ok(Self)",
|
"M:LibreMetaverse.Assets.ArchiveConstants.#ctor":
|
||||||
|
"/* native zero-sized constants namespace */ Ok(Self)",
|
||||||
"M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)":
|
"M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)":
|
||||||
"crate::asset_archive::clear_asset_folder(assets_path)",
|
"crate::asset_archive::clear_asset_folder(assets_path)",
|
||||||
"M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)":
|
"M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)":
|
||||||
@@ -525,8 +526,12 @@ NATIVE_MEMBER_BODIES = {
|
|||||||
"crate::asset_archive::save_terrain(sim, terrain_path)",
|
"crate::asset_archive::save_terrain(sim, terrain_path)",
|
||||||
"M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)":
|
"M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)":
|
||||||
"crate::asset_archive::unpackage_archive(filename, asset_callback, terrain_callback, object_callback, settings_callback)",
|
"crate::asset_archive::unpackage_archive(filename, asset_callback, terrain_callback, object_callback, settings_callback)",
|
||||||
|
"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",
|
||||||
"P:LibreMetaverse.GridClient.Estate": "self.native_estate().unwrap_or_else(|_| panic!(\"failed to construct EstateTools\"))",
|
"P:LibreMetaverse.GridClient.Estate": "self.native_estate().unwrap_or_else(|_| panic!(\"failed to construct EstateTools\"))",
|
||||||
"P:LibreMetaverse.GridClient.Estate#set": "self.native_set_estate(value)",
|
"P:LibreMetaverse.GridClient.Estate#set": "self.native_set_estate(value)",
|
||||||
|
"P:LibreMetaverse.GridClient.Marketplace": "self.native_marketplace()",
|
||||||
|
"P:LibreMetaverse.GridClient.Marketplace#set": "self.native_set_marketplace(value)",
|
||||||
"P:LibreMetaverse.GridClient.Environment":
|
"P:LibreMetaverse.GridClient.Environment":
|
||||||
"self.native_environment().unwrap_or_else(|_| panic!(\"failed to construct EnvironmentManager\"))",
|
"self.native_environment().unwrap_or_else(|_| panic!(\"failed to construct EnvironmentManager\"))",
|
||||||
"P:LibreMetaverse.GridClient.Environment#set": "self.native_set_environment(value)",
|
"P:LibreMetaverse.GridClient.Environment#set": "self.native_set_environment(value)",
|
||||||
|
|||||||
@@ -974,6 +974,7 @@ def validate_generated_shims() -> None:
|
|||||||
if (
|
if (
|
||||||
"native client-core implementation" in body
|
"native client-core implementation" in body
|
||||||
or "native network implementation" in body
|
or "native network implementation" in body
|
||||||
|
or "native zero-sized constants namespace" in body
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
uses_standardized_or_native_path = any(
|
uses_standardized_or_native_path = any(
|
||||||
@@ -1008,6 +1009,7 @@ def validate_generated_shims() -> None:
|
|||||||
"crate::inventory::",
|
"crate::inventory::",
|
||||||
"crate::inventory_ais::",
|
"crate::inventory_ais::",
|
||||||
"crate::inventory_manager::",
|
"crate::inventory_manager::",
|
||||||
|
"crate::asset_archive::",
|
||||||
"crate::initial_outfit::",
|
"crate::initial_outfit::",
|
||||||
"crate::current_outfit::",
|
"crate::current_outfit::",
|
||||||
"crate::message_codec::",
|
"crate::message_codec::",
|
||||||
|
|||||||
71
tools/test_milestone_09.py
Normal file
71
tools/test_milestone_09.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run only the Rust tests owned by the world-services milestone."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
COMPAT_TARGETS = (
|
||||||
|
"appearance_live_semantics",
|
||||||
|
"appearance_semantics",
|
||||||
|
"appearance_visual_semantics",
|
||||||
|
"archive_model_semantics",
|
||||||
|
"asset_capability_semantics",
|
||||||
|
"asset_document_semantics",
|
||||||
|
"avatar_manager_shims",
|
||||||
|
"current_outfit_folder_semantics",
|
||||||
|
"current_outfit_policy_semantics",
|
||||||
|
"inventory_ais_semantics",
|
||||||
|
"inventory_live_semantics",
|
||||||
|
"inventory_manager_semantics",
|
||||||
|
"inventory_store_semantics",
|
||||||
|
"marketplace_classifier_semantics",
|
||||||
|
"marketplace_manager_semantics",
|
||||||
|
"misclassified_link_semantics",
|
||||||
|
"slurl_semantics",
|
||||||
|
"social_constructor_semantics",
|
||||||
|
"social_message_semantics",
|
||||||
|
"task_inventory_semantics",
|
||||||
|
"user_report_semantics",
|
||||||
|
"wire_shims",
|
||||||
|
"world_capability_semantics",
|
||||||
|
"world_constructor_semantics",
|
||||||
|
"world_live_semantics",
|
||||||
|
"world_manager_shims",
|
||||||
|
"world_message_semantics",
|
||||||
|
"world_object_semantics",
|
||||||
|
"world_packet_semantics",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run(command: list[str]) -> None:
|
||||||
|
print("+", " ".join(command), flush=True)
|
||||||
|
subprocess.run(command, cwd=ROOT, env=ENV, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
ENV = os.environ.copy()
|
||||||
|
ENV.setdefault("CARGO_BUILD_JOBS", "1")
|
||||||
|
ENV.pop("RUN_LIVE_TESTS", None)
|
||||||
|
|
||||||
|
run(["cargo", "test", "-p", "libremetaverse", "--lib", "--locked"])
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"cargo",
|
||||||
|
"test",
|
||||||
|
"-p",
|
||||||
|
"libremetaverse",
|
||||||
|
"--test",
|
||||||
|
"network_manager",
|
||||||
|
"offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks",
|
||||||
|
"--locked",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
compat = ["cargo", "test", "-p", "libremetaverse-compat-tests"]
|
||||||
|
for target in COMPAT_TARGETS:
|
||||||
|
compat.extend(("--test", target))
|
||||||
|
compat.append("--locked")
|
||||||
|
run(compat)
|
||||||
Reference in New Issue
Block a user