goods/
lib.rs

1//! Asset loader.
2//!
3//! # Asset and AssetField derive macros
4//!
5//! Creates structures to act as two loading stages of asset and implement asset using those.
6//! First stages must be deserializable with serde.
7//! All fields with `#[external]` must implement `AssetField<External>`. Which has blanket impl for `Asset` implementors and some wrappers, like `Option<A>` and `Arc<[A]>` where `A: Asset`.
8//! All fields with `#[container]` attribute must implement `AssetField<Container>`. It can be derived using `derive(AssetField)`. They can in turn contain fields with `#[external]` and `#[container]` attributes. Also implemented for wrappers like `Option<A>` and `Arc<[A]>`.
9//! All fields without special attributes of the target struct must implement `DeserializeOwned`.
10//! All fields transiently with #[external] attribute will be replaced with id for first stage struct and `AssetResult`s for second stage.
11//! Second stages will have `AssetResult`s fields in place of the assets.
12//!
13//! # Example
14//!
15//! ```
16//!
17//! # use goods::*;
18//!
19//! /// Simple deserializable type. Included as-is into generated types for `#[derive(Asset)]` and #[derive(AssetField)].
20//! #[derive(Clone, serde::Deserialize)]
21//! struct Foo;
22//!
23//! /// Trivial asset type.
24//! #[derive(Clone, Asset)]
25//! #[asset(name = "bar")]
26//! struct Bar;
27//!
28//! /// Asset field type. `AssetField<Container>` implementation is generated, but not `Asset` implementation.
29//! /// Fields of types with `#[derive(AssetField)]` attribute are not replaced by uuids as external assets.
30//! #[derive(Clone, AssetField)]
31//! struct Baz;
32//!
33//! /// Asset structure. Implements Asset trait using
34//! /// two generated structures are intermediate phases.
35//! #[derive(Clone, Asset)]
36//! #[asset(name = "assetstruct")]
37//! struct AssetStruct {
38//!     /// Deserializable types are inlined into asset as is.
39//!     foo: Foo,
40//!
41//!     /// `AssetField<External>` is implemented for all `Asset` implementors.
42//!     /// Deserialized as `AssetId` and loaded recursively.
43//!     #[asset(external)]
44//!     bar: Bar,
45//!
46//!     /// Container fields are deserialized similar to types that derive `Asset`.
47//!     /// If there is no external asset somewhere in hierarchy, decoded `Baz` is structurally equivalent to `Baz`.
48//!     #[asset(container)]
49//!     baz: Baz,
50//! }
51//! ```
52
53mod asset;
54mod field;
55mod key;
56mod loader;
57pub mod source;
58
59use std::{
60    borrow::Borrow,
61    fmt::{self, Debug, Display, LowerHex, UpperHex},
62    marker::PhantomData,
63    num::NonZeroU64,
64    str::FromStr,
65    sync::Arc,
66};
67
68pub use self::{
69    asset::{Asset, AssetBuild, SimpleAsset, TrivialAsset},
70    field::{AssetField, AssetFieldBuild, Container, External},
71    loader::{AssetHandle, AssetResult, AssetResultPoisoned, Error, Key, Loader, LoaderBuilder},
72};
73pub use goods_proc::{Asset, AssetField};
74
75// Used by generated code.
76#[doc(hidden)]
77pub use {bincode, serde, serde_json, std::convert::Infallible, thiserror};
78
79#[derive(thiserror::Error)]
80#[error("Asset '{key}' is not found")]
81struct NotFound {
82    key: Arc<str>,
83}
84
85impl fmt::Debug for NotFound {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        Display::fmt(self, f)
88    }
89}
90
91/// Type for unique asset identification.
92/// There are 2^64-1 valid values of this type that should be enough for now.
93///
94/// Using `NonZero` makes `Option<AssetId>` same size as `AssetId` which is good for performance.
95#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
96#[repr(transparent)]
97pub struct AssetId(pub NonZeroU64);
98
99impl AssetId {
100    pub const fn new(value: u64) -> Option<Self> {
101        match NonZeroU64::new(value) {
102            None => None,
103            Some(value) => Some(AssetId(value)),
104        }
105    }
106}
107
108impl serde::Serialize for AssetId {
109    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: serde::Serializer,
112    {
113        use std::io::Write;
114
115        if serializer.is_human_readable() {
116            let mut hex = [0u8; 16];
117            write!(std::io::Cursor::new(&mut hex[..]), "{:x}", self.0).expect("Must fit");
118            debug_assert!(hex.is_ascii());
119            let hex = std::str::from_utf8(&hex).expect("Must be UTF-8");
120            serializer.serialize_str(hex)
121        } else {
122            serializer.serialize_u64(self.0.get())
123        }
124    }
125}
126
127impl<'de> serde::Deserialize<'de> for AssetId {
128    fn deserialize<D>(deserializer: D) -> Result<AssetId, D::Error>
129    where
130        D: serde::Deserializer<'de>,
131    {
132        if deserializer.is_human_readable() {
133            let hex = std::borrow::Cow::<str>::deserialize(deserializer)?;
134            hex.parse().map_err(serde::de::Error::custom)
135        } else {
136            let value = NonZeroU64::deserialize(deserializer)?;
137            Ok(AssetId(value))
138        }
139    }
140}
141
142#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
143pub enum ParseAssetIdError {
144    #[error(transparent)]
145    ParseIntError(#[from] std::num::ParseIntError),
146
147    #[error("AssetId cannot be zero")]
148    ZeroId,
149}
150
151impl FromStr for AssetId {
152    type Err = ParseAssetIdError;
153    fn from_str(s: &str) -> Result<Self, ParseAssetIdError> {
154        let value = u64::from_str_radix(s, 16)?;
155        match NonZeroU64::new(value) {
156            None => Err(ParseAssetIdError::ZeroId),
157            Some(value) => Ok(AssetId(value)),
158        }
159    }
160}
161
162impl Debug for AssetId {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        LowerHex::fmt(&self.0.get(), f)
165    }
166}
167
168impl Display for AssetId {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        LowerHex::fmt(&self.0.get(), f)
171    }
172}
173
174impl LowerHex for AssetId {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        LowerHex::fmt(&self.0.get(), f)
177    }
178}
179
180impl UpperHex for AssetId {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        UpperHex::fmt(&self.0.get(), f)
183    }
184}
185
186/// `AssetId` augmented with type information, specifying which asset type is referenced.
187#[derive(
188    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
189)]
190#[serde(transparent)]
191#[repr(transparent)]
192pub struct TypedAssetId<A> {
193    pub id: AssetId,
194    pub marker: PhantomData<A>,
195}
196
197impl<A> TypedAssetId<A> {
198    pub const fn new(value: u64) -> Option<Self> {
199        match AssetId::new(value) {
200            None => None,
201            Some(id) => Some(TypedAssetId {
202                id,
203                marker: PhantomData,
204            }),
205        }
206    }
207}
208
209impl<A> Borrow<AssetId> for TypedAssetId<A> {
210    fn borrow(&self) -> &AssetId {
211        &self.id
212    }
213}
214
215impl<A> Debug for TypedAssetId<A>
216where
217    A: Asset,
218{
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        if f.alternate() {
221            write!(f, "{}({:#?})", A::name(), self.id)
222        } else {
223            write!(f, "{}({:?})", A::name(), self.id)
224        }
225    }
226}
227
228impl<A> Display for TypedAssetId<A>
229where
230    A: Asset,
231{
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        if f.alternate() {
234            write!(f, "{}({:#})", A::name(), self.id)
235        } else {
236            write!(f, "{}({:})", A::name(), self.id)
237        }
238    }
239}
240
241impl<A> LowerHex for TypedAssetId<A>
242where
243    A: Asset,
244{
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        if f.alternate() {
247            write!(f, "{}({:#x})", A::name(), self.id)
248        } else {
249            write!(f, "{}({:x})", A::name(), self.id)
250        }
251    }
252}
253
254impl<A> UpperHex for TypedAssetId<A>
255where
256    A: Asset,
257{
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        if f.alternate() {
260            write!(f, "{}({:#X})", A::name(), self.id)
261        } else {
262            write!(f, "{}({:X})", A::name(), self.id)
263        }
264    }
265}
266
267/// Error type used by derive-macro.
268#[derive(::std::fmt::Debug, thiserror::Error)]
269pub enum DecodeError {
270    #[error("Failed to deserialize asset info from json")]
271    Json(#[source] serde_json::Error),
272
273    #[error("Failed to deserialize asset info from bincode")]
274    Bincode(#[source] bincode::Error),
275}