bones_asset/
handle.rs

1use std::{
2    alloc::Layout,
3    any::{type_name, TypeId},
4    marker::PhantomData,
5    sync::OnceLock,
6};
7
8use bones_schema::{prelude::*, raw_fns::*};
9use bones_utils::HashMap;
10use parking_lot::RwLock;
11use ulid::Ulid;
12
13use crate::{AssetServer, NetworkHandle};
14
15/// A typed handle to an asset.
16///
17/// To serialize for replication, use: `handle.network_handle(asset_server)`
18#[repr(C)]
19pub struct Handle<T> {
20    /// The runtime ID of the asset.
21    pub id: Ulid,
22    phantom: PhantomData<T>,
23}
24
25// Manually implement these traits we normally derive because the derive assumes that `T` must also
26// implement these traits.
27impl<T> Clone for Handle<T> {
28    fn clone(&self) -> Self {
29        *self
30    }
31}
32impl<T> Copy for Handle<T> {}
33impl<T> PartialEq for Handle<T> {
34    fn eq(&self, other: &Self) -> bool {
35        self.id == other.id
36    }
37}
38impl<T> Eq for Handle<T> {}
39impl<T> std::hash::Hash for Handle<T> {
40    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
41        self.id.hash(state);
42    }
43}
44impl<T> Default for Handle<T> {
45    fn default() -> Self {
46        Self {
47            id: Default::default(),
48            phantom: Default::default(),
49        }
50    }
51}
52
53impl<T> std::fmt::Debug for Handle<T> {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        f.debug_struct("Handle").field("id", &self.id).finish()
56    }
57}
58
59impl<T> Handle<T> {
60    /// Convert the handle to an [`UntypedHandle`].
61    pub fn untyped(self) -> UntypedHandle {
62        UntypedHandle { rid: self.id }
63    }
64
65    /// Get a [`NetworkHandle`] that can be Serialized and replicated over network.
66    /// (Panics if handle is not found in [`AssetServer`].)
67    pub fn network_handle(&self, asset_server: &AssetServer) -> NetworkHandle<T> {
68        // Use untyped handle to get Cid so we do not require HasSchema.
69        let content_id = *asset_server.get_untyped(self.untyped()).key();
70
71        NetworkHandle::<T>::from_cid(content_id)
72    }
73}
74
75/// An untyped handle to an asset.
76#[derive(Default, Clone, Debug, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)]
77#[repr(C)]
78pub struct UntypedHandle {
79    /// The runtime ID of the handle
80    pub rid: Ulid,
81}
82
83impl UntypedHandle {
84    /// Create a typed [`Handle<T>`] from this [`UntypedHandle`].
85    pub fn typed<T>(self) -> Handle<T> {
86        Handle {
87            id: self.rid,
88            phantom: PhantomData,
89        }
90    }
91}
92
93//
94// Schema implementations
95//
96
97/// [Type data][SchemaData::type_data] for asset handles.
98///
99/// This allows the asset loader to distinguish when a `SomeStruct(u128)` schema layout should be
100/// deserialized as a normal struct or as an asset handle.
101#[derive(HasSchema, Clone, Copy, Debug)]
102#[schema(opaque, no_default)]
103pub struct SchemaAssetHandle {
104    /// The schema of the type pointed to by the handle, if this is not an [`UntypedHandle`].
105    schema: Option<&'static Schema>,
106}
107
108impl SchemaAssetHandle {
109    /// Returns the schema of the type pointed to by the handle, if this is not an [`UntypedHandle`].
110    pub fn inner_schema(&self) -> Option<&'static Schema> {
111        self.schema
112    }
113}
114
115// SAFE: We return a valid schema.
116unsafe impl<T: HasSchema> HasSchema for Handle<T> {
117    fn schema() -> &'static bones_schema::Schema {
118        static S: OnceLock<RwLock<HashMap<TypeId, &'static Schema>>> = OnceLock::new();
119        // This is a hack to make sure that `Ulid` has the memory representation we
120        // expect. It is extremely unlike, but possible that this would otherwise be
121        // unsound in the event that Rust picks a weird representation for the
122        // `Ulid(u128)` struct, which doesn't have a `#[repr(C)]` or
123        // `#[repr(transparent)]` annotation.
124        assert_eq!(
125            Layout::new::<Ulid>(),
126            Layout::new::<u128>(),
127            "ULID memory layout is unexpected! Bad Rust compiler! 😡"
128        );
129
130        let map = S.get_or_init(|| RwLock::new(HashMap::default()));
131
132        let existing_schema = { map.read().get(&TypeId::of::<T>()).copied() };
133
134        if let Some(existing_schema) = existing_schema {
135            existing_schema
136        } else {
137            let schema = SCHEMA_REGISTRY.register(SchemaData {
138                name: type_name::<Self>().into(),
139                full_name: format!("{}{}", module_path!(), type_name::<Self>()).into(),
140                type_id: Some(TypeId::of::<Self>()),
141                kind: SchemaKind::Struct(StructSchemaInfo {
142                    fields: vec![StructFieldInfo {
143                        name: Some("id".into()),
144                        schema: u128::schema(),
145                    }],
146                }),
147                clone_fn: Some(<Self as RawClone>::raw_clone_cb()),
148                drop_fn: None,
149                default_fn: Some(<Self as RawDefault>::raw_default_cb()),
150                eq_fn: Some(<Self as RawEq>::raw_eq_cb()),
151                hash_fn: Some(<Self as RawHash>::raw_hash_cb()),
152                type_data: {
153                    let td = bones_schema::alloc::TypeDatas::default();
154                    td.insert(SchemaAssetHandle {
155                        schema: Some(T::schema()),
156                    })
157                    .unwrap();
158                    td
159                },
160            });
161
162            {
163                let mut map = map.write();
164                map.insert(TypeId::of::<T>(), schema);
165            }
166
167            schema
168        }
169    }
170}
171// SAFE: We return a valid schema.
172unsafe impl HasSchema for UntypedHandle {
173    fn schema() -> &'static bones_schema::Schema {
174        static S: OnceLock<&'static Schema> = OnceLock::new();
175        // This is a hack to make sure that `Ulid` has the memory representation we
176        // expect. It is extremely unlike, but possible that this would otherwise be
177        // unsound in the event that Rust picks a weird representation for the
178        // `Ulid(u128)` struct, which doesn't have a `#[repr(C)]` or
179        // `#[repr(transparent)]` annotation.
180        assert_eq!(
181            Layout::new::<Ulid>(),
182            Layout::new::<u128>(),
183            "ULID memory layout is unexpected! Bad Rust compiler! 😡"
184        );
185        S.get_or_init(|| {
186            SCHEMA_REGISTRY.register(SchemaData {
187                name: "UntypedHandle".into(),
188                full_name: format!("{}::{}", module_path!(), "UntypedHandle").into(),
189                type_id: Some(TypeId::of::<Self>()),
190                kind: SchemaKind::Struct(StructSchemaInfo {
191                    fields: vec![StructFieldInfo {
192                        name: Some("id".into()),
193                        schema: u128::schema(),
194                    }],
195                }),
196                clone_fn: Some(<Self as RawClone>::raw_clone_cb()),
197                drop_fn: None,
198                default_fn: Some(<Self as RawDefault>::raw_default_cb()),
199                eq_fn: Some(<Self as RawEq>::raw_eq_cb()),
200                hash_fn: Some(<Self as RawHash>::raw_hash_cb()),
201                type_data: {
202                    let td = bones_schema::alloc::TypeDatas::default();
203                    td.insert(SchemaAssetHandle { schema: None }).unwrap();
204                    td
205                },
206            })
207        })
208    }
209}