ferrum_interfaces/vnext/resource/
static_lease.rs1use super::{
2 expected_lease_transition, invalid_resource, AdmissionFitPolicy, AdmissionPressureAction, Arc,
3 BTreeSet, BufferDescriptor, CoreOwnedAllocation, DeviceRuntime, DynamicResourceShape,
4 ResourceAllocation, ResourceId, ResourceLeaseAction, ResourceLeaseEntry, ResourceLeaseState,
5 ResourceOwnedBuffer, ResourceReservation, ResourceReservationBatch,
6 ResourceTransactionIdentity, ResourceWorkShape, StaticProvisioningBinding, VNextError,
7};
8
9pub(super) struct OwnedLeaseSlot<B> {
10 pub(super) entry: ResourceLeaseEntry,
11 pub(super) actual_resource_id: Option<ResourceId>,
12 pub(super) actual_generation: Option<u64>,
13 pub(super) descriptor: Option<BufferDescriptor>,
14 pub(super) buffer: Option<B>,
15}
16
17impl<B> OwnedLeaseSlot<B> {
18 pub(super) fn new(reservation: &ResourceReservation) -> Self {
19 Self {
20 entry: ResourceLeaseEntry::from_reservation(reservation, ResourceLeaseState::Active),
21 actual_resource_id: None,
22 actual_generation: None,
23 descriptor: None,
24 buffer: None,
25 }
26 }
27
28 pub(super) fn install(&mut self, allocation: CoreOwnedAllocation<B>) {
29 self.actual_resource_id = Some(allocation.resource_id);
30 self.actual_generation = Some(allocation.generation);
31 self.descriptor = Some(allocation.descriptor);
32 self.buffer = Some(allocation.buffer);
33 }
34
35 pub(super) fn clear(&mut self) {
36 drop(self.buffer.take());
37 self.descriptor.take();
38 self.actual_resource_id.take();
39 self.actual_generation.take();
40 }
41
42 pub(super) fn take_allocation(&mut self) -> Option<CoreOwnedAllocation<B>> {
43 Some(CoreOwnedAllocation {
44 resource_id: self.actual_resource_id.take()?,
45 generation: self.actual_generation.take()?,
46 descriptor: self.descriptor.take()?,
47 buffer: self.buffer.take()?,
48 })
49 }
50
51 pub(super) fn restore_allocation(&mut self, allocation: CoreOwnedAllocation<B>) {
52 debug_assert!(self.buffer.is_none());
53 self.install(allocation);
54 }
55}
56
57pub struct LeasedBufferView<'a, B> {
59 pub(super) identity: &'a ResourceTransactionIdentity,
60 pub(super) admission: &'a StaticProvisioningBinding,
61 pub(super) resource_id: &'a ResourceId,
62 pub(super) generation: u64,
63 pub(super) descriptor: &'a BufferDescriptor,
64 pub(super) buffer: &'a B,
65}
66
67impl<'a, B> LeasedBufferView<'a, B> {
68 pub fn identity(&self) -> &ResourceTransactionIdentity {
69 self.identity
70 }
71
72 pub fn admission(&self) -> &StaticProvisioningBinding {
73 self.admission
74 }
75
76 pub fn resource_id(&self) -> &ResourceId {
77 self.resource_id
78 }
79
80 pub const fn generation(&self) -> u64 {
81 self.generation
82 }
83
84 pub fn committed_descriptor(&self) -> &BufferDescriptor {
85 self.descriptor
86 }
87
88 pub fn buffer(&self) -> &B {
89 self.buffer
90 }
91}
92
93#[must_use = "a resource lease is the batch owner of committed buffers"]
94pub struct StaticProvisioningLease<R>
95where
96 R: DeviceRuntime,
97{
98 pub(super) slots: Vec<OwnedLeaseSlot<R::Buffer>>,
99 pub(super) identity: ResourceTransactionIdentity,
100 pub(super) admission: StaticProvisioningBinding,
101 pub(super) runtime: Arc<R>,
103}
104
105impl<R> StaticProvisioningLease<R>
106where
107 R: DeviceRuntime,
108{
109 pub(crate) fn runtime(&self) -> &Arc<R> {
110 &self.runtime
111 }
112
113 pub(super) fn new(
114 runtime: Arc<R>,
115 identity: &ResourceTransactionIdentity,
116 admission: &StaticProvisioningBinding,
117 reservations: &ResourceReservationBatch,
118 ) -> Self {
119 Self {
120 slots: reservations
121 .reservations()
122 .iter()
123 .map(OwnedLeaseSlot::new)
124 .collect(),
125 identity: identity.clone(),
126 admission: admission.clone(),
127 runtime,
128 }
129 }
130
131 pub fn identity(&self) -> &ResourceTransactionIdentity {
132 &self.identity
133 }
134
135 pub fn admission(&self) -> &StaticProvisioningBinding {
136 &self.admission
137 }
138
139 pub fn state(&self) -> ResourceLeaseState {
140 let mut states = self
141 .slots
142 .iter()
143 .filter(|slot| slot.buffer.is_some())
144 .map(|slot| slot.entry.state);
145 let Some(first) = states.next() else {
146 return ResourceLeaseState::Cancelled;
147 };
148 if states.all(|state| state == first) {
149 first
150 } else {
151 ResourceLeaseState::Mixed
152 }
153 }
154
155 pub fn entries(&self) -> impl Iterator<Item = &ResourceLeaseEntry> {
156 self.slots
157 .iter()
158 .filter(|slot| slot.buffer.is_some())
159 .map(|slot| &slot.entry)
160 }
161
162 pub fn plan_static_entries(&self) -> impl Iterator<Item = &ResourceLeaseEntry> {
163 self.slots
164 .iter()
165 .filter(|slot| slot.buffer.is_some())
166 .map(|slot| &slot.entry)
167 }
168
169 pub(crate) fn view(
170 &self,
171 resource_id: &ResourceId,
172 generation: u64,
173 ) -> Result<LeasedBufferView<'_, R::Buffer>, VNextError> {
174 let slot = self
175 .slots
176 .iter()
177 .find(|slot| {
178 slot.entry.resource_id == *resource_id && slot.entry.generation == generation
179 })
180 .ok_or_else(|| invalid_resource("lease does not contain that resource generation"))?;
181 if slot.entry.state != ResourceLeaseState::Active {
182 return Err(VNextError::InvalidLeaseTransition {
183 lease_id: self.identity.transaction_id.to_string(),
184 from: slot.entry.state.as_str(),
185 action: "borrow_live_buffer",
186 });
187 }
188 let descriptor = slot
189 .descriptor
190 .as_ref()
191 .ok_or_else(|| invalid_resource("lease resource is not committed"))?;
192 let buffer = slot
193 .buffer
194 .as_ref()
195 .ok_or_else(|| invalid_resource("lease resource buffer is not live"))?;
196 Ok(LeasedBufferView {
197 identity: &self.identity,
198 admission: &self.admission,
199 resource_id: &slot.entry.resource_id,
200 generation: slot.entry.generation,
201 descriptor,
202 buffer,
203 })
204 }
205
206 pub(crate) fn plan_static_view(
210 &self,
211 slot_index: usize,
212 allocation: &ResourceAllocation,
213 ) -> Result<LeasedBufferView<'_, R::Buffer>, VNextError> {
214 let slot = self
215 .slots
216 .get(slot_index)
217 .ok_or_else(|| invalid_resource("plan-static slot index is out of range"))?;
218 let descriptor = slot
219 .descriptor
220 .as_ref()
221 .ok_or_else(|| invalid_resource("plan-static resource is not committed"))?;
222 let buffer = slot
223 .buffer
224 .as_ref()
225 .ok_or_else(|| invalid_resource("plan-static resource buffer is not live"))?;
226 if slot.entry.resource_id() != allocation.resource_id()
227 || slot.entry.size_bytes() != allocation.size_bytes()
228 || slot.entry.alignment_bytes() != allocation.alignment_bytes()
229 || slot.entry.usage() != allocation.usage()
230 || slot.entry.element_type() != allocation.element_type()
231 || slot.entry.generation() == 0
232 || slot.entry.state() != ResourceLeaseState::Active
233 || slot.actual_resource_id.as_ref() != Some(allocation.resource_id())
234 || slot.actual_generation != Some(slot.entry.generation())
235 || descriptor.resource_id != *allocation.resource_id()
236 || descriptor.size_bytes != allocation.size_bytes()
237 || descriptor.alignment_bytes != allocation.alignment_bytes()
238 || descriptor.usage != allocation.usage()
239 || descriptor.element_type != allocation.element_type()
240 {
241 return Err(invalid_resource(
242 "plan-static slot differs from its immutable allocation",
243 ));
244 }
245 Ok(LeasedBufferView {
246 identity: &self.identity,
247 admission: &self.admission,
248 resource_id: &slot.entry.resource_id,
249 generation: slot.entry.generation,
250 descriptor,
251 buffer,
252 })
253 }
254
255 pub(super) fn buffer(&self, order: usize) -> Option<&R::Buffer> {
256 self.slots.get(order).and_then(|slot| slot.buffer.as_ref())
257 }
258
259 pub(super) fn install(&mut self, order: usize, allocation: CoreOwnedAllocation<R::Buffer>) {
260 self.slots[order].install(allocation);
261 }
262
263 pub(super) fn clear(&mut self, order: usize) {
264 self.slots[order].clear();
265 }
266
267 pub(super) fn transition_subset(
268 &mut self,
269 orders: &[usize],
270 action: ResourceLeaseAction,
271 ) -> Result<
272 (
273 ResourceLeaseState,
274 ResourceLeaseState,
275 Vec<ResourceLeaseEntry>,
276 ),
277 VNextError,
278 > {
279 if orders.is_empty() {
280 return Err(invalid_resource(
281 "lease transition subset must not be empty",
282 ));
283 }
284 let mut unique = BTreeSet::new();
285 let mut common_before = None;
286 for &order in orders {
287 if !unique.insert(order) {
288 return Err(invalid_resource("lease transition subset is duplicated"));
289 }
290 let slot = self
291 .slots
292 .get(order)
293 .ok_or_else(|| invalid_resource("lease transition order is out of bounds"))?;
294 if slot.buffer.is_none() {
295 return Err(invalid_resource(
296 "lease transition targets a non-live buffer",
297 ));
298 }
299 let before = slot.entry.state;
300 if common_before.is_some_and(|common| common != before) {
301 return Err(invalid_resource(
302 "one lease receipt cannot hide heterogeneous before states",
303 ));
304 }
305 if expected_lease_transition(action, before).is_none() {
306 return Err(VNextError::InvalidLeaseTransition {
307 lease_id: self.identity.transaction_id.to_string(),
308 from: before.as_str(),
309 action: action.as_str(),
310 });
311 }
312 common_before = Some(before);
313 }
314 let before = common_before.expect("non-empty subset has a before state");
315 let after = expected_lease_transition(action, before)
316 .expect("lease subset was preflight validated");
317 for &order in orders {
318 self.slots[order].entry.state = after;
319 }
320 Ok((
321 before,
322 after,
323 orders
324 .iter()
325 .map(|&order| self.slots[order].entry.clone())
326 .collect(),
327 ))
328 }
329
330 pub(super) fn take_owned_buffers(
331 &mut self,
332 reservations: &ResourceReservationBatch,
333 ) -> Vec<ResourceOwnedBuffer<R::Buffer>> {
334 self.slots
335 .iter_mut()
336 .zip(reservations.reservations())
337 .enumerate()
338 .filter_map(|(order, (slot, reservation))| {
339 let allocation = slot.take_allocation()?;
340 Some(ResourceOwnedBuffer {
341 order,
342 expected_resource_id: reservation.resource_id.clone(),
343 actual_resource_id: allocation.resource_id,
344 expected_generation: reservation.generation,
345 actual_generation: allocation.generation,
346 expected_descriptor: BufferDescriptor {
347 resource_id: reservation.resource_id.clone(),
348 size_bytes: reservation.size_bytes,
349 alignment_bytes: reservation.alignment_bytes,
350 usage: reservation.usage,
351 element_type: reservation.element_type,
352 },
353 actual_descriptor: allocation.descriptor,
354 buffer: allocation.buffer,
355 })
356 })
357 .collect()
358 }
359
360 pub(super) fn restore_owned_buffers(&mut self, buffers: Vec<ResourceOwnedBuffer<R::Buffer>>) {
361 for buffer in buffers {
362 let (order, allocation) = buffer.into_allocation();
363 self.slots[order].restore_allocation(allocation);
364 }
365 }
366}
367
368macro_rules! scoped_resource_admission_request {
369 ($name:ident, $single_sequence:literal) => {
370 #[derive(Debug, Clone, PartialEq, Eq)]
371 pub struct $name {
372 pub(super) work_shape: ResourceWorkShape,
373 pub(super) fit_policy: AdmissionFitPolicy,
374 pub(super) pressure_action: AdmissionPressureAction,
375 }
376
377 impl $name {
378 pub fn new(
379 work_shape: ResourceWorkShape,
380 fit_policy: AdmissionFitPolicy,
381 pressure_action: AdmissionPressureAction,
382 ) -> Result<Self, VNextError> {
383 if $single_sequence
384 && (work_shape.immediate_sequences() != 1 || work_shape.fit_sequences() != 1)
385 {
386 return Err(invalid_resource(
387 "sequence resource admission requires a single-sequence shape",
388 ));
389 }
390 Ok(Self {
391 work_shape,
392 fit_policy,
393 pressure_action,
394 })
395 }
396
397 pub fn work_shape(&self) -> &ResourceWorkShape {
398 &self.work_shape
399 }
400
401 pub(crate) const fn immediate_shape(&self) -> DynamicResourceShape {
402 self.work_shape.immediate_shape()
403 }
404
405 pub(crate) const fn fit_shape(&self) -> DynamicResourceShape {
406 match self.fit_policy {
407 AdmissionFitPolicy::ImmediateOnly => self.work_shape.immediate_shape(),
408 AdmissionFitPolicy::FullInputMustFit => self.work_shape.fit_shape(),
409 }
410 }
411
412 pub const fn fit_policy(&self) -> AdmissionFitPolicy {
413 self.fit_policy
414 }
415
416 pub const fn pressure_action(&self) -> AdmissionPressureAction {
417 self.pressure_action
418 }
419 }
420 };
421}
422
423scoped_resource_admission_request!(RequestResourceAdmissionRequest, false);
424scoped_resource_admission_request!(SequenceResourceAdmissionRequest, true);