mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/data/dynamic.rs')
-rw-r--r--src/data/dynamic.rs550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/data/dynamic.rs b/src/data/dynamic.rs
new file mode 100644
index 0000000..344144b
--- /dev/null
+++ b/src/data/dynamic.rs
@@ -0,0 +1,550 @@
+//! variable type
+use thiserror::Error;
+
+use crate::content;
+use crate::data::command::{self, UnitCommand};
+use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
+use crate::logic::LogicField;
+use crate::team::Team;
+
+macro_rules! datamaker {
+ (
+ $($k: ident($v: ty),)+
+ ) => { paste::paste! {
+ #[derive(Clone, Debug, PartialEq)]
+ /// holds different kinds of data
+ pub enum DynData {
+ Empty,
+ Content(content::Type, u16),
+ Point2(i32, i32),
+ Vec2(f32, f32),
+ TechNode(content::Type, u16),
+ $($k($v),)+
+ }
+
+ $(
+ impl From<$v> for DynData {
+ #[doc = concat!(" to [`DynData::", stringify!($k), "`]")]
+ fn from(f: $v) -> Self {
+ Self::$k(f)
+ }
+ }
+ )+
+ } }
+}
+
+datamaker! {
+ Int(i32),
+ Long(i64),
+ Float(f32),
+ String(Option<String>),
+ IntArray(Vec<i32>),
+ Point2Array(Vec<(i16, i16)>),
+ Boolean(bool),
+ Double(f64),
+ Building(GridPos),
+ LogicField(LogicField),
+ ByteArray(Vec<u8>),
+ UnitCommand(UnitCommand),
+ BoolArray(Vec<bool>),
+ Unit(u32),
+ Vec2Array(Vec<(f32, f32)>),
+ Team(Team),
+}
+
+impl DynData {
+ #[must_use]
+ pub const fn get_type(&self) -> DynType {
+ match self {
+ Self::Empty => DynType::Empty,
+ Self::Int(..) => DynType::Int,
+ Self::Long(..) => DynType::Long,
+ Self::Float(..) => DynType::Float,
+ Self::String(..) => DynType::String,
+ Self::Content(..) => DynType::Content,
+ Self::IntArray(..) => DynType::IntArray,
+ Self::Point2(..) => DynType::Point2,
+ Self::Point2Array(..) => DynType::Point2Array,
+ Self::TechNode(..) => DynType::TechNode,
+ Self::Boolean(..) => DynType::Boolean,
+ Self::Double(..) => DynType::Double,
+ Self::Building(..) => DynType::Building,
+ Self::LogicField(..) => DynType::LogicField,
+ Self::ByteArray(..) => DynType::ByteArray,
+ Self::UnitCommand(..) => DynType::UnitCommand,
+ Self::BoolArray(..) => DynType::BoolArray,
+ Self::Unit(..) => DynType::Unit,
+ Self::Vec2Array(..) => DynType::Vec2Array,
+ Self::Vec2(..) => DynType::Vec2,
+ Self::Team(..) => DynType::Team,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum DynType {
+ Empty,
+ Int,
+ Long,
+ Float,
+ String,
+ Content,
+ IntArray,
+ Point2,
+ Point2Array,
+ TechNode,
+ Boolean,
+ Double,
+ Building,
+ LogicField,
+ ByteArray,
+ UnitCommand,
+ BoolArray,
+ Unit,
+ Vec2Array,
+ Vec2,
+ Team,
+}
+
+pub struct DynSerializer;
+
+impl Serializer<DynData> for DynSerializer {
+ type ReadError = ReadError;
+ type WriteError = WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<DynData, Self::ReadError> {
+ match buff.read_u8()? {
+ 0 => Ok(DynData::Empty),
+ 1 => Ok(DynData::from(buff.read_i32()?)),
+ 2 => Ok(DynData::from(buff.read_i64()?)),
+ 3 => Ok(DynData::from(buff.read_f32()?)),
+ 4 => {
+ if buff.read_bool()? {
+ Ok(DynData::from(Some(String::from(buff.read_utf()?))))
+ } else {
+ Ok(DynData::from(None))
+ }
+ }
+ 5 => Ok(DynData::Content(
+ content::Type::try_from(buff.read_u8()?)?,
+ buff.read_u16()?,
+ )),
+ 6 => {
+ let len = buff.read_i16()?;
+ let Ok(len) = usize::try_from(len) else {
+ return Err(ReadError::IntArrayLen(len));
+ };
+ let mut result = vec![0; len];
+ for item in result.iter_mut() {
+ *item = buff.read_i32()?;
+ }
+ Ok(DynData::from(result))
+ }
+ 7 => Ok(DynData::Point2(buff.read_i32()?, buff.read_i32()?)),
+ 8 => {
+ let len = buff.read_i8()?;
+ let Ok(len) = usize::try_from(len) else {
+ return Err(ReadError::Point2ArrayLen(len));
+ };
+ let mut result = vec![(0, 0); len];
+ for item in result.iter_mut() {
+ let pt = buff.read_i32()?;
+ *item = ((pt >> 16) as i16, pt as i16);
+ }
+ Ok(DynData::from(result))
+ }
+ 9 => Ok(DynData::TechNode(
+ content::Type::try_from(buff.read_u8()?)?,
+ buff.read_u16()?,
+ )),
+ 10 => Ok(DynData::from(buff.read_bool()?)),
+ 11 => Ok(DynData::from(buff.read_f64()?)),
+ 12 => Ok(DynData::from(GridPos::from(buff.read_u32()?))),
+ 13 => Ok(DynData::from(LogicField::try_from(buff.read_u8()?)?)),
+ 14 => {
+ let len = buff.read_i32()?;
+ let Ok(len) = usize::try_from(len) else {
+ return Err(ReadError::ByteArrayLen(len));
+ };
+ let mut result = vec![];
+ buff.read_vec(&mut result, len)?;
+ Ok(DynData::from(result))
+ }
+ 16 => {
+ let len = buff.read_i32()?;
+ let Ok(len) = usize::try_from(len) else {
+ return Err(ReadError::BoolArrayLen(len));
+ };
+ let mut result = vec![];
+ result.reserve(len);
+ for _ in 0..len {
+ result.push(buff.read_bool()?);
+ }
+ Ok(DynData::from(result))
+ }
+ 17 => Ok(DynData::Unit(buff.read_u32()?)),
+ 18 => {
+ let len = buff.read_i16()?;
+ let Ok(len) = usize::try_from(len) else {
+ return Err(ReadError::Vec2ArrayLen(len));
+ };
+ let mut result = vec![(0., 0.); len];
+ for item in result.iter_mut() {
+ *item = (buff.read_f32()?, buff.read_f32()?);
+ }
+ Ok(DynData::from(result))
+ }
+ 19 => Ok(DynData::Vec2(buff.read_f32()?, buff.read_f32()?)),
+ 20 => Ok(DynData::from(Team::of(buff.read_u8()?))),
+ 23 => Ok(DynData::from(
+ UnitCommand::try_from(buff.read_i16()? as u8)?,
+ )),
+ id => Err(ReadError::Type(id)),
+ }
+ }
+
+ fn serialize(
+ &mut self,
+ buff: &mut DataWrite<'_>,
+ data: &DynData,
+ ) -> Result<(), Self::WriteError> {
+ match data {
+ DynData::Empty => {
+ buff.write_u8(0)?;
+ Ok(())
+ }
+ DynData::Int(val) => {
+ buff.write_u8(1)?;
+ buff.write_i32(*val)?;
+ Ok(())
+ }
+ DynData::Long(val) => {
+ buff.write_u8(2)?;
+ buff.write_i64(*val)?;
+ Ok(())
+ }
+ DynData::Float(val) => {
+ buff.write_u8(3)?;
+ buff.write_f32(*val)?;
+ Ok(())
+ }
+ DynData::String(opt) => {
+ buff.write_u8(4)?;
+ match opt {
+ None => buff.write_bool(false)?,
+ Some(s) => {
+ buff.write_bool(true)?;
+ buff.write_utf(s)?;
+ }
+ }
+ Ok(())
+ }
+ DynData::Content(ty, id) => {
+ buff.write_u8(5)?;
+ buff.write_u8((*ty).into())?;
+ buff.write_u16(*id)?;
+ Ok(())
+ }
+ DynData::IntArray(arr) => {
+ if arr.len() > i16::MAX as usize {
+ return Err(WriteError::IntArrayLen(arr.len()));
+ }
+ buff.write_u8(6)?;
+ buff.write_i16(arr.len() as i16)?;
+ for &v in arr {
+ buff.write_i32(v)?;
+ }
+ Ok(())
+ }
+ DynData::Point2(x, y) => {
+ buff.write_u8(7)?;
+ buff.write_i32(*x)?;
+ buff.write_i32(*y)?;
+ Ok(())
+ }
+ DynData::Point2Array(arr) => {
+ if arr.len() > i8::MAX as usize {
+ return Err(WriteError::Point2ArrayLen(arr.len()));
+ }
+ buff.write_u8(8)?;
+ buff.write_i8(arr.len() as i8)?;
+ for &(x, y) in arr {
+ buff.write_i32((i32::from(x) << 16) | (i32::from(y) & 0xFFFF))?;
+ }
+ Ok(())
+ }
+ DynData::TechNode(ty, id) => {
+ buff.write_u8(9)?;
+ buff.write_u8((*ty).into())?;
+ buff.write_u16(*id)?;
+ Ok(())
+ }
+ DynData::Boolean(val) => {
+ buff.write_u8(10)?;
+ buff.write_bool(*val)?;
+ Ok(())
+ }
+ DynData::Double(val) => {
+ buff.write_u8(11)?;
+ buff.write_f64(*val)?;
+ Ok(())
+ }
+ DynData::Building(pos) => {
+ buff.write_u8(12)?;
+ buff.write_u32(u32::from(*pos))?;
+ Ok(())
+ }
+ DynData::LogicField(fld) => {
+ buff.write_u8(13)?;
+ buff.write_u8(u8::from(*fld))?;
+ Ok(())
+ }
+ DynData::ByteArray(arr) => {
+ if arr.len() > i32::MAX as usize {
+ return Err(WriteError::ByteArrayLen(arr.len()));
+ }
+ buff.write_u8(14)?;
+ buff.write_i32(arr.len() as i32)?;
+ buff.write_bytes(arr)?;
+ Ok(())
+ }
+ DynData::UnitCommand(cmd) => {
+ buff.write_u8(23)?;
+ buff.write_u16(u8::from(*cmd).into())?;
+ Ok(())
+ }
+ DynData::BoolArray(arr) => {
+ if arr.len() > i32::MAX as usize {
+ return Err(WriteError::BoolArrayLen(arr.len()));
+ }
+ buff.write_u8(16)?;
+ buff.write_i32(arr.len() as i32)?;
+ for &b in arr {
+ buff.write_bool(b)?;
+ }
+ Ok(())
+ }
+ DynData::Unit(id) => {
+ buff.write_u8(17)?;
+ buff.write_u32(*id)?;
+ Ok(())
+ }
+ DynData::Vec2Array(arr) => {
+ if arr.len() > i16::MAX as usize {
+ return Err(WriteError::Vec2ArrayLen(arr.len()));
+ }
+ buff.write_u8(18)?;
+ buff.write_i16(arr.len() as i16)?;
+ for &(x, y) in arr {
+ buff.write_f32(x)?;
+ buff.write_f32(y)?;
+ }
+ Ok(())
+ }
+ DynData::Vec2(x, y) => {
+ buff.write_u8(19)?;
+ buff.write_f32(*x)?;
+ buff.write_f32(*y)?;
+ Ok(())
+ }
+ DynData::Team(team) => {
+ buff.write_u8(20)?;
+ buff.write_u8(u8::from(*team))?;
+ Ok(())
+ }
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Error)]
+pub enum ReadError {
+ #[error("failed to read from buffer")]
+ Underlying(#[from] data::ReadError),
+ #[error("invalid dynamic data type ({0})")]
+ Type(u8),
+ #[error("content type not found")]
+ ContentType(#[from] content::TryFromU8Error),
+ #[error("integer array too long ({0})")]
+ IntArrayLen(i16),
+ #[error("point2 array too long ({0})")]
+ Point2ArrayLen(i8),
+ #[error("invalid logic field ({0})")]
+ LogicField(#[from] crate::logic::TryFromU8Error),
+ #[error("byte array too long ({0})")]
+ ByteArrayLen(i32),
+ #[error("unit command not found")]
+ UnitCommand(#[from] command::TryFromU8Error),
+ #[error("boolean array too long ({0}")]
+ BoolArrayLen(i32),
+ #[error("vec2 array too long ({0})")]
+ Vec2ArrayLen(i16),
+}
+
+#[derive(Debug, PartialEq, Error)]
+pub enum WriteError {
+ #[error("failed to write to buffer")]
+ Underlying(#[from] data::WriteError),
+ #[error("integer array too long ({0})")]
+ IntArrayLen(usize),
+ #[error("point2 array too long ({0})")]
+ Point2ArrayLen(usize),
+ #[error("byte array too long ({0})")]
+ ByteArrayLen(usize),
+ #[error("boolean array too long ({0})")]
+ BoolArrayLen(usize),
+ #[error("vec2 array too long ({0})")]
+ Vec2ArrayLen(usize),
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::team::{CRUX, DERELICT, SHARDED};
+
+ macro_rules! _zero {
+ ($tt:tt) => {
+ 0usize
+ };
+ }
+
+ macro_rules! make_dyn_test {
+ ($name:ident, $($val:expr),+) => {
+ #[test]
+ fn $name() {
+ let input = [$($val),+];
+ let mut positions = [$(_zero!($val)),+];
+ let mut writer = DataWrite::default();
+ for (i, d) in input.iter().enumerate()
+ {
+ assert_eq!(DynSerializer.serialize(&mut writer, d), Ok(()));
+ positions[i] = writer.get_written().len();
+ }
+ let written = writer.get_written();
+ let end = written.len();
+ let mut reader = DataRead::new(written);
+ for (i, original) in input.iter().enumerate()
+ {
+ match DynSerializer.deserialize(&mut reader)
+ {
+ Ok(read) => assert_eq!(*original, read, "serialization of {original:?} became {read:?}"),
+ e => assert!(false, "could not re-read {original:?} (at {i}), got {e:?}"),
+ }
+ let expect = end - reader.data.len();
+ let before = if i > 0 {positions[i - 1]} else {0};
+ assert_eq!(expect, positions[i], "uneven deserialization of {original:?} ({} vs {})", expect - before, positions[i] - before);
+ }
+ }
+ };
+ }
+
+ make_dyn_test!(
+ reparse_empty,
+ DynData::Empty,
+ DynData::Empty,
+ DynData::Empty
+ );
+ make_dyn_test!(
+ reparse_int,
+ DynData::Int(581_923),
+ DynData::Int(2_147_483_647),
+ DynData::Int(-1_047_563_850)
+ );
+ make_dyn_test!(
+ reparse_long,
+ DynData::Long(11_295_882_949_812),
+ DynData::Long(-5_222_358_074_010_407_789)
+ );
+ make_dyn_test!(
+ reparse_float,
+ DynData::Float(std::f32::consts::PI),
+ DynData::Float(f32::INFINITY),
+ DynData::Float(f32::EPSILON)
+ );
+ make_dyn_test!(
+ reparse_string,
+ DynData::String(None),
+ DynData::String(Some("hello \u{10FE03}".to_string())),
+ DynData::String(Some(String::new()))
+ );
+ make_dyn_test!(
+ reparse_content,
+ DynData::Content(content::Type::Item, 12345),
+ DynData::Content(content::Type::Planet, 25431)
+ );
+ make_dyn_test!(
+ reparse_int_array,
+ DynData::IntArray(vec![581_923, 2_147_483_647, -1_047_563_850]),
+ DynData::IntArray(vec![1_902_864_703])
+ );
+ make_dyn_test!(
+ reparse_point2,
+ DynData::Point2(17, 0),
+ DynData::Point2(234, -345),
+ DynData::Point2(-2_147_483_648, -1)
+ );
+ make_dyn_test!(
+ reparse_point2_array,
+ DynData::Point2Array(vec![(44, 55), (-33, 66), (-22, -77)]),
+ DynData::Point2Array(vec![(22, -88)])
+ );
+ make_dyn_test!(
+ reparse_technode,
+ DynData::TechNode(content::Type::Item, 12345),
+ DynData::TechNode(content::Type::Planet, 25431)
+ );
+ make_dyn_test!(
+ reparse_boolean,
+ DynData::Boolean(false),
+ DynData::Boolean(true),
+ DynData::Boolean(false)
+ );
+ make_dyn_test!(
+ reparse_double,
+ DynData::Double(std::f64::consts::E),
+ DynData::Double(-f64::INFINITY)
+ );
+ make_dyn_test!(
+ reparse_building,
+ DynData::Building(GridPos(10, 0)),
+ DynData::Building(GridPos(4444, 0xFE98))
+ );
+ make_dyn_test!(
+ reparse_logic,
+ DynData::LogicField(LogicField::Enabled),
+ DynData::LogicField(LogicField::Shoot),
+ DynData::LogicField(LogicField::Color)
+ );
+ make_dyn_test!(
+ reparse_byte_array,
+ DynData::ByteArray(b"c\x00nstruct \xADditio\nal pylons".to_vec()),
+ DynData::ByteArray(b"\x00\x01\xFE\xFF".to_vec())
+ );
+ make_dyn_test!(
+ reparse_unit_command,
+ DynData::UnitCommand(UnitCommand::Mine),
+ DynData::UnitCommand(UnitCommand::Mine)
+ );
+ make_dyn_test!(
+ reparse_bool_array,
+ DynData::BoolArray(vec![true, true, true, false, true, false, true]),
+ DynData::BoolArray(vec![false, true])
+ );
+ make_dyn_test!(reparse_unit, DynData::Unit(0), DynData::Unit(2_147_483_647));
+ make_dyn_test!(
+ reparse_vec2_array,
+ DynData::Vec2Array(vec![(4.4, 5.5), (-3.3, 6.6), (-2.2, -7.7)]),
+ DynData::Vec2Array(vec![(2.2, -8.8)])
+ );
+ make_dyn_test!(
+ reparse_vec2,
+ DynData::Vec2(1.5, 9.1234),
+ DynData::Vec2(-0.0, -17.0),
+ DynData::Vec2(-10.7, 3.8)
+ );
+ make_dyn_test!(
+ reparse_team,
+ DynData::Team(SHARDED),
+ DynData::Team(CRUX),
+ DynData::Team(DERELICT)
+ );
+}