use std::sync::Arc;
use crate::Side;
use crate::data::{Field, GeometryBuilder, Id, Name, Op, Patch, Table, Value, Values, Version};
use crate::derive::pyramid::{Leveling, Places, Placing};
use crate::derive::sight::Domain;
use crate::geo::mvt::Kind as Geometry;
use crate::geo::shapes::{earth_parts, triangles};
use crate::geo::tiles::{Decoded, decode};
use crate::layout::route::{Net, Terminal, route};
use crate::layout::{layered::layered, levels::Graph, stress::stress};
use crate::tiles::TileKey;
use crate::{Direction, Layered, Layout};
#[derive(Clone, PartialEq, Eq)]
pub struct Job(Vec<u8>);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Kind {
Layered = 1,
Stress = 2,
Routes = 3,
Shapes = 4,
Tile = 5,
GeoJson = 6,
Lanes = 7,
Pyramid = 8,
Image = 9,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum Shaping {
Triangles { earth: bool },
Path,
}
impl Shaping {
fn code(self) -> u32 {
match self {
Shaping::Triangles { earth: false } => 0,
Shaping::Triangles { earth: true } => 1,
Shaping::Path => 2,
}
}
fn from_code(code: u32) -> Option<Shaping> {
match code {
0 => Some(Shaping::Triangles { earth: false }),
1 => Some(Shaping::Triangles { earth: true }),
2 => Some(Shaping::Path),
_ => None,
}
}
pub(crate) fn depth(self, depth: u8) -> u8 {
match self {
Shaping::Triangles { .. } => 1,
Shaping::Path => depth,
}
}
pub(crate) fn shape(self, value: &Value) -> Option<Value> {
match (self, value) {
(Shaping::Triangles { earth }, Value::Polygons(polygons)) => Some(Value::Path(triangles(polygons, earth))),
(Shaping::Triangles { earth }, Value::Parts(rings)) => {
Some(Value::Path(triangles(std::slice::from_ref(rings), earth)))
}
(Shaping::Triangles { .. }, _) => Some(Value::Path(Vec::new())),
(Shaping::Path, Value::Path(path)) => {
earth_parts(std::slice::from_ref(path)).and_then(|mut parts| parts.pop()).map(Value::Path)
}
(Shaping::Path, Value::Parts(parts)) => earth_parts(parts).map(Value::Parts),
(Shaping::Path, Value::Polygons(polygons)) => {
let shaped: Vec<Option<Vec<Vec<[f64; 2]>>>> = polygons.iter().map(|rings| earth_parts(rings)).collect();
shaped.iter().any(Option::is_some).then(|| {
Value::Polygons(
shaped
.into_iter()
.zip(polygons)
.map(|(shaped, rings)| shaped.unwrap_or_else(|| rings.clone()))
.collect(),
)
})
}
(Shaping::Path, _) => None,
}
}
}
pub(crate) type Key = (Name, Id);
pub(crate) struct Placement<'a> {
pub(crate) sizes: &'a [[f64; 2]],
pub(crate) pinned: &'a [Option<[f64; 2]>],
pub(crate) groups: &'a [Option<u32>],
pub(crate) pads: &'a [[f64; 4]],
pub(crate) links: &'a [[u32; 2]],
pub(crate) keys: &'a [Option<Key>],
}
pub(crate) struct Wiring<'a> {
pub(crate) obstacles: &'a [[f64; 4]],
pub(crate) groups: &'a [[f64; 4]],
pub(crate) nets: &'a [Net],
pub(crate) keys: &'a [Key],
pub(crate) fixed: &'a [Vec<[f64; 2]>],
pub(crate) spacing: f64,
}
impl Job {
pub fn from_bytes(bytes: Vec<u8>) -> Job {
Job(bytes)
}
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
pub fn run(self) -> Vec<u8> {
let mut input = Reader::new(&self.0);
let kind = input.u32();
if kind == Some(Kind::Image as u32) {
return run_image(&mut input).unwrap_or_default();
}
let answer = match kind {
Some(k) if k == Kind::Layered as u32 || k == Kind::Stress as u32 => run_layout(k, &mut input),
Some(k) if k == Kind::Routes as u32 => run_routes(&mut input),
Some(k) if k == Kind::Shapes as u32 => run_shapes(&mut input),
Some(k) if k == Kind::Tile as u32 => run_tile(&mut input),
Some(k) if k == Kind::GeoJson as u32 => run_geojson(&mut input),
Some(k) if k == Kind::Lanes as u32 => run_lanes(&mut input),
Some(k) if k == Kind::Pyramid as u32 => run_pyramid(&mut input),
_ => None,
};
answer.map(|patch| patch.to_bytes()).unwrap_or_default()
}
pub(crate) fn layout(layout: &Layout, placement: &Placement<'_>) -> Job {
let mut out = match *layout {
Layout::Stress(stress) => {
let mut out = Writer::new(Kind::Stress);
out.f64(stress.gap);
out
}
Layout::Layered(layered) => {
let mut out = Writer::new(Kind::Layered);
out.u32(match layered.flow {
Direction::Right => 0,
Direction::Left => 1,
Direction::Down => 2,
Direction::Up => 3,
});
out.f64(layered.gap);
out.f64(layered.rank_gap);
out
}
Layout::None => unreachable!("a sheet asks for a layout only when its scene has one"),
};
out.u32(placement.sizes.len() as u32);
for v in 0..placement.sizes.len() {
out.f64s(&placement.sizes[v]);
out.u32(u32::from(placement.pinned[v].is_some()));
out.f64s(&placement.pinned[v].unwrap_or_default());
out.u32(placement.groups[v].unwrap_or(u32::MAX));
out.f64s(&placement.pads[v]);
out.u32(u32::from(placement.keys[v].is_some()));
if let Some(key) = &placement.keys[v] {
out.key(key);
}
}
out.u32(placement.links.len() as u32);
for link in placement.links {
out.u32(link[0]);
out.u32(link[1]);
}
Job(out.0)
}
pub(crate) fn shapes(shaping: Shaping, rows: Table) -> Job {
let mut out = Writer::new(Kind::Shapes);
out.u32(shaping.code());
out.0.extend(Patch::new().set(rows).to_bytes());
Job(out.0)
}
pub(crate) fn tile(key: TileKey, tables: &[Decoded], bytes: &[u8]) -> Job {
let mut out = Writer::new(Kind::Tile);
out.u32(u32::from(key.z));
out.u32(key.x);
out.u32(key.y);
out.u32(tables.len() as u32);
for decoded in tables {
out.text(&decoded.layer);
out.u32(match decoded.kind {
Geometry::Points => 0,
Geometry::Lines => 1,
Geometry::Polygons => 2,
});
out.text(&decoded.table);
}
out.0.extend_from_slice(bytes);
Job(out.0)
}
pub fn encode(image: &crate::Image, format: crate::ImageFormat) -> Job {
let mut out = Writer::new(Kind::Image);
out.u32(match format {
crate::ImageFormat::Png => 0,
crate::ImageFormat::Jpeg { quality } => 0x100 | u32::from(quality),
crate::ImageFormat::WebP => 1,
});
out.u32(image.size[0]);
out.u32(image.size[1]);
out.0.extend_from_slice(&image.rgba);
Job(out.0)
}
pub fn geojson(name: &str, bytes: &[u8]) -> Job {
let mut out = Writer::new(Kind::GeoJson);
out.text(name);
out.0.extend_from_slice(bytes);
Job(out.0)
}
pub(crate) fn lanes(from: &[u32], to: &[u32]) -> Job {
let mut out = Writer::new(Kind::Lanes);
out.u32(from.len() as u32);
out.words(from);
out.words(to);
Job(out.0)
}
pub(crate) fn pyramid(leveling: &Leveling) -> Job {
let mut out = Writer::new(Kind::Pyramid);
match leveling.domain {
Domain::Earth => out.f64s(&[0.0, 0.0, 0.0, 0.0]),
Domain::Plane { min, size } => out.f64s(&[1.0, min[0], min[1], size]),
}
out.u32(leveling.tables.len() as u32);
for table in &leveling.tables {
let places = match &table.places {
Places::None => 0,
Places::Points(_) => 1,
Places::Boxes(_) => 2,
};
let flags = u32::from(table.leveled)
| u32::from(table.answered) << 1
| places << 2
| u32::from(!table.priority.is_empty()) << 4
| u32::from(table.ends.is_some()) << 5;
out.u32(table.rows);
out.u32(flags);
match &table.places {
Places::None => {}
Places::Points(points) => out.words(bytemuck::cast_slice(&points[..])),
Places::Boxes(boxes) => out.words(bytemuck::cast_slice(&boxes[..])),
}
out.words(bytemuck::cast_slice(&table.priority));
if let Some((target, ends)) = &table.ends {
out.u32(*target);
out.u32(ends.len() as u32);
for end in ends {
out.words(end);
}
}
}
Job(out.0)
}
pub(crate) fn routes(wiring: &Wiring<'_>) -> Job {
let mut out = Writer::new(Kind::Routes);
out.f64(wiring.spacing);
for boxes in [wiring.obstacles, wiring.groups] {
out.u32(boxes.len() as u32);
for b in boxes {
out.f64s(b);
}
}
out.u32(wiring.fixed.len() as u32);
for line in wiring.fixed {
out.u32(line.len() as u32);
for point in line {
out.f64s(point);
}
}
out.u32(wiring.nets.len() as u32);
for (net, key) in wiring.nets.iter().zip(wiring.keys) {
out.key(key);
out.u32(net.terminals.len() as u32);
for terminal in &net.terminals {
out.f64s(&terminal.at);
out.u32(terminal.side.map_or(u32::MAX, Side::code));
out.u32(terminal.owner.unwrap_or(u32::MAX));
}
}
Job(out.0)
}
}
impl std::fmt::Debug for Job {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Job").field(&self.0.len()).finish()
}
}
fn keyed<T>(keys: &[Key], values: Vec<T>, write: impl Fn(Table, Vec<T>) -> Table) -> Patch {
let mut tables: Vec<(Name, Vec<Id>, Vec<T>)> = Vec::new();
for ((table, id), value) in keys.iter().zip(values) {
match tables.iter_mut().find(|(name, ..)| name == table) {
Some((_, ids, values)) => {
ids.push(id.clone());
values.push(value);
}
None => tables.push((table.clone(), vec![id.clone()], vec![value])),
}
}
tables.into_iter().fold(Patch::new(), |patch, (name, ids, values)| patch.set(write(Table::new(name, ids), values)))
}
fn run_layout(kind: u32, input: &mut Reader<'_>) -> Option<Patch> {
let options = if kind == Kind::Stress as u32 {
Layout::Stress(crate::Stress { gap: input.f64()? })
} else {
let flow = match input.u32()? {
0 => Direction::Right,
1 => Direction::Left,
2 => Direction::Down,
_ => Direction::Up,
};
Layout::Layered(Layered { flow, gap: input.f64()?, rank_gap: input.f64()? })
};
let count = input.u32()? as usize;
let (mut sizes, mut pinned, mut groups, mut pads, mut keys) =
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
for _ in 0..count {
sizes.push([input.f64()?, input.f64()?]);
let pin = input.u32()? != 0;
let at = [input.f64()?, input.f64()?];
pinned.push(pin.then_some(at));
groups.push(Some(input.u32()?).filter(|&g| (g as usize) < count));
pads.push([input.f64()?, input.f64()?, input.f64()?, input.f64()?]);
let keyed = input.u32()? != 0;
keys.push(if keyed { Some(input.key()?) } else { None });
}
let links = input.u32()?;
let links = (0..links).map(|_| Some([input.u32()?, input.u32()?])).collect::<Option<Vec<_>>>()?;
if links.iter().flatten().any(|&v| v as usize >= count) {
return None;
}
let graph = Graph { sizes: &sizes, links: &links, pinned: &pinned, groups: &groups, pads: &pads };
let centers = match options {
Layout::Stress(s) => stress(&graph, s.gap),
Layout::Layered(l) => layered(&graph, &l),
Layout::None => return None,
};
let placed: Vec<(Key, [f64; 2])> =
keys.into_iter().zip(centers).filter_map(|(key, center)| Some((key?, center))).collect();
let (keys, centers): (Vec<Key>, Vec<[f64; 2]>) = placed.into_iter().unzip();
Some(keyed(&keys, centers, |table, centers| {
let (x, y): (Vec<f64>, Vec<f64>) = centers.into_iter().map(|c| (c[0], c[1])).unzip();
table.field("x", x).field("y", y)
}))
}
fn run_routes(input: &mut Reader<'_>) -> Option<Patch> {
let spacing = input.f64()?;
let mut boxes = || {
let count = input.u32()?;
(0..count).map(|_| Some([input.f64()?, input.f64()?, input.f64()?, input.f64()?])).collect::<Option<Vec<_>>>()
};
let (obstacles, groups) = (boxes()?, boxes()?);
let count = input.u32()?;
let fixed = (0..count)
.map(|_| {
let points = input.u32()?;
(0..points).map(|_| Some([input.f64()?, input.f64()?])).collect::<Option<Vec<_>>>()
})
.collect::<Option<Vec<_>>>()?;
let count = input.u32()?;
let (mut nets, mut keys) = (Vec::new(), Vec::new());
for _ in 0..count {
keys.push(input.key()?);
let terminals = input.u32()?;
let terminals = (0..terminals)
.map(|_| {
let at = [input.f64()?, input.f64()?];
let side = Side::from_code(input.u32()?);
let owner = Some(input.u32()?).filter(|&o| (o as usize) < obstacles.len());
Some(Terminal { at, side, owner })
})
.collect::<Option<Vec<_>>>()?;
nets.push(Net { terminals });
}
let routes = route(&obstacles, &groups, &nets, &fixed, spacing);
Some(keyed(&keys, routes, |table, routes| table.field("route", routes)))
}
fn run_shapes(input: &mut Reader<'_>) -> Option<Patch> {
let shaping = Shaping::from_code(input.u32()?)?;
let Op::Set(rows) = Patch::from_bytes(input.rest()).ok()?.ops.into_iter().next()? else { return None };
let Some(Field::Values(column, _)) = rows.field_named("geometry") else { return None };
let depth = column.kind().depth()?;
let mut shapes = GeometryBuilder::new(shaping.depth(depth));
for row in 0..rows.len() {
match column.get(row) {
Some(value) => {
let shaped = shaping.shape(&value);
shapes.push_value(shaped.as_ref().unwrap_or(&value));
}
None => shapes.push_empty(),
}
}
let shapes = Values::of_geometry(shapes.finish());
let mut answer = rows.clone();
answer.fields.clear();
answer.insert("shape".into(), Field::Values(crate::data::Column::new(&shapes), Version::next()));
Some(Patch::new().set(answer))
}
fn run_tile(input: &mut Reader<'_>) -> Option<Patch> {
let key = TileKey { z: u8::try_from(input.u32()?).ok()?, x: input.u32()?, y: input.u32()? };
let count = input.u32()?;
let tables = (0..count)
.map(|_| {
let layer = input.text()?;
let kind = match input.u32()? {
0 => Geometry::Points,
1 => Geometry::Lines,
_ => Geometry::Polygons,
};
Some(Decoded { layer, kind, table: input.text()?.into() })
})
.collect::<Option<Vec<_>>>()?;
decode(input.rest(), key, &tables)
}
fn run_image(input: &mut Reader<'_>) -> Option<Vec<u8>> {
let format = match input.u32()? {
0 => crate::ImageFormat::Png,
1 => crate::ImageFormat::WebP,
jpeg => crate::ImageFormat::Jpeg { quality: (jpeg & 0xff) as u8 },
};
let size = [input.u32()?, input.u32()?];
crate::Image::new(size, input.rest()).ok()?.encode(format).ok()
}
fn run_geojson(input: &mut Reader<'_>) -> Option<Patch> {
let name = input.text()?;
crate::geo::geojson(name, input.rest()).ok().map(|data| data.to_patch())
}
fn run_pyramid(input: &mut Reader<'_>) -> Option<Patch> {
let domain = match [input.f64()?, input.f64()?, input.f64()?, input.f64()?] {
[0.0, ..] => Domain::Earth,
[_, x, y, size] => Domain::Plane { min: [x, y], size },
};
let count = input.u32()?;
let mut tables = Vec::with_capacity(count as usize);
for _ in 0..count {
let rows = input.u32()?;
let flags = input.u32()?;
let n = rows as usize;
let has = |bit: u32| flags & 1 << bit != 0;
let places = match flags >> 2 & 3 {
1 => Places::Points(input.words(2 * n)?.as_chunks::<2>().0.iter().map(|w| w.map(f32::from_bits)).collect()),
2 => Places::Boxes(input.words(4 * n)?.as_chunks::<4>().0.iter().map(|w| w.map(f32::from_bits)).collect()),
_ => Places::None,
};
let priority = if has(4) { input.words(n)?.into_iter().map(f32::from_bits).collect() } else { Arc::from([]) };
let ends = if has(5) {
let target = input.u32()?;
let count = input.u32()?;
Some((target, (0..count).map(|_| input.words(n).map(Arc::from)).collect::<Option<Vec<_>>>()?))
} else {
None
};
tables.push(Placing { rows, leveled: has(0), answered: has(1), places, priority, ends });
}
Some(crate::derive::pyramid::answer(&Leveling { domain, tables }))
}
fn run_lanes(input: &mut Reader<'_>) -> Option<Patch> {
let rows = input.u32()? as usize;
let (from, to) = (input.words(rows)?, input.words(rows)?);
Some(crate::derive::lanes_answer(&from, &to))
}
struct Writer(Vec<u8>);
impl Writer {
fn new(kind: Kind) -> Writer {
let mut out = Writer(Vec::new());
out.u32(kind as u32);
out
}
fn u32(&mut self, v: u32) {
self.0.extend(v.to_le_bytes());
}
fn f64(&mut self, v: f64) {
self.0.extend(v.to_le_bytes());
}
fn words(&mut self, words: &[u32]) {
if cfg!(target_endian = "little") {
self.0.extend_from_slice(bytemuck::cast_slice(words));
} else {
self.0.extend(words.iter().flat_map(|w| w.to_le_bytes()));
}
}
fn f64s(&mut self, vs: &[f64]) {
for &v in vs {
self.f64(v);
}
}
fn text(&mut self, text: &str) {
self.u32(text.len() as u32);
self.0.extend(text.as_bytes());
}
fn key(&mut self, (table, id): &Key) {
match id {
Id::Number(n) => {
self.u32(1);
self.0.extend(n.to_le_bytes());
}
Id::Text(t) => {
self.u32(2);
self.text(t);
}
}
self.text(table);
}
}
struct Reader<'a> {
bytes: &'a [u8],
}
impl<'a> Reader<'a> {
fn new(bytes: &'a [u8]) -> Reader<'a> {
Reader { bytes }
}
fn rest(&mut self) -> &'a [u8] {
std::mem::take(&mut self.bytes)
}
fn take<const N: usize>(&mut self) -> Option<[u8; N]> {
let (head, rest) = self.bytes.split_first_chunk::<N>()?;
self.bytes = rest;
Some(*head)
}
fn u32(&mut self) -> Option<u32> {
self.take().map(u32::from_le_bytes)
}
fn f64(&mut self) -> Option<f64> {
self.take().map(f64::from_le_bytes)
}
fn words(&mut self, n: usize) -> Option<Vec<u32>> {
let (head, rest) = self.bytes.split_at_checked(n.checked_mul(4)?)?;
self.bytes = rest;
Some(head.as_chunks::<4>().0.iter().map(|&w| u32::from_le_bytes(w)).collect())
}
fn text(&mut self) -> Option<String> {
let len = self.u32()? as usize;
let (head, rest) = self.bytes.split_at_checked(len)?;
self.bytes = rest;
String::from_utf8(head.to_vec()).ok()
}
fn key(&mut self) -> Option<Key> {
let id = match self.u32()? {
1 => Id::Number(u64::from_le_bytes(self.take()?)),
2 => Id::from(self.text()?),
_ => return None,
};
Some((self.text()?.into(), id))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::Value;
fn key(table: &str, id: u64) -> Key {
(table.into(), Id::Number(id))
}
fn answered(answer: &[u8], table: &str, id: u64, field: &str) -> Option<Value> {
let mut data = crate::Data::new("d");
data.apply(Patch::from_bytes(answer).ok()?).ok()?;
data.get(&crate::Row::new("d", table, id), field)
}
#[test]
fn layout_jobs_answer_the_rows_they_place() {
let sizes = [[10.0, 10.0]; 3];
let pinned = [None, Some([5.0, 5.0]), None];
let keys = [Some(key("V", 7)), None, Some(key("V", 9))];
let placement = Placement {
sizes: &sizes,
pinned: &pinned,
groups: &[None; 3],
pads: &[[0.0; 4]; 3],
links: &[[0, 1], [1, 2]],
keys: &keys,
};
let answer = Job::from_bytes(Job::layout(&Layout::Layered(Layered::default()), &placement).into_bytes()).run();
let ranked = layered(
&Graph {
sizes: &sizes,
links: &[[0, 1], [1, 2]],
pinned: &pinned,
groups: &[None; 3],
pads: &[[0.0; 4]; 3],
},
&Layered::default(),
);
assert_eq!(answered(&answer, "V", 9, "x"), Some(Value::Number(ranked[2][0])));
assert_eq!(answered(&answer, "V", 7, "y"), Some(Value::Number(ranked[0][1])));
let stress = Job::layout(&Layout::Stress(crate::Stress::default()), &placement).run();
assert!(matches!(answered(&stress, "V", 7, "x"), Some(Value::Number(_))), "stress answers alike");
}
#[test]
fn route_jobs_answer_routed_wires() {
let obstacles = [[0.0, 0.0, 10.0, 10.0], [40.0, 0.0, 50.0, 10.0]];
let terminal = |x: f64, side, owner| Terminal { at: [x, 5.0], side: Some(side), owner: Some(owner) };
let nets = [Net { terminals: vec![terminal(10.0, Side::Right, 0), terminal(40.0, Side::Left, 1)] }];
let wiring = Wiring {
obstacles: &obstacles,
groups: &[],
nets: &nets,
keys: &[key("Wire", 3)],
fixed: &[],
spacing: 2.0,
};
let answer = Job::routes(&wiring).run();
let want = route(&obstacles, &[], &nets, &[], 2.0).swap_remove(0);
assert_eq!(answered(&answer, "Wire", 3, "route"), Some(Value::Parts(want)));
}
#[test]
fn bytes_that_are_no_job_answer_nothing() {
assert_eq!(Job::from_bytes(vec![1, 0, 0, 0, 9]).run(), Vec::<u8>::new());
assert_eq!(Job::from_bytes(Vec::new()).run(), Vec::<u8>::new());
}
}