mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/block/logic.rs')
-rw-r--r--src/block/logic.rs631
1 files changed, 631 insertions, 0 deletions
diff --git a/src/block/logic.rs b/src/block/logic.rs
new file mode 100644
index 0000000..a099765
--- /dev/null
+++ b/src/block/logic.rs
@@ -0,0 +1,631 @@
+//! logic processors and stuff
+use std::borrow::Cow;
+use std::string::FromUtf8Error;
+
+use crate::block::simple::*;
+use crate::data::dynamic::{DynSerializer, DynType};
+use crate::{block::*, Serializer};
+
+use crate::data::{self, CompressError, DataRead, DataWrite};
+
+make_simple!(
+ MemoryBlock =>
+ |_, _, _, buff: &mut DataRead| {
+ // format:
+ // - iterate [`u32`]
+ // - memory: [`f64`]
+ let n = buff.read_u32()? as usize;
+ buff.skip(n * 8)
+ }
+);
+
+make_register! {
+ "reinforced-message" -> MessageLogic::new(1, true, cost!(Graphite: 10, Beryllium: 5));
+ "message" -> MessageLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
+ "switch" => SwitchLogic::new(1, true, cost!(Copper: 5, Graphite: 5));
+ "micro-processor" -> ProcessorLogic::new(1, true, cost!(Copper: 90, Lead: 50, Silicon: 50));
+ "logic-processor" -> ProcessorLogic::new(2, true, cost!(Lead: 320, Graphite: 60, Thorium: 50, Silicon: 80));
+ "hyper-processor" -> ProcessorLogic::new(3, true, cost!(Lead: 450, Thorium: 75, Silicon: 150, SurgeAlloy: 50));
+ "memory-cell" -> MemoryBlock::new(1, true, cost!(Copper: 30, Graphite: 30, Silicon: 30));
+ "memory-bank" -> MemoryBlock::new(2, true, cost!(Copper: 30, Graphite: 80, Silicon: 80, PhaseFabric: 30));
+ "logic-display" -> BasicBlock::new(3, true, cost!(Lead: 100, Metaglass: 50, Silicon: 50));
+ "large-logic-display" -> BasicBlock::new(6, true, cost!(Lead: 200, Metaglass: 100, Silicon: 150, PhaseFabric: 75));
+ "canvas" => CanvasBlock::new(2, true, cost!(Silicon: 30, Beryllium: 10), 12);
+ // editor only
+ "world-processor" -> BasicBlock::new(1, true, &[]);
+ "world-message" -> MessageLogic::new(1, true, &[]);
+ "world-cell" -> MemoryBlock::new(1, true, &[]);
+}
+
+pub struct CanvasBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ canvas_size: u8,
+}
+
+macro_rules! h {
+ ($x:literal) => {{
+ let v = color_hex::color_from_hex!($x);
+ (v[0], v[1], v[2])
+ }};
+}
+const PALETTE: &[(u8, u8, u8); 8] = &[
+ h!("#362944"),
+ h!("#c45d9f"),
+ h!("#e39aac"),
+ h!("#f0dab1"),
+ h!("#6461c2"),
+ h!("#2ba9b4"),
+ h!("#93d4b5"),
+ h!("#f0f6e8"),
+];
+
+impl CanvasBlock {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, canvas_size: u8) -> Self {
+ assert!(size != 0, "invalid size");
+ assert!(canvas_size != 0, "invalid size");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ canvas_size,
+ }
+ }
+
+ state_impl!(pub Image<Vec<u8>, 1>);
+}
+
+fn deser_canvas_image(b: &[u8], size: usize) -> Image<Vec<u8>, 1> {
+ let mut p = Image::alloc(size as u32, size as u32);
+ for i in 0..(size * size) {
+ let offset = i * 3;
+ let mut n = 0;
+ for i in 0..3 {
+ let word = (i + offset) >> 3;
+ n |= (((b[word] & (1 << ((i + offset) & 7))) != 0) as u8) << i;
+ }
+ unsafe { p.set_pixel(i as u32 % size as u32, i as u32 / size as u32, [n]) };
+ }
+ p
+}
+
+impl BlockLogic for CanvasBlock {
+ impl_block!();
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ DynData::ByteArray(b) => Ok(Some(Self::create_state(deser_canvas_image(
+ &b,
+ self.canvas_size as usize,
+ )))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::String,
+ }),
+ }
+ }
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ let mut o = vec![0; self.canvas_size as usize * self.canvas_size as usize * 3];
+ let p = Self::get_state(state);
+ for i in 0..(self.canvas_size * self.canvas_size) as usize {
+ let index = unsafe {
+ p.pixel(
+ i as u32 % self.canvas_size as u32,
+ i as u32 / self.canvas_size as u32,
+ )[0]
+ };
+ let offset = i * 3;
+ for i in 0..3 {
+ let word = (i + offset) >> 3;
+ if index >> i & 1 == 0 {
+ o[word] &= !(1 << ((i + offset) & 7));
+ } else {
+ o[word] |= 1 << ((i + offset) & 7);
+ }
+ }
+ }
+ Ok(DynData::ByteArray(o))
+ }
+
+ /// i thought about drawing the borders and stuff but it felt like too much work
+ fn draw(
+ &self,
+ _: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ _: Rotation,
+ s: Scale,
+ ) -> ImageHolder<4> {
+ if let Some(state) = state {
+ let p = Self::get_state(state);
+ let offset = match s {
+ Scale::Full => 7,
+ // Scale::Half => 3,
+ Scale::Quarter => 2,
+ Scale::Eigth => 1,
+ };
+ let mut img = Image::alloc(p.width(), p.height());
+ for ([r, g, b, a], &y) in img.chunked_mut().zip(p.buffer.iter()) {
+ (*r, *g, *b) = PALETTE[y as usize];
+ *a = 255;
+ }
+ let img = img.as_mut().scale((s * self.size as u32) - offset * 2);
+ let mut borders = load!("canvas", s);
+ unsafe {
+ borders
+ .borrow_mut()
+ .overlay_at(&img.as_ref(), offset, offset)
+ };
+ return borders;
+ }
+
+ let mut def = Image::alloc(s * self.size as u32, s * self.size as u32);
+ for [r, g, b, a] in def.buffer.array_chunks_mut::<4>() {
+ (*r, *g, *b) = PALETTE[0];
+ *a = 255;
+ }
+ ImageHolder::from(def)
+ }
+
+ /// format:
+ /// - len: [`i32`]
+ /// - read(len) -> [`deser_canvas_image`]
+ fn read(
+ &self,
+ build: &mut Build,
+ _: &BlockRegistry,
+ _: &EntityMapping,
+ buff: &mut DataRead,
+ ) -> Result<(), DataReadError> {
+ let n = buff.read_i32()? as usize;
+ let mut b = vec![0; n];
+ buff.read_bytes(&mut b)?;
+ build.state = Some(Self::create_state(deser_canvas_image(
+ &b,
+ self.canvas_size as usize,
+ )));
+ Ok(())
+ }
+}
+
+pub struct MessageLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+}
+
+impl MessageLogic {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ assert!(size != 0, "invalid size");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub String);
+}
+
+impl BlockLogic for MessageLogic {
+ impl_block!();
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn draw(
+ &self,
+ _: &str,
+ _: Option<&State>,
+ _: Option<&RenderingContext>,
+ _: Rotation,
+ _: Scale,
+ ) -> ImageHolder<4> {
+ unreachable!()
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ 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,
+ }),
+ }
+ }
+
+ fn mirror_state(&self, _: &mut State, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut State, _: bool) {}
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ Ok(DynData::String(Some(Self::get_state(state).clone())))
+ }
+
+ fn read(
+ &self,
+ b: &mut Build,
+ _: &BlockRegistry,
+ _: &EntityMapping,
+ buff: &mut DataRead,
+ ) -> Result<(), DataReadError> {
+ b.state = Some(Self::create_state(buff.read_utf()?.to_string()));
+ Ok(())
+ }
+}
+
+pub struct SwitchLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+}
+
+impl SwitchLogic {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ assert!(size != 0, "invalid size");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub bool);
+}
+
+impl BlockLogic for SwitchLogic {
+ impl_block!();
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ 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,
+ }),
+ }
+ }
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ Ok(DynData::Boolean(*Self::get_state(state)))
+ }
+
+ fn read(
+ &self,
+ build: &mut Build,
+ _: &BlockRegistry,
+ _: &EntityMapping,
+ buff: &mut DataRead,
+ ) -> Result<(), DataReadError> {
+ build.state = Some(Self::create_state(buff.read_bool()?));
+ Ok(())
+ }
+
+ fn draw(
+ &self,
+ _: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ _: Rotation,
+ s: Scale,
+ ) -> ImageHolder<4> {
+ let mut base = load!("switch", s);
+ if let Some(state) = state {
+ if *Self::get_state(state) {
+ let on = load!("switch-on", s);
+ unsafe { base.overlay(&on) };
+ return base;
+ }
+ }
+ base
+ }
+}
+
+pub struct ProcessorLogic {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+}
+
+impl ProcessorLogic {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ assert!(size != 0, "invalid size");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub ProcessorState);
+}
+
+fn read_decompressed(buff: &mut DataRead) -> Result<ProcessorState, ProcessorDeserializeError> {
+ let ver = buff.read_u8()?;
+ if ver != 1 {
+ return Err(ProcessorDeserializeError::Version(ver));
+ }
+
+ let code_len = buff.read_u32()? as usize;
+ if !(0..=500 * 1024).contains(&code_len) {
+ return Err(ProcessorDeserializeError::CodeLength(code_len));
+ }
+ let mut code = vec![0; code_len];
+ buff.read_bytes(&mut code)?;
+ let code = String::from_utf8(code)?;
+ let link_cnt = buff.read_u32()? as usize;
+ let mut links = vec![];
+ links.reserve(link_cnt);
+ for _ in 0..link_cnt {
+ let name = buff.read_utf()?;
+ let x = buff.read_i16()?;
+ let y = buff.read_i16()?;
+ links.push(ProcessorLink {
+ name: String::from(name),
+ x,
+ y,
+ });
+ }
+ Ok(ProcessorState { code, links })
+}
+
+impl BlockLogic for ProcessorLogic {
+ impl_block!();
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(ProcessorState::default()))),
+ DynData::ByteArray(arr) => {
+ let input = arr.as_ref();
+ let buff = DataRead::new(input).deflate()?;
+ Ok(Some(Self::create_state(
+ ProcessorDeserializeError::forward(read_decompressed(&mut DataRead::new(
+ &buff,
+ )))?,
+ )))
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Boolean,
+ }),
+ }
+ }
+
+ fn read(
+ &self,
+ b: &mut Build,
+ _: &BlockRegistry,
+ _: &EntityMapping,
+ buff: &mut DataRead,
+ ) -> Result<(), DataReadError> {
+ let n = buff.read_u32()? as usize;
+ let mut v = vec![0; n];
+ buff.read_bytes(&mut v)?;
+ v = DataRead::new(&v).deflate().unwrap();
+ b.state = Some(Self::create_state(
+ read_decompressed(&mut DataRead::new(&v)).unwrap(),
+ ));
+ for _ in 0..buff.read_u32()? {
+ let _ = buff.read_utf()?;
+ let _ = DynSerializer.deserialize(buff).unwrap();
+ }
+ let memory = buff.read_u32()? as usize;
+ buff.skip(memory * 8)?;
+ Ok(())
+ }
+
+ fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) {
+ for link in &mut Self::get_state_mut(state).links {
+ if horizontally {
+ link.x = -link.x;
+ }
+ if vertically {
+ link.y = -link.y;
+ }
+ }
+ }
+
+ fn rotate_state(&self, state: &mut State, clockwise: bool) {
+ for link in &mut Self::get_state_mut(state).links {
+ let (cdx, cdy) = link.get_pos();
+ link.x = if clockwise { cdy } else { -cdy };
+ link.y = if clockwise { -cdx } else { cdx };
+ }
+ }
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ let state = Self::get_state(state);
+ let mut rbuff = DataWrite::default();
+ 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 {
+ ProcessorSerializeError::forward(rbuff.write_utf(&link.name))?;
+ ProcessorSerializeError::forward(rbuff.write_i16(link.x))?;
+ ProcessorSerializeError::forward(rbuff.write_i16(link.y))?;
+ }
+ let mut out = DataWrite::default();
+ rbuff.inflate(&mut out)?;
+ Ok(DynData::ByteArray(out.consume()))
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ProcessorDeserializeError {
+ #[error("failed to read state data")]
+ Read(#[from] data::ReadError),
+ #[error("malformed utf-8 in processor code")]
+ FromUtf8(#[from] FromUtf8Error),
+ #[error("unsupported version ({0})")]
+ Version(u8),
+ #[error("invalid code length ({0})")]
+ CodeLength(usize),
+}
+
+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()))),
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ProcessorSerializeError {
+ #[error("failed to write state data")]
+ Write(#[from] data::WriteError),
+ #[error(transparent)]
+ Compress(#[from] CompressError),
+}
+
+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()))),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Default)]
+pub struct ProcessorLink {
+ name: String,
+ x: i16,
+ y: i16,
+}
+
+impl ProcessorLink {
+ #[must_use]
+ pub fn new(name: Cow<'_, str>, x: i16, y: i16) -> Self {
+ assert!(
+ u16::try_from(name.len()).is_ok(),
+ "name too long ({})",
+ name.len()
+ );
+ Self {
+ name: name.into_owned(),
+ x,
+ y,
+ }
+ }
+
+ #[must_use]
+ pub fn get_name(&self) -> &str {
+ &self.name
+ }
+
+ #[must_use]
+ pub const fn get_pos(&self) -> (i16, i16) {
+ (self.x, self.y)
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Default)]
+pub struct ProcessorState {
+ code: String,
+ links: Vec<ProcessorLink>,
+}
+
+impl ProcessorState {
+ #[must_use]
+ 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(())
+ }
+
+ #[must_use]
+ 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(name.len()));
+ }
+ for curr in &self.links {
+ 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, thiserror::Error)]
+pub enum CodeError {
+ #[error("code too long ({0} bytes)")]
+ TooLong(usize),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
+pub enum CreateError {
+ #[error("link name too long ({0} bytes)")]
+ NameLength(usize),
+ #[error("there is already a link named {0}")]
+ DuplicateName(String),
+ #[error("link {name} already points to ({x}, {y})")]
+ DuplicatePos { name: String, x: i16, y: i16 },
+}