mindustry logic execution, map- and schematic- parsing and rendering
cache ore and floor combinations
| -rw-r--r-- | Cargo.toml | 5 | ||||
| -rw-r--r-- | build.rs | 5 | ||||
| -rw-r--r-- | src/block/content.rs | 9 | ||||
| -rw-r--r-- | src/block/environment.rs | 10 | ||||
| -rw-r--r-- | src/content.rs | 2 | ||||
| -rw-r--r-- | src/data/map.rs | 107 | ||||
| -rw-r--r-- | src/data/renderer.rs | 33 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/utils/image.rs | 14 | ||||
| -rw-r--r-- | src/utils/lazy.rs | 2 |
10 files changed, 127 insertions, 62 deletions
@@ -1,6 +1,6 @@ [package] name = "mindus" -version = "3.0.0" +version = "3.0.1" edition = "2021" description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)" authors = [ @@ -22,6 +22,7 @@ thiserror = "1.0" bobbin-bits = "0.1" blurslice = { version = "0.1" } enum_dispatch = "0.3" +ahash = { version = "0.8.3", default-features = false, features = ["std", "no-rng"] } [features] bin = ["image/png"] @@ -40,7 +41,7 @@ path = "src/exe/mod.rs" [profile.release] debug = 2 opt-level = 3 -lto = true +lto = "thin" incremental = true [profile.dev.build-override] @@ -61,6 +61,7 @@ fn main() { let mut warmup = File::create(o.join("warmup.rs")).unwrap(); wr!(warmup => "/// SAFETY: this function must only be called once."); wr!(warmup => "pub unsafe fn warmup() {{"); + wr!(warmup => "LazyLock::load(&EMPTY);"); for e in walkdir.into_iter().filter_map(|e| e.ok()) { let path = e.path(); if path.is_file() && let Some(e) = path.extension() && e == "png" { @@ -80,8 +81,8 @@ fn main() { // boulders let (mx, my) = if p.width() + p.height() == 48+48 { (32, 32) - // vents - } else if path.contains("vent") { + // vents (dont match VENT_CONDENSER, do match (RHYOLITE_VENT) + } else if path.contains("_VENT") { (32, 32) } else { (p.height(), p.width()) diff --git a/src/block/content.rs b/src/block/content.rs index 55d5545..2275f6d 100644 --- a/src/block/content.rs +++ b/src/block/content.rs @@ -1,5 +1,5 @@ //! everything -use crate::content::{content_enum, Content}; +use crate::content::{content_enum}; content_enum! { pub enum Type / Block for u16 | TryFromU16Error @@ -418,9 +418,4 @@ content_enum! { "world-message", } } -use crate::block::*; -impl Type { - pub fn to<'l>(&self, reg: &'l BlockRegistry) -> Option<&'l Block> { - reg.get(self.get_name()) - } -} + diff --git a/src/block/environment.rs b/src/block/environment.rs index 267ebcd..3fff74f 100644 --- a/src/block/environment.rs +++ b/src/block/environment.rs @@ -67,14 +67,14 @@ register_env! { "stone": 1; "build1": 1; "boulder": 1; - "arkyic-vent": 3; + "arkyic-vent": 1; "arkyic-wall-large": 2; "arkyic-wall": 1; "beryllic-stone-wall-large": 2; "beryllic-stone-wall": 1; "beryllic-stone": 1; "bluemat": 1; - "carbon-vent": 3; + "carbon-vent": 1; "carbon-wall-large": 2; "carbon-wall": 1; "cliff": 1; @@ -108,7 +108,7 @@ register_env! { "red-ice-wall-large": 2; "red-ice-wall": 1; "red-ice": 1; - "red-stone-vent": 3; + "red-stone-vent": 1; "red-stone-wall-large": 2; "red-stone-wall": 1; "red-stone": 1; @@ -117,7 +117,7 @@ register_env! { "regolith-wall": 1; "regolith": 1; "rhyolite-crater": 1; - "rhyolite-vent": 3; + "rhyolite-vent": 1; "rhyolite-wall-large": 2; "rhyolite-wall": 1; "rhyolite": 1; @@ -143,7 +143,7 @@ register_env! { "tainted-water": 1; "tar": 1; "yellow-stone-plates": 1; - "yellow-stone-vent": 3; + "yellow-stone-vent": 1; "yellow-stone-wall-large": 2; "yellow-stone-wall": 1; // props diff --git a/src/content.rs b/src/content.rs index 5576458..44db19f 100644 --- a/src/content.rs +++ b/src/content.rs @@ -17,7 +17,7 @@ macro_rules! numeric_enum { ($vis:vis enum $tname:ident for $numeric:ty | $error:ident* {$($name:ident $(= $val:literal)?),* $(,)?}) => { #[repr($numeric)] - #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] + #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] $vis enum $tname { $($name $(= $val)?,)+ } #[derive(Copy, Clone, Debug, Eq, PartialEq)] diff --git a/src/data/map.rs b/src/data/map.rs index 5ee644d..acd0c88 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -71,10 +71,11 @@ //! - entity read use std::collections::HashMap; use std::ops::{Index, IndexMut}; +use std::sync::RwLock; use thiserror::Error; use crate::block::content::Type as BlockEnum; -use crate::block::{environment, Block, BlockRegistry, Rotation, State}; +use crate::block::{Block, BlockRegistry, Rotation, State}; use crate::data::dynamic::DynSerializer; use crate::data::renderer::*; use crate::data::DataRead; @@ -91,14 +92,14 @@ use crate::utils::image::ImageUtils; /// a tile in a map #[derive(Clone)] pub struct Tile<'l> { - pub floor: &'l Block, - pub ore: Option<&'l Block>, + pub floor: BlockEnum, + pub ore: BlockEnum, build: Option<Build<'l>>, } pub type EntityMapping = HashMap<u8, Box<dyn Content>>; impl<'l> Tile<'l> { - pub fn new(floor: &'l Block, ore: Option<&'l Block>) -> Self { + pub fn new(floor: BlockEnum, ore: BlockEnum) -> Self { Self { floor, ore, @@ -134,14 +135,85 @@ impl<'l> Tile<'l> { 1 } - pub unsafe fn floor_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { - let mut i = self.floor.image(None, context, Rotation::Up, s); - if let Some(ore) = self.ore { - i.overlay(ore.image(None, context, Rotation::Up, s).borrow()); + pub unsafe fn floor_image(&self, s: Scale) -> ImageHolder { + macro_rules! lo { + ($v:expr => [$(|)? $($k:literal $(|)?)+], $scale: ident) => { paste::paste! { + match $v { + $(BlockEnum::[<$k:camel>] => load!($k, $scale),)+ + n => unreachable!("{n:?}"), + } } + } + } + let floor = || { + lo!(self.floor => [ + | "darksand" + | "sand-floor" + | "dacite" + | "dirt" + | "arkycite-floor" + | "basalt" + | "moss" + | "mud" + | "magmarock" + | "grass" + | "ice-snow" + | "hotrock" + | "char" + | "snow" + | "salt" + | "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" + | "molten-slag" + | "deep-water" + | "sand-water" + | "shallow-water" + | "space" + | "stone" + | "bluemat" + | "ferric-craters" + | "beryllic-stone" + | "rhyolite-crater" + | "core-zone" + | "crater-stone" + | "crystal-floor" + | "dense-red-stone" + | "redmat" + | "red-ice" + | "spore-moss" + | "arkyic-vent" | "red-stone-vent" | "rhyolite-vent" | "carbon-vent" | "crystalline-vent" | "yellow-stone-vent" + | "regolith" + | "rhyolite" + | "tainted-water" + | "tar" + | "empty" + ], s) + }; + if self.ore != BlockEnum::Air { + type Floor = BlockEnum; + type Ore = BlockEnum; + // todo Rgb + static ORE_LAYS: RwLock<HashMap<(Floor, Ore), &'static RgbaImage, ahash::RandomState>> = + RwLock::new(HashMap::with_hasher(ahash::RandomState::with_seeds( + 401, 41209, 83123, 2110, + ))); + if let Some(v) = ORE_LAYS.read().unwrap().get(&(self.floor, self.ore)) { + return ImageHolder::from(*v); + } + return ImageHolder::from(*ORE_LAYS.write().unwrap().entry((self.floor, self.ore)).or_insert_with(|| { + let mut base = floor(); + base.overlay(lo!(self.ore => ["ore-copper" | "ore-beryllium" | "ore-lead" | "ore-scrap" | "ore-coal" | "ore-thorium" | "ore-titanium" | "ore-tungsten" | "pebbles" | "tendrils"], s).borrow()); + Box::leak(Box::new(base)) + })); } - i + floor() } + /// # Safety + /// Must call [`warmup`](crate::warmup) first pub unsafe fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { // building covers floore let Some(b) = &self.build else { @@ -156,9 +228,9 @@ impl std::fmt::Debug for Tile<'_> { write!( f, "Tile@{}{}{}", - self.floor.name(), - if let Some(ore) = &self.ore { - format!("+{}", ore.name()) + self.floor.get_name(), + if self.ore != BlockEnum::Air { + format!("+{}", self.ore.get_name()) } else { "".into() }, @@ -437,7 +509,7 @@ impl<'l> Crossable for Map<'l> { impl<'l> Map<'l> { pub fn new(width: usize, height: usize, tags: HashMap<String, String>) -> Self { Self { - tiles: vec![Tile::new(&environment::STONE, None); width * height], + tiles: vec![Tile::new(BlockEnum::Stone, BlockEnum::Air); width * height], height, width, tags, @@ -526,13 +598,8 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { 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) - .to(self.0) - .unwrap_or(&environment::STONE); - let ore = BlockEnum::try_from(overlay_id) - .unwrap_or(BlockEnum::Air) - .to(self.0); + let floor = BlockEnum::try_from(floor_id).unwrap_or(BlockEnum::Stone); + let ore = BlockEnum::try_from(overlay_id).unwrap_or(BlockEnum::Air); map[i] = Tile::new(floor, ore); let consecutives = buff.read_u8()? as usize; if consecutives > 0 { diff --git a/src/data/renderer.rs b/src/data/renderer.rs index 8423d98..70babd6 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -112,25 +112,32 @@ impl std::ops::Mul<u32> for Scale { #[macro_export] macro_rules! load { + ("empty", $scale:ident) => { + ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { + $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::EMPTY, + $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::EMPTY, + $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::EMPTY, + })}) + }; ($name:literal, $scale:ident) => { paste::paste! { - ImageHolder::from(unsafe { crate::utils::Lock::get(match $scale { + ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$name:snake:upper>], $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$name:snake:upper>], $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$name:snake:upper>], })}) } }; ($name: literal) => { paste::paste! { - [crate::data::renderer::full::[<$name:snake:upper>], crate::data::renderer::quar::[<$name:snake:upper>], crate::data::renderer::eigh::[<$name:snake:upper>]] + [$crate::data::renderer::full::[<$name:snake:upper>], $crate::data::renderer::quar::[<$name:snake:upper>], $crate::data::renderer::eigh::[<$name:snake:upper>]] } }; (from $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { - crate::data::renderer::load!($scale -> match $v { + $crate::data::renderer::load!($scale -> match $v { $($k => $k,)+ }) }; // turn load!(s -> match x { "v" => "y" }) into match x { "v" => load!("y", s) } ($scale:ident -> match $v:ident { $($k:pat => $nam:literal $(,)?)+ }) => { match $v { - $($k => crate::data::renderer::load!($nam, $scale),)+ + $($k => $crate::data::renderer::load!($nam, $scale),)+ #[allow(unreachable_patterns)] n => unreachable!("{n:?}"), } @@ -138,10 +145,10 @@ macro_rules! load { (concat $x:expr => $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { paste::paste! { match $v { $($k => - ImageHolder::from(unsafe { crate::utils::Lock::get(match $scale { - crate::data::renderer::Scale::Quarter => crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>], - crate::data::renderer::Scale::Eigth => crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>], - crate::data::renderer::Scale::Full => crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>], + ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { + $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>], + $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>], + $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>], }) }), )+ #[allow(unreachable_patterns)] @@ -255,10 +262,10 @@ impl Renderable for Map<'_> { ) }) { // draw the floor first. - let img: &RgbaImage = &tile.floor_image(None, scale); + let img: &RgbaImage = &tile.floor_image(scale); // println!("draw {tile:?} ({x}, {y}) + {scale:?}"); - // assert_eq!(img.width(), scale.px() as u32); - // assert_eq!(img.height(), scale.px() as u32); + debug_assert_eq!(img.width(), scale.px() as u32); + debug_assert_eq!(img.height(), scale.px() as u32); floor.overlay_at(img, scale * x as u32, scale * y as u32); if let Some(build) = tile.build() { let s = build.block.get_size(); @@ -279,8 +286,8 @@ impl Renderable for Map<'_> { None }; let img: &RgbaImage = &tile.build_image(ctx.as_ref(), scale); - // assert_eq!(img.width(), scale * build.block.get_size() as u32); - // assert_eq!(img.height(), scale * build.block.get_size() as u32); + debug_assert_eq!(img.width(), scale * build.block.get_size() as u32); + debug_assert_eq!(img.height(), scale * build.block.get_size() as u32); top.overlay_at(img, scale * x as u32, scale * y as u32); } } @@ -1,5 +1,5 @@ //! crate for dealing with mindustry -#![feature(array_chunks, const_trait_impl)] +#![feature(array_chunks, const_trait_impl, const_collections_with_hasher)] pub mod block; mod content; pub mod data; diff --git a/src/utils/image.rs b/src/utils/image.rs index 423a4c7..2aa6f21 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -33,16 +33,8 @@ impl Overlay<RgbImage> for RgbImage { fn overlay_at(&mut self, with: &RgbImage, x: u32, y: u32) -> &mut Self { for j in 0..with.height() { for i in 0..with.width() { - #[cfg(debug_assertions)] - { - let get = *with.get_pixel(i, j); - self.put_pixel(i + x, j + y, get); - } - #[cfg(not(debug_assertions))] - { - let get = unsafe { with.unsafe_get_pixel(i, j) }; - unsafe { self.unsafe_put_pixel(i + x, j + y, get) }; - } + let get = unsafe { with.unsafe_get_pixel(i, j) }; + unsafe { self.unsafe_put_pixel(i + x, j + y, get) }; } } self @@ -117,6 +109,8 @@ impl ImageUtils for RgbaImage { } fn overlay(&mut self, with: &RgbaImage) -> &mut Self { + debug_assert_eq!(self.width(), with.width()); + debug_assert_eq!(self.height(), with.height()); if self.len() % 4 != 0 || with.len() % 4 != 0 { unsafe { std::hint::unreachable_unchecked() }; } diff --git a/src/utils/lazy.rs b/src/utils/lazy.rs index 268a81c..5a19029 100644 --- a/src/utils/lazy.rs +++ b/src/utils/lazy.rs @@ -36,7 +36,7 @@ impl<T, F> Lock<T, F> { #[inline] // SAFETY: CALL [load] FIRST! pub unsafe fn get(&self) -> &T { - &*(*self.data.get()).value + &(*self.data.get()).value } } |