axiolid_contracts/
capability.rs1use core::fmt;
4
5const IDENTIFIER_CAPACITY: usize = 47;
11
12#[derive(Clone, Copy)]
21pub struct BackendId {
22 bytes: [u8; IDENTIFIER_CAPACITY],
23 len: u8,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct BackendIdTooLong {
33 pub len: usize,
35}
36
37impl fmt::Display for BackendIdTooLong {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(
40 f,
41 "backend identifier is {} bytes, limit is {}",
42 self.len,
43 BackendId::CAPACITY
44 )
45 }
46}
47
48impl core::error::Error for BackendIdTooLong {}
49
50impl BackendId {
51 pub const CAPACITY: usize = IDENTIFIER_CAPACITY;
53
54 pub const fn new(value: &str) -> Self {
62 match Self::build(value.as_bytes()) {
63 Some(id) => id,
64 None => panic!("backend identifier exceeds BackendId::CAPACITY"),
65 }
66 }
67
68 pub fn try_new(value: &str) -> Result<Self, BackendIdTooLong> {
76 Self::build(value.as_bytes()).ok_or(BackendIdTooLong { len: value.len() })
77 }
78
79 const fn build(source: &[u8]) -> Option<Self> {
83 if source.len() > IDENTIFIER_CAPACITY {
84 return None;
85 }
86 let mut bytes = [0_u8; IDENTIFIER_CAPACITY];
87 let mut index = 0;
88 while index < source.len() {
89 bytes[index] = source[index];
90 index += 1;
91 }
92 Some(Self {
93 bytes,
94 len: source.len() as u8,
95 })
96 }
97
98 pub fn as_str(&self) -> &str {
100 core::str::from_utf8(&self.bytes[..self.len as usize])
103 .expect("identifier bytes originate from &str")
104 }
105}
106
107impl PartialEq for BackendId {
110 fn eq(&self, other: &Self) -> bool {
111 self.as_str() == other.as_str()
112 }
113}
114
115impl Eq for BackendId {}
116
117impl PartialOrd for BackendId {
118 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
119 Some(self.cmp(other))
120 }
121}
122
123impl Ord for BackendId {
124 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
125 self.as_str().cmp(other.as_str())
126 }
127}
128
129impl core::hash::Hash for BackendId {
130 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
131 self.as_str().hash(state);
132 }
133}
134
135impl fmt::Debug for BackendId {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(f, "BackendId({:?})", self.as_str())
138 }
139}
140
141impl fmt::Display for BackendId {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 f.write_str(self.as_str())
144 }
145}
146
147#[non_exhaustive]
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum ExecutionTarget {
151 PortableCpu,
153 OptimizedCpu,
155 Gpu,
157 Accelerator,
159}
160
161#[non_exhaustive]
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub enum Operation {
166 CurveEvaluation,
167 SurfaceEvaluation,
168 ProfileTriangulation,
169 Sweep,
170 Tessellation,
171 MeshBoolean,
172 MeshPlaneSection,
173 PointcloudReconstruction,
174 SpatialQuery,
175 Measurement,
176 Healing,
177 GraphCompilation,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct BackendDescriptor {
183 pub id: BackendId,
185 pub target: ExecutionTarget,
187}
188
189impl BackendDescriptor {
190 pub const fn new(id: BackendId, target: ExecutionTarget) -> Self {
192 Self { id, target }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
203 fn identifiers_accept_runtime_owned_text() {
204 let ordinal = 1_u32;
205 let runtime = format!("cuda:{ordinal}");
206 let id = BackendId::try_new(&runtime).expect("short runtime identity");
207 assert_eq!(id.as_str(), "cuda:1");
208 assert_eq!(id.to_string(), "cuda:1");
209 }
210
211 #[test]
212 fn runtime_and_const_identifiers_compare_equal() {
213 const STATIC: BackendId = BackendId::new("cuda:0");
214 let runtime = BackendId::try_new(&String::from("cuda:0")).expect("identity");
215 assert_eq!(STATIC, runtime);
216 assert_eq!(STATIC.cmp(&runtime), core::cmp::Ordering::Equal);
217 }
218
219 #[test]
220 fn identifiers_stay_copy_and_orderable() {
221 fn assert_copy<T: Copy + Ord + core::hash::Hash>() {}
222 assert_copy::<BackendId>();
223 let a = BackendId::try_new("aa").expect("identity");
224 let b = BackendId::try_new("aab").expect("identity");
225 assert!(a < b, "zero padding must not invert lexicographic order");
226 }
227
228 #[test]
229 fn over_long_identifiers_are_rejected_not_truncated() {
230 let too_long = "x".repeat(BackendId::CAPACITY + 1);
231 assert!(BackendId::try_new(&too_long).is_err());
232 let at_limit = "x".repeat(BackendId::CAPACITY);
233 assert_eq!(
234 BackendId::try_new(&at_limit).expect("identity").as_str(),
235 at_limit
236 );
237 }
238
239 #[test]
242 fn long_shared_prefix_devices_do_not_alias() {
243 let base = "x".repeat(BackendId::CAPACITY - 1);
244 let first = BackendId::try_new(&format!("{base}0")).expect("identity");
245 let second = BackendId::try_new(&format!("{base}1")).expect("identity");
246 assert_ne!(first, second);
247 }
248
249 #[test]
250 fn hashing_matches_equality_across_construction_paths() {
251 use std::collections::HashSet;
252 let mut seen = HashSet::new();
253 seen.insert(BackendId::new("hip:0"));
254 assert!(seen.contains(&BackendId::try_new("hip:0").expect("identity")));
255 }
256
257 #[test]
258 fn multibyte_identifiers_round_trip() {
259 let id = BackendId::try_new("gpu-µ-0").expect("identity");
260 assert_eq!(id.as_str(), "gpu-µ-0");
261 }
262}