1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use std::{collections::HashMap, hash::Hash, ops::Deref};

use parking_lot::{RwLock, RwLockReadGuard};
use slotmap::{Key, SlotMap};

use super::resource::{PoolError, ResourceStatistics};

pub struct StoredResource<Res> {
    resource: Res,
    statistics: ResourceStatistics,
}

impl<Res> Deref for StoredResource<Res> {
    type Target = Res;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.resource
    }
}

/// Generic resource pool for all resources that are fully described upon creation.
///
/// This implies, a resource is uniquely defined by its description.
/// We call these resources "static" because they never change their content during rendering.
/// However, the description may be respect to indirect changes which may to recreation of a resource.
/// The prime example of this is shader reloading:
/// * The resource is semantically the exact same despite having a different wgpu resource.
/// * We do **not** want its handle to change.
pub(super) struct StaticResourcePool<Handle: Key, Desc, Res> {
    resources: RwLock<SlotMap<Handle, StoredResource<Res>>>,
    lookup: RwLock<HashMap<Desc, Handle>>,
    pub current_frame_index: u64,
}

/// We cannot #derive(Default) as that would require Handle/Desc/Res to implement Default too.
impl<Handle: Key, Desc, Res> Default for StaticResourcePool<Handle, Desc, Res> {
    fn default() -> Self {
        Self {
            resources: Default::default(),
            lookup: Default::default(),
            current_frame_index: Default::default(),
        }
    }
}

impl<Handle, Desc, Res> StaticResourcePool<Handle, Desc, Res>
where
    Handle: Key,
    Desc: std::fmt::Debug + Clone + Eq + Hash,
{
    pub fn get_or_create<F: FnOnce(&Desc) -> Res>(&self, desc: &Desc, creation_func: F) -> Handle {
        // Ensure the lock isn't held in the creation case.
        if let Some(handle) = self.lookup.read().get(desc) {
            return *handle;
        }

        re_tracing::profile_scope!("Creating new static resource", std::any::type_name::<Res>());

        let resource = creation_func(desc);
        let handle = self.resources.write().insert(StoredResource {
            resource,
            statistics: ResourceStatistics {
                frame_created: self.current_frame_index,
                last_frame_used: self.current_frame_index.into(),
            },
        });
        self.lookup.write().insert(desc.clone(), handle);

        handle
    }

    pub fn recreate_resources<F: FnMut(&Desc) -> Option<Res>>(&mut self, mut recreation_func: F) {
        re_tracing::profile_function!();

        for (desc, handle) in self.lookup.get_mut() {
            if let Some(new_resource) = recreation_func(desc) {
                let resource = self.resources.get_mut().get_mut(*handle).unwrap();
                resource.statistics.frame_created = self.current_frame_index;
                resource.resource = new_resource;
            }
        }
    }

    /// Locks the resource pool for resolving handles.
    ///
    /// While it is locked, no new resources can be added.
    pub fn resources(&self) -> StaticResourcePoolReadLockAccessor<'_, Handle, Res> {
        StaticResourcePoolReadLockAccessor {
            resources: self.resources.read(),
            current_frame_index: self.current_frame_index,
        }
    }

    pub fn num_resources(&self) -> usize {
        self.resources.read().len()
    }
}

fn to_pool_error<T>(get_result: Option<T>, handle: impl Key) -> Result<T, PoolError> {
    get_result.ok_or_else(|| {
        if handle.is_null() {
            PoolError::NullHandle
        } else {
            PoolError::ResourceNotAvailable
        }
    })
}

/// Accessor to the resource pool by taking a read lock.
pub struct StaticResourcePoolReadLockAccessor<'a, Handle: Key, Res> {
    resources: RwLockReadGuard<'a, SlotMap<Handle, StoredResource<Res>>>,
    current_frame_index: u64,
}

impl<'a, Handle: Key, Res> StaticResourcePoolReadLockAccessor<'a, Handle, Res> {
    pub fn get(&self, handle: Handle) -> Result<&Res, PoolError> {
        to_pool_error(
            self.resources.get(handle).map(|resource| {
                resource.statistics.last_frame_used.store(
                    self.current_frame_index,
                    std::sync::atomic::Ordering::Relaxed,
                );
                &resource.resource
            }),
            handle,
        )
    }

    pub fn get_statistics(&self, handle: Handle) -> Result<&ResourceStatistics, PoolError> {
        to_pool_error(
            self.resources
                .get(handle)
                .map(|resource| &resource.statistics),
            handle,
        )
    }
}

#[cfg(test)]
mod tests {
    use std::cell::Cell;

    use slotmap::Key;

    use super::StaticResourcePool;
    use crate::wgpu_resources::resource::PoolError;

    slotmap::new_key_type! { pub struct ConcreteHandle; }

    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    pub struct ConcreteResourceDesc(u32);

    #[derive(PartialEq, Eq, Debug)]
    pub struct ConcreteResource(u32);

    type Pool = StaticResourcePool<ConcreteHandle, ConcreteResourceDesc, ConcreteResource>;

    #[test]
    fn resource_reuse() {
        let pool = Pool::default();

        // New resource
        let res0 = {
            let new_resource_created = Cell::new(false);
            let handle = pool.get_or_create(&ConcreteResourceDesc(0), |d| {
                new_resource_created.set(true);
                ConcreteResource(d.0)
            });
            assert!(new_resource_created.get());
            handle
        };

        // Get same resource again
        {
            let new_resource_created = Cell::new(false);
            let handle = pool.get_or_create(&ConcreteResourceDesc(0), |d| {
                new_resource_created.set(true);
                ConcreteResource(d.0)
            });
            assert!(!new_resource_created.get());
            assert_eq!(handle, res0);
        }
    }

    #[test]
    fn get_resource() {
        let pool = Pool::default();
        let handle = pool.get_or_create(&ConcreteResourceDesc(0), |d| ConcreteResource(d.0));

        // Query with valid handle
        let resources = pool.resources();
        assert!(resources.get(handle).is_ok());
        assert_eq!(*resources.get(handle).unwrap(), ConcreteResource(0));

        // Query with null handle
        assert_eq!(
            resources.get(ConcreteHandle::null()),
            Err(PoolError::NullHandle)
        );

        // Query with invalid handle
        let pool = Pool::default();
        let resources = pool.resources();
        assert_eq!(resources.get(handle), Err(PoolError::ResourceNotAvailable));
    }
}