stardust_xr/
values.rs

1pub use color;
2pub use mint::{Vector2, Vector3};
3pub type Quaternion = mint::Quaternion<f32>;
4pub type Mat4 = mint::ColumnMatrix4<f32>;
5pub use stardust_xr_schemas::flex::Datamap;
6pub type Color = color::Rgba<f32, color::color_space::LinearRgb>;
7pub use rustc_hash::FxHashMap as Map;
8
9use serde::{Deserialize, Serialize, Serializer};
10use std::{
11	path::{Path, PathBuf},
12	str::FromStr,
13};
14
15#[derive(Debug, Clone, Hash, PartialEq, Eq)]
16/// An identifier to a resource, such as a sound or
17pub enum ResourceID {
18	/// An absolute path to a resource, not themed at all.
19	/// You should only use this for content not included with your client.
20	Direct(PathBuf),
21	/// A resource that is relative to a prefix, meant for resources that are included with the client.
22	/// Allows switching of prefix by the server as well to theme clients.
23	///
24	/// # Example
25	/// ```
26	/// use stardust_xr_fusion::{drawable::Model, resource::ResourceID};
27	///
28	/// // For a client named "star"
29	/// let model_resource = ResourceID::new_namespaced("star", "icon");
30	/// // Build a model at "[prefix]/star/icon.glb" if it exists
31	/// let model = Model::create(client.get_root(), Transform::none(), model_resource).unwrap();
32	/// ```
33	Namespaced {
34		/// Group that this resource is in, generally the client or library's name.
35		namespace: String,
36		/// Path inside the namespace for the exact file. Leave out the extension and ensure no leading slash.
37		path: PathBuf,
38	},
39}
40impl ResourceID {
41	pub fn new_direct(path: impl AsRef<Path>) -> std::io::Result<ResourceID> {
42		let path = path.as_ref();
43		path.try_exists()?;
44		if !path.is_absolute() {
45			return Err(std::io::Error::new(
46				std::io::ErrorKind::NotFound,
47				"Path is not absolute",
48			));
49		}
50		Ok(ResourceID::Direct(path.to_path_buf()))
51	}
52	pub fn new_namespaced(namespace: &str, path: impl AsRef<Path>) -> Self {
53		ResourceID::Namespaced {
54			namespace: namespace.to_string(),
55			path: path.as_ref().to_path_buf(),
56		}
57	}
58	pub(crate) fn parse(&self) -> String {
59		match self {
60			ResourceID::Direct(p) => p.to_str().unwrap().to_string(),
61			ResourceID::Namespaced { namespace, path } => {
62				format!("{namespace}:{}", path.display())
63			}
64		}
65	}
66}
67impl Serialize for ResourceID {
68	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
69		serializer.serialize_str(&self.parse())
70	}
71}
72impl<'de> Deserialize<'de> for ResourceID {
73	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
74		let v = String::deserialize(deserializer)?;
75		Ok(if v.starts_with('/') {
76			let path = PathBuf::from(v);
77			path.metadata().map_err(serde::de::Error::custom)?;
78			ResourceID::Direct(path)
79		} else if let Some((namespace, path)) = v.split_once(':') {
80			ResourceID::Namespaced {
81				namespace: namespace.to_string(),
82				path: PathBuf::from_str(path).map_err(serde::de::Error::custom)?,
83			}
84		} else {
85			return Err(serde::de::Error::custom("Invalid format for string"));
86		})
87	}
88}