Skip to main content

vk/mem/synchronized/
virtual_block.rs

1//! Internally synchronized host placement over an integer byte range.
2//!
3//! Donor: VMA 3.4.0's externally synchronized `VmaVirtualBlock` placement.
4//! Rust adaptation: an optional, explicitly owned mutex around the canonical
5//! `VirtualBlock`, safe `&self` operation access, poison refusal and consuming
6//! recovery. Tokens remain explicit and freely movable between threads;
7//! dropping one never releases its region. No Vulkan memory or GPU work exists.
8use super::super::virtual_block::{
9	VirtualAllocation, VirtualAllocationInfo, VirtualBlock, VirtualBlockDetails, VirtualBlockStats,
10};
11use super::super::{AllocError, AllocationAlgorithm, PlacementStrategy};
12use crate as vk;
13use std::sync::{Mutex, PoisonError};
14
15/// One virtual block with serialized operation access from any host thread.
16///
17/// Default-constructed blocks allocate metadata with the system allocator and
18/// are safe to share immediately. Callback-backed blocks are adopted through
19/// unsafe constructors under the same any-thread contract as `VirtualBlock`'s
20/// unsafe callback construction. Access serializes per block; independent
21/// blocks run concurrently. There is no GPU or native completion tracking.
22/// Caller-provided diagnostic writers must not reenter this block.
23pub struct SynchronizedVirtualBlock<'callbacks> {
24	inner: Mutex<Transferable<'callbacks>>,
25}
26
27struct Transferable<'callbacks>(VirtualBlock<'callbacks>);
28
29// SAFETY: this wrapper is created only by the synchronized constructors. The
30// default constructor builds system-allocator metadata with no borrowed
31// record; unsafe constructors transfer VirtualBlock's established any-thread
32// callback/user-data contract, which also covers final CPU cleanup on the
33// releasing thread. The metadata buffers own their initialized storage and
34// retain no Rust references between operations. Moving changes no identity:
35// tokens validate against the block's generation identity, not its address.
36unsafe impl Send for Transferable<'_> {}
37
38impl<'callbacks> SynchronizedVirtualBlock<'callbacks> {
39	fn locked<R>(
40		&self,
41		operation: impl FnOnce(&mut VirtualBlock<'callbacks>) -> Result<R, AllocError>,
42	) -> Result<R, AllocError> {
43		let mut guard = self
44			.inner
45			.lock()
46			.map_err(|_| AllocError::SynchronizationPoisoned)?;
47		operation(&mut guard.0)
48	}
49
50	/// Create TLSF host-only placement behind one mutex. No callbacks are borrowed.
51	/// # Errors
52	/// Rejects a zero size or failed host metadata reservation.
53	pub fn new(size: u64) -> Result<Self, AllocError> {
54		Self::new_with_algorithm(size, AllocationAlgorithm::Tlsf)
55	}
56
57	/// Create TLSF or linear host-only placement behind one mutex.
58	/// # Errors
59	/// Rejects a zero size or failed host metadata reservation.
60	pub fn new_with_algorithm(size: u64, algorithm: AllocationAlgorithm) -> Result<Self, AllocError> {
61		Ok(Self {
62			inner: Mutex::new(Transferable(VirtualBlock::new_with_algorithm(
63				size, algorithm,
64			)?)),
65		})
66	}
67
68	/// Create callback-backed placement behind one mutex.
69	/// # Errors
70	/// Same errors as `VirtualBlock::new_with_callbacks`.
71	/// # Safety
72	/// The complete `VirtualBlock::new_with_callbacks` contract applies. Callback
73	/// code and user data additionally must support calls on any participating
74	/// worker thread, not only the constructing thread, through final cleanup.
75	pub unsafe fn new_with_callbacks(
76		size: u64,
77		algorithm: AllocationAlgorithm,
78		callbacks: &'callbacks vk::AllocationCallbacks,
79	) -> Result<Self, AllocError> {
80		// SAFETY: caller establishes the callback lifetime/thread contract.
81		let block = unsafe { VirtualBlock::new_with_callbacks(size, algorithm, callbacks)? };
82		Ok(Self {
83			inner: Mutex::new(Transferable(block)),
84		})
85	}
86
87	/// Adopt an existing block for serialized access from any host thread.
88	/// # Safety
89	/// If the block borrows allocation callbacks, their records/code/user data
90	/// must satisfy `VirtualBlock::new_with_callbacks`'s contract and support
91	/// calls on any participating worker thread through final cleanup.
92	pub unsafe fn new_from_block(block: VirtualBlock<'callbacks>) -> Self {
93		Self {
94			inner: Mutex::new(Transferable(block)),
95		}
96	}
97
98	/// Run `VirtualBlock::allocate` under this block's lock.
99	/// # Errors
100	/// Same errors as `VirtualBlock::allocate`, plus SynchronizationPoisoned.
101	pub fn allocate(
102		&self,
103		size: u64,
104		alignment: u64,
105		strategy: PlacementStrategy,
106	) -> Result<VirtualAllocation, AllocError> {
107		self.locked(|block| block.allocate(size, alignment, strategy))
108	}
109
110	/// Run `VirtualBlock::allocate_upper` under this block's lock.
111	/// # Errors
112	/// Same errors as `VirtualBlock::allocate_upper`, plus SynchronizationPoisoned.
113	pub fn allocate_upper(&self, size: u64, alignment: u64) -> Result<VirtualAllocation, AllocError> {
114		self.locked(|block| block.allocate_upper(size, alignment))
115	}
116
117	/// Run `VirtualBlock::free` under this block's lock.
118	/// # Errors
119	/// Same errors as `VirtualBlock::free`, plus SynchronizationPoisoned.
120	pub fn free(&self, allocation: &mut VirtualAllocation) -> Result<(), AllocError> {
121		self.locked(|block| block.free(allocation))
122	}
123
124	/// Run `VirtualBlock::allocation_info` under this block's lock.
125	/// # Errors
126	/// Same errors as `VirtualBlock::allocation_info`, plus SynchronizationPoisoned.
127	pub fn allocation_info(
128		&self,
129		allocation: &VirtualAllocation,
130	) -> Result<VirtualAllocationInfo, AllocError> {
131		self.locked(|block| block.allocation_info(allocation))
132	}
133
134	/// Run `VirtualBlock::set_user_data` under this block's lock.
135	/// # Errors
136	/// Same errors as `VirtualBlock::set_user_data`, plus SynchronizationPoisoned.
137	pub fn set_user_data(
138		&self,
139		allocation: &VirtualAllocation,
140		data: usize,
141	) -> Result<(), AllocError> {
142		self.locked(|block| block.set_user_data(allocation, data))
143	}
144
145	/// Run `VirtualBlock::is_empty` under this block's lock.
146	/// # Errors
147	/// Returns SynchronizationPoisoned after a closure panic.
148	pub fn is_empty(&self) -> Result<bool, AllocError> {
149		self.locked(|block| Ok(block.is_empty()))
150	}
151
152	/// Run `VirtualBlock::clear` under this block's lock.
153	/// Users of previously returned offsets must stop using them before reuse.
154	/// # Errors
155	/// Returns SynchronizationPoisoned after a closure panic.
156	pub fn clear(&self) -> Result<(), AllocError> {
157		self.locked(|block| {
158			block.clear();
159			Ok(())
160		})
161	}
162
163	/// Run `VirtualBlock::statistics` under this block's lock.
164	/// # Errors
165	/// Returns SynchronizationPoisoned after a closure panic.
166	pub fn statistics(&self) -> Result<VirtualBlockStats, AllocError> {
167		self.locked(|block| Ok(block.statistics()))
168	}
169
170	/// Run `VirtualBlock::detailed_statistics` under this block's lock.
171	/// # Errors
172	/// Returns SynchronizationPoisoned after a closure panic.
173	pub fn detailed_statistics(&self) -> Result<VirtualBlockDetails, AllocError> {
174		self.locked(|block| Ok(block.detailed_statistics()))
175	}
176
177	/// Run `VirtualBlock::write_statistics_json` under this block's lock.
178	/// # Errors
179	/// Same errors as `VirtualBlock::write_statistics_json`, plus SynchronizationPoisoned.
180	/// # Panics
181	/// A writer panic propagates and poisons subsequent operations. Writers
182	/// must not reenter the block while this method holds its lock.
183	pub fn write_statistics_json(
184		&self,
185		output: &mut impl std::io::Write,
186		detailed: bool,
187	) -> Result<(), AllocError> {
188		self.locked(|block| block.write_statistics_json(output, detailed))
189	}
190
191	/// Recover the exclusive block without native cleanup.
192	/// # Errors
193	/// PoisonError retains the block for explicit inspection/cleanup after a panic.
194	#[allow(clippy::result_large_err)]
195	pub fn into_inner(
196		self,
197	) -> Result<VirtualBlock<'callbacks>, PoisonError<VirtualBlock<'callbacks>>> {
198		match self.inner.into_inner() {
199			Ok(owner) => Ok(owner.0),
200			Err(error) => Err(PoisonError::new(error.into_inner().0)),
201		}
202	}
203}