serializer.rs
4.1 kB · rust · 119 lines
1use super::Cell3d;2use crate::core::errors::{value_error, Result};3use crate::core::tensor::Tensor;4use crate::core::{json, Json};5use crate::math::dim::serializer::{byte_cube, count_cube, parse, tag_layer, types_field};6use serde::Deserialize;78/// Unrolls the cell into nested lists, plane by row by site.9fn 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.25fn 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}3839fn color_cube(value: &Json) -> Result<Vec<Vec<Vec<[u8; 4]>>>> {40 Ok(Vec::deserialize(value)?)41}4243fn planes<T: Clone>(flat: &[T], shape: &[usize]) -> Vec<Vec<Vec<T>>> {44 flat.chunks(shape[1] * shape[2])45 .map(|plane| plane.chunks(shape[2]).map(<[T]>::to_vec).collect())46 .collect()47}4849/// Serializes the cell's shape and types to JSON, with colors and tags when present.50pub fn to_json(cell: &Cell3d) -> String {51 let shape = &cell.types().shape;52 let mut data = json!({53 "v": 1,54 "height": shape[0],55 "width": shape[1],56 "depth": shape[2],57 "types": to_lists(cell),58 });59 if let Some(colors) = &cell.cell.colors {60 data["colors"] = json!(planes(colors, shape));61 }62 if let Some(tags) = &cell.cell.tags {63 let flat: Vec<i64> = (0..tags.size()).map(|at| tags.at(at)).collect();64 data["tags"] = json!(planes(&flat, shape));65 }66 data.to_string()67}6869/// Parses a cell from its JSON, colors and tags included, or a parse error.70pub fn from_json(text: &str) -> Result<Cell3d> {71 let data = parse(text)?;72 let lists = byte_cube(types_field(&data)?)?;73 let mut cell = from_lists(&lists)?;74 if let Some(colors) = data.get("colors") {75 let nested = color_cube(colors)?;76 cell.cell.colors = Some(nested.into_iter().flatten().flatten().collect());77 }78 if let Some(tags) = data.get("tags") {79 let shape = cell.types().shape.clone();80 cell.cell.tags = Some(tag_layer(&count_cube(tags)?, shape)?);81 }82 Ok(cell)83}8485#[cfg(test)]86mod tests {87 use super::*;88 use crate::core::cell::mapping;89 use crate::core::enums::Mode;90 use crate::math::three::designs;91 #[test]92 fn json_round_trip_with_colors_and_tags() {93 let c = designs::carpet(3, 1)94 .unwrap()95 .layers()96 .paint(&mapping(), Mode::Type);97 let restored = from_json(&to_json(&c)).unwrap();98 assert_eq!(c, restored);99 assert!(restored.cell.colors.is_some());100 assert!(restored.cell.tags.is_some());101 }102 #[test]103 fn json_round_trip_with_tags_past_a_byte() {104 use crate::core::tensor::Dtype;105 use crate::math::three::manhattan_layers;106 let long = manhattan_layers(Cell3d::new(Tensor::full(vec![1, 1, 600], 1)));107 let tags = long.cell.tags.as_ref().unwrap();108 assert_eq!(tags.dtype(), Dtype::U16);109 assert_eq!(tags.at(0), 299);110 let restored = from_json(&to_json(&long)).unwrap();111 assert_eq!(restored, long);112 assert_eq!(restored.cell.tags.as_ref().unwrap().at(0), 299);113 }114 #[test]115 fn lists_round_trip() {116 let c = designs::void(4, 1).unwrap();117 assert_eq!(from_lists(&to_lists(&c)).unwrap(), c);118 }119}