1use crate::{VizError, VizResult};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct DeviceIdentity {
7 pub backend: String,
9 pub device: String,
11}
12
13impl DeviceIdentity {
14 pub fn try_new(backend: impl Into<String>, device: impl Into<String>) -> VizResult<Self> {
16 let backend = backend.into();
17 let device = device.into();
18 if backend.trim().is_empty() || device.trim().is_empty() {
19 return Err(VizError::InvalidTransfer(
20 "device backend and identifier must not be empty".into(),
21 ));
22 }
23 Ok(Self { backend, device })
24 }
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum VisualResidency {
31 Host,
33 Device(DeviceIdentity),
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub enum TransferDirection {
41 Upload,
43 Readback,
45 DeviceToDevice,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct TransferEvent {
53 pub stage: String,
55 pub direction: TransferDirection,
57 pub source: VisualResidency,
59 pub destination: VisualResidency,
61 pub bytes: u64,
63}
64
65impl TransferEvent {
66 pub fn try_new(
68 stage: impl Into<String>,
69 direction: TransferDirection,
70 source: VisualResidency,
71 destination: VisualResidency,
72 bytes: u64,
73 ) -> VizResult<Self> {
74 let stage = stage.into();
75 if stage.trim().is_empty() {
76 return Err(VizError::InvalidTransfer("transfer stage must not be empty".into()));
77 }
78 let residency_matches = match direction {
79 TransferDirection::Upload => {
80 matches!(&source, VisualResidency::Host)
81 && matches!(&destination, VisualResidency::Device(_))
82 }
83 TransferDirection::Readback => {
84 matches!(&source, VisualResidency::Device(_))
85 && matches!(&destination, VisualResidency::Host)
86 }
87 TransferDirection::DeviceToDevice => {
88 matches!(&source, VisualResidency::Device(_))
89 && matches!(&destination, VisualResidency::Device(_))
90 }
91 };
92 if !residency_matches {
93 return Err(VizError::InvalidTransfer(
94 "transfer direction does not match source and destination residency".into(),
95 ));
96 }
97 Ok(Self { stage, direction, source, destination, bytes })
98 }
99}
100
101#[derive(Clone, Debug, Default, PartialEq, Eq)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104pub struct TransferReceipt {
105 events: Vec<TransferEvent>,
106}
107
108impl TransferReceipt {
109 #[must_use]
111 pub const fn new() -> Self {
112 Self { events: Vec::new() }
113 }
114
115 pub fn push(&mut self, event: TransferEvent) {
117 self.events.push(event);
118 }
119
120 #[must_use]
122 pub fn events(&self) -> &[TransferEvent] {
123 &self.events
124 }
125
126 pub fn total_bytes(&self) -> VizResult<u64> {
128 self.events.iter().try_fold(0_u64, |total, event| {
129 total.checked_add(event.bytes).ok_or_else(|| {
130 VizError::InvalidTransfer("transfer byte total overflowed u64".into())
131 })
132 })
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{
139 DeviceIdentity, TransferDirection, TransferEvent, TransferReceipt, VisualResidency,
140 };
141
142 #[test]
143 fn receipt_requires_direction_to_match_residency() {
144 let device = VisualResidency::Device(DeviceIdentity::try_new("wgpu", "adapter-0").unwrap());
145 let upload = TransferEvent::try_new(
146 "point-upload",
147 TransferDirection::Upload,
148 VisualResidency::Host,
149 device.clone(),
150 96,
151 )
152 .unwrap();
153 let mut receipt = TransferReceipt::new();
154 receipt.push(upload);
155 assert_eq!(receipt.total_bytes().unwrap(), 96);
156
157 assert!(TransferEvent::try_new(
158 "wrong",
159 TransferDirection::Readback,
160 VisualResidency::Host,
161 device,
162 1,
163 )
164 .is_err());
165 }
166
167 #[test]
168 fn total_bytes_fails_closed_on_overflow() {
169 let device = VisualResidency::Device(DeviceIdentity::try_new("wgpu", "adapter-0").unwrap());
170 let mut receipt = TransferReceipt::new();
171 receipt.push(
172 TransferEvent::try_new(
173 "first",
174 TransferDirection::Upload,
175 VisualResidency::Host,
176 device.clone(),
177 u64::MAX,
178 )
179 .unwrap(),
180 );
181 receipt.push(
182 TransferEvent::try_new(
183 "second",
184 TransferDirection::Upload,
185 VisualResidency::Host,
186 device,
187 1,
188 )
189 .unwrap(),
190 );
191 assert!(receipt.total_bytes().is_err());
192 }
193}