use std::sync::{Arc, RwLock, TryLockError};
use dlpk::sys::{DLDataType, DLDevice, DLPackVersion};
use dlpk::{DLPackPointerCast, DLPackTensor, GetDLPackDataType};
use ndarray::ArrayD;
use crate::error::ParseError;
pub trait Array: std::any::Any + Send + Sync {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn shape(&self) -> Vec<usize>;
fn dtype(&self) -> DLDataType;
fn device(&self) -> DLDevice;
fn as_dlpack(
&self,
device: DLDevice,
stream: Option<i64>,
max_version: DLPackVersion,
) -> Result<DLPackTensor, ParseError>;
fn copy(&self) -> Box<dyn Array>;
}
impl<T> Array for Arc<RwLock<ArrayD<T>>>
where
T: 'static + Send + Sync + Clone + Default + GetDLPackDataType + DLPackPointerCast,
{
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn shape(&self) -> Vec<usize> {
match self.try_read() {
Ok(lock) => lock.shape().to_vec(),
Err(TryLockError::Poisoned(_)) => panic!("readcon-core array lock is poisoned"),
Err(TryLockError::WouldBlock) => panic!("readcon-core array is already locked"),
}
}
fn dtype(&self) -> DLDataType {
T::get_dlpack_data_type()
}
fn device(&self) -> DLDevice {
DLDevice::cpu()
}
fn as_dlpack(
&self,
device: DLDevice,
_stream: Option<i64>,
_max_version: DLPackVersion,
) -> Result<DLPackTensor, ParseError> {
if device != DLDevice::cpu() {
return Err(ParseError::ValidationError(format!(
"Arc<RwLock<ArrayD>> is CPU-only; requested device {device:?} unsupported"
)));
}
let lock = match self.try_read() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => {
return Err(ParseError::ValidationError(
"readcon-core array lock is poisoned".into(),
));
}
Err(TryLockError::WouldBlock) => {
return Err(ParseError::ValidationError(
"readcon-core array is already locked".into(),
));
}
};
let owned: ArrayD<T> = lock.to_owned();
DLPackTensor::try_from(owned).map_err(|e| {
ParseError::ValidationError(format!("dlpk ArrayD conversion failed: {e}"))
})
}
fn copy(&self) -> Box<dyn Array> {
Box::new(Arc::clone(self))
}
}
pub fn array_from_shape<T>(shape: &[usize]) -> Box<dyn Array>
where
T: 'static + Send + Sync + Clone + Default + GetDLPackDataType + DLPackPointerCast,
{
let arr: ArrayD<T> = ArrayD::default(ndarray::IxDyn(shape));
Box::new(Arc::new(RwLock::new(arr)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn array_from_shape_reports_shape_and_dtype() {
let a: Box<dyn Array> = array_from_shape::<f64>(&[5, 3]);
assert_eq!(a.shape(), vec![5, 3]);
let dt = a.dtype();
assert_eq!(dt.code, dlpk::sys::DLDataTypeCode::kDLFloat);
assert_eq!(dt.bits, 64);
assert_eq!(dt.lanes, 1);
assert_eq!(a.device(), DLDevice::cpu());
}
#[test]
fn array_copy_shares_storage_via_arc() {
let a = array_from_shape::<f64>(&[2, 3]);
let b = a.copy();
assert_eq!(a.shape(), b.shape());
}
#[test]
fn array_dlpack_export_round_trip() {
let a = array_from_shape::<f64>(&[4, 3]);
let tensor = a
.as_dlpack(DLDevice::cpu(), None, DLPackVersion::current())
.expect("DLPack export should succeed for CPU array");
assert_eq!(tensor.shape(), &[4, 3]);
}
}