Skip to main content

pebble/assets/
upload.rs

1use crate::assets::deps::Dependencies;
2
3/// Declares what a CPU-side asset type turns into once uploaded. Usually
4/// implemented together with [`Asset<B>`] — see the [`asset!`](crate::asset)
5/// macro for the terser way to write both at once.
6pub trait AssetSource: 'static {
7    type Processed: 'static;
8}
9
10/// Describes how to turn a CPU-side source into its GPU-side (or otherwise
11/// processed) form using backend `B`. `Deps<'a>` names any other resources
12/// the upload needs; if they, or `B` itself, aren't available yet,
13/// `upload` returning `None` just retries next tick — no manual ordering.
14pub trait Asset<B>: AssetSource {
15    type Deps<'a>: Dependencies<'a>;
16
17    fn upload<'a>(&self, backend: &B, deps: &Self::Deps<'a>) -> Option<Self::Processed>;
18}
19
20/// Expands to the same [`AssetSource`]/[`Asset<B>`] impls you'd write by
21/// hand — a pure syntax transform, so every built-in asset type keeps using
22/// the trait impls directly. Only exists to make a *new*, user-defined
23/// asset type cheaper to write:
24///
25/// ```ignore
26/// asset!(MyThing => GPUMyThing, |self, backend: &Backend| {
27///     Some(GPUMyThing { .. })
28/// });
29///
30/// asset!(MyThing => GPUMyThing, deps: [SomePool], |self, backend: &Backend, deps| {
31///     Some(GPUMyThing { .. })
32/// });
33/// ```
34///
35/// `deps: [..]` takes bare types — wrapped in `Read<'a, _>` for one, or a
36/// tuple of `Read<'a, _>` for several (matching what [`Dependencies`] supports).
37#[macro_export]
38macro_rules! asset {
39    ($ty:ty => $processed:ty, |$self:ident, $backend:ident : &$backend_ty:ty| $body:block) => {
40        impl $crate::assets::upload::AssetSource for $ty {
41            type Processed = $processed;
42        }
43        impl $crate::assets::upload::Asset<$backend_ty> for $ty {
44            type Deps<'a> = ();
45
46            fn upload<'a>(&$self, $backend: &$backend_ty, _deps: &()) -> Option<$processed> $body
47        }
48    };
49
50    ($ty:ty => $processed:ty, deps: [$dep:ty], |$self:ident, $backend:ident : &$backend_ty:ty, $deps:ident| $body:block) => {
51        impl $crate::assets::upload::AssetSource for $ty {
52            type Processed = $processed;
53        }
54        impl $crate::assets::upload::Asset<$backend_ty> for $ty {
55            type Deps<'a> = $crate::ecs::resources::Read<'a, $dep>;
56
57            fn upload<'a>(&$self, $backend: &$backend_ty, $deps: &Self::Deps<'a>) -> Option<$processed> $body
58        }
59    };
60
61    ($ty:ty => $processed:ty, deps: [$($dep:ty),+ $(,)?], |$self:ident, $backend:ident : &$backend_ty:ty, $deps:ident| $body:block) => {
62        impl $crate::assets::upload::AssetSource for $ty {
63            type Processed = $processed;
64        }
65        impl $crate::assets::upload::Asset<$backend_ty> for $ty {
66            type Deps<'a> = ($($crate::ecs::resources::Read<'a, $dep>,)+);
67
68            fn upload<'a>(&$self, $backend: &$backend_ty, $deps: &Self::Deps<'a>) -> Option<$processed> $body
69        }
70    };
71}
72
73#[cfg(test)]
74mod tests {
75    use crate::{
76        assets::{plugin::sync_assets, storage::Assets},
77        ecs::{resources::Resources, schedule::Schedule},
78    };
79
80    struct TestBackend(i32);
81    struct DepA(i32);
82    struct DepB(i32);
83
84    struct NoDepsAsset(i32);
85    crate::asset!(NoDepsAsset => i32, |self, backend: &TestBackend| {
86        Some(self.0 * backend.0)
87    });
88
89    struct OneDepAsset(i32);
90    crate::asset!(OneDepAsset => i32, deps: [DepA], |self, backend: &TestBackend, deps| {
91        Some(self.0 * backend.0 * deps.0)
92    });
93
94    struct TwoDepAsset(i32);
95    crate::asset!(TwoDepAsset => i32, deps: [DepA, DepB], |self, backend: &TestBackend, deps| {
96        Some(self.0 * backend.0 * deps.0.0 * deps.1.0)
97    });
98
99    #[test]
100    fn no_deps_asset_uploads_once_the_backend_exists() {
101        let mut world = hecs::World::default();
102        let mut resources = Resources::default();
103        resources.insert(hecs::CommandBuffer::default());
104        resources.insert(Assets::<NoDepsAsset>::new());
105
106        let handle = resources.get_mut::<Assets<NoDepsAsset>>().insert("test", NoDepsAsset(3));
107
108        let mut schedule = Schedule::default();
109        schedule.add_system(sync_assets::<TestBackend, NoDepsAsset>);
110
111        schedule.run(&mut world, &mut resources);
112        assert!(!resources.get::<Assets<NoDepsAsset>>().is_ready(handle));
113
114        resources.insert(TestBackend(2));
115        schedule.run(&mut world, &mut resources);
116        assert_eq!(*resources.get::<Assets<NoDepsAsset>>().get(handle).unwrap(), 6);
117    }
118
119    #[test]
120    fn one_dep_asset_waits_for_its_dependency() {
121        let mut world = hecs::World::default();
122        let mut resources = Resources::default();
123        resources.insert(hecs::CommandBuffer::default());
124        resources.insert(Assets::<OneDepAsset>::new());
125        resources.insert(TestBackend(2));
126
127        let handle = resources.get_mut::<Assets<OneDepAsset>>().insert("test", OneDepAsset(3));
128
129        let mut schedule = Schedule::default();
130        schedule.add_system(sync_assets::<TestBackend, OneDepAsset>);
131
132        schedule.run(&mut world, &mut resources);
133        assert!(!resources.get::<Assets<OneDepAsset>>().is_ready(handle));
134
135        resources.insert(DepA(5));
136        schedule.run(&mut world, &mut resources);
137        assert_eq!(*resources.get::<Assets<OneDepAsset>>().get(handle).unwrap(), 30);
138    }
139
140    #[test]
141    fn two_dep_asset_waits_for_both_dependencies() {
142        let mut world = hecs::World::default();
143        let mut resources = Resources::default();
144        resources.insert(hecs::CommandBuffer::default());
145        resources.insert(Assets::<TwoDepAsset>::new());
146        resources.insert(TestBackend(2));
147        resources.insert(DepA(5));
148
149        let handle = resources.get_mut::<Assets<TwoDepAsset>>().insert("test", TwoDepAsset(3));
150
151        let mut schedule = Schedule::default();
152        schedule.add_system(sync_assets::<TestBackend, TwoDepAsset>);
153
154        schedule.run(&mut world, &mut resources);
155        assert!(!resources.get::<Assets<TwoDepAsset>>().is_ready(handle));
156
157        resources.insert(DepB(7));
158        schedule.run(&mut world, &mut resources);
159        assert_eq!(*resources.get::<Assets<TwoDepAsset>>().get(handle).unwrap(), 210);
160    }
161}