use core::fmt;
const IDENTIFIER_CAPACITY: usize = 47;
#[derive(Clone, Copy)]
pub struct BackendId {
bytes: [u8; IDENTIFIER_CAPACITY],
len: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackendIdTooLong {
pub len: usize,
}
impl fmt::Display for BackendIdTooLong {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"backend identifier is {} bytes, limit is {}",
self.len,
BackendId::CAPACITY
)
}
}
impl core::error::Error for BackendIdTooLong {}
impl BackendId {
pub const CAPACITY: usize = IDENTIFIER_CAPACITY;
pub const fn new(value: &str) -> Self {
match Self::build(value.as_bytes()) {
Some(id) => id,
None => panic!("backend identifier exceeds BackendId::CAPACITY"),
}
}
pub fn try_new(value: &str) -> Result<Self, BackendIdTooLong> {
Self::build(value.as_bytes()).ok_or(BackendIdTooLong { len: value.len() })
}
const fn build(source: &[u8]) -> Option<Self> {
if source.len() > IDENTIFIER_CAPACITY {
return None;
}
let mut bytes = [0_u8; IDENTIFIER_CAPACITY];
let mut index = 0;
while index < source.len() {
bytes[index] = source[index];
index += 1;
}
Some(Self {
bytes,
len: source.len() as u8,
})
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len as usize])
.expect("identifier bytes originate from &str")
}
}
impl PartialEq for BackendId {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for BackendId {}
impl PartialOrd for BackendId {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BackendId {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl core::hash::Hash for BackendId {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl fmt::Debug for BackendId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BackendId({:?})", self.as_str())
}
}
impl fmt::Display for BackendId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExecutionTarget {
PortableCpu,
OptimizedCpu,
Gpu,
Accelerator,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Operation {
CurveEvaluation,
SurfaceEvaluation,
ProfileTriangulation,
Sweep,
Tessellation,
MeshBoolean,
MeshPlaneSection,
PointcloudReconstruction,
SpatialQuery,
Measurement,
Healing,
GraphCompilation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackendDescriptor {
pub id: BackendId,
pub target: ExecutionTarget,
}
impl BackendDescriptor {
pub const fn new(id: BackendId, target: ExecutionTarget) -> Self {
Self { id, target }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifiers_accept_runtime_owned_text() {
let ordinal = 1_u32;
let runtime = format!("cuda:{ordinal}");
let id = BackendId::try_new(&runtime).expect("short runtime identity");
assert_eq!(id.as_str(), "cuda:1");
assert_eq!(id.to_string(), "cuda:1");
}
#[test]
fn runtime_and_const_identifiers_compare_equal() {
const STATIC: BackendId = BackendId::new("cuda:0");
let runtime = BackendId::try_new(&String::from("cuda:0")).expect("identity");
assert_eq!(STATIC, runtime);
assert_eq!(STATIC.cmp(&runtime), core::cmp::Ordering::Equal);
}
#[test]
fn identifiers_stay_copy_and_orderable() {
fn assert_copy<T: Copy + Ord + core::hash::Hash>() {}
assert_copy::<BackendId>();
let a = BackendId::try_new("aa").expect("identity");
let b = BackendId::try_new("aab").expect("identity");
assert!(a < b, "zero padding must not invert lexicographic order");
}
#[test]
fn over_long_identifiers_are_rejected_not_truncated() {
let too_long = "x".repeat(BackendId::CAPACITY + 1);
assert!(BackendId::try_new(&too_long).is_err());
let at_limit = "x".repeat(BackendId::CAPACITY);
assert_eq!(
BackendId::try_new(&at_limit).expect("identity").as_str(),
at_limit
);
}
#[test]
fn long_shared_prefix_devices_do_not_alias() {
let base = "x".repeat(BackendId::CAPACITY - 1);
let first = BackendId::try_new(&format!("{base}0")).expect("identity");
let second = BackendId::try_new(&format!("{base}1")).expect("identity");
assert_ne!(first, second);
}
#[test]
fn hashing_matches_equality_across_construction_paths() {
use std::collections::HashSet;
let mut seen = HashSet::new();
seen.insert(BackendId::new("hip:0"));
assert!(seen.contains(&BackendId::try_new("hip:0").expect("identity")));
}
#[test]
fn multibyte_identifiers_round_trip() {
let id = BackendId::try_new("gpu-µ-0").expect("identity");
assert_eq!(id.as_str(), "gpu-µ-0");
}
}