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:
676
crates/libremetaverse/src/asset_models.rs
Normal file
676
crates/libremetaverse/src/asset_models.rs
Normal file
@@ -0,0 +1,676 @@
|
||||
//! Native asset value models and their format boundaries.
|
||||
|
||||
#![allow(
|
||||
clippy::format_push_string,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::must_use_candidate,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::struct_field_names
|
||||
)]
|
||||
|
||||
use crate::{Error, Permissions};
|
||||
use libremetaverse_imaging::ManagedImage;
|
||||
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
|
||||
use libremetaverse_types::{AssetType, SaleType, UUID, Vector3, WearableType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// Maximum accepted in-memory asset payload. Network limits are enforced before
|
||||
/// this boundary too, but constructors are public and must defend themselves.
|
||||
pub const MAX_ASSET_BYTES: usize = 64 * 1024 * 1024;
|
||||
|
||||
fn validate_bytes(bytes: &[u8]) -> Result<(), Error> {
|
||||
if bytes.len() > MAX_ASSET_BYTES {
|
||||
Err(Error::Argument)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Asset {
|
||||
pub asset_data: Vec<u8>,
|
||||
pub temporary: bool,
|
||||
asset_id: UUID,
|
||||
asset_type: AssetType,
|
||||
}
|
||||
|
||||
impl Asset {
|
||||
pub(crate) fn native_new(
|
||||
asset_type: AssetType,
|
||||
asset_id: UUID,
|
||||
data: Vec<u8>,
|
||||
) -> Result<Self, Error> {
|
||||
validate_bytes(&data)?;
|
||||
Ok(Self {
|
||||
asset_data: data,
|
||||
temporary: false,
|
||||
asset_id,
|
||||
asset_type,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
validate_bytes(&self.asset_data)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
validate_bytes(&self.asset_data)
|
||||
}
|
||||
|
||||
pub const fn asset_id(&self) -> UUID {
|
||||
self.asset_id
|
||||
}
|
||||
pub fn set_asset_id(&mut self, value: UUID) {
|
||||
self.asset_id = value;
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
self.asset_type
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RawAsset(Arc<RwLock<Asset>>);
|
||||
|
||||
impl RawAsset {
|
||||
fn empty(asset_type: AssetType) -> Result<Self, Error> {
|
||||
Ok(Self(Arc::new(RwLock::new(Asset::native_new(
|
||||
asset_type,
|
||||
UUID::zero(),
|
||||
Vec::new(),
|
||||
)?))))
|
||||
}
|
||||
|
||||
fn with_data(asset_type: AssetType, id: UUID, data: Vec<u8>) -> Result<Self, Error> {
|
||||
Ok(Self(Arc::new(RwLock::new(Asset::native_new(
|
||||
asset_type, id, data,
|
||||
)?))))
|
||||
}
|
||||
|
||||
fn decode(&self) -> Result<bool, Error> {
|
||||
self.0
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.decode()
|
||||
}
|
||||
|
||||
fn encode(&self) -> Result<(), Error> {
|
||||
self.0
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.encode()
|
||||
}
|
||||
|
||||
fn bytes(&self) -> Vec<u8> {
|
||||
self.0
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.asset_data
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn replace(&self, bytes: Vec<u8>) -> Result<(), Error> {
|
||||
validate_bytes(&bytes)?;
|
||||
self.0
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.asset_data = bytes;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! raw_asset {
|
||||
($name:ident, $kind:expr) => {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct $name {
|
||||
raw: RawAsset,
|
||||
}
|
||||
impl $name {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty($kind)?,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data($kind, asset_id, asset_data)?,
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
self.raw.decode()
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw.encode()
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
$kind
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
raw_asset!(AssetAnimation, AssetType::Animation);
|
||||
raw_asset!(AssetScriptBinary, AssetType::LSLBytecode);
|
||||
raw_asset!(AssetSound, AssetType::Sound);
|
||||
|
||||
impl AssetSound {
|
||||
pub fn pcm_to_ogg(
|
||||
pcm_data: Vec<u8>,
|
||||
sample_rate: i32,
|
||||
channels: i32,
|
||||
bits_per_sample: Option<i32>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
validate_bytes(&pcm_data)?;
|
||||
if pcm_data.is_empty()
|
||||
|| sample_rate <= 0
|
||||
|| !matches!(channels, 1 | 2)
|
||||
|| !matches!(bits_per_sample.unwrap_or(16), 8 | 16)
|
||||
{
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let bits = bits_per_sample.unwrap_or(16);
|
||||
let bytes_per_sample = usize::try_from(bits / 8).map_err(|_| Error::Argument)?;
|
||||
let channels = usize::try_from(channels).map_err(|_| Error::Argument)?;
|
||||
let frame_size = bytes_per_sample
|
||||
.checked_mul(channels)
|
||||
.ok_or(Error::Argument)?;
|
||||
if !pcm_data.len().is_multiple_of(frame_size) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let frequency =
|
||||
std::num::NonZeroU32::new(u32::try_from(sample_rate).map_err(|_| Error::Argument)?)
|
||||
.ok_or(Error::Argument)?;
|
||||
let channel_count =
|
||||
std::num::NonZeroU8::new(u8::try_from(channels).map_err(|_| Error::Argument)?)
|
||||
.ok_or(Error::Argument)?;
|
||||
let mut builder = vorbis_rs::VorbisEncoderBuilder::new_with_serial(
|
||||
frequency,
|
||||
channel_count,
|
||||
Vec::new(),
|
||||
0x4d43_4154,
|
||||
);
|
||||
let mut encoder = builder.build().map_err(|_| Error::InvalidOperation)?;
|
||||
let frame_count = pcm_data.len() / frame_size;
|
||||
for first_frame in (0..frame_count).step_by(1024) {
|
||||
let end_frame = first_frame.saturating_add(1024).min(frame_count);
|
||||
let mut planar = vec![Vec::with_capacity(end_frame - first_frame); channels];
|
||||
for frame in first_frame..end_frame {
|
||||
for (channel, samples) in planar.iter_mut().enumerate() {
|
||||
let offset = frame * frame_size + channel * bytes_per_sample;
|
||||
let sample = if bits == 8 {
|
||||
(f32::from(pcm_data[offset]) - 128.0) / 128.0
|
||||
} else {
|
||||
f32::from(i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]]))
|
||||
/ 32768.0
|
||||
};
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
encoder
|
||||
.encode_audio_block(&planar)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
}
|
||||
let ogg_data = encoder.finish().map_err(|_| Error::InvalidOperation)?;
|
||||
validate_bytes(&ogg_data)?;
|
||||
Ok(ogg_data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetMutable {
|
||||
raw: RawAsset,
|
||||
pub current_type: AssetType,
|
||||
}
|
||||
impl AssetMutable {
|
||||
pub fn new_with_asset_type(type_: AssetType) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(type_)?,
|
||||
current_type: type_,
|
||||
})
|
||||
}
|
||||
pub fn new_with_asset_type_uuid_bytes(
|
||||
type_: AssetType,
|
||||
asset_id: UUID,
|
||||
asset_data: Vec<u8>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data(type_, asset_id, asset_data)?,
|
||||
current_type: type_,
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
self.raw.decode()
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw.encode()
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
self.current_type
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetCallingCard {
|
||||
raw: RawAsset,
|
||||
pub avatar_id: UUID,
|
||||
}
|
||||
impl AssetCallingCard {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Self::new_with_uuid(UUID::zero())
|
||||
}
|
||||
pub fn new_with_uuid(avatar_id: UUID) -> Result<Self, Error> {
|
||||
let value = Self {
|
||||
raw: RawAsset::empty(AssetType::CallingCard)?,
|
||||
avatar_id,
|
||||
};
|
||||
value.encode()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let raw = RawAsset::with_data(AssetType::CallingCard, asset_id, asset_data)?;
|
||||
let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?;
|
||||
let id = text
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("avatar_id "))
|
||||
.ok_or(Error::Argument)
|
||||
.and_then(|id| UUID::new_with_string(id.trim().to_owned()))?;
|
||||
Ok(Self { raw, avatar_id: id })
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
let text = String::from_utf8(self.raw.bytes()).map_err(|_| Error::Argument)?;
|
||||
Ok(text.lines().any(|line| {
|
||||
line.strip_prefix("avatar_id ")
|
||||
.and_then(|id| UUID::new_with_string(id.trim().to_owned()).ok())
|
||||
== Some(self.avatar_id)
|
||||
}))
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw.replace(
|
||||
format!(
|
||||
"Linden text version 2\n{{\navatar_id {}\n}}\n",
|
||||
self.avatar_id
|
||||
)
|
||||
.into_bytes(),
|
||||
)
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::CallingCard
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetLandmark {
|
||||
raw: RawAsset,
|
||||
pub position: Vector3,
|
||||
pub region_id: UUID,
|
||||
}
|
||||
impl AssetLandmark {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Landmark)?,
|
||||
position: Vector3::zero(),
|
||||
region_id: UUID::zero(),
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let raw = RawAsset::with_data(AssetType::Landmark, asset_id, asset_data)?;
|
||||
let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?;
|
||||
let region = text
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("region_id "))
|
||||
.ok_or(Error::Argument)
|
||||
.and_then(|id| UUID::new_with_string(id.trim().to_owned()))?;
|
||||
let values: Vec<f32> = text
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("local_pos "))
|
||||
.ok_or(Error::Argument)?
|
||||
.split_whitespace()
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| Error::Argument)?;
|
||||
if values.len() != 3 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
Ok(Self {
|
||||
raw,
|
||||
region_id: region,
|
||||
position: Vector3 {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
z: values[2],
|
||||
},
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok())
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw.replace(
|
||||
format!(
|
||||
"Landmark version 2\nregion_id {}\nlocal_pos {} {} {}\n",
|
||||
self.region_id, self.position.x, self.position.y, self.position.z
|
||||
)
|
||||
.into_bytes(),
|
||||
)
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Landmark
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetScriptText {
|
||||
raw: RawAsset,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
impl AssetScriptText {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::LSLText)?,
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let source = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data(AssetType::LSLText, asset_id, asset_data)?,
|
||||
source: Some(source),
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(String::from_utf8(self.raw.bytes()).is_ok())
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw
|
||||
.replace(self.source.clone().unwrap_or_default().into_bytes())
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::LSLText
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetSettings {
|
||||
raw: RawAsset,
|
||||
pub settings: Option<OSD>,
|
||||
}
|
||||
impl AssetSettings {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Settings)?,
|
||||
settings: None,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
|
||||
let settings = OSDParser::deserialize_with_string(text)?;
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data(AssetType::Settings, asset_id, asset_data)?,
|
||||
settings: Some(settings),
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(OSDParser::deserialize_with_bytes(self.raw.bytes()).is_ok())
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
let value = self.settings.clone().unwrap_or_default();
|
||||
self.raw.replace(value.as_string()?.into_bytes())
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Settings
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetMesh {
|
||||
raw: RawAsset,
|
||||
pub mesh_data: OSDMap,
|
||||
}
|
||||
impl AssetMesh {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Mesh)?,
|
||||
mesh_data: OSDMap::new_with_constructor()?,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let raw = RawAsset::with_data(AssetType::Mesh, asset_id, asset_data.clone())?;
|
||||
let mesh_data = match OSDParser::deserialize_llsd_binary_with_bytes(asset_data)? {
|
||||
OSD::Map(map) => OSDMap::new_with_dictionary(map)?,
|
||||
_ => return Err(Error::Argument),
|
||||
};
|
||||
Ok(Self { raw, mesh_data })
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(matches!(
|
||||
OSDParser::deserialize_llsd_binary_with_bytes(self.raw.bytes()),
|
||||
Ok(OSD::Map(_))
|
||||
))
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.raw
|
||||
.replace(OSDParser::serialize_llsd_binary_with_osd(OSD::Map(
|
||||
self.mesh_data.snapshot(),
|
||||
))?)
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Mesh
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetTexture {
|
||||
raw: RawAsset,
|
||||
pub components: i32,
|
||||
pub image: Option<ManagedImage>,
|
||||
}
|
||||
impl AssetTexture {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Texture)?,
|
||||
components: 0,
|
||||
image: None,
|
||||
})
|
||||
}
|
||||
pub fn new_with_managed_image(image: ManagedImage) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Texture)?,
|
||||
components: 0,
|
||||
image: Some(image),
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data(AssetType::Texture, asset_id, asset_data)?,
|
||||
components: 0,
|
||||
image: None,
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
#[cfg(feature = "jpeg2000")]
|
||||
{
|
||||
Ok(libremetaverse_imaging::J2kCodec::decode_bytes(
|
||||
&self.raw.bytes(),
|
||||
libremetaverse_imaging::J2kDecodeOptions::default(),
|
||||
)
|
||||
.is_ok())
|
||||
}
|
||||
#[cfg(not(feature = "jpeg2000"))]
|
||||
{
|
||||
validate_bytes(&self.raw.bytes())?;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
let image = self.image.as_ref().ok_or(Error::InvalidOperation)?;
|
||||
#[cfg(feature = "jpeg2000")]
|
||||
{
|
||||
let encoded = libremetaverse_imaging::J2kCodec::encode(
|
||||
image,
|
||||
libremetaverse_imaging::J2kEncodeOptions::default(),
|
||||
)?;
|
||||
self.raw.replace(encoded)
|
||||
}
|
||||
#[cfg(not(feature = "jpeg2000"))]
|
||||
{
|
||||
let _ = image;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Texture
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetWearable {
|
||||
raw: RawAsset,
|
||||
pub creator: UUID,
|
||||
pub description: String,
|
||||
pub for_sale: SaleType,
|
||||
pub group: UUID,
|
||||
pub group_owned: bool,
|
||||
pub last_owner: UUID,
|
||||
pub name: String,
|
||||
pub owner: UUID,
|
||||
pub params: HashMap<i32, f32>,
|
||||
pub permissions: Permissions,
|
||||
pub sale_price: i32,
|
||||
pub textures: HashMap<crate::AvatarTextureIndex, UUID>,
|
||||
pub wearable_type: WearableType,
|
||||
}
|
||||
impl AssetWearable {
|
||||
fn empty(kind: AssetType) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(kind)?,
|
||||
creator: UUID::zero(),
|
||||
description: String::new(),
|
||||
for_sale: SaleType::Not,
|
||||
group: UUID::zero(),
|
||||
group_owned: false,
|
||||
last_owner: UUID::zero(),
|
||||
name: String::new(),
|
||||
owner: UUID::zero(),
|
||||
params: HashMap::new(),
|
||||
permissions: Permissions::default(),
|
||||
sale_price: 0,
|
||||
textures: HashMap::new(),
|
||||
wearable_type: WearableType::Invalid,
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(std::str::from_utf8(&self.raw.bytes()).is_ok())
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
let mut text = format!(
|
||||
"LLWearable version 22\n{}\n{}\ntype {}\nparameters {}\n",
|
||||
self.name,
|
||||
self.description,
|
||||
self.wearable_type as i32,
|
||||
self.params.len()
|
||||
);
|
||||
let mut params: Vec<_> = self.params.iter().collect();
|
||||
params.sort_unstable_by_key(|(id, _)| **id);
|
||||
for (id, value) in params {
|
||||
text.push_str(&format!("{id} {value}\n"));
|
||||
}
|
||||
text.push_str(&format!("textures {}\n", self.textures.len()));
|
||||
self.raw.replace(text.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetBodypart {
|
||||
wearable: AssetWearable,
|
||||
}
|
||||
impl AssetBodypart {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
wearable: AssetWearable::empty(AssetType::Bodypart)?,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let mut wearable = AssetWearable::empty(AssetType::Bodypart)?;
|
||||
wearable.raw = RawAsset::with_data(AssetType::Bodypart, asset_id, asset_data)?;
|
||||
Ok(Self { wearable })
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Bodypart
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
self.wearable.decode()
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.wearable.encode()
|
||||
}
|
||||
}
|
||||
pub struct AssetClothing {
|
||||
wearable: AssetWearable,
|
||||
}
|
||||
impl AssetClothing {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
wearable: AssetWearable::empty(AssetType::Clothing)?,
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let mut wearable = AssetWearable::empty(AssetType::Clothing)?;
|
||||
wearable.raw = RawAsset::with_data(AssetType::Clothing, asset_id, asset_data)?;
|
||||
Ok(Self { wearable })
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Clothing
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
self.wearable.decode()
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
self.wearable.encode()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetNotecard {
|
||||
raw: RawAsset,
|
||||
pub body_text: String,
|
||||
pub embedded_items: Vec<crate::InventoryItem>,
|
||||
}
|
||||
impl AssetNotecard {
|
||||
pub fn new_with_constructor() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
raw: RawAsset::empty(AssetType::Notecard)?,
|
||||
body_text: String::new(),
|
||||
embedded_items: Vec::new(),
|
||||
})
|
||||
}
|
||||
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
|
||||
let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
|
||||
let marker = "Text length ";
|
||||
let start = text.find(marker).ok_or(Error::Argument)? + marker.len();
|
||||
let line_end = text[start..].find('\n').ok_or(Error::Argument)? + start;
|
||||
let length: usize = text[start..line_end]
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|_| Error::Argument)?;
|
||||
let body_start = line_end + 1;
|
||||
let body_end = body_start.checked_add(length).ok_or(Error::Argument)?;
|
||||
let body_text = text
|
||||
.get(body_start..body_end)
|
||||
.ok_or(Error::Argument)?
|
||||
.to_owned();
|
||||
Ok(Self {
|
||||
raw: RawAsset::with_data(AssetType::Notecard, asset_id, asset_data)?,
|
||||
body_text,
|
||||
embedded_items: Vec::new(),
|
||||
})
|
||||
}
|
||||
pub fn decode(&self) -> Result<bool, Error> {
|
||||
Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok())
|
||||
}
|
||||
pub fn encode(&self) -> Result<(), Error> {
|
||||
if !self.embedded_items.is_empty() {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
let bytes = self.body_text.as_bytes();
|
||||
self.raw.replace(format!("Linden text version 2\n{{\nLLEmbeddedItems version 1\n{{\ncount 0\n}}\nText length {}\n{}\n}}\n", bytes.len(), self.body_text).into_bytes())
|
||||
}
|
||||
pub const fn asset_type(&self) -> AssetType {
|
||||
AssetType::Notecard
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user