mindustry logic execution, map- and schematic- parsing and rendering
add MapReader
bendn 2024-01-25
parent b91c780 · commit baffdf0
-rw-r--r--mindus/Cargo.toml4
-rw-r--r--mindus/src/data/map.rs586
-rw-r--r--mindus/src/data/mod.rs8
-rw-r--r--mindus/src/data/renderer.rs186
-rw-r--r--mindus/src/data/schematic.rs20
-rw-r--r--mindus/src/exe/map.rs38
-rw-r--r--mindus/src/lib.rs3
7 files changed, 646 insertions, 199 deletions
diff --git a/mindus/Cargo.toml b/mindus/Cargo.toml
index 0c8ea9e..08ef178 100644
--- a/mindus/Cargo.toml
+++ b/mindus/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mindus"
-version = "5.0.17"
+version = "5.0.18"
edition = "2021"
description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)"
authors = [
@@ -31,7 +31,7 @@ bin = ["fimg/save"]
default = ["bin"]
[build-dependencies]
-fimg = { version = "0.4.33", features = ["scale", "save", "blur"], default-features = false }
+fimg = { version = "0.4.33", features = ["scale", "blur", "save"], default-features = false }
walkdir = "2"
[[bin]]
diff --git a/mindus/src/data/map.rs b/mindus/src/data/map.rs
index 6eb9b26..44df8fb 100644
--- a/mindus/src/data/map.rs
+++ b/mindus/src/data/map.rs
@@ -7,18 +7,19 @@
//! key: `: T` and `x<T>` both mean read T, `iterate T` means iterate `read_T()` times
//!
//! ZLIB compressed stream contains:
-//! - header: 4b = `MSCH`
-//! - version: `u32` (should be 7)
-//! - tag section `<u32>`
+//! - header: 4b = `MSCH` [`MapReader::header`]
+//! - version: `u32` (should be 7) [`MapReader::version`]
+//! - tag section `<u32>` [`MapReader::tags`]
//! - 1 byte of idk (skip)
//! - string map (`u16` for map len, iterate each, read `utf`)
-//! - content header section `<u32>`:
+//! - content header section `<u32>`: [`MapReader::skip`]
//! - iterate `i8` (should = `8`)'
-//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u16` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7)
+//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13
+//! - item count: `u16` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7)
//! - these types all have their own modules: [`item`], [`content`], [`fluid`], [`modifier`], [`mod@unit`], [`weather`], [`sector`], [`planet`]
//! - iterate `u16`
//! - name: `utf`
-//! - map section `<u32>`
+//! - map section `<u32>` [`MapReader::map`]
//! - width: `u16`, height: `u16`
//! - floor and tiles:
//! - for `i` in `w * h`
@@ -50,7 +51,7 @@
//! - iterate `(i + 1)..(i + 1 + consecutives)`
//! - same block
//! - i += consecutives
-//! - entities section `<u32>`
+//! - entities section `<u32>` [`MapReader::entities`]
//! - entity mapping
//! - iterate `u16`
//! - id: `i16`, name: `utf`
@@ -70,7 +71,9 @@
//! - id: `u32`
//! - entity read
use std::collections::HashMap;
-use std::ops::{Index, IndexMut};
+use std::ops::CoroutineState::*;
+use std::ops::{Coroutine, Index, IndexMut};
+use std::pin::Pin;
use thiserror::Error;
use crate::block::content::Type as BlockEnum;
@@ -105,6 +108,51 @@ macro_rules! lo {
} };
}
+#[inline]
+pub(crate) fn ore(ore: BlockEnum, s: Scale) -> Image<&'static [u8], 4> {
+ lo!(ore => ["ore-copper" | "ore-beryllium" | "ore-lead" | "ore-scrap" | "ore-coal" | "ore-thorium" | "ore-titanium" | "ore-tungsten" | "pebbles" | "tendrils" | "ore-wall-tungsten" | "ore-wall-beryllium" | "ore-wall-thorium" | "spawn" | "ore-crystal-thorium"], s)
+}
+
+#[inline]
+pub(crate) fn floor(tile: BlockEnum, s: Scale) -> Image<&'static [u8], 3> {
+ lo!(tile => [
+ | "darksand"
+ | "sand-floor"
+ | "dacite"
+ | "dirt"
+ | "arkycite-floor"
+ | "basalt"
+ | "moss"
+ | "mud"
+ | "grass"
+ | "ice-snow" | "snow" | "salt" | "ice"
+ | "hotrock" | "char" | "magmarock"
+ | "shale"
+ | "metal-floor" | "metal-floor-2" | "metal-floor-3" | "metal-floor-4" | "metal-floor-5" | "metal-floor-damaged"
+ | "dark-panel-1" | "dark-panel-2" | "dark-panel-3" | "dark-panel-4" | "dark-panel-5" | "dark-panel-6"
+ | "darksand-tainted-water" | "darksand-water" | "deep-tainted-water" | "deep-water" | "sand-water" | "shallow-water" | "tainted-water"
+ | "tar" | "pooled-cryofluid" | "molten-slag"
+ | "space"
+ | "stone"
+ | "bluemat"
+ | "ferric-craters"
+ | "beryllic-stone"
+ | "rhyolite" | "rough-rhyolite" | "rhyolite-crater" | "rhyolite-vent"
+ | "core-zone"
+ | "crater-stone"
+ | "redmat"
+ | "red-ice"
+ | "spore-moss"
+ | "regolith"
+ | "ferric-stone"
+ | "arkyic-stone" | "arkyic-vent"
+ | "yellow-stone" | "yellow-stone-plates" | "yellow-stone-vent"
+ | "red-stone" | "red-stone-vent" | "dense-red-stone"
+ | "carbon-stone" | "carbon-vent"
+ | "crystal-floor" | "crystalline-stone" | "crystalline-vent"
+ | "empty"], s)
+}
+
impl Tile {
#[must_use]
pub const fn new(floor: BlockEnum, ore: BlockEnum) -> Self {
@@ -145,48 +193,13 @@ impl Tile {
#[inline]
pub(crate) fn floor(&self, s: Scale) -> Image<&'static [u8], 3> {
- lo!(self.floor => [
- | "darksand"
- | "sand-floor"
- | "dacite"
- | "dirt"
- | "arkycite-floor"
- | "basalt"
- | "moss"
- | "mud"
- | "grass"
- | "ice-snow" | "snow" | "salt" | "ice"
- | "hotrock" | "char" | "magmarock"
- | "shale"
- | "metal-floor" | "metal-floor-2" | "metal-floor-3" | "metal-floor-4" | "metal-floor-5" | "metal-floor-damaged"
- | "dark-panel-1" | "dark-panel-2" | "dark-panel-3" | "dark-panel-4" | "dark-panel-5" | "dark-panel-6"
- | "darksand-tainted-water" | "darksand-water" | "deep-tainted-water" | "deep-water" | "sand-water" | "shallow-water" | "tainted-water"
- | "tar" | "pooled-cryofluid" | "molten-slag"
- | "space"
- | "stone"
- | "bluemat"
- | "ferric-craters"
- | "beryllic-stone"
- | "rhyolite" | "rough-rhyolite" | "rhyolite-crater" | "rhyolite-vent"
- | "core-zone"
- | "crater-stone"
- | "redmat"
- | "red-ice"
- | "spore-moss"
- | "regolith"
- | "ferric-stone"
- | "arkyic-stone" | "arkyic-vent"
- | "yellow-stone" | "yellow-stone-plates" | "yellow-stone-vent"
- | "red-stone" | "red-stone-vent" | "dense-red-stone"
- | "carbon-stone" | "carbon-vent"
- | "crystal-floor" | "crystalline-stone" | "crystalline-vent"
- | "empty"], s)
+ floor(self.floor, s)
}
#[must_use]
#[inline]
pub(crate) fn ore(&self, s: Scale) -> Image<&'static [u8], 4> {
- lo!(self.ore => ["ore-copper" | "ore-beryllium" | "ore-lead" | "ore-scrap" | "ore-coal" | "ore-thorium" | "ore-titanium" | "ore-tungsten" | "pebbles" | "tendrils" | "ore-wall-tungsten" | "ore-wall-beryllium" | "ore-wall-thorium" | "spawn" | "ore-crystal-thorium"], s)
+ ore(self.ore, s)
}
#[must_use]
@@ -341,9 +354,8 @@ impl Build {
}
// "overridden by subclasses"
self.block.read(self, buff)?;
- // implementation not complete, simply error, causing the remaining bytes in the chunk to be skipped (TODO finish impl)
- Err(ReadError::Version(0x0))
- // Ok(())
+
+ Ok(())
}
}
@@ -513,142 +525,416 @@ pub enum ReadError {
ReadState(#[from] super::dynamic::ReadError),
}
-/// serde map
-impl Serializable for Map {
- type ReadError = ReadError;
- type WriteError = ();
- /// deserialize a map
- ///
- /// notes:
- /// - does not deserialize data
- /// - does not deserialize entities
- fn deserialize(buff: &mut DataRead<'_>) -> Result<Map, Self::ReadError> {
- let buff = buff.deflate()?;
- let mut buff = DataRead::new(&buff);
- {
- let mut b = [0; 4];
- buff.read_bytes(&mut b)?;
- if b != MAP_HEADER {
- return Err(ReadError::Header(b));
+/// Struct for granular map deserialization.
+pub struct MapReader {
+ #[allow(dead_code)]
+ backing: Vec<u8>,
+ // dataread references 'backing'
+ buff: DataRead<'static>,
+}
+
+#[derive(Debug)]
+pub enum ThinBloc {
+ None(u8),
+ Build(Rotation, &'static Block),
+ Many(&'static Block, u8),
+}
+
+#[derive(Debug)]
+pub enum ThinMapData {
+ Init { width: u16, height: u16 },
+ Bloc(ThinBloc),
+ Tile { floor: BlockEnum, ore: BlockEnum },
+}
+
+#[derive(Debug)]
+pub enum Bloc {
+ None(u8),
+ Build(Build, &'static Block),
+ Data(&'static Block, i8),
+ Many(&'static Block, u8),
+}
+
+#[derive(Debug)]
+pub enum MapData {
+ Init { width: u16, height: u16 },
+ Bloc(Bloc),
+ Tile { floor: BlockEnum, ore: BlockEnum },
+}
+
+#[derive(Debug)]
+pub enum EntityData {
+ Length(u32),
+ Data(Unit),
+}
+
+macro_rules! tiles {
+ ($count:ident, $me:ident, $w: ident) => {
+ let mut i = 0;
+ while i < $count {
+ let floor_id = $me.buff.read_u16()?;
+ let overlay_id = $me.buff.read_u16()?;
+ let floor = BlockEnum::try_from(floor_id).unwrap_or(BlockEnum::Stone);
+ let ore = BlockEnum::try_from(overlay_id).unwrap_or(BlockEnum::Air);
+ yield $w::Tile { floor, ore };
+ let consecutives = $me.buff.read_u8()? as usize;
+ for _ in 0..consecutives {
+ yield $w::Tile { floor, ore };
}
+ i += consecutives;
+ i += 1;
}
- let version = buff.read_u32()?;
- if version != 7 {
- return Err(ReadError::Version(version.try_into().unwrap_or(0)));
+ };
+}
+
+impl MapReader {
+ pub fn new(buff: &mut DataRead<'_>) -> Result<Self, ReadError> {
+ let backing = buff.deflate()?;
+ Ok(Self {
+ buff: DataRead::new(unsafe {
+ std::mem::transmute::<&'_ [u8], &'static [u8]>(&backing)
+ }),
+ backing,
+ })
+ }
+
+ pub fn header(&mut self) -> Result<[u8; 4], ReadError> {
+ let b = self.buff.readN::<4>()?;
+ (b == MAP_HEADER).then_some(b).ok_or(ReadError::Header(b))
+ }
+
+ pub fn version(&mut self) -> Result<u32, ReadError> {
+ // NOTE: will change to 8 soon
+ let x = self.buff.read_u32()?;
+ (x == 7)
+ .then_some(x)
+ .ok_or(ReadError::Version(x.try_into().unwrap_or(0)))
+ }
+
+ pub fn tags(&mut self) -> Result<HashMap<&str, &str>, ReadError> {
+ let mut tags = HashMap::new();
+ self.buff.skip(5)?;
+ for _ in 0..self.buff.read_u8()? {
+ let key = self.buff.read_utf()?;
+ let value = self.buff.read_utf()?;
+ tags.insert(key, value);
}
+ Ok(tags)
+ }
+
+ pub fn tags_alloc(&mut self) -> Result<HashMap<String, String>, ReadError> {
let mut tags = HashMap::new();
- buff.read_chunk(true, |buff| {
- buff.skip(1)?;
- for _ in 0..buff.read_u8()? {
- let key = buff.read_utf()?;
- let value = buff.read_utf()?;
- tags.insert(key.to_owned(), value.to_owned());
- }
- Ok::<(), super::ReadError>(())
- })?;
- // we skip the content header (just keep the respective modules updated)
- buff.skip_chunk()?;
- // map section
- let mut w = 0;
- let mut h = 0;
- let mut map = buff.read_chunk(true, |buff| {
- w = buff.read_u16()? as usize;
- h = buff.read_u16()? as usize;
- let mut map = Map::new(w, h, tags);
+ self.buff
+ .read_chunk(true, |buff| {
+ buff.skip(1)?;
+ for _ in 0..buff.read_u8()? {
+ let key = buff.read_utf()?;
+ let value = buff.read_utf()?;
+ tags.insert(key.to_owned(), value.to_owned());
+ }
+ Ok::<(), ReadError>(())
+ })
+ .map(|_| tags)
+ }
+
+ pub fn skip(&mut self) -> Result<(), ReadError> {
+ let len = self.buff.read_u32()? as usize;
+ self.buff.skip(len)?;
+ Ok(())
+ }
+
+ pub fn thin_map(
+ &mut self,
+ ) -> Result<
+ impl Coroutine<(), Return = Result<(), ReadError>, Yield = ThinMapData> + '_,
+ ReadError,
+ > {
+ let len = self.buff.read_u32()? as usize;
+ let rb4 = self.buff.read;
+ let map = move || {
+ let w = self.buff.read_u16()?;
+ let h = self.buff.read_u16()?;
+ yield ThinMapData::Init {
+ width: w,
+ height: h,
+ };
+ let w = w as usize;
+ let h = h as usize;
let count = w * h;
+ tiles!(count, self, ThinMapData);
+
let mut i = 0;
while i < count {
- let floor_id = buff.read_u16()?;
- let overlay_id = buff.read_u16()?;
- let floor = BlockEnum::try_from(floor_id).unwrap_or(BlockEnum::Stone);
- let ore = BlockEnum::try_from(overlay_id).unwrap_or(BlockEnum::Air);
- map.push(Tile::new(floor, ore));
- let consecutives = buff.read_u8()? as usize;
- if consecutives > 0 {
- for _ in 0..consecutives {
- map.push(Tile::new(floor, ore));
+ let block_id = self.buff.read_u16()?;
+ let packed = self.buff.read_u8()?;
+ let entity = (packed & 1) != 0;
+ let data = (packed & 2) != 0;
+ let central = if entity {
+ self.buff.read_bool()?
+ } else {
+ false
+ };
+ let block = BlockEnum::try_from(block_id)
+ .map_err(|_| ReadError::NoSuchBlock(block_id.to_string()))?;
+ let block = block.to_block();
+ let Some(block) = block else {
+ let consecutives = self.buff.read_u8()?;
+ yield ThinMapData::Bloc(ThinBloc::None(consecutives));
+ i += consecutives as usize;
+ i += 1;
+ continue;
+ };
+ yield if entity {
+ if central {
+ let len = self.buff.read_u16()? as usize;
+ let rb4 = self.buff.read;
+
+ #[cfg(debug_assertions)]
+ println!("reading {block:?} ");
+ let _ = self.buff.read_i8()?;
+ let _ = self.buff.read_f32()?;
+ let rot = self.buff.read_i8()?;
+ let rot = Rotation::try_from((rot & 127) as u8).unwrap_or(Rotation::Up);
+ let read = self.buff.read - rb4;
+ let n = len - read;
+ self.buff.skip(n)?;
+
+ ThinMapData::Bloc(ThinBloc::Build(rot, block))
+ } else {
+ ThinMapData::Bloc(ThinBloc::None(0))
}
- i += consecutives;
- }
+ } else if data {
+ _ = self.buff.read_i8()?;
+ ThinMapData::Bloc(ThinBloc::Many(block, 0))
+ } else {
+ let consecutives = self.buff.read_u8()?;
+ i += consecutives as usize;
+ ThinMapData::Bloc(ThinBloc::Many(block, consecutives))
+ };
+
i += 1;
}
- let mut i = 0usize;
+ let read = self.buff.read - rb4;
+ debug_assert!(len >= read, "overread; supposed to read {len}; read {read}");
+ debug_assert!((len - read) == 0, "supposed to read {len}; read {read}");
+ Ok(())
+ };
+ Ok(map)
+ }
+
+ pub fn collect_map(&mut self, tags: HashMap<String, String>) -> Result<Map, ReadError> {
+ let mut co = self.map()?;
+ let (w, h) = match Pin::new(&mut co).resume(()) {
+ Yielded(MapData::Init { width, height }) => (width as usize, height as usize),
+ Complete(Err(x)) => return Err(x),
+ _ => unreachable!(),
+ };
+ let mut m = Map::new(w, h, tags);
+ for _ in 0..w * h {
+ match Pin::new(&mut co).resume(()) {
+ Yielded(MapData::Tile { floor, ore }) => m.push(Tile::new(floor, ore)),
+ Complete(Err(x)) => return Err(x),
+ _ => unreachable!(),
+ }
+ }
+ let mut i = 0;
+ while i < w * h {
+ match Pin::new(&mut co).resume(()) {
+ Yielded(MapData::Bloc(Bloc::None(n))) => i += n as usize,
+ Yielded(MapData::Bloc(Bloc::Build(x, y))) => {
+ m[i].set_block(y);
+ m[i].build = Some(x);
+ }
+ Yielded(MapData::Bloc(Bloc::Data(x, y))) => {
+ m[i].set_block(x);
+ m[i].build.as_mut().unwrap().data = y;
+ }
+ Yielded(MapData::Bloc(Bloc::Many(bloc, n))) => {
+ for i in i..=i + n as usize {
+ m[i].set_block(bloc);
+ }
+ i += n as usize;
+ }
+ Complete(Err(x)) => return Err(x),
+ _ => unreachable!(),
+ }
+ i += 1;
+ }
+ match Pin::new(&mut co).resume(()) {
+ Complete(Ok(())) => (),
+ _ => unreachable!(),
+ };
+ Ok(m)
+ }
+
+ pub fn map(
+ &mut self,
+ ) -> Result<impl Coroutine<(), Return = Result<(), ReadError>, Yield = MapData> + '_, ReadError>
+ {
+ let len = self.buff.read_u32()? as usize;
+ let rb4 = self.buff.read;
+ Ok(move || {
+ let w = self.buff.read_u16()?;
+ let h = self.buff.read_u16()?;
+ yield MapData::Init {
+ width: w,
+ height: h,
+ };
+ let w = w as usize;
+ let h = h as usize;
+ let count = w * h;
+ tiles!(count, self, MapData);
+
+ let mut i = 0;
while i < count {
- let block_id = buff.read_u16()?;
- let packed = buff.read_u8()?;
+ let block_id = self.buff.read_u16()?;
+ let packed = self.buff.read_u8()?;
let entity = (packed & 1) != 0;
let data = (packed & 2) != 0;
- let central = if entity { buff.read_bool()? } else { false };
+ let central = if entity {
+ self.buff.read_bool()?
+ } else {
+ false
+ };
let block = BlockEnum::try_from(block_id)
.map_err(|_| ReadError::NoSuchBlock(block_id.to_string()))?;
let block = block.to_block();
- if central && let Some(block) = block {
- map[i].set_block(block);
- }
- if entity {
+ let Some(block) = block else {
+ let consecutives = self.buff.read_u8()?;
+ yield MapData::Bloc(Bloc::None(consecutives));
+ i += consecutives as usize;
+ i += 1;
+ continue;
+ };
+ yield if entity {
if central {
- let mut output = [0u8; 2];
- output.copy_from_slice(&buff.data[..2]);
- let _ = buff.read_chunk(false, |buff| {
+ let len = self.buff.read_u16()? as usize;
+ let rb4 = self.buff.read;
+
+ #[cfg(debug_assertions)]
+ println!("reading {block:?} ");
+ let _ = self.buff.read_i8()?;
+ let mut b = Build::new(block);
+ b.read(&mut self.buff)?;
+ // implementation not complete, skip remaining bytes (TODO finish impl)
+ let read = self.buff.read - rb4;
+
+ // skip this chunk
+ assert!(len >= read, "overread; supposed to read {len}; read {read}");
+ let n = len - read;
+ if n != 0 {
#[cfg(debug_assertions)]
- println!("reading {:?}", map[i].build.as_ref().unwrap());
- let _ = buff.read_i8()?;
-
- map[i].build.as_mut().unwrap().read(buff)?;
- Ok::<(), ReadError>(())
- });
+ println!(
+ "({block:?}) supposed to read {len}; read {read} - skipping excess"
+ );
+ self.buff.skip(n)?;
+ };
+
+ MapData::Bloc(Bloc::Build(b, block))
+ } else {
+ MapData::Bloc(Bloc::None(0))
}
} else if data {
- if let Some(block) = block {
- map[i].set_block(block);
- }
- map[i].build.as_mut().unwrap().data = buff.read_i8()?;
+ MapData::Bloc(Bloc::Data(block, self.buff.read_i8()?))
} else {
- let consecutives = buff.read_u8()? as usize;
- for i in i..=i + consecutives {
- if let Some(block) = block {
- map.tiles[i].set_block(block);
- }
- }
- i += consecutives;
- }
+ let consecutives = self.buff.read_u8()?;
+ i += consecutives as usize;
+ MapData::Bloc(Bloc::Many(block, consecutives))
+ };
+
i += 1;
}
- Ok::<_, ReadError>(map)
- })?;
- map.entities = buff.read_chunk(true, |buff| {
- // read entity mapping (SaveVersion.java#436)
- for _ in 0..buff.read_u16()? {
- buff.skip(2)?;
- let _ = buff.read_utf()?;
+ let read = self.buff.read - rb4;
+ debug_assert!(len >= read, "overread; supposed to read {len}; read {read}");
+ debug_assert!((len - read) == 0, "supposed to read {len}; read {read}");
+ Ok(())
+ })
+ }
+
+ pub fn entities(
+ &mut self,
+ ) -> Result<
+ impl Coroutine<(), Yield = EntityData, Return = Result<(), ReadError>> + '_,
+ ReadError,
+ > {
+ let len = self.buff.read_u32()? as usize;
+ let rb4 = self.buff.read;
+
+ Ok(move || {
+ for _ in 0..self.buff.read_u16()? {
+ self.buff.skip(2)?;
+ let _ = self.buff.read_utf()?;
}
// read team block plans (ghosts) (SaveVersion.java#389)
- for _ in 0..buff.read_u32()? {
- buff.skip(4)?;
- for _ in 0..buff.read_u32()? {
- buff.skip(8usize)?;
- let _ = DynData::deserialize(buff)?;
+ for _ in 0..self.buff.read_u32()? {
+ self.buff.skip(4)?;
+ for _ in 0..self.buff.read_u32()? {
+ self.buff.skip(8usize)?;
+ let _ = DynData::deserialize(&mut self.buff)?;
}
}
// read world entities (#412). eg units
- let n = buff.read_u32()?;
- let mut entities = Vec::with_capacity(n as usize);
+ let n = self.buff.read_u32()?;
+ yield EntityData::Length(n);
for _ in 0..n {
- let len = buff.read_u16()? as usize;
- let id = buff.read_u8()? as usize;
+ let len = self.buff.read_u16()? as usize;
+ let id = self.buff.read_u8()? as usize;
let Some(&Some(u)) = entity_mapping::ID.get(id) else {
- buff.skip(len - 1)?;
+ self.buff.skip(len - 1)?;
continue;
- // return Ok(());
};
- buff.skip(4)?;
- entities.push(u.read(buff)?);
+ self.buff.skip(4)?;
+ yield EntityData::Data(u.read(&mut self.buff)?);
+ }
+ let read = self.buff.read - rb4;
+ debug_assert!(len >= read, "overread; supposed to read {len}; read {read}");
+ debug_assert!((len - read) == 0, "supposed to read {len}; read {read}");
+ Ok(())
+ })
+ }
+
+ pub fn collect_entities(&mut self) -> Result<Vec<Unit>, ReadError> {
+ let mut co = self.entities()?;
+ let n = match Pin::new(&mut co).resume(()) {
+ Yielded(EntityData::Length(x)) => x,
+ Complete(Err(e)) => return Err(e),
+ _ => unreachable!(),
+ };
+ let mut o = Vec::with_capacity(n as usize);
+ for _ in 0..n {
+ match Pin::new(&mut co).resume(()) {
+ Yielded(EntityData::Data(x)) => o.push(x),
+ Complete(Err(e)) => return Err(e),
+ _ => unreachable!(),
}
- Ok::<_, ReadError>(entities)
- })?;
+ }
+ match Pin::new(&mut co).resume(()) {
+ Complete(Ok(())) => (),
+ _ => unreachable!(),
+ };
+ Ok(o)
+ }
+}
+
+/// serde map
+impl Serializable for Map {
+ type ReadError = ReadError;
+ type WriteError = ();
+ /// deserialize a map
+ ///
+ /// note: does not deserialize all data
+ fn deserialize(buff: &mut DataRead<'_>) -> Result<Map, Self::ReadError> {
+ let mut buff = MapReader::new(buff)?;
+ buff.header()?;
+ buff.version()?;
+ let tags = buff.tags_alloc()?;
+ buff.skip()?;
+ let mut m = buff.collect_map(tags)?;
+ m.entities = buff.collect_entities()?;
+
// skip custom chunks
- buff.skip_chunk()?;
- Ok(map)
+ buff.skip()?;
+ Ok(m)
}
/// serialize a map (todo)
diff --git a/mindus/src/data/mod.rs b/mindus/src/data/mod.rs
index 0ad80a1..bb47b04 100644
--- a/mindus/src/data/mod.rs
+++ b/mindus/src/data/mod.rs
@@ -67,7 +67,7 @@ impl<'d> DataRead<'d> {
pub fn read_utf(&mut self) -> Result<&'d str, ReadError> {
let len = self.read_u16()?;
- let result = std::str::from_utf8(&self.eat(len as usize)?)?;
+ let result = std::str::from_utf8(self.eat(len as usize)?)?;
Ok(result)
}
@@ -100,12 +100,6 @@ impl<'d> DataRead<'d> {
Ok(())
}
- pub fn skip_chunk(&mut self) -> Result<usize, ReadError> {
- let len = self.read_u32()? as usize;
- self.skip(len)?;
- Ok(len)
- }
-
pub fn read_chunk<E: Error + From<ReadError>, T>(
&mut self,
big: bool,
diff --git a/mindus/src/data/renderer.rs b/mindus/src/data/renderer.rs
index 1f07d72..35ed908 100644
--- a/mindus/src/data/renderer.rs
+++ b/mindus/src/data/renderer.rs
@@ -1,10 +1,17 @@
//! schematic drawing
+use std::ops::Coroutine;
+use std::pin::Pin;
+
pub(crate) use super::autotile::*;
use super::schematic::Schematic;
use super::GridPos;
-use crate::block::Rotation;
+use crate::block::content::Type;
pub(crate) use crate::utils::*;
use crate::Map;
+use crate::{
+ block::Rotation,
+ data::map::{ThinBloc, ThinMapData},
+};
use fimg::{uninit, BlendingOverlay};
include!(concat!(env!("OUT_DIR"), "/full.rs"));
@@ -289,3 +296,180 @@ fn all_blocks() {
);
}
}
+
+pub fn draw_units(
+ map: &mut crate::data::map::MapReader,
+ mut img: Image<&mut [u8], 3>,
+ size: (u16, u16),
+) -> Result<(), super::map::ReadError> {
+ use std::ops::CoroutineState::*;
+ let scale = if size.0 + size.1 < 2000 {
+ Scale::Quarter
+ } else {
+ Scale::Eigth
+ };
+
+ let mut co = map.entities()?;
+ let n = match Pin::new(&mut co).resume(()) {
+ Yielded(crate::data::map::EntityData::Length(x)) => x,
+ Complete(Err(e)) => return Err(e),
+ _ => unreachable!(),
+ };
+ 'out: {
+ for _ in 0..n {
+ match Pin::new(&mut co).resume(()) {
+ Yielded(crate::data::map::EntityData::Data(entity)) => {
+ // bounds checks
+ let (x, y) = (
+ entity.state.position.0 as u32,
+ size.1 as u32 - entity.state.position.1 as u32 - 1,
+ );
+ if x < 10
+ || x as usize > size.0 as usize - 10
+ || y < 10
+ || y as usize > size.1 as usize - 10
+ {
+ continue;
+ }
+ unsafe {
+ img.as_mut()
+ .overlay_at(&entity.draw(scale).borrow(), scale * x, scale * y)
+ };
+ }
+ Complete(Err(e)) => return Err(e),
+ Complete(Ok(())) => break 'out,
+ x => unreachable!("{x:?}"),
+ }
+ }
+ match Pin::new(&mut co).resume(()) {
+ Complete(Ok(())) => (),
+ _ => unreachable!(),
+ };
+ }
+ Ok(())
+}
+
+/// Draws a map in a single pass.
+/// This is quite fast, but, unfortunately, has no memory, so conveyors will be drawing imporperly. As will sorters.
+///
+/// Reader must have read to the map section.
+/// Will walk through the map section. use [`draw_units`] after, if you like.
+pub fn draw_map_single(
+ map: &mut crate::data::map::MapReader,
+) -> Result<(Image<Box<[u8]>, 3>, (u16, u16)), super::map::ReadError> {
+ use std::ops::CoroutineState::*;
+ let mut co = map.thin_map()?;
+ let (w, h) = match Pin::new(&mut co).resume(()) {
+ Yielded(ThinMapData::Init { width, height }) => (width, height),
+ Complete(Err(x)) => return Err(x),
+ _ => unreachable!(),
+ };
+ let scale = if w + h < 2000 {
+ Scale::Quarter
+ } else {
+ Scale::Eigth
+ };
+ let mut img = uninit::Image::<_, 3>::new(
+ (scale * w as u32).try_into().unwrap(),
+ (scale * h as u32).try_into().unwrap(),
+ );
+ // loop1 draws the floor
+ for y in 0..w {
+ for x in 0..h {
+ let (floor, ore) = match Pin::new(&mut co).resume(()) {
+ Yielded(ThinMapData::Tile { floor, ore }) => (floor, ore),
+ Complete(Err(x)) => return Err(x),
+ _ => unreachable!(),
+ };
+ let y = h - y - 1;
+ // println!("draw {tile:?} ({x}, {y})");
+ unsafe {
+ img.overlay_at(
+ &crate::data::map::floor(floor, scale),
+ scale * x as u32,
+ scale * y as u32,
+ )
+ };
+ if ore != Type::Air {
+ unsafe {
+ img.overlay_at(
+ &crate::data::map::ore(ore, scale),
+ scale * x as u32,
+ scale * y as u32,
+ )
+ };
+ }
+ }
+ }
+ let mut img = unsafe { img.assume_init() }.boxed();
+ let mut i = 0;
+ while i < (w as usize * h as usize) {
+ let mut draw = |i, r, b: &'static crate::block::Block| {
+ let x = i % w as usize;
+ let y = i / w as usize;
+ let y = h as usize - y - 1;
+ let s = b.get_size();
+ let x = x
+ - (match s {
+ 1 | 2 => 0,
+ 3 | 4 => 1,
+ 5 | 6 => 2,
+ 7 | 8 => 3,
+ 9 => 4,
+ // SAFETY: no block too big
+ _ => unsafe { std::hint::unreachable_unchecked() },
+ }) as usize;
+ let y = y
+ - (match s {
+ 1 => 0,
+ 2 | 3 => 1,
+ 4 | 5 => 2,
+ 6 | 7 => 3,
+ 8 | 9 => 4,
+ // SAFETY: no block too big
+ _ => unsafe { std::hint::unreachable_unchecked() },
+ }) as usize;
+ let ctx = b.wants_context().then(|| {
+ let pctx = PositionContext {
+ position: GridPos(x, y),
+ width: w as usize,
+ height: h as usize,
+ };
+ RenderingContext {
+ cross: [None; 4], // woe
+ position: pctx,
+ }
+ });
+ unsafe {
+ img.as_mut().overlay_at(
+ &b.image(None, ctx.as_ref(), r, scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ )
+ };
+ };
+ match Pin::new(&mut co).resume(()) {
+ Yielded(ThinMapData::Bloc(ThinBloc::None(n))) => {
+ i += n as usize;
+ }
+ Yielded(ThinMapData::Bloc(ThinBloc::Build(r, bloc))) => {
+ draw(i, r, bloc);
+ }
+ Yielded(ThinMapData::Bloc(ThinBloc::Many(bloc, n))) => {
+ for i in i..=i + n as usize {
+ draw(i, Rotation::Up, bloc);
+ }
+ i += n as usize;
+ }
+ Complete(Err(x)) => return Err(x),
+ x => unreachable!("{x:?}"),
+ }
+ i += 1;
+ }
+ match Pin::new(&mut co).resume(()) {
+ Complete(Ok(())) => (),
+ f => unreachable!("{f:?}"),
+ };
+
+ Ok((img, (w, h)))
+}
diff --git a/mindus/src/data/schematic.rs b/mindus/src/data/schematic.rs
index 7f85680..3e4aad8 100644
--- a/mindus/src/data/schematic.rs
+++ b/mindus/src/data/schematic.rs
@@ -97,19 +97,13 @@ impl RotationState for Placement {
impl BlockState for Option<Placement> {
fn get_block(&self) -> Option<&'static Block> {
- let Some(p) = self else {
- return None;
- };
- Some(p.block)
+ Some(self.as_ref()?.block)
}
}
impl RotationState for Option<Placement> {
fn get_rotation(&self) -> Option<Rotation> {
- let Some(p) = self else {
- return None;
- };
- Some(p.rot)
+ Some(self.as_ref()?.rot)
}
}
@@ -365,12 +359,10 @@ impl Schematic {
/// iterate over all the blocks
pub fn block_iter(&self) -> impl Iterator<Item = (GridPos, &Placement)> {
- self.blocks.iter().enumerate().filter_map(|(i, p)| {
- let Some(p) = p else {
- return None;
- };
- Some((GridPos(i / self.height, i % self.height), p))
- })
+ self.blocks
+ .iter()
+ .enumerate()
+ .filter_map(|(i, p)| Some((GridPos(i / self.height, i % self.height), p.as_ref()?)))
}
#[must_use]
diff --git a/mindus/src/exe/map.rs b/mindus/src/exe/map.rs
index f61ecdf..7aba2d9 100644
--- a/mindus/src/exe/map.rs
+++ b/mindus/src/exe/map.rs
@@ -1,45 +1,33 @@
+use mindus::data::map::MapReader;
use mindus::data::DataRead;
-use mindus::Renderable;
-use mindus::{Map, Serializable};
use std::env::Args;
-use std::time::Instant;
use super::print_err;
pub fn main(args: Args) {
- let runs = std::env::var("RUNS").map_or(10u8, |x| x.parse().unwrap_or(10u8));
-
// process schematics from command line
- println!("starting timing");
- let then = Instant::now();
for curr in args {
let Ok(s) = std::fs::read(curr) else {
continue;
};
- let starting_deser = Instant::now();
- match Map::deserialize(&mut DataRead::new(&s)) {
+ match (|| {
+ let mut m = MapReader::new(&mut DataRead::new(&s))?;
+ m.header()?;
+ m.version()?;
+ let t = m.tags()?;
+ println!("rendering {}", t["name"]);
+ m.skip()?;
+ let (mut img, sz) = mindus::data::renderer::draw_map_single(&mut m)?;
+ mindus::data::renderer::draw_units(&mut m, img.as_mut(), sz)?;
+ Ok::<_, mindus::data::map::ReadError>(img)
+ })() {
Err(e) => print_err!(e, "fail"),
Ok(m) => {
- let deser_took = starting_deser.elapsed();
if let Ok(v) = std::env::var("SAVE")
&& v == "1"
{
- m.render().save("x.png");
+ m.save("x.png");
continue;
}
- let starting_render = Instant::now();
- for _ in 0..runs {
- drop(m.render());
- }
- let renders_took = starting_render.elapsed();
- let took = then.elapsed();
- println!(
- "μ total: {:.2}s ({} runs) (deser: {}ms, render: {:.2}s) on map {}",
- took.as_secs_f32() / runs as f32,
- runs,
- deser_took.as_millis(),
- renders_took.as_secs_f32() / runs as f32,
- m.tags.get("mapname").unwrap(),
- );
}
}
}
diff --git a/mindus/src/lib.rs b/mindus/src/lib.rs
index 0388762..eb3e42a 100644
--- a/mindus/src/lib.rs
+++ b/mindus/src/lib.rs
@@ -1,9 +1,12 @@
//! crate for dealing with mindustry
#![feature(
+ iter_from_coroutine,
generic_arg_infer,
const_trait_impl,
+ coroutine_trait,
const_option,
derive_const,
+ coroutines,
slice_take,
let_chains,
effects