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}