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}