use std::{cell::RefCell, collections::HashMap};
use scale_info::{form::PortableForm, PortableRegistry, Type};
pub struct Transformer<'a, R, S = ()> {
cache: RefCell<HashMap<u32, Cached<R>>>,
state: S,
policy: fn(u32, &Type<PortableForm>, &Self) -> anyhow::Result<R>,
recurse_policy: fn(u32, &Type<PortableForm>, &Self) -> Option<anyhow::Result<R>>,
#[allow(clippy::type_complexity)]
cache_hit_policy: fn(u32, &Type<PortableForm>, &R, &Self) -> Option<anyhow::Result<R>>,
registry: &'a PortableRegistry,
}
#[derive(Clone, Debug)]
enum Cached<Out> {
Recursive,
Computed(Out),
}
impl<'a, R, S> Transformer<'a, R, S>
where
R: Clone + std::fmt::Debug,
{
#[allow(clippy::type_complexity)]
pub fn new(
policy: fn(u32, &Type<PortableForm>, &Self) -> anyhow::Result<R>,
recurse_policy: fn(u32, &Type<PortableForm>, &Self) -> Option<anyhow::Result<R>>,
cache_hit_policy: fn(u32, &Type<PortableForm>, &R, &Self) -> Option<anyhow::Result<R>>,
state: S,
registry: &'a PortableRegistry,
) -> Self {
Transformer {
cache: RefCell::new(HashMap::new()),
state,
policy,
recurse_policy,
registry,
cache_hit_policy,
}
}
pub fn state(&self) -> &S {
&self.state
}
pub fn types(&self) -> &PortableRegistry {
self.registry
}
pub fn resolve(&self, type_id: u32) -> anyhow::Result<R> {
let ty = self.registry.resolve(type_id).ok_or(anyhow::anyhow!(
"Type with id {} not found in registry",
type_id
))?;
if let Some(cache_value) = self.cache.borrow().get(&type_id) {
let result_or_continue = match cache_value {
Cached::Recursive => (self.recurse_policy)(type_id, ty, self),
Cached::Computed(repr) => (self.cache_hit_policy)(type_id, ty, repr, self),
};
if let Some(result) = result_or_continue {
return result;
}
};
self.cache.borrow_mut().insert(type_id, Cached::Recursive);
let r = (self.policy)(type_id, ty, self)?;
self.cache
.borrow_mut()
.insert(type_id, Cached::Computed(r.clone()));
Ok(r)
}
}