Implement native asset pipeline and cache (#64)
Some checks failed
Native code generation / deterministic (push) Failing after 2m18s
Imaging and meshing gate / native (push) Failing after 1m30s
JPEG 2000 feature / linux (push) Successful in 2m40s
Native Rust workspace compile / compile (push) Failing after 57s
Skia feature / linux (push) Successful in 31m8s
Some checks failed
Native code generation / deterministic (push) Failing after 2m18s
Imaging and meshing gate / native (push) Failing after 1m30s
JPEG 2000 feature / linux (push) Successful in 2m40s
Native Rust workspace compile / compile (push) Failing after 57s
Skia feature / linux (push) Successful in 31m8s
This commit is contained in:
133
crates/libremetaverse/src/asset_pipeline_semantics.rs
Normal file
133
crates/libremetaverse/src/asset_pipeline_semantics.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use crate::assets::{AssetMesh, AssetMutable, AssetSound, AssetTexture};
|
||||
use crate::{
|
||||
AssetCache, AssetCacheComputeAssetCacheFilenameDelegate, Error, GridClient, ImageCodec,
|
||||
};
|
||||
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
|
||||
use libremetaverse_types::{AssetType, UUID};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn fixture(max_size: i64) -> (GridClient, AssetCache, PathBuf) {
|
||||
let path =
|
||||
std::env::temp_dir().join(format!("metacrate-asset-cache-{}", UUID::random().unwrap()));
|
||||
let mut client = GridClient::new().unwrap();
|
||||
let settings = client.settings();
|
||||
settings.asset_cache_mut().dir = path.to_string_lossy().into_owned();
|
||||
settings.asset_cache_mut().enabled = true;
|
||||
settings.asset_cache_mut().max_size = max_size;
|
||||
let cache = AssetCache::new(client.clone()).unwrap();
|
||||
(client, cache, path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asset_models_retain_type_and_reject_malformed_meshes() {
|
||||
let asset = AssetMutable::new_with_asset_type_uuid_bytes(
|
||||
AssetType::Sound,
|
||||
UUID::random().unwrap(),
|
||||
vec![1, 2, 3],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(asset.asset_type(), AssetType::Sound);
|
||||
assert!(asset.decode().unwrap());
|
||||
assert!(AssetMesh::new_with_uuid_bytes(UUID::random().unwrap(), b"not-llsd".to_vec()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_and_texture_codecs_produce_real_valid_payloads() {
|
||||
let ogg = AssetSound::pcm_to_ogg(vec![0_u8; 256 * 2], 44_100, 1, Some(16)).unwrap();
|
||||
assert!(ogg.starts_with(b"OggS"));
|
||||
|
||||
let mut image = ManagedImage::new(2, 2, ManagedImageImageChannels::COLOR).unwrap();
|
||||
image.red.fill(255);
|
||||
let texture = AssetTexture::new_with_managed_image(image).unwrap();
|
||||
texture.encode().unwrap();
|
||||
assert!(texture.decode().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_writes_are_atomic_and_corruption_is_typed() {
|
||||
let (_client, cache, path) = fixture(1024);
|
||||
let id = UUID::random().unwrap();
|
||||
cache
|
||||
.save_asset_to_cache_with_uuid_bytes(id, vec![1, 2, 3, 4])
|
||||
.unwrap();
|
||||
cache
|
||||
.save_asset_to_cache_with_uuid_bytes(id, vec![9, 9, 9, 9])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cache.get_cached_asset_bytes_with_uuid(id).unwrap(),
|
||||
Some(vec![1, 2, 3, 4])
|
||||
);
|
||||
|
||||
let image = cache.get_cached_image(id).unwrap().unwrap();
|
||||
assert_eq!(image.codec, ImageCodec::J2C);
|
||||
assert_eq!(image.base.asset_data, vec![1, 2, 3, 4]);
|
||||
|
||||
let corrupt = UUID::random().unwrap();
|
||||
fs::write(path.join(corrupt.to_string()), []).unwrap();
|
||||
assert_eq!(
|
||||
cache.get_cached_asset_bytes_with_uuid(corrupt),
|
||||
Err(Error::Argument)
|
||||
);
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_cache_naming_is_used_but_cannot_escape_the_cache_directory() {
|
||||
let (_client, mut cache, path) = fixture(1024);
|
||||
cache.compute_asset_cache_filename = Some(
|
||||
AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, id| {
|
||||
Ok(PathBuf::from(directory)
|
||||
.join(format!("{id}.asset"))
|
||||
.to_string_lossy()
|
||||
.into_owned())
|
||||
}),
|
||||
);
|
||||
let id = UUID::random().unwrap();
|
||||
cache
|
||||
.save_asset_to_cache_with_uuid_bytes(id, vec![7, 8, 9])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cache.get_cached_asset_bytes_with_uuid(id).unwrap(),
|
||||
Some(vec![7, 8, 9])
|
||||
);
|
||||
assert!(path.join(format!("{id}.asset")).is_file());
|
||||
cache.clear().unwrap();
|
||||
assert!(!path.join(format!("{id}.asset")).exists());
|
||||
|
||||
cache.compute_asset_cache_filename = Some(
|
||||
AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, _| {
|
||||
Ok(PathBuf::from(directory)
|
||||
.join("..")
|
||||
.join("escape")
|
||||
.to_string_lossy()
|
||||
.into_owned())
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
cache.has_asset(UUID::random().unwrap()),
|
||||
Err(Error::Argument)
|
||||
);
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cache_pruning_reclaims_old_entries_to_below_the_limit() {
|
||||
let (_client, cache, path) = fixture(12);
|
||||
cache
|
||||
.save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![1; 8])
|
||||
.unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
cache
|
||||
.save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![2; 8])
|
||||
.unwrap();
|
||||
cache.prune(None).await.unwrap();
|
||||
let bytes: u64 = fs::read_dir(&path)
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|entry| entry.metadata().ok())
|
||||
.map(|metadata| metadata.len())
|
||||
.sum();
|
||||
assert!(bytes <= 10);
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user