mindustry logic execution, map- and schematic- parsing and rendering
Support errors in block state serialization
| -rw-r--r-- | src/block/logic.rs | 280 | ||||
| -rw-r--r-- | src/block/mod.rs | 95 | ||||
| -rw-r--r-- | src/block/simple.rs | 10 | ||||
| -rw-r--r-- | src/data/schematic.rs | 53 |
4 files changed, 361 insertions, 77 deletions
diff --git a/src/block/logic.rs b/src/block/logic.rs index edd4297..d426970 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -1,12 +1,15 @@ -use std::any::{Any, type_name}; +use std::any::Any; use std::borrow::Cow; +use std::error::Error; +use std::fmt; +use std::string::FromUtf8Error; -use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status}; +use flate2::{Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress, FlushDecompress, Status}; -use crate::block::{BlockLogic, make_register}; +use crate::block::{BlockLogic, DeserializeError, make_register, SerializeError}; use crate::block::simple::{SimpleBlock, state_impl}; -use crate::data::{DataRead, DataWrite}; -use crate::data::dynamic::DynData; +use crate::data::{self, DataRead, DataWrite}; +use crate::data::dynamic::{DynData, DynType}; make_register! ( @@ -45,13 +48,13 @@ impl BlockLogic for MessageLogic DynData::Empty } - fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>> + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { match data { - DynData::Empty | DynData::String(None) => Some(Self::create_state(String::new())), - DynData::String(Some(s)) => Some(Self::create_state(s)), - _ => panic!("{} cannot use data of {:?}", type_name::<Self>(), data.get_type()), + DynData::Empty | DynData::String(None) => Ok(Some(Self::create_state(String::new()))), + DynData::String(Some(s)) => Ok(Some(Self::create_state(s))), + _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::String}), } } @@ -60,9 +63,9 @@ impl BlockLogic for MessageLogic Box::new(Self::get_state(state).clone()) } - fn serialize_state(&self, state: &dyn Any) -> DynData + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { - DynData::String(Some(Self::get_state(state).clone())) + Ok(DynData::String(Some(Self::get_state(state).clone()))) } } @@ -90,13 +93,13 @@ impl BlockLogic for SwitchLogic DynData::Empty } - fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>> + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { match data { - DynData::Empty => Some(Self::create_state(true)), - DynData::Boolean(enabled) => Some(Self::create_state(enabled)), - _ => panic!("{} cannot use data of {:?}", type_name::<Self>(), data.get_type()), + DynData::Empty => Ok(Some(Self::create_state(true))), + DynData::Boolean(enabled) => Ok(Some(Self::create_state(enabled))), + _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}), } } @@ -105,9 +108,9 @@ impl BlockLogic for SwitchLogic Box::new(Self::get_state(state).clone()) } - fn serialize_state(&self, state: &dyn Any) -> DynData + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { - DynData::Boolean(*Self::get_state(state)) + Ok(DynData::Boolean(*Self::get_state(state))) } } @@ -138,11 +141,11 @@ impl BlockLogic for ProcessorLogic DynData::Empty } - fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>> + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { match data { - DynData::Empty => Some(Self::create_state(ProcessorState::new())), + DynData::Empty => Ok(Some(Self::create_state(ProcessorState::new()))), DynData::ByteArray(arr) => { let mut input = arr.as_ref(); @@ -153,7 +156,7 @@ impl BlockLogic for ProcessorLogic { let t_in = dec.total_in(); let t_out = dec.total_out(); - let res = dec.decompress_vec(input, &mut raw, FlushDecompress::Finish).unwrap(); + let res = ProcessorDeserializeError::forward(dec.decompress_vec(input, &mut raw, FlushDecompress::Finish))?; if dec.total_in() > t_in { // we have to advance input every time, decompress_vec only knows the output position @@ -169,43 +172,43 @@ impl BlockLogic for ProcessorLogic if dec.total_in() == t_in && dec.total_out() == t_out { // protect against looping forever - panic!("decompressor stalled"); + return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::DecompressStall))); } raw.reserve(1024); } let mut buff = DataRead::new(&raw); - let ver = buff.read_u8().unwrap(); + let ver = ProcessorDeserializeError::forward(buff.read_u8())?; if ver != 1 { - panic!("unknown version {ver}"); + return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::Version(ver)))); } - let code_len = buff.read_i32().unwrap(); + let code_len = ProcessorDeserializeError::forward(buff.read_i32())?; if code_len < 0 || code_len > 500 * 1024 { - panic!("invalid code length ({code_len})"); + return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::CodeLength(code_len)))); } let mut code = Vec::<u8>::new(); code.resize(code_len as usize, 0); - buff.read_bytes(&mut code).unwrap(); - let code = String::from_utf8(code).unwrap(); - let link_cnt = buff.read_i32().unwrap(); + ProcessorDeserializeError::forward(buff.read_bytes(&mut code))?; + let code = ProcessorDeserializeError::forward(String::from_utf8(code))?; + let link_cnt = ProcessorDeserializeError::forward(buff.read_i32())?; if link_cnt < 0 { - panic!("link count is negative ({link_cnt})"); + return Err(DeserializeError::Custom(Box::new(ProcessorDeserializeError::LinkCount(link_cnt)))); } let mut links = Vec::<ProcessorLink>::new(); links.reserve(link_cnt as usize); for _ in 0..link_cnt { - let name = buff.read_utf().unwrap(); - let x = buff.read_i16().unwrap(); - let y = buff.read_i16().unwrap(); + let name = ProcessorDeserializeError::forward(buff.read_utf())?; + let x = ProcessorDeserializeError::forward(buff.read_i16())?; + let y = ProcessorDeserializeError::forward(buff.read_i16())?; links.push(ProcessorLink{name: String::from(name), x, y}); } - Some(Self::create_state(ProcessorState{code, links})) + Ok(Some(Self::create_state(ProcessorState{code, links}))) }, - _ => panic!("{} cannot use data of {:?}", type_name::<Self>(), data.get_type()), + _ => Err(DeserializeError::InvalidType{have: data.get_type(), expect: DynType::Boolean}), } } @@ -214,27 +217,21 @@ impl BlockLogic for ProcessorLogic Box::new(Self::get_state(state).clone()) } - fn serialize_state(&self, state: &dyn Any) -> DynData + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { let state = Self::get_state(state); let mut rbuff = DataWrite::new(); - rbuff.write_u8(1).unwrap(); - if state.code.len() > i32::MAX as usize - { - panic!("code too long ({})", state.code.len()); - } - rbuff.write_i32(state.code.len() as i32).unwrap(); - rbuff.write_bytes(state.code.as_bytes()).unwrap(); - if state.links.len() > i32::MAX as usize - { - panic!("too many links ({})", state.links.len()); - } - rbuff.write_i32(state.links.len() as i32).unwrap(); + ProcessorSerializeError::forward(rbuff.write_u8(1))?; + assert!(state.code.len() < 500 * 1024); + ProcessorSerializeError::forward(rbuff.write_i32(state.code.len() as i32))?; + ProcessorSerializeError::forward(rbuff.write_bytes(state.code.as_bytes()))?; + assert!(state.links.len() < i32::MAX as usize); + ProcessorSerializeError::forward(rbuff.write_i32(state.links.len() as i32))?; for link in state.links.iter() { - rbuff.write_utf(&link.name).unwrap(); - rbuff.write_i16(link.x).unwrap(); - rbuff.write_i16(link.y).unwrap(); + ProcessorSerializeError::forward(rbuff.write_utf(&link.name))?; + ProcessorSerializeError::forward(rbuff.write_i16(link.x))?; + ProcessorSerializeError::forward(rbuff.write_i16(link.y))?; } let mut input = rbuff.get_written(); let mut comp = Compress::new(Compression::default(), true); @@ -244,7 +241,7 @@ impl BlockLogic for ProcessorLogic { let t_in = comp.total_in(); let t_out = comp.total_out(); - let res = comp.compress_vec(input, &mut dst, FlushCompress::Finish).unwrap(); + let res = ProcessorSerializeError::forward(comp.compress_vec(input, &mut dst, FlushCompress::Finish))?; if comp.total_in() > t_in { // we have to advance input every time, compress_vec only knows the output position @@ -260,11 +257,152 @@ impl BlockLogic for ProcessorLogic if comp.total_in() == t_in && comp.total_out() == t_out { // protect against looping forever - panic!("compressor stalled"); + return Err(SerializeError::Custom(Box::new(ProcessorSerializeError::CompressStall))); } dst.reserve(1024); } - DynData::ByteArray(dst) + Ok(DynData::ByteArray(dst)) + } +} + +#[derive(Debug)] +pub enum ProcessorDeserializeError +{ + Read(data::ReadError), + Decompress(DecompressError), + DecompressStall, + FromUtf8(FromUtf8Error), + Version(u8), + CodeLength(i32), + LinkCount(i32), +} + +impl ProcessorDeserializeError +{ + pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> + { + match result + { + Ok(v) => Ok(v), + Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))), + } + } +} + +impl From<data::ReadError> for ProcessorDeserializeError +{ + fn from(value: data::ReadError) -> Self + { + Self::Read(value) + } +} + +impl From<DecompressError> for ProcessorDeserializeError +{ + fn from(value: DecompressError) -> Self + { + Self::Decompress(value) + } +} + +impl From<FromUtf8Error> for ProcessorDeserializeError +{ + fn from(value: FromUtf8Error) -> Self + { + Self::FromUtf8(value) + } +} + +impl fmt::Display for ProcessorDeserializeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::Read(..) => write!(f, "Failed to read data from buffer"), + Self::Decompress(e) => e.fmt(f), + Self::DecompressStall => write!(f, "Decompressor stalled before completion"), + Self::FromUtf8(e) => e.fmt(f), + Self::Version(ver) => write!(f, "Unsupported version ({ver})"), + Self::CodeLength(len) => write!(f, "Invalid code length ({len})"), + Self::LinkCount(cnt) => write!(f, "Invalid link count ({cnt})"), + } + } +} + +impl Error for ProcessorDeserializeError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> + { + match self + { + Self::Decompress(e) => Some(e), + Self::FromUtf8(e) => Some(e), + _ => None, + } + } +} + +#[derive(Debug)] +pub enum ProcessorSerializeError +{ + Write(data::WriteError), + Compress(CompressError), + CompressEof(usize), + CompressStall, +} + +impl ProcessorSerializeError +{ + pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, SerializeError> + { + match result + { + Ok(v) => Ok(v), + Err(e) => Err(SerializeError::Custom(Box::new(e.into()))), + } + } +} + +impl From<data::WriteError> for ProcessorSerializeError +{ + fn from(value: data::WriteError) -> Self + { + Self::Write(value) + } +} + +impl From<CompressError> for ProcessorSerializeError +{ + fn from(value: CompressError) -> Self + { + Self::Compress(value) + } +} + +impl fmt::Display for ProcessorSerializeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::Write(..) => write!(f, "Failed to write data to buffer"), + Self::Compress(e) => e.fmt(f), + Self::CompressEof(remain) => write!(f, "Compression overflow with {remain} bytes of input remaining"), + Self::CompressStall => write!(f, "Compressor stalled before completion"), + } + } +} + +impl Error for ProcessorSerializeError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> + { + match self + { + Self::Compress(e) => Some(e), + _ => None, + } } } @@ -345,13 +483,13 @@ impl ProcessorState { if name.len() > u16::MAX as usize { - return Err(CreateError::NameLength{len: name.len()}) + return Err(CreateError::NameLength(name.len())) } for curr in self.links.iter() { if &name == &curr.name { - return Err(CreateError::DuplicateName{name}); + return Err(CreateError::DuplicateName(name)); } if x == curr.x && y == curr.y { @@ -386,10 +524,38 @@ pub enum CodeError TooLong(usize), } +impl fmt::Display for CodeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::TooLong(len) => write!(f, "Code too long ({len} bytes)"), + } + } +} + +impl Error for CodeError {} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum CreateError { - NameLength{len: usize}, - DuplicateName{name: String}, + NameLength(usize), + DuplicateName(String), DuplicatePos{name: String, x: i16, y: i16}, } + +impl fmt::Display for CreateError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::NameLength(len) => write!(f, "Link name too long ({len} bytes)"), + Self::DuplicateName(name) => write!(f, "There already is a link named {name}"), + Self::DuplicatePos{name, x, y} => write!(f, "Link {name} already points to {x} / {y}"), + } + } +} + +impl Error for CreateError {} diff --git a/src/block/mod.rs b/src/block/mod.rs index bcd475e..82ca0a0 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -2,9 +2,11 @@ use std::any::Any; use std::borrow::Cow; use std::collections::HashMap; use std::collections::hash_map::Entry; +use std::error::Error; +use std::fmt; use crate::access::BoxAccess; -use crate::data::dynamic::DynData; +use crate::data::dynamic::{DynData, DynType}; pub mod base; pub mod defense; @@ -26,11 +28,94 @@ pub trait BlockLogic fn data_from_i32(&self, config: i32) -> DynData; - fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>>; + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError>; fn clone_state(&self, state: &dyn Any) -> Box<dyn Any>; - fn serialize_state(&self, state: &dyn Any) -> DynData; + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError>; +} + +#[derive(Debug)] +pub enum DeserializeError +{ + InvalidType{have: DynType, expect: DynType}, + Custom(Box<dyn Error>), +} + +impl DeserializeError +{ + pub fn filter<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self> + { + match result + { + Ok(v) => Ok(v), + Err(e) => Err(Self::Custom(Box::new(e))), + } + } +} + +impl fmt::Display for DeserializeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::InvalidType{have, expect} => write!(f, "Expected type {expect:?} but got {have:?}"), + Self::Custom(e) => e.fmt(f), + } + } +} + +impl Error for DeserializeError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> + { + match self + { + Self::Custom(e) => Some(e.as_ref()), + _ => None, + } + } +} + +#[derive(Debug)] +pub enum SerializeError +{ + Custom(Box<dyn Error>), +} + +impl SerializeError +{ + pub fn filter<T, E: Error + 'static>(result: Result<T, E>) -> Result<T, Self> + { + match result + { + Ok(v) => Ok(v), + Err(e) => Err(Self::Custom(Box::new(e))), + } + } +} + +impl fmt::Display for SerializeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::Custom(e) => e.fmt(f), + } + } +} + +impl Error for SerializeError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> + { + match self + { + Self::Custom(e) => Some(e.as_ref()), + } + } } pub struct Block @@ -66,7 +151,7 @@ impl Block self.logic.data_from_i32(config) } - pub fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>> + pub fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { self.logic.deserialize_state(data) } @@ -76,7 +161,7 @@ impl Block self.logic.clone_state(state) } - pub fn serialize_state(&self, state: &dyn Any) -> DynData + pub fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { self.logic.serialize_state(state) } diff --git a/src/block/simple.rs b/src/block/simple.rs index c363064..87f631f 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -1,6 +1,6 @@ use std::any::{Any, type_name}; -use crate::block::BlockLogic; +use crate::block::{BlockLogic, DeserializeError, SerializeError}; use crate::data::dynamic::DynData; macro_rules!gen_state_empty @@ -12,9 +12,9 @@ macro_rules!gen_state_empty DynData::Empty } - fn deserialize_state(&self, _: DynData) -> Option<Box<dyn Any>> + fn deserialize_state(&self, _: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { - None + Ok(None) } fn clone_state(&self, _: &dyn Any) -> Box<dyn Any> @@ -22,9 +22,9 @@ macro_rules!gen_state_empty panic!("{} has no custom state", type_name::<Self>()) } - fn serialize_state(&self, _: &dyn Any) -> DynData + fn serialize_state(&self, _: &dyn Any) -> Result<DynData, SerializeError> { - DynData::Empty + Ok(DynData::Empty) } }; } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 4cdad07..eca03a2 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -1,13 +1,14 @@ use std::any::Any; use std::collections::HashMap; use std::collections::hash_map::Entry; +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 crate::block::{Block, BlockRegistry, Rotation}; +use crate::block::{self, Block, BlockRegistry, Rotation}; use crate::data::{self, DataRead, DataWrite, GridPos, Serializer}; use crate::data::base64; use crate::data::dynamic::{self, DynSerializer, DynData}; @@ -53,10 +54,10 @@ impl Placement } } - pub fn set_state(&mut self, data: DynData) -> Option<Box<dyn Any>> + pub fn set_state(&mut self, data: DynData) -> Result<Option<Box<dyn Any>>, block::DeserializeError> { - let state = self.block.deserialize_state(data); - std::mem::replace(&mut self.state, state) + let state = self.block.deserialize_state(data)?; + Ok(std::mem::replace(&mut self.state, state)) } pub fn get_rotation(&self) -> Rotation @@ -261,7 +262,7 @@ impl Schematic if self.is_region_empty(x - off, y - off, sz, sz) { let idx = self.blocks.len(); - let state = block.deserialize_state(data); + 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]) @@ -298,7 +299,7 @@ impl Schematic } } let idx = self.blocks.len(); - let state = block.deserialize_state(data); + 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) @@ -311,14 +312,14 @@ impl Schematic None => { let idx = self.blocks.len(); - let state = block.deserialize_state(data); + 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 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)); @@ -470,11 +471,20 @@ impl fmt::Display for PosError } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Debug)] 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 fmt::Display for PlaceError @@ -485,6 +495,19 @@ impl fmt::Display for PlaceError { PlaceError::Bounds{x, y, sz, w, h} => write!(f, "Block placement {x} / {y} (size {sz}) within {w} / {h}"), PlaceError::Overlap{x, y} => write!(f, "Overlapping an existing block at {x} / {y}"), + PlaceError::Deserialize(e) => e.fmt(f), + } + } +} + +impl Error for PlaceError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> + { + match self + { + PlaceError::Deserialize(e) => Some(e), + _ => None, } } } @@ -835,7 +858,7 @@ impl<'l> Serializer<Schematic> for SchematicSerializer<'l> let data = match curr.state { None => DynData::Empty, - Some(ref s) => curr.block.serialize_state(s.as_ref()), + Some(ref s) => curr.block.serialize_state(s.as_ref())?, }; DynSerializer.serialize(&mut rbuff, &data)?; rbuff.write_u8(curr.rot.into())?; @@ -974,6 +997,7 @@ pub enum WriteError Write(data::WriteError), TagCount(usize), TableSize(usize), + StateSerialize(block::SerializeError), BlockState(dynamic::WriteError), Compress(CompressError), CompressEof(usize), @@ -988,6 +1012,14 @@ impl From<data::WriteError> for WriteError } } +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 @@ -1013,6 +1045,7 @@ impl fmt::Display for WriteError WriteError::Write(..) => write!(f, "Failed to write data to buffer"), WriteError::TagCount(cnt) => write!(f, "Invalid tag count ({cnt})"), WriteError::TableSize(cnt) => write!(f, "Invalid block table size ({cnt})"), + WriteError::StateSerialize(e) => e.fmt(f), WriteError::BlockState(..) => write!(f, "Failed to write block state"), WriteError::Compress(e) => e.fmt(f), WriteError::CompressEof(remain) => write!(f, "Compression overflow with {remain} bytes of input remaining"), |