use std::sync::{Arc, Mutex};
use crate::{Backend, tensor::FloatTensor};
trait TensorStorage<B: Backend>: Send + Sync {
fn access(&self, callback: &mut dyn FnMut(&mut FloatTensor<B>));
}
struct OwnedStorage<B: Backend>(Mutex<FloatTensor<B>>);
impl<B: Backend> TensorStorage<B> for OwnedStorage<B> {
fn access(&self, callback: &mut dyn FnMut(&mut FloatTensor<B>)) {
let mut tensor = self.0.lock().expect("gradient storage lock poisoned");
callback(&mut tensor);
}
}
struct ProjectedStorage<B: Backend, T: Backend> {
parent: TensorRef<B>,
project: fn(&mut FloatTensor<B>) -> &mut FloatTensor<T>,
}
impl<B: Backend, T: Backend> TensorStorage<T> for ProjectedStorage<B, T> {
fn access(&self, callback: &mut dyn FnMut(&mut FloatTensor<T>)) {
self.parent.with_mut(|tensor| callback((self.project)(tensor)));
}
}
#[derive(Clone)]
pub struct TensorRef<B: Backend> {
storage: Arc<dyn TensorStorage<B>>,
}
impl<B: Backend> TensorRef<B> {
pub fn new(tensor: FloatTensor<B>) -> Self {
Self {
storage: Arc::new(OwnedStorage::<B>(Mutex::new(tensor))),
}
}
pub fn map<T: Backend>(
self,
project: fn(&mut FloatTensor<B>) -> &mut FloatTensor<T>,
) -> TensorRef<T> {
TensorRef {
storage: Arc::new(ProjectedStorage::<B, T> { parent: self, project }),
}
}
pub(crate) fn with_mut<R>(&self, operation: impl FnOnce(&mut FloatTensor<B>) -> R) -> R {
let mut operation = Some(operation);
let mut result = None;
self.storage.access(&mut |tensor| {
result = Some(operation.take().expect("gradient storage accessed twice")(tensor));
});
result.expect("gradient storage was not accessed")
}
pub fn replace(&self, tensor: FloatTensor<B>) {
self.with_mut(|current| *current = tensor);
}
}