mindustry logic execution, map- and schematic- parsing and rendering
Implement logic processor block states
| -rw-r--r-- | src/block/logic.rs | 293 |
1 files changed, 290 insertions, 3 deletions
diff --git a/src/block/logic.rs b/src/block/logic.rs index bc35a60..edd4297 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -1,16 +1,20 @@ use std::any::{Any, type_name}; +use std::borrow::Cow; + +use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status}; use crate::block::{BlockLogic, make_register}; use crate::block::simple::{SimpleBlock, state_impl}; +use crate::data::{DataRead, DataWrite}; use crate::data::dynamic::DynData; make_register! ( MESSAGE: "message" => MessageLogic; SWITCH: "switch" => SwitchLogic; - MICRO_PROCESSOR: "micro-processor" => SimpleBlock::new(1, true); // TODO config: code & links - LOGIC_PROCESSOR: "logic-processor" => SimpleBlock::new(2, true); // TODO config: code & links - HYPER_PROCESSOR: "hyper-processor" => SimpleBlock::new(3, true); // TODO config: code & links + MICRO_PROCESSOR: "micro-processor" => ProcessorLogic{size: 1}; + LOGIC_PROCESSOR: "logic-processor" => ProcessorLogic{size: 2}; + HYPER_PROCESSOR: "hyper-processor" => ProcessorLogic{size: 3}; MEMORY_CELL: "memory-cell" => SimpleBlock::new(1, true); MEMORY_BANK: "memory-bank" => SimpleBlock::new(2, true); LOGIC_DISPLAY: "logic-display" => SimpleBlock::new(3, true); @@ -106,3 +110,286 @@ impl BlockLogic for SwitchLogic DynData::Boolean(*Self::get_state(state)) } } + +pub struct ProcessorLogic +{ + size: u8, +} + +impl ProcessorLogic +{ + state_impl!(pub ProcessorState); +} + +impl BlockLogic for ProcessorLogic +{ + fn get_size(&self) -> u8 + { + self.size + } + + fn is_symmetric(&self) -> bool + { + true + } + + fn data_from_i32(&self, _: i32) -> DynData + { + DynData::Empty + } + + fn deserialize_state(&self, data: DynData) -> Option<Box<dyn Any>> + { + match data + { + DynData::Empty => Some(Self::create_state(ProcessorState::new())), + DynData::ByteArray(arr) => + { + let mut input = arr.as_ref(); + 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(input, &mut raw, FlushDecompress::Finish).unwrap(); + if dec.total_in() > t_in + { + // we have to advance input every time, decompress_vec only knows the output position + input = &input[(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 + panic!("decompressor stalled"); + } + raw.reserve(1024); + } + let mut buff = DataRead::new(&raw); + let ver = buff.read_u8().unwrap(); + if ver != 1 + { + panic!("unknown version {ver}"); + } + + let code_len = buff.read_i32().unwrap(); + if code_len < 0 || code_len > 500 * 1024 + { + panic!("invalid code length ({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(); + if link_cnt < 0 + { + panic!("link count is negative ({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(); + links.push(ProcessorLink{name: String::from(name), x, y}); + } + Some(Self::create_state(ProcessorState{code, links})) + }, + _ => panic!("{} cannot use data of {:?}", type_name::<Self>(), data.get_type()), + } + } + + fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> + { + Box::new(Self::get_state(state).clone()) + } + + fn serialize_state(&self, state: &dyn Any) -> DynData + { + 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(); + for link in state.links.iter() + { + rbuff.write_utf(&link.name).unwrap(); + rbuff.write_i16(link.x).unwrap(); + rbuff.write_i16(link.y).unwrap(); + } + let mut input = rbuff.get_written(); + let mut comp = Compress::new(Compression::default(), true); + let mut dst = Vec::<u8>::new(); + dst.reserve(1024); + loop + { + let t_in = comp.total_in(); + let t_out = comp.total_out(); + let res = comp.compress_vec(input, &mut dst, FlushCompress::Finish).unwrap(); + 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 + panic!("compressor stalled"); + } + dst.reserve(1024); + } + DynData::ByteArray(dst) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProcessorLink +{ + name: String, + x: i16, + y: i16, +} + +impl ProcessorLink +{ + pub fn new(name: Cow<'_, str>, x: i16, y: i16) -> Self + { + if name.len() > u16::MAX as usize + { + panic!("name too long ({})", name.len()); + } + Self{name: name.into_owned(), x, y} + } + + pub fn get_name(&self) -> &str + { + &self.name + } + + pub fn get_pos(&self) -> (i16, i16) + { + (self.x, self.y) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProcessorState +{ + code: String, + links: Vec<ProcessorLink> +} + +impl ProcessorState +{ + pub fn new() -> Self + { + Self{code: String::new(), links: Vec::new()} + } + + pub fn get_code(&self) -> &str + { + &self.code + } + + pub fn set_code(&mut self, code: Cow<'_, str>) -> Result<(), CodeError> + { + let as_str = &code as &str; + if as_str.len() > 500 * 1024 + { + return Err(CodeError::TooLong(as_str.len())); + } + match code + { + Cow::Borrowed(s) => + { + self.code.clear(); + self.code.push_str(s); + }, + Cow::Owned(s) => self.code = s, + } + Ok(()) + } + + pub fn get_links(&self) -> &[ProcessorLink] + { + &self.links + } + + pub fn create_link(&mut self, mut name: String, x: i16, y: i16) -> Result<&ProcessorLink, CreateError> + { + if name.len() > u16::MAX as usize + { + return Err(CreateError::NameLength{len: name.len()}) + } + for curr in self.links.iter() + { + if &name == &curr.name + { + return Err(CreateError::DuplicateName{name}); + } + if x == curr.x && y == curr.y + { + name.clear(); + name.push_str(&curr.name); + return Err(CreateError::DuplicatePos{name, x, y}); + } + } + let idx = self.links.len(); + self.links.push(ProcessorLink{name, x, y}); + Ok(&self.links[idx]) + } + + pub fn add_link(&mut self, link: ProcessorLink) -> Result<&ProcessorLink, CreateError> + { + self.create_link(link.name, link.x, link.y) + } + + pub fn remove_link(&mut self, idx: usize) -> Option<ProcessorLink> + { + if idx < self.links.len() + { + Some(self.links.remove(idx)) + } + else {None} + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CodeError +{ + TooLong(usize), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CreateError +{ + NameLength{len: usize}, + DuplicateName{name: String}, + DuplicatePos{name: String, x: i16, y: i16}, +} |