mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/data/schematic.rs')
-rw-r--r--src/data/schematic.rs2571
1 files changed, 1286 insertions, 1285 deletions
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 20b7f60..6331606 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -1,1414 +1,1406 @@
use std::any::Any;
-use std::collections::HashMap;
use std::collections::hash_map::Entry;
+use std::collections::HashMap;
use std::error::Error;
use std::fmt::{self, Write};
use std::iter::FusedIterator;
use std::slice::Iter;
-use flate2::{Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, FlushDecompress, Status};
+use flate2::{
+ Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress,
+ FlushDecompress, Status,
+};
use crate::block::{self, Block, BlockRegistry, Rotation};
-use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::data::base64;
use crate::data::dynamic::{self, DynData, DynSerializer};
+use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::item::storage::Storage as ItemStorage;
use crate::registry::RegistryEntry;
pub const MAX_DIMENSION: u16 = 128;
pub const MAX_BLOCKS: u32 = 128 * 128;
-pub struct Placement<'l>
-{
- pos: GridPos,
- block: &'l Block,
- state: Option<Box<dyn Any>>,
- rot: Rotation,
+pub struct Placement<'l> {
+ pos: GridPos,
+ block: &'l Block,
+ state: Option<Box<dyn Any>>,
+ rot: Rotation,
}
-impl<'l> Placement<'l>
-{
- pub fn get_pos(&self) -> GridPos
- {
- self.pos
- }
-
- pub fn get_block(&self) -> &'l Block
- {
- self.block
- }
-
- pub fn get_state(&self) -> Option<&dyn Any>
- {
- match self.state
- {
- None => None,
- Some(ref b) => Some(b.as_ref()),
- }
- }
-
- pub fn get_state_mut(&mut self) -> Option<&mut dyn Any>
- {
- match self.state
- {
- None => None,
- Some(ref mut b) => Some(b.as_mut()),
- }
- }
-
- pub fn set_state(&mut self, data: DynData) -> Result<Option<Box<dyn Any>>, block::DeserializeError>
- {
- let state = self.block.deserialize_state(data)?;
- Ok(std::mem::replace(&mut self.state, state))
- }
-
- pub fn get_rotation(&self) -> Rotation
- {
- self.rot
- }
-
- pub fn set_rotation(&mut self, rot: Rotation) -> Rotation
- {
- std::mem::replace(&mut self.rot, rot)
- }
+impl<'l> Placement<'l> {
+ pub fn get_pos(&self) -> GridPos {
+ self.pos
+ }
+
+ pub fn get_block(&self) -> &'l Block {
+ self.block
+ }
+
+ pub fn get_state(&self) -> Option<&dyn Any> {
+ match self.state {
+ None => None,
+ Some(ref b) => Some(b.as_ref()),
+ }
+ }
+
+ pub fn get_state_mut(&mut self) -> Option<&mut dyn Any> {
+ match self.state {
+ None => None,
+ Some(ref mut b) => Some(b.as_mut()),
+ }
+ }
+
+ pub fn set_state(
+ &mut self,
+ data: DynData,
+ ) -> Result<Option<Box<dyn Any>>, block::DeserializeError> {
+ let state = self.block.deserialize_state(data)?;
+ Ok(std::mem::replace(&mut self.state, state))
+ }
+
+ pub fn get_rotation(&self) -> Rotation {
+ self.rot
+ }
+
+ pub fn set_rotation(&mut self, rot: Rotation) -> Rotation {
+ std::mem::replace(&mut self.rot, rot)
+ }
}
// manual impl because trait objects cannot be cloned
-impl<'l> Clone for Placement<'l>
-{
- fn clone(&self) -> Self
- {
- Self
- {
- pos: self.pos,
- block: self.block,
- state: match self.state
- {
- None => None,
- Some(ref s) => Some(self.block.clone_state(s)),
- },
- rot: self.rot,
- }
- }
+impl<'l> Clone for Placement<'l> {
+ fn clone(&self) -> Self {
+ Self {
+ pos: self.pos,
+ block: self.block,
+ state: match self.state {
+ None => None,
+ Some(ref s) => Some(self.block.clone_state(s)),
+ },
+ rot: self.rot,
+ }
+ }
}
#[derive(Clone)]
-pub struct Schematic<'l>
-{
- width: u16,
- height: u16,
- tags: HashMap<String, String>,
- blocks: Vec<Placement<'l>>,
- lookup: Vec<Option<usize>>,
+pub struct Schematic<'l> {
+ width: u16,
+ height: u16,
+ tags: HashMap<String, String>,
+ blocks: Vec<Placement<'l>>,
+ lookup: Vec<Option<usize>>,
}
-impl<'l> Schematic<'l>
-{
- pub fn new(width: u16, height: u16) -> Self
- {
- match Self::try_new(width, height)
- {
- Ok(s) => s,
- Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"),
- Err(NewError::Height(h)) => panic!("invalid schematic height ({h})"),
- }
- }
-
- pub fn try_new(width: u16, height: u16) -> Result<Self, NewError>
- {
- if width > MAX_DIMENSION
- {
- return Err(NewError::Width(width));
- }
- if height > MAX_DIMENSION
- {
- return Err(NewError::Height(height));
- }
- let mut tags = HashMap::<String, String>::new();
- tags.insert("name".to_string(), String::new());
- tags.insert("description".to_string(), String::new());
- tags.insert("labels".to_string(), "[]".to_string());
- Ok(Self{width, height, tags, blocks: Vec::new(), lookup: Vec::new()})
- }
-
- pub fn get_width(&self) -> u16
- {
- self.width
- }
-
- pub fn get_height(&self) -> u16
- {
- self.height
- }
-
- pub fn get_tags(&self) -> &HashMap<String, String>
- {
- &self.tags
- }
-
- pub fn get_tags_mut(&mut self) -> &mut HashMap<String, String>
- {
- &mut self.tags
- }
-
- pub fn is_empty(&self) -> bool
- {
- self.blocks.is_empty()
- }
-
- pub fn get_block_count(&self) -> usize
- {
- self.blocks.len()
- }
-
- pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool
- {
- if self.blocks.len() == 0 {return true;}
- if x >= self.width || y >= self.height || w == 0 || h == 0 {return true;}
- if w > 1 || h > 1
- {
- let stride = self.width as usize;
- let x_end = if self.width - x > w {x + w} else {self.width} as usize;
- let y_end = if self.height - y > h {y + h} else {self.height} as usize;
- let x = x as usize;
- let y = y as usize;
- for cy in y..y_end
- {
- for cx in x..x_end
- {
- if self.lookup[cx + cy * stride].is_some() {return false;}
- }
- }
- true
- }
- else {self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none()}
- }
-
- pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() == 0 {return Ok(None);}
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(&self.blocks[idx])),
- }
- }
-
- pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() == 0 {return Ok(None);}
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(&mut self.blocks[idx])),
- }
- }
-
- fn swap_remove(&mut self, idx: usize) -> Placement<'l>
- {
- // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do
- let prev = self.blocks.swap_remove(idx);
- self.fill_lookup(prev.pos.0 as usize, prev.pos.1 as usize, prev.block.get_size() as usize, None);
- if idx < self.blocks.len()
- {
- // fix the swapped block's lookup entries
- let swapped = &self.blocks[idx];
- self.fill_lookup(swapped.pos.0 as usize, swapped.pos.1 as usize, swapped.block.get_size() as usize, Some(idx));
- }
- prev
- }
-
- fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>)
- {
- if self.lookup.len() == 0
- {
- self.lookup.resize((self.width as usize) * (self.height as usize), None);
- }
- if sz > 1
- {
- let off = ((sz - 1) / 2) as usize;
- let (x0, y0) = (x - off, y - off);
- for dy in 0..sz
- {
- for dx in 0..sz
- {
- self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val;
- }
- }
- }
- else {self.lookup[x + y * (self.width as usize)] = val;}
- }
-
- pub fn set(&mut self, x: u16, y: u16, block: &'l Block, data: DynData, rot: Rotation) -> Result<&Placement<'l>, PlaceError>
- {
- let sz = block.get_size() as u16;
- let off = (sz - 1) / 2;
- if x < off || y < off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.width - x < sz - off || self.height - y < sz - off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.is_region_empty(x - off, y - off, sz, sz)
- {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx));
- Ok(&self.blocks[idx])
- }
- else {Err(PlaceError::Overlap{x, y})}
- }
-
- pub fn replace(&mut self, x: u16, y: u16, block: &'l Block, data: DynData, rot: Rotation, collect: bool)
- -> Result<Option<Vec<Placement<'l>>>, PlaceError>
- {
- let sz = block.get_size() as u16;
- let off = (sz - 1) / 2;
- if x < off || y < off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if self.width - x < sz - off || self.height - y < sz - off
- {
- return Err(PlaceError::Bounds{x, y, sz: block.get_size(), w: self.width, h: self.height});
- }
- if sz > 1
- {
- let mut result = if collect {Some(Vec::new())} else {None};
- // remove all blocks in the region
- for dy in 0..(sz as usize)
- {
- for dx in 0..(sz as usize)
- {
- if let Some(idx) = self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)]
- {
- let prev = self.swap_remove(idx);
- if let Some(ref mut v) = result {v.push(prev);}
- }
- }
- }
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(result)
- }
- else
- {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None =>
- {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement{pos: GridPos(x, y), block, state, rot});
- self.lookup[pos] = Some(idx);
- Ok(if collect {Some(Vec::new())} else {None})
- },
- Some(idx) =>
- {
- let state = block.deserialize_state(data)?;
- let prev = std::mem::replace(&mut self.blocks[idx], Placement{pos: GridPos(x, y), block, state, rot});
- self.fill_lookup(prev.pos.0 as usize, prev.pos.1 as usize, prev.block.get_size() as usize, None);
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(if collect {Some(vec![prev])} else {None})
- }
- }
- }
- }
-
- pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError>
- {
- if x >= self.width || y >= self.height
- {
- return Err(PosError{x, y, w: self.width, h: self.height});
- }
- if self.blocks.len() > 0
- {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos]
- {
- None => Ok(None),
- Some(idx) => Ok(Some(self.swap_remove(idx))),
- }
- }
- else {Ok(None)}
- }
-
- fn rebuild_lookup(&mut self)
- {
- self.lookup.clear();
- if self.blocks.len() > 0
- {
- self.lookup.resize((self.width as usize) * (self.height as usize), None);
- for (i, curr) in self.blocks.iter().enumerate()
- {
- let sz = curr.block.get_size() as usize;
- let x = curr.pos.0 as usize - (sz - 1) / 2;
- let y = curr.pos.1 as usize - (sz - 1) / 2;
- if sz > 1
- {
- for dy in 0..sz
- {
- for dx in 0..sz
- {
- self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i);
- }
- }
- }
- else {self.lookup[x + y * (self.width as usize)] = Some(i);}
- }
- }
- }
-
- pub fn mirror(&mut self, horizontally: bool, vertically: bool)
- {
- if !self.blocks.is_empty() && (horizontally || vertically)
- {
- for curr in self.blocks.iter_mut()
- {
- // because position is the bottom left of the center (which changes during mirroring)
- let shift = (curr.block.get_size() as u16 - 1) % 2;
- if horizontally {curr.pos.0 = self.width - 1 - curr.pos.0 - shift;}
- if vertically {curr.pos.1 = self.height - 1 - curr.pos.1 - shift;}
- if !curr.block.is_symmetric() {curr.rot.mirror(horizontally, vertically);}
- if let Some(ref mut state) = curr.state
- {
- curr.block.mirror_state(state.as_mut(), horizontally, vertically);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- pub fn rotate(&mut self, clockwise: bool)
- {
- let w = self.width;
- let h = self.height;
- self.width = h;
- self.height = w;
- if !self.blocks.is_empty()
- {
- for curr in self.blocks.iter_mut()
- {
- let x = curr.pos.0;
- let y = curr.pos.1;
- // because position is the bottom left of the center (which changes during rotation)
- let shift = (curr.block.get_size() as u16 - 1) % 2;
- if clockwise
- {
- curr.pos.0 = y;
- curr.pos.1 = w - 1 - x - shift;
- }
- else
- {
- curr.pos.0 = h - 1 - y - shift;
- curr.pos.1 = x;
- }
- if !curr.block.is_symmetric() {curr.rot.rotate(clockwise);}
- if let Some(ref mut state) = curr.state
- {
- curr.block.rotate_state(state.as_mut(), clockwise);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError>
- {
- if w > MAX_DIMENSION
- {
- return Err(ResizeError::TargetWidth(w));
- }
- if h > MAX_DIMENSION
- {
- return Err(ResizeError::TargetHeight(h));
- }
- if dx <= -(w as i16) || dx >= self.width as i16
- {
- return Err(ResizeError::XOffset{dx, old_w: self.width, new_w: w});
- }
- if dy <= -(h as i16) || dy >= self.height as i16
- {
- return Err(ResizeError::YOffset{dy, old_h: self.height, new_h: h});
- }
- // check that all blocks fit into the new bounds
- let mut right = 0u16;
- let mut top = 0u16;
- let mut left = 0u16;
- let mut bottom = 0u16;
- let right_bound = dx + w as i16 - 1;
- let top_bound = dy + h as i16 - 1;
- let left_bound = dx;
- let bottom_bound = dy;
- for Placement{pos, block, ..} in self.blocks.iter()
- {
- let sz = block.get_size() as u16;
- let (x0, y0, x1, y1) = (pos.0 - (sz - 1) / 2, pos.1 - (sz - 1) / 2, pos.0 + sz / 2, pos.1 + sz / 2);
- if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {right = x1 - right_bound as u16;}
- if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {top = y1 - top_bound as u16;}
- if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {left = left_bound as u16 - x0;}
- if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {bottom = bottom_bound as u16 - y0;}
- }
- if left > 0 || top > 0 || left > 0 || bottom > 0
- {
- return Err(ResizeError::Truncated{right, top, left, bottom});
- }
- self.width = w;
- self.height = h;
- for Placement{pos, ..} in self.blocks.iter_mut()
- {
- pos.0 = (pos.0 as i16 + dx) as u16;
- pos.1 = (pos.1 as i16 + dy) as u16;
- }
- Ok(())
- }
-
- pub fn rotate_180(&mut self)
- {
- self.mirror(true, true);
- }
-
- pub fn pos_iter(&self) -> PosIter
- {
- PosIter{x: 0, y: 0, w: self.width, h: self.height}
- }
-
- pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>>
- {
- self.blocks.iter()
- }
-
- pub fn compute_total_cost(&self) -> (ItemStorage, bool)
- {
- let mut cost = ItemStorage::new();
- let mut sandbox = false;
- for &Placement{block, ..} in self.blocks.iter()
- {
- if let Some(curr) = block.get_build_cost()
- {
- cost.add_all(curr, u32::MAX);
- }
- else {sandbox = true;}
- }
- (cost, sandbox)
- }
+impl<'l> Schematic<'l> {
+ pub fn new(width: u16, height: u16) -> Self {
+ match Self::try_new(width, height) {
+ Ok(s) => s,
+ Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"),
+ Err(NewError::Height(h)) => panic!("invalid schematic height ({h})"),
+ }
+ }
+
+ pub fn try_new(width: u16, height: u16) -> Result<Self, NewError> {
+ if width > MAX_DIMENSION {
+ return Err(NewError::Width(width));
+ }
+ if height > MAX_DIMENSION {
+ return Err(NewError::Height(height));
+ }
+ let mut tags = HashMap::<String, String>::new();
+ tags.insert("name".to_string(), String::new());
+ tags.insert("description".to_string(), String::new());
+ tags.insert("labels".to_string(), "[]".to_string());
+ Ok(Self {
+ width,
+ height,
+ tags,
+ blocks: Vec::new(),
+ lookup: Vec::new(),
+ })
+ }
+
+ pub fn get_width(&self) -> u16 {
+ self.width
+ }
+
+ pub fn get_height(&self) -> u16 {
+ self.height
+ }
+
+ pub fn get_tags(&self) -> &HashMap<String, String> {
+ &self.tags
+ }
+
+ pub fn get_tags_mut(&mut self) -> &mut HashMap<String, String> {
+ &mut self.tags
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.blocks.is_empty()
+ }
+
+ pub fn get_block_count(&self) -> usize {
+ self.blocks.len()
+ }
+
+ pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool {
+ if self.blocks.len() == 0 {
+ return true;
+ }
+ if x >= self.width || y >= self.height || w == 0 || h == 0 {
+ return true;
+ }
+ if w > 1 || h > 1 {
+ let stride = self.width as usize;
+ let x_end = if self.width - x > w {
+ x + w
+ } else {
+ self.width
+ } as usize;
+ let y_end = if self.height - y > h {
+ y + h
+ } else {
+ self.height
+ } as usize;
+ let x = x as usize;
+ let y = y as usize;
+ for cy in y..y_end {
+ for cx in x..x_end {
+ if self.lookup[cx + cy * stride].is_some() {
+ return false;
+ }
+ }
+ }
+ true
+ } else {
+ self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none()
+ }
+ }
+
+ pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() == 0 {
+ return Ok(None);
+ }
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(&self.blocks[idx])),
+ }
+ }
+
+ pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() == 0 {
+ return Ok(None);
+ }
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(&mut self.blocks[idx])),
+ }
+ }
+
+ fn swap_remove(&mut self, idx: usize) -> Placement<'l> {
+ // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do
+ let prev = self.blocks.swap_remove(idx);
+ self.fill_lookup(
+ prev.pos.0 as usize,
+ prev.pos.1 as usize,
+ prev.block.get_size() as usize,
+ None,
+ );
+ if idx < self.blocks.len() {
+ // fix the swapped block's lookup entries
+ let swapped = &self.blocks[idx];
+ self.fill_lookup(
+ swapped.pos.0 as usize,
+ swapped.pos.1 as usize,
+ swapped.block.get_size() as usize,
+ Some(idx),
+ );
+ }
+ prev
+ }
+
+ fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>) {
+ if self.lookup.len() == 0 {
+ self.lookup
+ .resize((self.width as usize) * (self.height as usize), None);
+ }
+ if sz > 1 {
+ let off = ((sz - 1) / 2) as usize;
+ let (x0, y0) = (x - off, y - off);
+ for dy in 0..sz {
+ for dx in 0..sz {
+ self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val;
+ }
+ }
+ } else {
+ self.lookup[x + y * (self.width as usize)] = val;
+ }
+ }
+
+ pub fn set(
+ &mut self,
+ x: u16,
+ y: u16,
+ block: &'l Block,
+ data: DynData,
+ rot: Rotation,
+ ) -> Result<&Placement<'l>, PlaceError> {
+ let sz = block.get_size() as u16;
+ let off = (sz - 1) / 2;
+ if x < off || y < off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.width - x < sz - off || self.height - y < sz - off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.is_region_empty(x - off, y - off, sz, sz) {
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx));
+ Ok(&self.blocks[idx])
+ } else {
+ Err(PlaceError::Overlap { x, y })
+ }
+ }
+
+ pub fn replace(
+ &mut self,
+ x: u16,
+ y: u16,
+ block: &'l Block,
+ data: DynData,
+ rot: Rotation,
+ collect: bool,
+ ) -> Result<Option<Vec<Placement<'l>>>, PlaceError> {
+ let sz = block.get_size() as u16;
+ let off = (sz - 1) / 2;
+ if x < off || y < off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.width - x < sz - off || self.height - y < sz - off {
+ return Err(PlaceError::Bounds {
+ x,
+ y,
+ sz: block.get_size(),
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if sz > 1 {
+ let mut result = if collect { Some(Vec::new()) } else { None };
+ // remove all blocks in the region
+ for dy in 0..(sz as usize) {
+ for dx in 0..(sz as usize) {
+ if let Some(idx) =
+ self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)]
+ {
+ let prev = self.swap_remove(idx);
+ if let Some(ref mut v) = result {
+ v.push(prev);
+ }
+ }
+ }
+ }
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
+ Ok(result)
+ } else {
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => {
+ let idx = self.blocks.len();
+ let state = block.deserialize_state(data)?;
+ self.blocks.push(Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ });
+ self.lookup[pos] = Some(idx);
+ Ok(if collect { Some(Vec::new()) } else { None })
+ }
+ Some(idx) => {
+ let state = block.deserialize_state(data)?;
+ let prev = std::mem::replace(
+ &mut self.blocks[idx],
+ Placement {
+ pos: GridPos(x, y),
+ block,
+ state,
+ rot,
+ },
+ );
+ self.fill_lookup(
+ prev.pos.0 as usize,
+ prev.pos.1 as usize,
+ prev.block.get_size() as usize,
+ None,
+ );
+ self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
+ Ok(if collect { Some(vec![prev]) } else { None })
+ }
+ }
+ }
+ }
+
+ pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError> {
+ if x >= self.width || y >= self.height {
+ return Err(PosError {
+ x,
+ y,
+ w: self.width,
+ h: self.height,
+ });
+ }
+ if self.blocks.len() > 0 {
+ let pos = (x as usize) + (y as usize) * (self.width as usize);
+ match self.lookup[pos] {
+ None => Ok(None),
+ Some(idx) => Ok(Some(self.swap_remove(idx))),
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn rebuild_lookup(&mut self) {
+ self.lookup.clear();
+ if self.blocks.len() > 0 {
+ self.lookup
+ .resize((self.width as usize) * (self.height as usize), None);
+ for (i, curr) in self.blocks.iter().enumerate() {
+ let sz = curr.block.get_size() as usize;
+ let x = curr.pos.0 as usize - (sz - 1) / 2;
+ let y = curr.pos.1 as usize - (sz - 1) / 2;
+ if sz > 1 {
+ for dy in 0..sz {
+ for dx in 0..sz {
+ self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i);
+ }
+ }
+ } else {
+ self.lookup[x + y * (self.width as usize)] = Some(i);
+ }
+ }
+ }
+ }
+
+ pub fn mirror(&mut self, horizontally: bool, vertically: bool) {
+ if !self.blocks.is_empty() && (horizontally || vertically) {
+ for curr in self.blocks.iter_mut() {
+ // because position is the bottom left of the center (which changes during mirroring)
+ let shift = (curr.block.get_size() as u16 - 1) % 2;
+ if horizontally {
+ curr.pos.0 = self.width - 1 - curr.pos.0 - shift;
+ }
+ if vertically {
+ curr.pos.1 = self.height - 1 - curr.pos.1 - shift;
+ }
+ if !curr.block.is_symmetric() {
+ curr.rot.mirror(horizontally, vertically);
+ }
+ if let Some(ref mut state) = curr.state {
+ curr.block
+ .mirror_state(state.as_mut(), horizontally, vertically);
+ }
+ }
+ self.rebuild_lookup();
+ }
+ }
+
+ pub fn rotate(&mut self, clockwise: bool) {
+ let w = self.width;
+ let h = self.height;
+ self.width = h;
+ self.height = w;
+ if !self.blocks.is_empty() {
+ for curr in self.blocks.iter_mut() {
+ let x = curr.pos.0;
+ let y = curr.pos.1;
+ // because position is the bottom left of the center (which changes during rotation)
+ let shift = (curr.block.get_size() as u16 - 1) % 2;
+ if clockwise {
+ curr.pos.0 = y;
+ curr.pos.1 = w - 1 - x - shift;
+ } else {
+ curr.pos.0 = h - 1 - y - shift;
+ curr.pos.1 = x;
+ }
+ if !curr.block.is_symmetric() {
+ curr.rot.rotate(clockwise);
+ }
+ if let Some(ref mut state) = curr.state {
+ curr.block.rotate_state(state.as_mut(), clockwise);
+ }
+ }
+ self.rebuild_lookup();
+ }
+ }
+
+ pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError> {
+ if w > MAX_DIMENSION {
+ return Err(ResizeError::TargetWidth(w));
+ }
+ if h > MAX_DIMENSION {
+ return Err(ResizeError::TargetHeight(h));
+ }
+ if dx <= -(w as i16) || dx >= self.width as i16 {
+ return Err(ResizeError::XOffset {
+ dx,
+ old_w: self.width,
+ new_w: w,
+ });
+ }
+ if dy <= -(h as i16) || dy >= self.height as i16 {
+ return Err(ResizeError::YOffset {
+ dy,
+ old_h: self.height,
+ new_h: h,
+ });
+ }
+ // check that all blocks fit into the new bounds
+ let mut right = 0u16;
+ let mut top = 0u16;
+ let mut left = 0u16;
+ let mut bottom = 0u16;
+ let right_bound = dx + w as i16 - 1;
+ let top_bound = dy + h as i16 - 1;
+ let left_bound = dx;
+ let bottom_bound = dy;
+ for Placement { pos, block, .. } in self.blocks.iter() {
+ let sz = block.get_size() as u16;
+ let (x0, y0, x1, y1) = (
+ pos.0 - (sz - 1) / 2,
+ pos.1 - (sz - 1) / 2,
+ pos.0 + sz / 2,
+ pos.1 + sz / 2,
+ );
+ if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {
+ right = x1 - right_bound as u16;
+ }
+ if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {
+ top = y1 - top_bound as u16;
+ }
+ if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {
+ left = left_bound as u16 - x0;
+ }
+ if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {
+ bottom = bottom_bound as u16 - y0;
+ }
+ }
+ if left > 0 || top > 0 || left > 0 || bottom > 0 {
+ return Err(ResizeError::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ });
+ }
+ self.width = w;
+ self.height = h;
+ for Placement { pos, .. } in self.blocks.iter_mut() {
+ pos.0 = (pos.0 as i16 + dx) as u16;
+ pos.1 = (pos.1 as i16 + dy) as u16;
+ }
+ Ok(())
+ }
+
+ pub fn rotate_180(&mut self) {
+ self.mirror(true, true);
+ }
+
+ pub fn pos_iter(&self) -> PosIter {
+ PosIter {
+ x: 0,
+ y: 0,
+ w: self.width,
+ h: self.height,
+ }
+ }
+
+ pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>> {
+ self.blocks.iter()
+ }
+
+ pub fn compute_total_cost(&self) -> (ItemStorage, bool) {
+ let mut cost = ItemStorage::new();
+ let mut sandbox = false;
+ for &Placement { block, .. } in self.blocks.iter() {
+ if let Some(curr) = block.get_build_cost() {
+ cost.add_all(curr, u32::MAX);
+ } else {
+ sandbox = true;
+ }
+ }
+ (cost, sandbox)
+ }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum NewError
-{
- Width(u16),
- Height(u16),
+pub enum NewError {
+ Width(u16),
+ Height(u16),
}
-impl fmt::Display for NewError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Width(w) => write!(f, "invalid schematic width ({w})"),
- Self::Height(h) => write!(f, "invalid schematic height ({h})"),
- }
- }
+impl fmt::Display for NewError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Width(w) => write!(f, "invalid schematic width ({w})"),
+ Self::Height(h) => write!(f, "invalid schematic height ({h})"),
+ }
+ }
}
impl Error for NewError {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub struct PosError
-{
- pub x: u16,
- pub y: u16,
- pub w: u16,
- pub h: u16,
+pub struct PosError {
+ pub x: u16,
+ pub y: u16,
+ pub w: u16,
+ pub h: u16,
}
-impl fmt::Display for PosError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- write!(f, "position {x} / {y} out of bounds {w} / {h}", x = self.x, y = self.y, w = self.w, h = self.h)
- }
+impl fmt::Display for PosError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "position {x} / {y} out of bounds {w} / {h}",
+ x = self.x,
+ y = self.y,
+ w = self.w,
+ h = self.h
+ )
+ }
}
impl Error for PosError {}
#[derive(Debug)]
-pub enum PlaceError
-{
- Bounds{x: u16, y: u16, sz: u8, w: u16, h: u16},
- Overlap{x: u16, y: u16},
- Deserialize(block::DeserializeError),
+pub enum PlaceError {
+ Bounds {
+ x: u16,
+ y: u16,
+ sz: u8,
+ w: u16,
+ h: u16,
+ },
+ Overlap {
+ x: u16,
+ y: u16,
+ },
+ Deserialize(block::DeserializeError),
}
-impl From<block::DeserializeError> for PlaceError
-{
- fn from(value: block::DeserializeError) -> Self
- {
- PlaceError::Deserialize(value)
- }
+impl From<block::DeserializeError> for PlaceError {
+ fn from(value: block::DeserializeError) -> Self {
+ PlaceError::Deserialize(value)
+ }
}
-impl fmt::Display for PlaceError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Bounds{x, y, sz, w, h} => write!(f, "invalid block placement {x} / {y} (size {sz}) within {w} / {h}"),
- Self::Overlap{x, y} => write!(f, "overlapping an existing block at {x} / {y}"),
- Self::Deserialize(..) => f.write_str("block state deserialization failed"),
- }
- }
+impl fmt::Display for PlaceError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Bounds { x, y, sz, w, h } => write!(
+ f,
+ "invalid block placement {x} / {y} (size {sz}) within {w} / {h}"
+ ),
+ Self::Overlap { x, y } => write!(f, "overlapping an existing block at {x} / {y}"),
+ Self::Deserialize(..) => f.write_str("block state deserialization failed"),
+ }
+ }
}
-impl Error for PlaceError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- PlaceError::Deserialize(e) => Some(e),
- _ => None,
- }
- }
+impl Error for PlaceError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ PlaceError::Deserialize(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ResizeError
-{
- TargetWidth(u16),
- TargetHeight(u16),
- XOffset{dx: i16, old_w: u16, new_w: u16},
- YOffset{dy: i16, old_h: u16, new_h: u16},
- Truncated{right: u16, top: u16, left: u16, bottom: u16},
+pub enum ResizeError {
+ TargetWidth(u16),
+ TargetHeight(u16),
+ XOffset {
+ dx: i16,
+ old_w: u16,
+ new_w: u16,
+ },
+ YOffset {
+ dy: i16,
+ old_h: u16,
+ new_h: u16,
+ },
+ Truncated {
+ right: u16,
+ top: u16,
+ left: u16,
+ bottom: u16,
+ },
}
-impl fmt::Display for ResizeError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::TargetWidth(w) => write!(f, "invalid target width ({w})"),
- Self::TargetHeight(w) => write!(f, "invalid target height ({w})"),
- Self::XOffset{dx, old_w, new_w} => write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]"),
- Self::YOffset{dy, old_h, new_h} => write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]"),
- Self::Truncated{right, top, left, bottom} =>
- {
- macro_rules!fmt_dir
- {
- ($f:ident, $first:ident, $name:expr, $value:expr) =>
- {
- if $value != 0
- {
- if $first
- {
- f.write_str(" (")?;
- $first = false;
- }
- else {f.write_str(", ")?;}
- write!(f, "{}: {}", $name, $value)?;
- }
- };
- }
-
- f.write_str("truncated blocks")?;
- let mut first = true;
- fmt_dir!(f, first, "right", *right);
- fmt_dir!(f, first, "top", *top);
- fmt_dir!(f, first, "left", *left);
- fmt_dir!(f, first, "bottom", *bottom);
- if !first {f.write_char(')')?;}
- Ok(())
- },
- }
- }
+impl fmt::Display for ResizeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::TargetWidth(w) => write!(f, "invalid target width ({w})"),
+ Self::TargetHeight(w) => write!(f, "invalid target height ({w})"),
+ Self::XOffset { dx, old_w, new_w } => {
+ write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]")
+ }
+ Self::YOffset { dy, old_h, new_h } => {
+ write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]")
+ }
+ Self::Truncated {
+ right,
+ top,
+ left,
+ bottom,
+ } => {
+ macro_rules! fmt_dir {
+ ($f:ident, $first:ident, $name:expr, $value:expr) => {
+ if $value != 0 {
+ if $first {
+ f.write_str(" (")?;
+ $first = false;
+ } else {
+ f.write_str(", ")?;
+ }
+ write!(f, "{}: {}", $name, $value)?;
+ }
+ };
+ }
+
+ f.write_str("truncated blocks")?;
+ let mut first = true;
+ fmt_dir!(f, first, "right", *right);
+ fmt_dir!(f, first, "top", *top);
+ fmt_dir!(f, first, "left", *left);
+ fmt_dir!(f, first, "bottom", *bottom);
+ if !first {
+ f.write_char(')')?;
+ }
+ Ok(())
+ }
+ }
+ }
}
impl Error for ResizeError {}
-impl<'l> fmt::Display for Schematic<'l>
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- /*
- Because characters are about twice as tall as they are wide, two are used to represent a single block.
- Each block has a single letter to describe what it is + an optional rotation.
- For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations.
- Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces.
- Then, the letter is placed inside followed by the rotation (if any).
- */
-
- // find unique letters for each block, more common blocks pick first
- let mut name_cnt = HashMap::<&str, u16>::new();
- for p in self.blocks.iter()
- {
- match name_cnt.entry(p.block.get_name())
- {
- Entry::Occupied(mut e) => *e.get_mut() += 1,
- Entry::Vacant(e) => {e.insert(1);},
- }
- }
- // only needed the map for counting
- let mut name_cnt = Vec::from_iter(name_cnt);
- name_cnt.sort_by(|l, r| r.1.cmp(&l.1));
- // set for control characters, space, b'*', DEL and b">^<v]/|\\-"
- let mut used = [0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8, 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8];
- let mut types = HashMap::<&str, char>::new();
- for &(name, _) in name_cnt.iter()
- {
- let mut found = false;
- for c in name.chars()
- {
- if c > ' ' && c <= '~'
- {
- let upper = c.to_ascii_uppercase() as usize;
- let lower = c.to_ascii_lowercase() as usize;
- if used[upper >> 3] & (1 << (upper & 7)) == 0
- {
- found = true;
- used[upper >> 3] |= 1 << (upper & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(upper as u32)});
- break;
- }
- if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0
- {
- found = true;
- used[lower >> 3] |= 1 << (lower & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(lower as u32)});
- break;
- }
- }
- }
- if !found
- {
- // just take whatever symbol's still free (avoids collisions with letters)
- match used.iter().enumerate().find(|(_, &v)| v != u8::MAX)
- {
- // there's no more free symbols... how? use b'*' instead for all of them (reserved)
- None => {types.insert(name, '*');},
- Some((i, v)) =>
- {
- let idx = i + v.trailing_ones() as usize;
- used[idx >> 3] |= 1 << (idx & 7);
- types.insert(name, unsafe{char::from_u32_unchecked(idx as u32)});
- },
- }
- }
- }
-
- // coordinates start in the bottom left, so y starts at self.height - 1
- if self.blocks.len() > 0
- {
- for y in (0..self.height as usize).rev()
- {
- let mut x = 0usize;
- while x < self.width as usize
- {
- if let Some(idx) = self.lookup[x + y * (self.width as usize)]
- {
- let Placement{pos, block, state: _, rot} = self.blocks[idx];
- let c = *types.get(block.get_name()).unwrap();
- match block.get_size() as usize
- {
- 0 => unreachable!(),
- 1 =>
- {
- f.write_char(c)?;
- match rot
- {
- _ if block.is_symmetric() => f.write_char(']')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- },
- s =>
- {
- let y0 = pos.1 as usize - (s - 1) / 2;
- if y == y0 + (s - 1)
- {
- // top row, which looks like /---[...]---\
- f.write_char('/')?;
- if s == 2
- {
- // label & rotation are in this row
- f.write_char(c)?;
- match rot
- {
- _ if block.is_symmetric() => f.write_char('-')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- }
- else
- {
- // label & rotation are not in this row
- for _ in 0..(2 * s - 2)
- {
- f.write_char('-')?;
- }
- }
- f.write_char('\\')?;
- }
- else if y == y0
- {
- // bottom row, which looks like \---[...]---/
- f.write_char('\\')?;
- for _ in 0..(2 * s - 2)
- {
- f.write_char('-')?;
- }
- f.write_char('/')?;
- }
- else if s > 2 && y == y0 + s / 2
- {
- // middle row with label
- f.write_char('|')?;
- for cx in 0..(2 * s - 2)
- {
- if cx == s - 2 {f.write_char(c)?;}
- else if cx == s - 1
- {
- match rot
- {
- _ if block.is_symmetric() => f.write_char(' ')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- }
- else {f.write_char(' ')?;}
- }
- f.write_char('|')?;
- }
- else
- {
- // middle row, which looks like | [...] |
- f.write_char('|')?;
- for _ in 0..(2 * s - 2)
- {
- f.write_char(' ')?;
- }
- f.write_char('|')?;
- }
- },
- }
- x += block.get_size() as usize;
- }
- else
- {
- f.write_str(" ")?;
- x += 1;
- }
- }
- writeln!(f)?;
- }
- // print the letters assigned to blocks
- for (k, _) in name_cnt
- {
- let v = *types.get(k).unwrap();
- write!(f, "\n({v}) {k}")?;
- }
- }
- else {write!(f, "<empty {} * {}>", self.width, self.height)?;}
- Ok(())
- }
+impl<'l> fmt::Display for Schematic<'l> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /*
+ Because characters are about twice as tall as they are wide, two are used to represent a single block.
+ Each block has a single letter to describe what it is + an optional rotation.
+ For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations.
+ Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces.
+ Then, the letter is placed inside followed by the rotation (if any).
+ */
+
+ // find unique letters for each block, more common blocks pick first
+ let mut name_cnt = HashMap::<&str, u16>::new();
+ for p in self.blocks.iter() {
+ match name_cnt.entry(p.block.get_name()) {
+ Entry::Occupied(mut e) => *e.get_mut() += 1,
+ Entry::Vacant(e) => {
+ e.insert(1);
+ }
+ }
+ }
+ // only needed the map for counting
+ let mut name_cnt = Vec::from_iter(name_cnt);
+ name_cnt.sort_by(|l, r| r.1.cmp(&l.1));
+ // set for control characters, space, b'*', DEL and b">^<v]/|\\-"
+ let mut used = [
+ 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8,
+ 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8,
+ ];
+ let mut types = HashMap::<&str, char>::new();
+ for &(name, _) in name_cnt.iter() {
+ let mut found = false;
+ for c in name.chars() {
+ if c > ' ' && c <= '~' {
+ let upper = c.to_ascii_uppercase() as usize;
+ let lower = c.to_ascii_lowercase() as usize;
+ if used[upper >> 3] & (1 << (upper & 7)) == 0 {
+ found = true;
+ used[upper >> 3] |= 1 << (upper & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(upper as u32) });
+ break;
+ }
+ if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0 {
+ found = true;
+ used[lower >> 3] |= 1 << (lower & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(lower as u32) });
+ break;
+ }
+ }
+ }
+ if !found {
+ // just take whatever symbol's still free (avoids collisions with letters)
+ match used.iter().enumerate().find(|(_, &v)| v != u8::MAX) {
+ // there's no more free symbols... how? use b'*' instead for all of them (reserved)
+ None => {
+ types.insert(name, '*');
+ }
+ Some((i, v)) => {
+ let idx = i + v.trailing_ones() as usize;
+ used[idx >> 3] |= 1 << (idx & 7);
+ types.insert(name, unsafe { char::from_u32_unchecked(idx as u32) });
+ }
+ }
+ }
+ }
+
+ // coordinates start in the bottom left, so y starts at self.height - 1
+ if self.blocks.len() > 0 {
+ for y in (0..self.height as usize).rev() {
+ let mut x = 0usize;
+ while x < self.width as usize {
+ if let Some(idx) = self.lookup[x + y * (self.width as usize)] {
+ let Placement {
+ pos,
+ block,
+ state: _,
+ rot,
+ } = self.blocks[idx];
+ let c = *types.get(block.get_name()).unwrap();
+ match block.get_size() as usize {
+ 0 => unreachable!(),
+ 1 => {
+ f.write_char(c)?;
+ match rot {
+ _ if block.is_symmetric() => f.write_char(']')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ }
+ s => {
+ let y0 = pos.1 as usize - (s - 1) / 2;
+ if y == y0 + (s - 1) {
+ // top row, which looks like /---[...]---\
+ f.write_char('/')?;
+ if s == 2 {
+ // label & rotation are in this row
+ f.write_char(c)?;
+ match rot {
+ _ if block.is_symmetric() => f.write_char('-')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ } else {
+ // label & rotation are not in this row
+ for _ in 0..(2 * s - 2) {
+ f.write_char('-')?;
+ }
+ }
+ f.write_char('\\')?;
+ } else if y == y0 {
+ // bottom row, which looks like \---[...]---/
+ f.write_char('\\')?;
+ for _ in 0..(2 * s - 2) {
+ f.write_char('-')?;
+ }
+ f.write_char('/')?;
+ } else if s > 2 && y == y0 + s / 2 {
+ // middle row with label
+ f.write_char('|')?;
+ for cx in 0..(2 * s - 2) {
+ if cx == s - 2 {
+ f.write_char(c)?;
+ } else if cx == s - 1 {
+ match rot {
+ _ if block.is_symmetric() => f.write_char(' ')?,
+ Rotation::Right => f.write_char('>')?,
+ Rotation::Up => f.write_char('^')?,
+ Rotation::Left => f.write_char('<')?,
+ Rotation::Down => f.write_char('v')?,
+ }
+ } else {
+ f.write_char(' ')?;
+ }
+ }
+ f.write_char('|')?;
+ } else {
+ // middle row, which looks like | [...] |
+ f.write_char('|')?;
+ for _ in 0..(2 * s - 2) {
+ f.write_char(' ')?;
+ }
+ f.write_char('|')?;
+ }
+ }
+ }
+ x += block.get_size() as usize;
+ } else {
+ f.write_str(" ")?;
+ x += 1;
+ }
+ }
+ writeln!(f)?;
+ }
+ // print the letters assigned to blocks
+ for (k, _) in name_cnt {
+ let v = *types.get(k).unwrap();
+ write!(f, "\n({v}) {k}")?;
+ }
+ } else {
+ write!(f, "<empty {} * {}>", self.width, self.height)?;
+ }
+ Ok(())
+ }
}
-const SCHEMATIC_HEADER: u32 = ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32);
+const SCHEMATIC_HEADER: u32 =
+ ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32);
pub struct SchematicSerializer<'l>(pub &'l BlockRegistry<'l>);
-impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l>
-{
- type ReadError = ReadError;
- type WriteError = WriteError;
-
- fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Schematic<'l>, Self::ReadError>
- {
- let hdr = buff.read_u32()?;
- if hdr != SCHEMATIC_HEADER {return Err(ReadError::Header(hdr));}
- let version = buff.read_u8()?;
- if version > 1 {return Err(ReadError::Version(version));}
- let mut dec = Decompress::new(true);
- let mut raw = Vec::<u8>::new();
- raw.reserve(1024);
- loop
- {
- let t_in = dec.total_in();
- let t_out = dec.total_out();
- let res = dec.decompress_vec(buff.data, &mut raw, FlushDecompress::Finish)?;
- if dec.total_in() > t_in
- {
- // we have to advance input every time, decompress_vec only knows the output position
- buff.data = &buff.data[(dec.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if dec.total_in() == t_in && dec.total_out() == t_out
- {
- // protect against looping forever
- return Err(ReadError::DecompressStall);
- }
- raw.reserve(1024);
- }
- assert_eq!(dec.total_out() as usize, raw.len());
- let mut rbuff = DataRead::new(&raw);
- let w = rbuff.read_i16()?;
- let h = rbuff.read_i16()?;
- if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION
- {
- return Err(ReadError::Dimensions(w, h));
- }
- let mut schematic = Schematic::new(w as u16, h as u16);
- for _ in 0..rbuff.read_u8()?
- {
- let key = rbuff.read_utf()?;
- let value = rbuff.read_utf()?;
- schematic.tags.insert(key.to_owned(), value.to_owned());
- }
- let num_table = rbuff.read_i8()?;
- if num_table < 0
- {
- return Err(ReadError::TableSize(num_table));
- }
- let mut block_table = Vec::<&'l Block>::new();
- block_table.reserve(num_table as usize);
- for _ in 0..num_table
- {
- let name = rbuff.read_utf()?;
- match self.0.get(name)
- {
- None => return Err(ReadError::NoSuchBlock(name.to_owned())),
- Some(b) => block_table.push(b),
- }
- }
- let num_blocks = rbuff.read_i32()?;
- if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS
- {
- return Err(ReadError::BlockCount(num_blocks));
- }
- for _ in 0..num_blocks
- {
- let idx = rbuff.read_i8()?;
- if idx < 0 || idx as usize >= block_table.len()
- {
- return Err(ReadError::BlockIndex(idx, block_table.len()));
- }
- let pos = GridPos::from(rbuff.read_u32()?);
- let block = block_table[idx as usize];
- let config = if version < 1
- {
- block.data_from_i32(rbuff.read_i32()?, pos)?
- }
- else {DynSerializer.deserialize(&mut rbuff)?};
- let rot = Rotation::from(rbuff.read_u8()?);
- schematic.set(pos.0, pos.1, block, config, rot)?;
- }
- Ok(schematic)
- }
-
- fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &Schematic) -> Result<(), Self::WriteError>
- {
- // write the header first just in case
- buff.write_u32(SCHEMATIC_HEADER)?;
- buff.write_u8(1)?;
-
- let mut rbuff = DataWrite::new();
- // don't have to check dimensions because they're already limited to MAX_DIMENSION
- rbuff.write_i16(data.width as i16)?;
- rbuff.write_i16(data.height as i16)?;
- if data.tags.len() > u8::MAX as usize
- {
- return Err(WriteError::TagCount(data.tags.len()));
- }
- rbuff.write_u8(data.tags.len() as u8)?;
- for (k, v) in data.tags.iter()
- {
- rbuff.write_utf(k)?;
- rbuff.write_utf(v)?;
- }
- // use string keys here to avoid issues with different block refs with the same name
- let mut block_map = HashMap::<&str, u32>::new();
- let mut block_table = Vec::<&str>::new();
- for curr in data.blocks.iter()
- {
- match block_map.entry(curr.block.get_name())
- {
- Entry::Vacant(e) =>
- {
- e.insert(block_table.len() as u32);
- block_table.push(curr.block.get_name());
- },
- _ => (),
- }
- }
- if block_table.len() > i8::MAX as usize
- {
- return Err(WriteError::TableSize(block_table.len()));
- }
- // else: implies contents are also valid i8 (they're strictly less than the map length)
- rbuff.write_i8(block_table.len() as i8)?;
- for &name in block_table.iter()
- {
- rbuff.write_utf(name)?;
- }
- // don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS
- rbuff.write_i32(data.blocks.len() as i32)?;
- let mut num = 0;
- for curr in data.blocks.iter()
- {
- rbuff.write_i8(block_map[curr.block.get_name()] as i8)?;
- rbuff.write_u32(u32::from(curr.pos))?;
- let data = match curr.state
- {
- None => DynData::Empty,
- Some(ref s) => curr.block.serialize_state(s.as_ref())?,
- };
- DynSerializer.serialize(&mut rbuff, &data)?;
- rbuff.write_u8(curr.rot.into())?;
- num += 1;
- }
- assert_eq!(num, data.blocks.len());
-
- // compress into the provided buffer
- let raw = match rbuff.data
- {
- data::WriteBuff::Vec(v) => v,
- _ => unreachable!("write buffer not owned"),
- };
- let mut comp = Compress::new(Compression::default(), true);
- // compress the immediate buffer into a temp buffer to copy it to buff? no thanks
- match buff.data
- {
- data::WriteBuff::Ref{raw: ref mut dst, ref mut pos} =>
- {
- match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)?
- {
- // there's no more input (and the flush mode says so), but we can't resize the output
- Status::Ok | Status::BufError => return Err(WriteError::CompressEof(raw.len() - comp.total_in() as usize)),
- Status::StreamEnd => (),
- }
- },
- data::WriteBuff::Vec(ref mut dst) =>
- {
- let mut input = raw.as_ref();
- dst.reserve(1024);
- loop
- {
- let t_in = comp.total_in();
- let t_out = comp.total_out();
- let res = comp.compress_vec(input, dst, FlushCompress::Finish)?;
- if comp.total_in() > t_in
- {
- // we have to advance input every time, compress_vec only knows the output position
- input = &input[(comp.total_in() - t_in) as usize..];
- }
- match res
- {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if comp.total_in() == t_in && comp.total_out() == t_out
- {
- // protect against looping forever
- return Err(WriteError::CompressStall);
- }
- dst.reserve(1024);
- }
- },
- }
- assert_eq!(comp.total_in() as usize, raw.len());
- Ok(())
- }
+impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
+ type ReadError = ReadError;
+ type WriteError = WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Schematic<'l>, Self::ReadError> {
+ let hdr = buff.read_u32()?;
+ if hdr != SCHEMATIC_HEADER {
+ return Err(ReadError::Header(hdr));
+ }
+ let version = buff.read_u8()?;
+ if version > 1 {
+ return Err(ReadError::Version(version));
+ }
+ let mut dec = Decompress::new(true);
+ let mut raw = Vec::<u8>::new();
+ raw.reserve(1024);
+ loop {
+ let t_in = dec.total_in();
+ let t_out = dec.total_out();
+ let res = dec.decompress_vec(buff.data, &mut raw, FlushDecompress::Finish)?;
+ if dec.total_in() > t_in {
+ // we have to advance input every time, decompress_vec only knows the output position
+ buff.data = &buff.data[(dec.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if dec.total_in() == t_in && dec.total_out() == t_out {
+ // protect against looping forever
+ return Err(ReadError::DecompressStall);
+ }
+ raw.reserve(1024);
+ }
+ assert_eq!(dec.total_out() as usize, raw.len());
+ let mut rbuff = DataRead::new(&raw);
+ let w = rbuff.read_i16()?;
+ let h = rbuff.read_i16()?;
+ if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION {
+ return Err(ReadError::Dimensions(w, h));
+ }
+ let mut schematic = Schematic::new(w as u16, h as u16);
+ for _ in 0..rbuff.read_u8()? {
+ let key = rbuff.read_utf()?;
+ let value = rbuff.read_utf()?;
+ schematic.tags.insert(key.to_owned(), value.to_owned());
+ }
+ let num_table = rbuff.read_i8()?;
+ if num_table < 0 {
+ return Err(ReadError::TableSize(num_table));
+ }
+ let mut block_table = Vec::<&'l Block>::new();
+ block_table.reserve(num_table as usize);
+ for _ in 0..num_table {
+ let name = rbuff.read_utf()?;
+ match self.0.get(name) {
+ None => return Err(ReadError::NoSuchBlock(name.to_owned())),
+ Some(b) => block_table.push(b),
+ }
+ }
+ let num_blocks = rbuff.read_i32()?;
+ if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS {
+ return Err(ReadError::BlockCount(num_blocks));
+ }
+ for _ in 0..num_blocks {
+ let idx = rbuff.read_i8()?;
+ if idx < 0 || idx as usize >= block_table.len() {
+ return Err(ReadError::BlockIndex(idx, block_table.len()));
+ }
+ let pos = GridPos::from(rbuff.read_u32()?);
+ let block = block_table[idx as usize];
+ let config = if version < 1 {
+ block.data_from_i32(rbuff.read_i32()?, pos)?
+ } else {
+ DynSerializer.deserialize(&mut rbuff)?
+ };
+ let rot = Rotation::from(rbuff.read_u8()?);
+ schematic.set(pos.0, pos.1, block, config, rot)?;
+ }
+ Ok(schematic)
+ }
+
+ fn serialize(
+ &mut self,
+ buff: &mut DataWrite<'_>,
+ data: &Schematic,
+ ) -> Result<(), Self::WriteError> {
+ // write the header first just in case
+ buff.write_u32(SCHEMATIC_HEADER)?;
+ buff.write_u8(1)?;
+
+ let mut rbuff = DataWrite::new();
+ // don't have to check dimensions because they're already limited to MAX_DIMENSION
+ rbuff.write_i16(data.width as i16)?;
+ rbuff.write_i16(data.height as i16)?;
+ if data.tags.len() > u8::MAX as usize {
+ return Err(WriteError::TagCount(data.tags.len()));
+ }
+ rbuff.write_u8(data.tags.len() as u8)?;
+ for (k, v) in data.tags.iter() {
+ rbuff.write_utf(k)?;
+ rbuff.write_utf(v)?;
+ }
+ // use string keys here to avoid issues with different block refs with the same name
+ let mut block_map = HashMap::<&str, u32>::new();
+ let mut block_table = Vec::<&str>::new();
+ for curr in data.blocks.iter() {
+ match block_map.entry(curr.block.get_name()) {
+ Entry::Vacant(e) => {
+ e.insert(block_table.len() as u32);
+ block_table.push(curr.block.get_name());
+ }
+ _ => (),
+ }
+ }
+ if block_table.len() > i8::MAX as usize {
+ return Err(WriteError::TableSize(block_table.len()));
+ }
+ // else: implies contents are also valid i8 (they're strictly less than the map length)
+ rbuff.write_i8(block_table.len() as i8)?;
+ for &name in block_table.iter() {
+ rbuff.write_utf(name)?;
+ }
+ // don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS
+ rbuff.write_i32(data.blocks.len() as i32)?;
+ let mut num = 0;
+ for curr in data.blocks.iter() {
+ rbuff.write_i8(block_map[curr.block.get_name()] as i8)?;
+ rbuff.write_u32(u32::from(curr.pos))?;
+ let data = match curr.state {
+ None => DynData::Empty,
+ Some(ref s) => curr.block.serialize_state(s.as_ref())?,
+ };
+ DynSerializer.serialize(&mut rbuff, &data)?;
+ rbuff.write_u8(curr.rot.into())?;
+ num += 1;
+ }
+ assert_eq!(num, data.blocks.len());
+
+ // compress into the provided buffer
+ let raw = match rbuff.data {
+ data::WriteBuff::Vec(v) => v,
+ _ => unreachable!("write buffer not owned"),
+ };
+ let mut comp = Compress::new(Compression::default(), true);
+ // compress the immediate buffer into a temp buffer to copy it to buff? no thanks
+ match buff.data {
+ data::WriteBuff::Ref {
+ raw: ref mut dst,
+ ref mut pos,
+ } => {
+ match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)? {
+ // there's no more input (and the flush mode says so), but we can't resize the output
+ Status::Ok | Status::BufError => {
+ return Err(WriteError::CompressEof(
+ raw.len() - comp.total_in() as usize,
+ ))
+ }
+ Status::StreamEnd => (),
+ }
+ }
+ data::WriteBuff::Vec(ref mut dst) => {
+ let mut input = raw.as_ref();
+ dst.reserve(1024);
+ loop {
+ let t_in = comp.total_in();
+ let t_out = comp.total_out();
+ let res = comp.compress_vec(input, dst, FlushCompress::Finish)?;
+ if comp.total_in() > t_in {
+ // we have to advance input every time, compress_vec only knows the output position
+ input = &input[(comp.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if comp.total_in() == t_in && comp.total_out() == t_out {
+ // protect against looping forever
+ return Err(WriteError::CompressStall);
+ }
+ dst.reserve(1024);
+ }
+ }
+ }
+ assert_eq!(comp.total_in() as usize, raw.len());
+ Ok(())
+ }
}
#[derive(Debug)]
-pub enum ReadError
-{
- Read(data::ReadError),
- Header(u32),
- Version(u8),
- Decompress(DecompressError),
- DecompressStall,
- Dimensions(i16, i16),
- TableSize(i8),
- NoSuchBlock(String),
- BlockCount(i32),
- BlockIndex(i8, usize),
- BlockConfig(block::DataConvertError),
- ReadState(dynamic::ReadError),
- Placement(PlaceError),
+pub enum ReadError {
+ Read(data::ReadError),
+ Header(u32),
+ Version(u8),
+ Decompress(DecompressError),
+ DecompressStall,
+ Dimensions(i16, i16),
+ TableSize(i8),
+ NoSuchBlock(String),
+ BlockCount(i32),
+ BlockIndex(i8, usize),
+ BlockConfig(block::DataConvertError),
+ ReadState(dynamic::ReadError),
+ Placement(PlaceError),
}
-impl From<data::ReadError> for ReadError
-{
- fn from(value: data::ReadError) -> Self
- {
- Self::Read(value)
- }
+impl From<data::ReadError> for ReadError {
+ fn from(value: data::ReadError) -> Self {
+ Self::Read(value)
+ }
}
-impl From<DecompressError> for ReadError
-{
- fn from(value: DecompressError) -> Self
- {
- Self::Decompress(value)
- }
+impl From<DecompressError> for ReadError {
+ fn from(value: DecompressError) -> Self {
+ Self::Decompress(value)
+ }
}
-impl From<dynamic::ReadError> for ReadError
-{
- fn from(value: dynamic::ReadError) -> Self
- {
- Self::ReadState(value)
- }
+impl From<dynamic::ReadError> for ReadError {
+ fn from(value: dynamic::ReadError) -> Self {
+ Self::ReadState(value)
+ }
}
-impl From<block::DataConvertError> for ReadError
-{
- fn from(value: block::DataConvertError) -> Self
- {
- Self::BlockConfig(value)
- }
+impl From<block::DataConvertError> for ReadError {
+ fn from(value: block::DataConvertError) -> Self {
+ Self::BlockConfig(value)
+ }
}
-impl From<PlaceError> for ReadError
-{
- fn from(value: PlaceError) -> Self
- {
- Self::Placement(value)
- }
+impl From<PlaceError> for ReadError {
+ fn from(value: PlaceError) -> Self {
+ Self::Placement(value)
+ }
}
-impl fmt::Display for ReadError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Read(..) => f.write_str("failed to read from buffer"),
- Self::Header(hdr) => write!(f, "incorrect header ({hdr:08X})"),
- Self::Version(ver) => write!(f, "unsupported version ({ver})"),
- Self::Decompress(..) => f.write_str("zlib decompression failed"),
- Self::DecompressStall => f.write_str("decompressor stalled before completion"),
- Self::Dimensions(w, h) => write!(f, "invalid schematic dimensions ({w} * {h})"),
- Self::TableSize(cnt) => write!(f, "invalid block table size ({cnt})"),
- Self::NoSuchBlock(name) => write!(f, "unknown block {name:?}"),
- Self::BlockCount(cnt) => write!(f, "invalid total block count ({cnt})"),
- Self::BlockIndex(idx, cnt) => write!(f, "invalid block index ({idx} / {cnt})"),
- Self::BlockConfig(..) => f.write_str("block config conversion failed"),
- Self::ReadState(..) => f.write_str("failed to read block data"),
- Self::Placement(..) => f.write_str("deserialized block could not be placed"),
- }
- }
+impl fmt::Display for ReadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Read(..) => f.write_str("failed to read from buffer"),
+ Self::Header(hdr) => write!(f, "incorrect header ({hdr:08X})"),
+ Self::Version(ver) => write!(f, "unsupported version ({ver})"),
+ Self::Decompress(..) => f.write_str("zlib decompression failed"),
+ Self::DecompressStall => f.write_str("decompressor stalled before completion"),
+ Self::Dimensions(w, h) => write!(f, "invalid schematic dimensions ({w} * {h})"),
+ Self::TableSize(cnt) => write!(f, "invalid block table size ({cnt})"),
+ Self::NoSuchBlock(name) => write!(f, "unknown block {name:?}"),
+ Self::BlockCount(cnt) => write!(f, "invalid total block count ({cnt})"),
+ Self::BlockIndex(idx, cnt) => write!(f, "invalid block index ({idx} / {cnt})"),
+ Self::BlockConfig(..) => f.write_str("block config conversion failed"),
+ Self::ReadState(..) => f.write_str("failed to read block data"),
+ Self::Placement(..) => f.write_str("deserialized block could not be placed"),
+ }
+ }
}
-impl Error for ReadError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Read(e) => Some(e),
- Self::Decompress(e) => Some(e),
- Self::BlockConfig(e) => Some(e),
- Self::ReadState(e) => Some(e),
- Self::Placement(e) => Some(e),
- _ => None,
- }
- }
+impl Error for ReadError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Read(e) => Some(e),
+ Self::Decompress(e) => Some(e),
+ Self::BlockConfig(e) => Some(e),
+ Self::ReadState(e) => Some(e),
+ Self::Placement(e) => Some(e),
+ _ => None,
+ }
+ }
}
#[derive(Debug)]
-pub enum WriteError
-{
- Write(data::WriteError),
- TagCount(usize),
- TableSize(usize),
- StateSerialize(block::SerializeError),
- WriteState(dynamic::WriteError),
- Compress(CompressError),
- CompressEof(usize),
- CompressStall,
+pub enum WriteError {
+ Write(data::WriteError),
+ TagCount(usize),
+ TableSize(usize),
+ StateSerialize(block::SerializeError),
+ WriteState(dynamic::WriteError),
+ Compress(CompressError),
+ CompressEof(usize),
+ CompressStall,
}
-impl From<data::WriteError> for WriteError
-{
- fn from(value: data::WriteError) -> Self
- {
- Self::Write(value)
- }
+impl From<data::WriteError> for WriteError {
+ fn from(value: data::WriteError) -> Self {
+ Self::Write(value)
+ }
}
-impl From<block::SerializeError> for WriteError
-{
- fn from(value: block::SerializeError) -> Self
- {
- Self::StateSerialize(value)
- }
+impl From<block::SerializeError> for WriteError {
+ fn from(value: block::SerializeError) -> Self {
+ Self::StateSerialize(value)
+ }
}
-impl From<CompressError> for WriteError
-{
- fn from(value: CompressError) -> Self
- {
- Self::Compress(value)
- }
+impl From<CompressError> for WriteError {
+ fn from(value: CompressError) -> Self {
+ Self::Compress(value)
+ }
}
-impl From<dynamic::WriteError> for WriteError
-{
- fn from(value: dynamic::WriteError) -> Self
- {
- Self::WriteState(value)
- }
+impl From<dynamic::WriteError> for WriteError {
+ fn from(value: dynamic::WriteError) -> Self {
+ Self::WriteState(value)
+ }
}
-impl fmt::Display for WriteError
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Write(..) => f.write_str("failed to write data to buffer"),
- Self::TagCount(len) => write!(f, "tag list too long ({len})"),
- Self::TableSize(len) => write!(f, "block table too long ({len})"),
- Self::StateSerialize(e) => e.fmt(f),
- Self::WriteState(..) => f.write_str("failed to write block data"),
- Self::Compress(..) => f.write_str("zlib compression failed"),
- Self::CompressEof(remain) => write!(f, "compression overflow with {remain} bytes of input remaining"),
- Self::CompressStall => f.write_str("compressor stalled before completion"),
- }
- }
+impl fmt::Display for WriteError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Write(..) => f.write_str("failed to write data to buffer"),
+ Self::TagCount(len) => write!(f, "tag list too long ({len})"),
+ Self::TableSize(len) => write!(f, "block table too long ({len})"),
+ Self::StateSerialize(e) => e.fmt(f),
+ Self::WriteState(..) => f.write_str("failed to write block data"),
+ Self::Compress(..) => f.write_str("zlib compression failed"),
+ Self::CompressEof(remain) => write!(
+ f,
+ "compression overflow with {remain} bytes of input remaining"
+ ),
+ Self::CompressStall => f.write_str("compressor stalled before completion"),
+ }
+ }
}
-impl Error for WriteError
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Write(e) => Some(e),
- Self::StateSerialize(e) => e.source(),
- Self::Compress(e) => Some(e),
- _ => None,
- }
- }
+impl Error for WriteError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Write(e) => Some(e),
+ Self::StateSerialize(e) => e.source(),
+ Self::Compress(e) => Some(e),
+ _ => None,
+ }
+ }
}
-impl<'l> SchematicSerializer<'l>
-{
- pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error>
- {
- let mut buff = Vec::<u8>::new();
- buff.resize(data.len() / 4 * 3 + 1, 0);
- let n_out = base64::decode(data.as_bytes(), buff.as_mut())?;
- Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?)
- }
-
- pub fn serialize_base64(&mut self, data: &Schematic<'l>) -> Result<String, W64Error>
- {
- let mut buff = DataWrite::new();
- self.serialize(&mut buff, data)?;
- let buff = buff.get_written();
- // round up because of padding
- let required = 4 * (buff.len() / 3 + if buff.len() % 3 != 0 {1} else {0});
- let mut text = Vec::<u8>::new();
- text.resize(required, 0);
- let n_out = base64::encode(buff, text.as_mut())?;
- // trailing zeros are valid UTF8, but not valid base64
- assert_eq!(n_out, text.len());
- // SAFETY: base64 encoding outputs pure ASCII (see base64::CHARS)
- Ok(unsafe{String::from_utf8_unchecked(text)})
- }
+impl<'l> SchematicSerializer<'l> {
+ pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> {
+ let mut buff = Vec::<u8>::new();
+ buff.resize(data.len() / 4 * 3 + 1, 0);
+ let n_out = base64::decode(data.as_bytes(), buff.as_mut())?;
+ Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?)
+ }
+
+ pub fn serialize_base64(&mut self, data: &Schematic<'l>) -> Result<String, W64Error> {
+ let mut buff = DataWrite::new();
+ self.serialize(&mut buff, data)?;
+ let buff = buff.get_written();
+ // round up because of padding
+ let required = 4 * (buff.len() / 3 + if buff.len() % 3 != 0 { 1 } else { 0 });
+ let mut text = Vec::<u8>::new();
+ text.resize(required, 0);
+ let n_out = base64::encode(buff, text.as_mut())?;
+ // trailing zeros are valid UTF8, but not valid base64
+ assert_eq!(n_out, text.len());
+ // SAFETY: base64 encoding outputs pure ASCII (see base64::CHARS)
+ Ok(unsafe { String::from_utf8_unchecked(text) })
+ }
}
#[derive(Debug)]
-pub enum R64Error
-{
- Base64(base64::DecodeError),
- Content(ReadError),
+pub enum R64Error {
+ Base64(base64::DecodeError),
+ Content(ReadError),
}
-impl From<base64::DecodeError> for R64Error
-{
- fn from(value: base64::DecodeError) -> Self
- {
- Self::Base64(value)
- }
+impl From<base64::DecodeError> for R64Error {
+ fn from(value: base64::DecodeError) -> Self {
+ Self::Base64(value)
+ }
}
-impl From<ReadError> for R64Error
-{
- fn from(value: ReadError) -> Self
- {
- Self::Content(value)
- }
+impl From<ReadError> for R64Error {
+ fn from(value: ReadError) -> Self {
+ Self::Content(value)
+ }
}
-impl fmt::Display for R64Error
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Base64(..) => f.write_str("base-64 decoding failed"),
- Self::Content(e) => e.fmt(f),
- }
- }
+impl fmt::Display for R64Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Base64(..) => f.write_str("base-64 decoding failed"),
+ Self::Content(e) => e.fmt(f),
+ }
+ }
}
-impl Error for R64Error
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Base64(e) => Some(e),
- Self::Content(e) => e.source(),
- }
- }
+impl Error for R64Error {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Base64(e) => Some(e),
+ Self::Content(e) => e.source(),
+ }
+ }
}
#[derive(Debug)]
-pub enum W64Error
-{
- Base64(base64::EncodeError),
- Content(WriteError),
+pub enum W64Error {
+ Base64(base64::EncodeError),
+ Content(WriteError),
}
-impl From<base64::EncodeError> for W64Error
-{
- fn from(value: base64::EncodeError) -> Self
- {
- Self::Base64(value)
- }
+impl From<base64::EncodeError> for W64Error {
+ fn from(value: base64::EncodeError) -> Self {
+ Self::Base64(value)
+ }
}
-impl From<WriteError> for W64Error
-{
- fn from(value: WriteError) -> Self
- {
- Self::Content(value)
- }
+impl From<WriteError> for W64Error {
+ fn from(value: WriteError) -> Self {
+ Self::Content(value)
+ }
}
-impl fmt::Display for W64Error
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
- {
- match self
- {
- Self::Base64(..) => f.write_str("base-64 encoding failed"),
- Self::Content(e) => e.fmt(f),
- }
- }
+impl fmt::Display for W64Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Base64(..) => f.write_str("base-64 encoding failed"),
+ Self::Content(e) => e.fmt(f),
+ }
+ }
}
-impl Error for W64Error
-{
- fn source(&self) -> Option<&(dyn Error + 'static)>
- {
- match self
- {
- Self::Base64(e) => Some(e),
- Self::Content(e) => e.source(),
- }
- }
+impl Error for W64Error {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Base64(e) => Some(e),
+ Self::Content(e) => e.source(),
+ }
+ }
}
-pub struct PosIter
-{
- x: u16,
- y: u16,
- w: u16,
- h: u16,
+pub struct PosIter {
+ x: u16,
+ y: u16,
+ w: u16,
+ h: u16,
}
-impl Iterator for PosIter
-{
- type Item = GridPos;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- if self.w > 0 && self.y < self.h
- {
- let p = GridPos(self.x, self.y);
- self.x += 1;
- if self.x == self.w
- {
- self.x = 0;
- self.y += 1;
- }
- Some(p)
- }
- else {None}
- }
-
- fn size_hint(&self) -> (usize, Option<usize>)
- {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- (end - pos, Some(end - pos))
- }
-
- fn count(self) -> usize
- {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- end - pos
- }
-
- fn last(self) -> Option<Self::Item>
- {
- // self.y < self.h implies self.h > 0
- if self.w > 0 && self.y < self.h
- {
- Some(GridPos(self.w - 1, self.h - 1))
- }
- else {None}
- }
+impl Iterator for PosIter {
+ type Item = GridPos;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.w > 0 && self.y < self.h {
+ let p = GridPos(self.x, self.y);
+ self.x += 1;
+ if self.x == self.w {
+ self.x = 0;
+ self.y += 1;
+ }
+ Some(p)
+ } else {
+ None
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
+ let end = (self.w as usize) * (self.h as usize);
+ (end - pos, Some(end - pos))
+ }
+
+ fn count(self) -> usize {
+ let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
+ let end = (self.w as usize) * (self.h as usize);
+ end - pos
+ }
+
+ fn last(self) -> Option<Self::Item> {
+ // self.y < self.h implies self.h > 0
+ if self.w > 0 && self.y < self.h {
+ Some(GridPos(self.w - 1, self.h - 1))
+ } else {
+ None
+ }
+ }
}
impl FusedIterator for PosIter {}
#[cfg(test)]
-mod test
-{
- use super::*;
-
- macro_rules!test_iter
+mod test {
+ use super::*;
+
+ macro_rules!test_iter
{
($name:ident, $it:expr, $($val:expr),+) =>
{
@@ -1431,7 +1423,16 @@ mod test
assert_eq!($it.next(), $val);
};
}
-
- test_iter!(block_iter, Schematic::new(3, 4).pos_iter(), Some(GridPos(0, 0)), Some(GridPos(1, 0)), Some(GridPos(2, 0)),
- Some(GridPos(0, 1)), 7, Some(GridPos(2, 3)), None);
+
+ test_iter!(
+ block_iter,
+ Schematic::new(3, 4).pos_iter(),
+ Some(GridPos(0, 0)),
+ Some(GridPos(1, 0)),
+ Some(GridPos(2, 0)),
+ Some(GridPos(0, 1)),
+ 7,
+ Some(GridPos(2, 3)),
+ None
+ );
}