use std::borrow::{Borrow, BorrowMut};
use std::convert::{AsMut, AsRef};
use std::ops::{Deref, DerefMut};
use ash::vk;
use crate::Destroyable;
pub type Guarded<'d, Resource> =
GuardedResource<'static, Resource, &'d <Resource as Destroyable>::Destroyer>;
#[derive(Debug)]
pub struct GuardedResource<'alloc_cb, Resource, Destroyer>(
Option<ResourceAndDestroyer<'alloc_cb, Resource, Destroyer>>,
)
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>;
#[derive(Debug)]
struct ResourceAndDestroyer<'alloc_cb, Resource, Destroyer> {
resource: Resource,
destroyer: Destroyer,
allocation_callbacks: Option<&'alloc_cb vk::AllocationCallbacks>,
}
impl<'alloc_cb, Resource, Destroyer> GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
pub unsafe fn new(
resource: Resource,
destroyer: Destroyer,
allocation_callbacks: Option<&'alloc_cb vk::AllocationCallbacks>,
) -> Self {
Self(Some(ResourceAndDestroyer {
resource,
destroyer,
allocation_callbacks,
}))
}
pub fn destroyer(&self) -> Destroyer
where
Destroyer: Clone,
{
self.0.as_ref().unwrap().destroyer.clone()
}
pub fn allocation_callbacks(&self) -> Option<&'alloc_cb vk::AllocationCallbacks> {
self.0.as_ref().unwrap().allocation_callbacks
}
pub fn take(mut self) -> Resource {
self.0.take().unwrap().resource
}
}
impl<'alloc_cb, Resource, Destroyer> GuardedResource<'alloc_cb, Vec<Resource>, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
pub unsafe fn try_new_from<E>(
resources: impl IntoIterator<Item = Result<Resource, E>>,
destroyer: Destroyer,
allocation_callbacks: Option<&'alloc_cb vk::AllocationCallbacks>,
) -> Result<Self, E> {
let resources = resources.into_iter();
let (min_capacity, _) = resources.size_hint();
let mut guarded_resources = Self::new(
Vec::with_capacity(min_capacity),
destroyer,
allocation_callbacks,
);
for resource in resources {
guarded_resources.push(resource?);
}
Ok(guarded_resources)
}
}
impl<'alloc_cb, Resource, Destroyer, const N: usize>
GuardedResource<'alloc_cb, [Resource; N], Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
pub unsafe fn try_new_with<E>(
mut resource_factory: impl FnMut(usize) -> Result<Resource, E>,
destroyer: Destroyer,
allocation_callbacks: Option<&'alloc_cb vk::AllocationCallbacks>,
) -> Result<Self, E> {
let mut resources = [(); N].map(|_| None);
for (i, resource) in resources.iter_mut().enumerate() {
*resource = Some(GuardedResource::new(
resource_factory(i)?,
&*destroyer,
allocation_callbacks,
));
}
let resources = resources.map(|resource| {
resource
.expect("Bug in GuardedResource::new_with(): uninitialized resources")
.take()
});
Ok(Self::new(resources, destroyer, allocation_callbacks))
}
}
impl<'alloc_cb, Resource, Destroyer> AsRef<Resource>
for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn as_ref(&self) -> &Resource {
&*self
}
}
impl<'alloc_cb, Resource, Destroyer> AsMut<Resource>
for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn as_mut(&mut self) -> &mut Resource {
&mut *self
}
}
impl<'alloc_cb, Resource, Destroyer> Borrow<Resource>
for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn borrow(&self) -> &Resource {
&*self
}
}
impl<'alloc_cb, Resource, Destroyer> BorrowMut<Resource>
for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn borrow_mut(&mut self) -> &mut Resource {
&mut *self
}
}
impl<'alloc_cb, Resource, Destroyer> Deref for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
type Target = Resource;
fn deref(&self) -> &Self::Target {
&self.0.as_ref().unwrap().resource
}
}
impl<'alloc_cb, Resource, Destroyer> DerefMut for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0.as_mut().unwrap().resource
}
}
impl<'alloc_cb, Resource, Destroyer> Drop for GuardedResource<'alloc_cb, Resource, Destroyer>
where
Resource: Destroyable,
Destroyer: Deref<Target = <Resource as Destroyable>::Destroyer>,
{
fn drop(&mut self) {
if let Some(ResourceAndDestroyer {
resource,
destroyer,
allocation_callbacks,
}) = self.0.as_mut()
{
unsafe { resource.destroy_with(destroyer, *allocation_callbacks) }
}
}
}
#[cfg(test)]
mod tests {
use crate::{Destroyable, Guarded, GuardedResource};
use ash::vk;
#[derive(Debug, PartialEq)]
struct DestructorCalled<Destroyer> {
destroyer: Destroyer,
allocation_callbacks: Option<*const vk::AllocationCallbacks>,
}
#[derive(Debug)]
struct TestResource<'a, Destroyer>(&'a mut Option<DestructorCalled<Destroyer>>);
impl<'a, Destroyer: Copy> Destroyable for TestResource<'a, Destroyer> {
type Destroyer = Destroyer;
unsafe fn destroy_with(
&mut self,
&destroyer: &Destroyer,
allocation_callbacks: Option<&vk::AllocationCallbacks>,
) {
*(self.0) = Some(DestructorCalled {
destroyer,
allocation_callbacks: allocation_callbacks.map(|a| a as _),
});
}
}
#[derive(Debug)]
struct TestWrapper<T>(T);
impl<T: Clone> TestWrapper<T> {
fn value(&self) -> T {
self.0.clone()
}
fn set_value(&mut self, new_value: T) {
self.0 = new_value;
}
}
impl<T> Destroyable for TestWrapper<T> {
type Destroyer = ();
unsafe fn destroy_with(
&mut self,
_destroyer: &(),
_allocation_callbacks: Option<&vk::AllocationCallbacks>,
) {
}
}
#[test]
fn methods_can_be_called_on_guarded_resource() {
let mut guarded = unsafe { Guarded::new(TestWrapper(12332), &(), None) };
assert_eq!(guarded.value(), 12332);
guarded.set_value(42);
assert_eq!(guarded.value(), 42);
}
#[test]
fn guarded_resources_are_destroyed_when_dropped() {
let allocation_callbacks = Default::default();
let mut destructor_called = None;
let resource = TestResource(&mut destructor_called);
{
let _guarded =
unsafe { GuardedResource::new(resource, &(), Some(&allocation_callbacks)) };
}
assert_eq!(
destructor_called,
Some(DestructorCalled {
destroyer: (),
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
}
#[test]
fn guarded_resources_are_not_destroyed_when_taken() {
let allocation_callbacks = Default::default();
let mut destructor_called = None;
let resource = TestResource(&mut destructor_called);
{
let guarded =
unsafe { GuardedResource::new(resource, &(), Some(&allocation_callbacks)) };
guarded.take();
}
assert!(destructor_called.is_none());
}
#[test]
fn guarded_vec_has_accessible_elements() {
let resources_to_create: [Result<_, ()>; 3] = [
Ok(TestWrapper(321)),
Ok(TestWrapper(432)),
Ok(TestWrapper(543)),
];
let guarded = unsafe { Guarded::try_new_from(resources_to_create, &(), None) }.unwrap();
assert_eq!(guarded[0].value(), 321);
assert_eq!(guarded[1].value(), 432);
assert_eq!(guarded[2].value(), 543);
}
#[test]
fn guarded_vec_destroys_elements_upon_drop() {
let allocation_callbacks: vk::AllocationCallbacks = Default::default();
let mut destructor_called_0 = None;
let mut destructor_called_1 = None;
let mut destructor_called_2 = None;
{
let resources_to_create: [Result<_, ()>; 3] = [
Ok(TestResource(&mut destructor_called_0)),
Ok(TestResource(&mut destructor_called_1)),
Ok(TestResource(&mut destructor_called_2)),
];
let mut guarded = unsafe {
GuardedResource::try_new_from(resources_to_create, &42, Some(&allocation_callbacks))
}
.unwrap();
guarded.pop();
}
assert_eq!(
destructor_called_0,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert_eq!(
destructor_called_1,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert!(destructor_called_2.is_none());
}
#[test]
fn guarded_vec_drops_previously_created_elements_upon_error() {
let allocation_callbacks: vk::AllocationCallbacks = Default::default();
let mut destructor_called_0 = None;
let mut destructor_called_1 = None;
let mut destructor_called_2 = None;
{
let resources_to_create = [
Ok(TestResource(&mut destructor_called_0)),
Ok(TestResource(&mut destructor_called_1)),
Err("oh no"),
Err("another failure"),
Ok(TestResource(&mut destructor_called_2)),
];
let _guarded = unsafe {
GuardedResource::try_new_from(resources_to_create, &42, Some(&allocation_callbacks))
};
}
assert_eq!(
destructor_called_0,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert_eq!(
destructor_called_1,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert!(destructor_called_2.is_none());
}
#[test]
fn guarded_vec_returns_first_error() {
let resources_to_create = [
Ok(TestWrapper(5)),
Err("oh no"),
Err("another failure"),
Ok(TestWrapper(15)),
]
.into_iter();
let guarded = unsafe { GuardedResource::try_new_from(resources_to_create, &(), None) };
assert_eq!(guarded.unwrap_err(), "oh no");
}
#[test]
fn guarded_array_has_accessible_elements() {
let mut values = [321, 432, 543].into_iter();
let create_resource = |_| Result::<_, ()>::Ok(TestWrapper(values.next().unwrap()));
let guarded = unsafe { Guarded::try_new_with(create_resource, &(), None) }.unwrap();
assert_eq!(guarded[0].value(), 321);
assert_eq!(guarded[1].value(), 432);
assert_eq!(guarded[2].value(), 543);
let _: [_; 3] = guarded.take();
}
#[test]
fn guarded_array_destroys_elements_upon_drop() {
let allocation_callbacks: vk::AllocationCallbacks = Default::default();
let mut destructor_called_0 = None;
let mut destructor_called_1 = None;
let mut destructor_called_2 = None;
{
let resources_to_create: [Result<_, ()>; 3] = [
Ok(TestResource(&mut destructor_called_0)),
Ok(TestResource(&mut destructor_called_1)),
Ok(TestResource(&mut destructor_called_2)),
];
let mut resources_to_create = resources_to_create.into_iter();
let create_resource = |_| resources_to_create.next().unwrap();
let _guarded: GuardedResource<[_; 3], _> = unsafe {
GuardedResource::try_new_with(create_resource, &42, Some(&allocation_callbacks))
}
.unwrap();
}
assert_eq!(
destructor_called_0,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert_eq!(
destructor_called_1,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert_eq!(
destructor_called_2,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
}
#[test]
fn guarded_array_drops_previously_created_elements_upon_error() {
let allocation_callbacks: vk::AllocationCallbacks = Default::default();
let mut destructor_called_0 = None;
let mut destructor_called_1 = None;
let mut destructor_called_2 = None;
{
let mut resources_to_create = [
Ok(TestResource(&mut destructor_called_0)),
Ok(TestResource(&mut destructor_called_1)),
Err("oh no"),
Ok(TestResource(&mut destructor_called_2)),
]
.into_iter();
let create_resource = |_| resources_to_create.next().unwrap();
let _guarded: Result<GuardedResource<[_; 4], _>, _> = unsafe {
GuardedResource::try_new_with(create_resource, &42, Some(&allocation_callbacks))
};
}
assert_eq!(
destructor_called_0,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert_eq!(
destructor_called_1,
Some(DestructorCalled {
destroyer: 42,
allocation_callbacks: Some(&allocation_callbacks as _)
})
);
assert!(destructor_called_2.is_none());
}
#[test]
fn guarded_array_returns_first_error() {
let mut resources_to_create = [
Ok(TestWrapper(5)),
Err("oh no"),
Err("another failure"),
Ok(TestWrapper(15)),
]
.into_iter();
let create_resource = |_| resources_to_create.next().unwrap();
let guarded: Result<GuardedResource<[_; 4], _>, _> =
unsafe { GuardedResource::try_new_with(create_resource, &(), None) };
assert_eq!(guarded.unwrap_err(), "oh no");
}
}