daedalus_runtime/
convert.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex, OnceLock};
4
5use image::{DynamicImage, GrayAlphaImage, GrayImage, ImageBuffer, Luma, LumaA, Rgb, RgbImage, Rgba, RgbaImage};
6
7/// Runtime conversion registry for common CPU-side types.
8///
9/// This mirrors the `GpuSendable` story: users can register additional conversions
10/// at runtime if needed, while we ship a set of defaults (numeric widenings, image casts).
11type ConvertFn =
12    Box<dyn Fn(&Arc<dyn Any + Send + Sync>) -> Option<Box<dyn Any + Send + Sync>> + Send + Sync>;
13
14pub struct ConversionRegistry {
15    inner: HashMap<(TypeId, TypeId), ConvertFn>,
16    resolved_paths: HashMap<(TypeId, TypeId), Option<Vec<TypeId>>>,
17}
18
19impl ConversionRegistry {
20    pub fn new() -> Self {
21        Self {
22            inner: HashMap::new(),
23            resolved_paths: HashMap::new(),
24        }
25    }
26
27    pub fn register<S: Any + Send + Sync + 'static, T: Any + Send + Sync + 'static>(
28        &mut self,
29        f: fn(&S) -> Option<T>,
30    ) -> &mut Self {
31        let key = (TypeId::of::<S>(), TypeId::of::<T>());
32        self.inner.insert(
33            key,
34            Box::new(move |a| {
35                a.downcast_ref::<S>()
36                    .and_then(|s| f(s).map(|t| Box::new(t) as Box<dyn Any + Send + Sync>))
37            }),
38        );
39        self.resolved_paths.clear();
40        self
41    }
42
43    fn convert_boxed(
44        &self,
45        a: &Arc<dyn Any + Send + Sync>,
46        to: TypeId,
47    ) -> Option<Box<dyn Any + Send + Sync>> {
48        let from = a.as_ref().type_id();
49        self.inner.get(&(from, to)).and_then(|f| f(a))
50    }
51
52    fn resolve_path(&mut self, from: TypeId, to: TypeId) -> Option<Vec<TypeId>> {
53        if let Some(cached) = self.resolved_paths.get(&(from, to)) {
54            return cached.clone();
55        }
56
57        if from == to {
58            self.resolved_paths.insert((from, to), Some(vec![from]));
59            return Some(vec![from]);
60        }
61
62        // BFS over available conversions (unweighted; good enough for now).
63        let mut queue: std::collections::VecDeque<TypeId> = std::collections::VecDeque::new();
64        let mut prev: HashMap<TypeId, TypeId> = HashMap::new();
65        queue.push_back(from);
66        prev.insert(from, from);
67
68        while let Some(cur) = queue.pop_front() {
69            if cur == to {
70                break;
71            }
72            for &(src, dst) in self.inner.keys() {
73                if src != cur {
74                    continue;
75                }
76                if prev.contains_key(&dst) {
77                    continue;
78                }
79                prev.insert(dst, cur);
80                queue.push_back(dst);
81            }
82        }
83
84        if !prev.contains_key(&to) {
85            self.resolved_paths.insert((from, to), None);
86            return None;
87        }
88
89        let mut path: Vec<TypeId> = Vec::new();
90        let mut cur = to;
91        loop {
92            path.push(cur);
93            let p = *prev.get(&cur).unwrap();
94            if p == cur {
95                break;
96            }
97            cur = p;
98        }
99        path.reverse();
100        self.resolved_paths.insert((from, to), Some(path.clone()));
101        Some(path)
102    }
103
104    pub fn convert_to<T: Any + Clone + Send + Sync + 'static>(
105        &mut self,
106        a: &Arc<dyn Any + Send + Sync>,
107    ) -> Option<T> {
108        let from = a.as_ref().type_id();
109        let to = TypeId::of::<T>();
110
111        // Fast path: direct conversion.
112        if let Some(b) = self
113            .convert_boxed(a, to)
114            .and_then(|b| b.downcast::<T>().ok())
115        {
116            return Some((*b).clone());
117        }
118
119        // Chained conversion.
120        let path = self.resolve_path(from, to)?;
121        if path.len() < 2 {
122            return None;
123        }
124
125        let mut cur: Arc<dyn Any + Send + Sync> = a.clone();
126        for win in path.windows(2) {
127            let from = win[0];
128            let to = win[1];
129            if cur.as_ref().type_id() != from {
130                return None;
131            }
132            let boxed = self.convert_boxed(&cur, to)?;
133            cur = Arc::from(boxed);
134        }
135
136        cur.downcast_ref::<T>().cloned()
137    }
138}
139
140impl Default for ConversionRegistry {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146/// Global registry with common conversions.
147fn default_registry() -> &'static Mutex<ConversionRegistry> {
148    static REG: OnceLock<Mutex<ConversionRegistry>> = OnceLock::new();
149    REG.get_or_init(|| {
150        let mut reg = ConversionRegistry::new();
151        // Numeric widenings (lossless).
152        reg.register::<i8, i16>(|v| Some(*v as i16))
153            .register::<i8, i32>(|v| Some(*v as i32))
154            .register::<i8, i64>(|v| Some(*v as i64))
155            .register::<i8, f64>(|v| Some(*v as f64))
156            .register::<i16, i32>(|v| Some(*v as i32))
157            .register::<i16, i64>(|v| Some(*v as i64))
158            .register::<i16, f64>(|v| Some(*v as f64))
159            .register::<i32, i64>(|v| Some(*v as i64))
160            .register::<i32, f64>(|v| Some(*v as f64))
161            .register::<i64, f64>(|v| Some(*v as f64))
162            .register::<u8, u16>(|v| Some(*v as u16))
163            .register::<u8, u32>(|v| Some(*v as u32))
164            .register::<u8, u64>(|v| Some(*v as u64))
165            .register::<u8, f64>(|v| Some(*v as f64))
166            .register::<u16, u32>(|v| Some(*v as u32))
167            .register::<u16, u64>(|v| Some(*v as u64))
168            .register::<u16, f64>(|v| Some(*v as f64))
169            .register::<u32, u64>(|v| Some(*v as u64))
170            .register::<u32, f64>(|v| Some(*v as f64))
171            .register::<f32, f64>(|v| Some(*v as f64))
172            .register::<bool, u8>(|v| Some(if *v { 1 } else { 0 }))
173            .register::<u8, bool>(|v| Some(*v != 0));
174
175        // Common image -> DynamicImage conversions.
176        reg.register::<DynamicImage, DynamicImage>(|img| Some(img.clone()))
177            .register::<RgbaImage, DynamicImage>(|img| Some(DynamicImage::ImageRgba8(img.clone())))
178            .register::<RgbImage, DynamicImage>(|img| Some(DynamicImage::ImageRgb8(img.clone())))
179            .register::<GrayImage, DynamicImage>(|img| Some(DynamicImage::ImageLuma8(img.clone())))
180            .register::<GrayAlphaImage, DynamicImage>(|img| {
181                Some(DynamicImage::ImageLumaA8(img.clone()))
182            })
183            .register::<ImageBuffer<Rgba<u8>, Vec<u8>>, DynamicImage>(|img| {
184                Some(DynamicImage::ImageRgba8(img.clone()))
185            })
186            .register::<ImageBuffer<Rgb<u8>, Vec<u8>>, DynamicImage>(|img| {
187                Some(DynamicImage::ImageRgb8(img.clone()))
188            })
189            .register::<ImageBuffer<Luma<u8>, Vec<u8>>, DynamicImage>(|img| {
190                Some(DynamicImage::ImageLuma8(img.clone()))
191            })
192            .register::<ImageBuffer<LumaA<u8>, Vec<u8>>, DynamicImage>(|img| {
193                Some(DynamicImage::ImageLumaA8(img.clone()))
194            });
195
196        Mutex::new(reg)
197    })
198}
199
200/// Attempt to convert an `Arc<dyn Any>` into `T` using the default registry.
201pub fn convert_arc<T: Any + Clone + Send + Sync + 'static>(
202    a: &Arc<dyn Any + Send + Sync>,
203) -> Option<T> {
204    default_registry()
205        .lock()
206        .ok()
207        .and_then(|mut r| r.convert_to::<T>(a))
208}
209
210/// Allow callers (including plugins) to extend the global conversion registry.
211#[allow(dead_code)]
212pub fn register_conversion<S, T>(f: fn(&S) -> Option<T>)
213where
214    S: Any + Send + Sync + 'static,
215    T: Any + Send + Sync + 'static,
216{
217    if let Ok(mut reg) = default_registry().lock() {
218        reg.register::<S, T>(f);
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[derive(Clone, Debug, PartialEq, Eq)]
227    struct A(u8);
228    #[derive(Clone, Debug, PartialEq, Eq)]
229    struct B(u16);
230    #[derive(Clone, Debug, PartialEq, Eq)]
231    struct C(u32);
232
233    #[test]
234    fn chained_conversion_resolves() {
235        register_conversion::<A, B>(|a| Some(B(a.0 as u16)));
236        register_conversion::<B, C>(|b| Some(C(b.0 as u32)));
237
238        let a: Arc<dyn Any + Send + Sync> = Arc::new(A(7));
239        let out: Option<C> = convert_arc::<C>(&a);
240        assert_eq!(out, Some(C(7)));
241    }
242}