serializer.rs

5.6 kB · rust · 167 lines

1use super::models::Cell3d;2use crate::dim::serializer::{byte_cube, count_cube, parse, tag_layer, types_field};3use mrlycore::errors::{value_error, MrlyError, Result};4use mrlycore::tensor::Tensor;5use mrlycore::{json, Json};6use serde::Deserialize;78/// Unrolls the cell into nested lists, plane by row by site.9pub fn to_lists(cell: &Cell3d) -> Vec<Vec<Vec<u8>>> {10    let shape = &cell.types().shape;11    (0..shape[0])12        .map(|i| {13            (0..shape[1])14                .map(|j| {15                    (0..shape[2])16                        .map(|k| cell.types().get(&[i, j, k]))17                        .collect()18                })19                .collect()20        })21        .collect()22}2324/// Builds a cell from nested lists, or an error when they are empty or ragged.25pub fn from_lists(lists: &[Vec<Vec<u8>>]) -> Result<Cell3d> {26    if lists.is_empty() || lists[0].is_empty() || lists[0][0].is_empty() {27        return value_error("cannot build a cell from an empty list.");28    }29    let (a, b, c) = (lists.len(), lists[0].len(), lists[0][0].len());30    for plane in lists {31        if plane.len() != b || plane.iter().any(|row| row.len() != c) {32            return value_error("all planes and rows must have the same lengths.");33        }34    }35    let data: Vec<u8> = lists.iter().flatten().flatten().copied().collect();36    Ok(Cell3d::new(Tensor::of(data, vec![a, b, c])))37}3839/// Returns the cell's types as planes of digit-string rows.40pub fn to_strings(cell: &Cell3d) -> Vec<Vec<String>> {41    to_lists(cell)42        .iter()43        .map(|plane| {44            plane45                .iter()46                .map(|row| row.iter().map(|v| v.to_string()).collect())47                .collect()48        })49        .collect()50}5152/// Builds a cell from planes of digit-string rows, or an error at any non-digit.53///54/// ```55/// let planes = vec![vec!["11".to_string(), "10".to_string()]];56/// let cell = mrlymath::three::from_strings(&planes).unwrap();57/// assert_eq!(cell.types().sum(), 3);58/// ```59pub fn from_strings(planes: &[Vec<String>]) -> Result<Cell3d> {60    let lists: Result<Vec<Vec<Vec<u8>>>> = planes61        .iter()62        .map(|plane| {63            plane64                .iter()65                .map(|row| {66                    row.chars()67                        .map(|ch| {68                            ch.to_digit(10)69                                .map(|d| d as u8)70                                .ok_or_else(|| MrlyError::Value(format!("invalid digit {ch:?}.")))71                        })72                        .collect()73                })74                .collect()75        })76        .collect();77    from_lists(&lists?)78}7980fn color_cube(value: &Json) -> Result<Vec<Vec<Vec<[u8; 4]>>>> {81    Ok(Vec::deserialize(value)?)82}8384fn planes<T: Clone>(flat: &[T], shape: &[usize]) -> Vec<Vec<Vec<T>>> {85    flat.chunks(shape[1] * shape[2])86        .map(|plane| plane.chunks(shape[2]).map(<[T]>::to_vec).collect())87        .collect()88}8990/// Serializes the cell's shape and types to JSON, with colors and tags when present.91pub fn to_json(cell: &Cell3d) -> String {92    let shape = &cell.types().shape;93    let mut data = json!({94        "v": 1,95        "height": shape[0],96        "width": shape[1],97        "depth": shape[2],98        "types": to_lists(cell),99    });100    if let Some(colors) = &cell.cell.colors {101        data["colors"] = json!(planes(colors, shape));102    }103    if let Some(tags) = &cell.cell.tags {104        let flat: Vec<i64> = (0..tags.size()).map(|at| tags.at(at)).collect();105        data["tags"] = json!(planes(&flat, shape));106    }107    data.to_string()108}109110/// Parses a cell from its JSON, colors and tags included, or a parse error.111pub fn from_json(text: &str) -> Result<Cell3d> {112    let data = parse(text)?;113    let lists = byte_cube(types_field(&data)?)?;114    let mut cell = from_lists(&lists)?;115    if let Some(colors) = data.get("colors") {116        let nested = color_cube(colors)?;117        cell.cell.colors = Some(nested.into_iter().flatten().flatten().collect());118    }119    if let Some(tags) = data.get("tags") {120        let shape = cell.types().shape.clone();121        cell.cell.tags = Some(tag_layer(&count_cube(tags)?, shape)?);122    }123    Ok(cell)124}125126#[cfg(test)]127mod tests {128    use super::*;129    use crate::three::designs;130    use mrlycore::cell::mapping;131    use mrlycore::enums::Mode;132    #[test]133    fn json_round_trip_with_colors_and_tags() {134        let c = designs::carpet(3, 1)135            .unwrap()136            .layers()137            .paint(&mapping(), Mode::Type);138        let restored = from_json(&to_json(&c)).unwrap();139        assert_eq!(c, restored);140        assert!(restored.cell.colors.is_some());141        assert!(restored.cell.tags.is_some());142    }143    #[test]144    fn json_round_trip_with_tags_past_a_byte() {145        use crate::three::manhattan_layers;146        use mrlycore::tensor::Dtype;147        let long = manhattan_layers(Cell3d::new(Tensor::full(vec![1, 1, 600], 1)));148        let tags = long.cell.tags.as_ref().unwrap();149        assert_eq!(tags.dtype(), Dtype::U16);150        assert_eq!(tags.at(0), 299);151        let restored = from_json(&to_json(&long)).unwrap();152        assert_eq!(restored, long);153        assert_eq!(restored.cell.tags.as_ref().unwrap().at(0), 299);154    }155    #[test]156    fn lists_round_trip() {157        let c = designs::void(4, 1).unwrap();158        assert_eq!(from_lists(&to_lists(&c)).unwrap(), c);159    }160    #[test]161    fn strings_round_trip() {162        let c = designs::net(3, 2).unwrap();163        assert_eq!(from_strings(&to_strings(&c)).unwrap(), c);164        assert_eq!(to_strings(&designs::ones(2, 1).unwrap())[0], ["11", "11"]);165        assert!(from_strings(&[vec!["1x1".to_string()]]).is_err());166    }167}