mindustry logic execution, map- and schematic- parsing and rendering
some optimizations:
- use slice_as_chunks
- dont load zips, just put it all in
- some clone saves
| -rw-r--r-- | Cargo.toml | 15 | ||||
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | assets/blocks/storage/unloader-center.png (renamed from assets/blocks/storage/center.png) | bin | 267 -> 267 bytes | |||
| -rw-r--r-- | build.rs | 87 | ||||
| -rw-r--r-- | rust-toolchain.toml | 2 | ||||
| -rw-r--r-- | src/block/distribution.rs | 175 | ||||
| -rw-r--r-- | src/block/drills.rs | 38 | ||||
| -rw-r--r-- | src/block/environment.rs | 13 | ||||
| -rw-r--r-- | src/block/liquid.rs | 28 | ||||
| -rw-r--r-- | src/block/logic.rs | 53 | ||||
| -rw-r--r-- | src/block/mod.rs | 29 | ||||
| -rw-r--r-- | src/block/payload.rs | 22 | ||||
| -rw-r--r-- | src/block/power.rs | 39 | ||||
| -rw-r--r-- | src/block/production.rs | 6 | ||||
| -rw-r--r-- | src/block/simple.rs | 17 | ||||
| -rw-r--r-- | src/block/turrets.rs | 13 | ||||
| -rw-r--r-- | src/block/units.rs | 180 | ||||
| -rw-r--r-- | src/block/walls.rs | 27 | ||||
| -rw-r--r-- | src/data/autotile.rs | 34 | ||||
| -rw-r--r-- | src/data/map.rs | 14 | ||||
| -rw-r--r-- | src/data/renderer.rs | 105 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/utils/image.rs | 28 |
23 files changed, 405 insertions, 526 deletions
@@ -1,6 +1,6 @@ [package] name = "mindus" -version = "1.4.3" +version = "1.4.5" edition = "2021" description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)" authors = [ @@ -16,17 +16,16 @@ flate2 = { version = "1.0", features = ["zlib"], default-features = false } base64 = "0.21" paste = "1.0" strconv = "0.1" +# png feature not needed; only for binary. todo seperate bin image = { version = "0.24", features = ["png"], default-features = false } -const-str = "0.5" color-hex = "0.2" -zip = { version = "0.6", features = ["zstd"], default-features = false } tinyrand = "0.5" tinyrand-std = "0.5" -dashmap = "5.4" fast_image_resize = "2.7" thiserror = "1.0" bobbin-bits = "0.1" blurslice = { version = "0.1", optional = true } +dashmap = { version = "5.5", features = ["inline"] } [features] schem_shadow = ["dep:blurslice"] @@ -34,7 +33,7 @@ map_shadow = ["dep:blurslice"] default = ["schem_shadow", "map_shadow"] [build-dependencies] -zip = { version = "0.6.6", features = ["zstd"], default-features = false } +image = { version = "0.24", features = ["png"], default-features = false } walkdir = "2" [[bin]] @@ -47,5 +46,11 @@ opt-level = 3 lto = true incremental = true +[profile.dev.build-override] +opt-level = 3 + +[profile.release.build-override] +opt-level = 3 + [dev-dependencies] diff = "0.1" @@ -1,4 +1,7 @@ # mindus + + + Mindus is a library for working with [Mindustry](https://github.com/Anuken/Mindustry) formats. ## Usage @@ -14,4 +17,4 @@ output.save("output.png"); This produces: -
\ No newline at end of file + diff --git a/assets/blocks/storage/center.png b/assets/blocks/storage/unloader-center.png Binary files differindex 847b884..847b884 100644 --- a/assets/blocks/storage/center.png +++ b/assets/blocks/storage/unloader-center.png @@ -1,67 +1,38 @@ +#![feature(let_chains)] +use image::codecs::png::PngDecoder; +use image::DynamicImage; use std::fs::File; -use std::io::prelude::*; -use std::io::{Seek, Write}; +use std::io::{BufReader, Write as _}; use std::iter::Iterator; use std::path::Path; -use walkdir::{DirEntry, WalkDir}; -use zip::result::ZipError; -use zip::write::FileOptions; +use walkdir::WalkDir; -fn zip_dir<T>( - it: &mut dyn Iterator<Item = DirEntry>, - prefix: &str, - writer: T, -) -> zip::result::ZipResult<()> -where - T: Write + Seek, -{ - let mut zip = zip::ZipWriter::new(writer); - let mut options = FileOptions::default() - .compression_method(zip::CompressionMethod::Zstd) - .unix_permissions(0o755); - if let Ok(v) = std::env::var("COMPRESS") { - if v == "1" { - options = options.compression_level(Some(22)); - } - } - - let mut buffer = Vec::new(); - for entry in it { - let path = entry.path(); - let name = path.strip_prefix(Path::new(prefix)).unwrap(); - - // Write file or directory explicitly - // Some unzip tools unzip files with directory paths correctly, some do not! - if path.is_file() { - println!("adding file {path:?} as {name:?} ..."); - #[allow(deprecated)] - zip.start_file_from_path(name, options)?; - let mut f = File::open(path)?; - - f.read_to_end(&mut buffer)?; - zip.write_all(&buffer)?; - buffer.clear(); - } else if !name.as_os_str().is_empty() { - // Only if not root! Avoids path spec / warning - // and mapname conversion failed error on unzip - println!("adding dir {path:?} as {name:?} ..."); - #[allow(deprecated)] - zip.add_directory_from_path(name, options)?; - } - } - zip.finish()?; - Result::Ok(()) -} - -fn main() -> Result<(), ZipError> { +fn main() { let _ = std::fs::remove_dir_all("target/out"); let walkdir = WalkDir::new("assets"); - let it = walkdir.into_iter(); println!("cargo:rerun-if-changed=assets/"); println!("cargo:rerun-if-changed=build.rs"); - zip_dir( - &mut it.filter_map(|e| e.ok()), - "assets", - File::create(std::env::var("OUT_DIR").unwrap() + "/asset").unwrap(), - ) + let o = std::env::var("OUT_DIR").unwrap(); + let mut f = File::create(Path::new(&o).join("asset")).unwrap(); + let mut n = 1usize; + f.write_all(b"fn put(map: &DashMap<String, RgbaImage>) {") + .unwrap(); + let mut s = String::new(); // idk write_all / write wasnt working + 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" { + let p = DynamicImage::from_decoder(PngDecoder::new(BufReader::new(File::open(path).unwrap())).unwrap()).unwrap().into_rgba8(); + let x = p.width(); + let y = p.height(); + let path = path.with_extension(""); + let path = path.file_name().unwrap().to_str().unwrap(); + let mut f = File::create(Path::new(&o).join(n.to_string())).unwrap(); + f.write_all(&p.into_raw()).unwrap(); + println!("writing {path:?}"); + s+= &format!("\tmap.insert(String::from(\"{path}\"), RgbaImage::from_vec({x}, {y}, include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{n}\")).to_vec()).unwrap());\n"); + n += 1; + } + } + f.write_all(s.as_bytes()).unwrap(); + f.write_all(b"}").unwrap(); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/block/distribution.rs b/src/block/distribution.rs index baa6ee6..06ce5b8 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -8,9 +8,9 @@ use crate::item; make_simple!( ConveyorBlock, - |_, _, name, _, ctx: Option<&RenderingContext>, rot: Rotation| { + |_, name, _, ctx: Option<&RenderingContext>, rot: Rotation| { let ctx = ctx.unwrap(); // we set want_context to true - Some(tile(ctx, "distribution", "conveyors", name, rot)) + tile(ctx, name, rot) }, |_, _, _, buff: &mut DataRead| { // format: @@ -31,9 +31,9 @@ make_simple!( make_simple!( DuctBlock, - |_, _, name, _, ctx: Option<&RenderingContext>, rot| { + |_, name, _, ctx: Option<&RenderingContext>, rot| { let ctx = ctx.unwrap(); - Some(tile(ctx, "distribution", "ducts", name, rot)) + tile(ctx, name, rot) }, |_, _, _, buff: &mut DataRead| { // format: @@ -43,35 +43,25 @@ make_simple!( true ); -make_simple!( - JunctionBlock, - |_, _, _, _, _, _| None, - |_, _, _, buff: &mut DataRead| { read_directional_item_buffer(buff) }, - false -); - -make_simple!(SimpleDuctBlock, |_, _, name, _, _, rot: Rotation| { - let mut base = load("distribution/ducts", "duct-base").unwrap().clone(); - let mut top = load("distribution/ducts", name).unwrap().clone(); +make_simple!(JunctionBlock => |_, _, _, buff: &mut DataRead| { read_directional_item_buffer(buff) }); +make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation| { + let mut base = load("duct-base"); + let mut top = load(name); top.rotate(rot.rotated(false).count()); base.overlay(&top); - Some(ImageHolder::from(base)) + base }); fn draw_stack( _: &StackConveyor, - _: &str, name: &str, _: Option<&State>, ctx: Option<&RenderingContext>, rot: Rotation, -) -> Option<ImageHolder> { +) -> ImageHolder { let ctx = ctx.unwrap(); let mask = mask(ctx, rot, name); - // clone to not hold lock - let edge = load("distribution/stack-conveyors", &format!("{name}-edge")) - .unwrap() - .clone(); + let edge = load(&format!("{name}-edge")); let edgify = |skip, to: &mut RgbaImage| { for i in 0..4 { if i == skip { @@ -82,45 +72,40 @@ fn draw_stack( to.overlay(&edge); } }; - let gimme = |n: u8| { - load("distribution/stack-conveyors", &format!("{name}-{n}")) - .unwrap() - .clone() - }; + let gimme = |n: u8| load(&format!("{name}-{n}")); let empty = ctx.cross[rot.count() as usize].map_or(true, |(v, _)| v.name != name); // mindustry says fuck this and just draws the arrow convs in schems but im better than that - Some(ImageHolder::from( - if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" { - // end - let mut base = gimme(2); - edgify(rot.mirrored(true, true).rotated(false).count(), &mut base); - base - } else if mask == B0000 && empty { - // single - let mut base = gimme(0); - base.rotate(rot.rotated(false).count()); - edgify(5, &mut base); - base - } else if mask == B0000 { - // input - let mut base = gimme(1); - edgify(rot.rotated(false).count(), &mut base); - base - } else { - // directional - let mut base = gimme(0); - let going = rot.rotated(false).count(); - base.rotate(going); - for [r, i] in [[3, 0b1000], [0, 0b0100], [1, 0b0010], [2, 0b0001]] { - if (mask.into_u8() & i) == 0 && (going != r || empty) { - let mut edge = edge.clone(); - edge.rotate(r); - base.overlay(&edge); - } + + if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" { + // end + let mut base = gimme(2); + edgify(rot.mirrored(true, true).rotated(false).count(), &mut base); + base + } else if mask == B0000 && empty { + // single + let mut base = gimme(0); + base.rotate(rot.rotated(false).count()); + edgify(5, &mut base); + base + } else if mask == B0000 { + // input + let mut base = gimme(1); + edgify(rot.rotated(false).count(), &mut base); + base + } else { + // directional + let mut base = gimme(0); + let going = rot.rotated(false).count(); + base.rotate(going); + for [r, i] in [[3, 0b1000], [0, 0b0100], [1, 0b0010], [2, 0b0001]] { + if (mask.into_u8() & i) == 0 && (going != r || empty) { + let mut edge = edge.clone(); + edge.rotate(r); + base.overlay(&edge); } - base - }, - )) + } + base + } } make_simple!( @@ -231,60 +216,43 @@ impl BlockLogic for ItemBlock { fn draw( &self, - _: &str, name: &str, state: Option<&State>, _: Option<&RenderingContext>, rot: Rotation, - ) -> Option<ImageHolder> { - let mut p = load( - match name { - "unloader" => "storage", - "duct-router" | "duct-unloader" => "distribution/ducts", - _ => "distribution", - }, - name, - ) - .unwrap() - .clone(); + ) -> ImageHolder { + let mut p = load(name); if let Some(state) = state { if let Some(s) = Self::get_state(state) { - let mut top = load( - match name { - "unloader" => "storage", - _ => "distribution", - }, - match name { - "unit-cargo-unload-point" => "unit-cargo-unload-point-top", - _ => "center", - }, - ) - .unwrap() - .clone(); + let mut top = load(match name { + "unit-cargo-unload-point" => "unit-cargo-unload-point-top", + "unloader" => "unloader-center", + _ => "center", + }); p.overlay(top.tint(s.color())); - return Some(ImageHolder::from(p)); + return p; } } if matches!(name, "unloader" | "unit-cargo-unload-point") { - return Some(ImageHolder::from(p)); + return p; } - if matches!(name, "duct-unloader" | "duct-router") { - let mut null = load("distribution/ducts", "top").unwrap().to_owned(); - null.rotate(rot.rotated(false).count()); - if name == "duct-unloader" { - let mut top = load("distribution/ducts", "duct-unloader-top") - .unwrap() - .to_owned(); - // this rotate call could be omitted if rotation == Right to save a clone - top.rotate(rot.rotated(false).count()); - null.overlay(&top); - } - p.overlay(&null); - Some(ImageHolder::from(p)) + if name == "duct-router" { + let mut arrow = load("top"); + arrow.rotate(rot.rotated(false).count()); + p.overlay(&arrow); + p + } else if name == "duct-unloader" { + let mut top = load("duct-unloader-top"); + top.rotate(rot.rotated(false).count()); + p.overlay(&top); + let mut arrow = load("top"); + arrow.rotate(rot.rotated(false).count()); + p.overlay(&arrow); + p } else { - let mut null = load("distribution", "cross-full").unwrap().clone(); + let mut null = load("cross-full"); null.overlay(&p); - Some(ImageHolder::from(null)) + null } } @@ -486,6 +454,17 @@ impl BlockLogic for BridgeBlock { } Ok(()) } + + + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } } #[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] diff --git a/src/block/drills.rs b/src/block/drills.rs index 5d8ac30..f46788c 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -5,35 +5,25 @@ use crate::block::*; make_simple!( DrillBlock, - |_, _, name, _, _, rot: Rotation| { + |_, name, _, _, rot: Rotation| { if matches!(name, "large-plasma-bore" | "plasma-bore") { - let mut base = load("drills", name).unwrap().clone(); - let top = load("drills", &format!("{name}-top")).unwrap(); - if rot == Rotation::Right { - base.overlay(&top); - } else { - let mut top = top.clone(); - top.rotate(rot.rotated(false).count()); - base.overlay(&top); - } - return Some(ImageHolder::from(base)); + let mut base = load(name); + let mut top = load(&format!("{name}-top")); + top.rotate(rot.rotated(false).count()); + base.overlay(&top); + return base; } - Some(ImageHolder::Borrow(load("drills", name).unwrap())) + load(name) }, - |_, _, _, buff: &mut DataRead| { read_drill(buff) } + |_, _, _, buff: &mut DataRead| read_drill(buff) ); make_simple!(ExtractorBlock); -make_simple!(WallCrafter, |_, _, _, _, _, rot: Rotation| { - let mut base = load("drills", "cliff-crusher").unwrap().clone(); - let top = load("drills", "cliff-crusher-top").unwrap(); - if rot == Rotation::Right { - base.overlay(&top); - } else { - let mut top = top.clone(); - top.rotate(rot.rotated(false).count()); - base.overlay(&top); - } - Some(ImageHolder::from(base)) +make_simple!(WallCrafter, |_, _, _, _, rot: Rotation| { + let mut base = load("cliff-crusher"); + let mut top = load("cliff-crusher-top"); + top.rotate(rot.rotated(false).count()); + base.overlay(&top); + base }); make_register! { diff --git a/src/block/environment.rs b/src/block/environment.rs index 168bdfd..fd07bc3 100644 --- a/src/block/environment.rs +++ b/src/block/environment.rs @@ -11,15 +11,18 @@ macro_rules! register_env { $($field => EnvironmentBlock::new($size, true, &[]);)* ); - make_simple!(EnvironmentBlock, |_, _, name, _, _, _| { + make_simple!(EnvironmentBlock, |_, name, _, _, _| { let mut rand = StdRand::seed(ClockSeed::default().next_u64()); match name { $($field => { #[allow(clippy::reversed_empty_ranges)] - if $variations == 1 { Some(ImageHolder::Borrow(load("environment", $field).unwrap())) } - else if $variations == 0 { return None } - else { Some(ImageHolder::Borrow(load("environment", &format!("{}{}", $field, rand.next_range(1usize..$variations))).unwrap())) } - },)* + match $variations { + 2..=6 => load(&format!("{}{}", $field, rand.next_range(1usize..$variations))), + 1 => load($field), + 0 => ImageHolder::from(RgbaImage::new($size * 32, $size * 32)), + _ => unreachable!(), + } + },)* _ => { unreachable!() } } }); diff --git a/src/block/liquid.rs b/src/block/liquid.rs index 0a8a566..a4cadb6 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -13,24 +13,17 @@ use crate::utils::ImageUtils; make_simple!(LiquidBlock); make_simple!( ConduitBlock, - |_, _, name, _, ctx: Option<&RenderingContext>, rot| { + |_, name, _, ctx: Option<&RenderingContext>, rot| { let ctx = ctx.unwrap(); let mask = mask(ctx, rot, name); let (index, rot, flip) = mask2rotations(mask, rot); - let tile = rotations2tile( - (index, rot, flip), - "liquid", - "conduits", - &format!("{name}-top"), - ); - let mut bottom = load("liquid", &format!("conduits/conduit-bottom-{index}")) - .unwrap() - .to_owned(); + let tile = rotations2tile((index, rot, flip), &format!("{name}-top")); + let mut bottom = load(&format!("conduit-bottom-{index}")); flrot(flip, rot, &mut bottom); bottom.tint(image::Rgb([74, 75, 83])); bottom.overlay(tile.borrow()); // TODO caps. stopped trying bcz too complex - Some(ImageHolder::from(bottom)) + bottom }, true ); @@ -122,23 +115,22 @@ impl BlockLogic for FluidBlock { fn draw( &self, - category: &str, name: &str, state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, - ) -> Option<ImageHolder> { - let mut p = load(category, name).unwrap().clone(); + ) -> ImageHolder { + let mut p = load(name); if let Some(state) = state { if let Some(s) = Self::get_state(state) { - let mut top = load("distribution", "center").unwrap().clone(); + let mut top = load("center"); p.overlay(top.tint(s.color())); - return Some(ImageHolder::Own(p)); + return p; } } - let mut null = load("distribution", "cross-full").unwrap().clone(); + let mut null = load("cross-full"); null.overlay(&p); - Some(ImageHolder::Own(null)) + null } /// format: diff --git a/src/block/logic.rs b/src/block/logic.rs index ce202bc..9595673 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -12,8 +12,7 @@ use crate::data::{self, CompressError, DataRead, DataWrite}; make_simple!(LogicBlock); make_simple!( - MemoryBlock, - |_, _, _, _, _, _| None, + MemoryBlock => |_, _, _, buff: &mut DataRead| { // format: // - iterate [`u32`] @@ -147,12 +146,11 @@ impl BlockLogic for CanvasBlock { /// i thought about drawing the borders and stuff but it felt like too much work fn draw( &self, - c: &str, n: &str, state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, - ) -> Option<ImageHolder> { + ) -> ImageHolder { if let Some(state) = state { let state = self.clone_state(state); let p = state.downcast::<RgbImage>().unwrap(); @@ -169,15 +167,18 @@ impl BlockLogic for CanvasBlock { .into_rgba8() .scale((self.size as u32 * 32) - 14) }; - let mut borders = load(c, n).unwrap().to_owned(); + let mut borders = load(n); borders.overlay_at(&p, 7, 7); - return Some(ImageHolder::from(borders)); + return borders; } - Some(ImageHolder::from(RgbaImage::new( - self.size as u32 * 32, - self.size as u32 * 32, - ))) + let mut def = RgbaImage::new(self.size as u32 * 32, self.size as u32 * 32); + for image::Rgba([r, g, b, _]) in def.pixels_mut() { + *r = PALETTE[0][0]; + *g = PALETTE[0][1]; + *b = PALETTE[0][2]; + } + ImageHolder::from(def) } /// format: @@ -228,6 +229,16 @@ impl BlockLogic for MessageLogic { Ok(DynData::Empty) } + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } + fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> { match data { DynData::Empty | DynData::String(None) => Ok(Some(Self::create_state(String::new()))), @@ -323,21 +334,19 @@ impl BlockLogic for SwitchLogic { fn draw( &self, _: &str, - _: &str, state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, - ) -> Option<ImageHolder> { - let base = load("logic", "switch").unwrap(); + ) -> ImageHolder { + let mut base = load("switch"); if let Some(state) = state { if *Self::get_state(state) { - let mut base = base.clone(); - let on = load("logic", "switch-on").unwrap(); + let on = load("switch-on"); base.overlay(&on); - return Some(ImageHolder::from(base)); + return base; } } - Some(ImageHolder::from(base)) + base } } @@ -394,6 +403,16 @@ fn read_decompressed(buff: &mut DataRead) -> Result<ProcessorState, ProcessorDes impl BlockLogic for ProcessorLogic { impl_block!(); + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } + fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { Ok(DynData::Empty) } diff --git a/src/block/mod.rs b/src/block/mod.rs index a0120ca..c7feb9a 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -59,14 +59,11 @@ pub trait BlockLogic { #[allow(unused_variables)] fn draw( &self, - category: &str, name: &str, state: Option<&State>, context: Option<&RenderingContext>, rot: Rotation, - ) -> Option<ImageHolder> { - None - } + ) -> ImageHolder; fn want_context(&self) -> bool { false @@ -163,7 +160,6 @@ impl SerializeError { /// a block. put it in stuff! pub struct Block { - category: Cow<'static, str>, name: Cow<'static, str>, pub(crate) logic: BoxAccess<'static, dyn BlockLogic + Sync>, } @@ -178,15 +174,10 @@ impl Block { #[must_use] /// create a new block pub const fn new( - category: Cow<'static, str>, name: Cow<'static, str>, logic: BoxAccess<'static, dyn BlockLogic + Sync>, ) -> Self { - Self { - category, - name, - logic, - } + Self { name, logic } } /// this blocks name @@ -209,18 +200,7 @@ impl Block { context: Option<&RenderingContext>, rot: Rotation, ) -> ImageHolder { - if let Some(p) = self - .logic - .as_ref() - .draw(&self.category, &self.name, state, context, rot) - { - return p; - } - ImageHolder::Own(read(&self.category, &self.name, self.get_size())) - } - - pub fn has_building(&self) -> bool { - &self.category != "environment" + self.logic.as_ref().draw(&self.name, state, context, rot) } /// size. @@ -448,9 +428,6 @@ macro_rules! make_register { ($($field:literal => $logic:expr;)+) => { paste::paste! { $( pub static [<$field:snake:upper>]: $crate::block::Block = $crate::block::Block::new( - std::borrow::Cow::Borrowed( - const_str::split!(module_path!(), "::")[2] - ), std::borrow::Cow::Borrowed($field), $crate::access::Access::Borrowed(&$logic)); )+ diff --git a/src/block/payload.rs b/src/block/payload.rs index 4a842b7..20485bb 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -13,12 +13,8 @@ use crate::unit; use super::BlockRegistry; make_simple!(SimplePayloadBlock); -make_simple!( - PayloadConveyor, - |_, _, _, _, _, _| None, - read_payload_conveyor -); -// make_simple!(PayloadRouter, |_, _, _, _, _, _| None, read_payload_router); +make_simple!(PayloadConveyor => read_payload_conveyor); +// make_simple!(PayloadRouter => read_payload_router); make_register! { "payload-conveyor" => PayloadConveyor::new(3, false, cost!(Copper: 10, Graphite: 10)); @@ -70,6 +66,16 @@ impl PayloadBlock { impl BlockLogic for PayloadBlock { impl_block!(); + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } + fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { Ok(DynData::Empty) } @@ -189,8 +195,8 @@ fn read_payload( UNIT => { let u = buff.read_u8()?; let Some(_u) = entity_mapping.get(&u) else { - return Err(ReadError::Expected("map entry")); - }; + return Err(ReadError::Expected("map entry")); + }; // unit::Type::try_from(u).unwrap_or(unit::Type::Alpha).read(todo!()); } _ => return Err(ReadError::Expected("0 | 1")), diff --git a/src/block/power.rs b/src/block/power.rs index 566a710..529282e 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -10,16 +10,15 @@ make_simple!(NuclearGeneratorBlock => |_, _, _, buff: &mut DataRead| read_nuclea make_simple!(ImpactReactorBlock => |_, _, _, buff: &mut DataRead| read_impact(buff)); make_simple!(HeaterGeneratorBlock => |_, _, _, buff: &mut DataRead| read_heater(buff)); make_simple!(BatteryBlock); -make_simple!(DiodeBlock, |_, _, _, _, _, rot: Rotation| { - let base = load("power", "diode").unwrap(); - if rot == Rotation::Right { - return Some(ImageHolder::from(base)); - } - let mut base = base.clone(); - let mut top = load("power", "diode-arrow").unwrap().clone(); - top.rotate(rot.rotated(false).count()); - base.overlay(&top); - Some(ImageHolder::from(base)) +make_simple!(DiodeBlock, |_, _, _, _, rot: Rotation| { + let mut base = load("diode"); + if rot == Rotation::Right { + return base; + } + let mut top = load("diode-arrow"); + top.rotate(rot.rotated(false).count()); + base.overlay(&top); + base }); make_register! { @@ -130,6 +129,16 @@ impl BlockLogic for ConnectorBlock { fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> { Ok(DynData::Point2Array(Self::get_state(state).clone())) } + + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } } #[derive(Debug, Error)] @@ -207,6 +216,16 @@ impl BlockLogic for LampBlock { } } + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } + fn clone_state(&self, state: &State) -> State { let state = Self::get_state(state); Box::new(Self::create_state(*state)) diff --git a/src/block/production.rs b/src/block/production.rs index d268e8d..5796ca9 100644 --- a/src/block/production.rs +++ b/src/block/production.rs @@ -44,8 +44,7 @@ make_register! { } make_simple!( - ProductionBlock, - |_, _, _, _, _, _| None, + ProductionBlock => |_, _, _, buff: &mut DataRead| { // format: // - progress: `f32` @@ -56,8 +55,7 @@ make_simple!( ); make_simple!( - HeatCrafter, - |_, _, _, _, _, _| None, + HeatCrafter => |_, _, _, buff: &mut DataRead| { // format: // - progress: `f32` diff --git a/src/block/simple.rs b/src/block/simple.rs index 0f87f50..40cacf5 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -22,7 +22,7 @@ macro_rules! state_impl { pub(crate) use state_impl; -/// draw is called with self, category, name, state, context, rotation +/// draw is called with self, name, state, context, rotation /// read is called with build, reg, entity_mapping, buff macro_rules! make_simple { ($name: ident, $draw: expr, $read: expr, $wants_context: literal) => { @@ -86,13 +86,13 @@ macro_rules! make_simple { fn draw( &self, - category: &str, name: &str, state: Option<&crate::block::State>, context: Option<&crate::data::renderer::RenderingContext>, rot: crate::block::Rotation, - ) -> Option<crate::data::renderer::ImageHolder> { - $draw(self, category, name, state, context, rot) + ) -> crate::data::renderer::ImageHolder { + #[allow(clippy::redundant_closure_call)] + $draw(self, name, state, context, rot) } fn want_context(&self) -> bool { @@ -106,6 +106,7 @@ macro_rules! make_simple { entity_mapping: &crate::data::map::EntityMapping, buff: &mut crate::data::DataRead, ) -> Result<(), crate::data::ReadError> { + #[allow(clippy::redundant_closure_call)] $read(build, reg, entity_mapping, buff) } } @@ -120,12 +121,16 @@ macro_rules! make_simple { crate::block::simple::make_simple!($name, $draw, $read, false); }; ($name: ident => $read: expr) => { - crate::block::simple::make_simple!($name, |_, _, _, _, _, _| None, $read); + crate::block::simple::make_simple!( + $name, + |m: &Self, n: &str, _, _, _| crate::data::renderer::read(n, m.get_size()), + $read + ); }; ($name: ident) => { crate::block::simple::make_simple!( $name, - |_, _, _, _, _, _| None, + |m: &Self, n: &str, _, _, _| crate::data::renderer::read(n, m.get_size()), |_, _, _, _| Ok(()), false ); diff --git a/src/block/turrets.rs b/src/block/turrets.rs index 061074c..d1617fc 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -39,20 +39,19 @@ make_register! { fn draw_turret( me: &impl BlockLogic, - _: &str, name: &str, _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, -) -> Option<ImageHolder> { +) -> ImageHolder { let path = match name { "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" - | "scathe" | "malign" | "smite" => format!("bases/reinforced-block-{}", me.get_size()), - _ => format!("bases/block-{}", me.get_size()), + | "scathe" | "malign" | "smite" => format!("reinforced-block-{}", me.get_size()), + _ => format!("block-{}", me.get_size()), }; - let mut base = load("turrets", &path).unwrap().value().clone(); - base.overlay(load("turrets", name).unwrap().value()); - Some(ImageHolder::from(base)) + let mut base = load(&path); + base.overlay(&load(name)); + base } make_simple!(Turret, draw_turret, |_, _, _, buff: &mut DataRead| { diff --git a/src/block/units.rs b/src/block/units.rs index 5bcda05..21a7b3b 100644 --- a/src/block/units.rs +++ b/src/block/units.rs @@ -33,86 +33,58 @@ use crate::unit; // ) // } -make_simple!( - ConstructorBlock, - |me: &Self, _, name, _, _, rot: Rotation| { - let mut base = load("units", name).unwrap().to_owned(); - let times = rot.rotated(false).count(); - { - let out = load( - "payload", - &match name { - "additive-reconstructor" - | "multiplicative-reconstructor" - | "exponential-reconstructor" - | "tetrative-reconstructor" => format!("factory-out-{}", me.size), - _ => format!("factory-out-{}-dark", me.size), - }, - ) - .unwrap(); - if times != 0 { - let mut out = out.clone(); - out.rotate(times); - base.overlay(&out); - } else { - base.overlay(&out); - } - } - { - let input = load( - "payload", - &match name { - "additive-reconstructor" - | "multiplicative-reconstructor" - | "exponential-reconstructor" - | "tetrative-reconstructor" => format!("factory-in-{}", me.size), - _ => format!("factory-in-{}-dark", me.size), - }, - ) - .unwrap(); - if times != 0 { - let mut input = input.clone(); - input.rotate(times); - base.overlay(&input); - } else { - base.overlay(&input); - } - } - // TODO: the context cross is too small - // for i in 0..4u8 { - // if let Some((b, rot)) = dbg!(ctx.cross[i as usize]) { - // if rot.mirrored(true, true) != ctx.rotation && match rot { - // Rotation::Up => i == 3, - // Rotation::Right => i == 4, - // Rotation::Down => i == 0, - // Rotation::Left => i == 2, - // } && is_pay(b.name()) - // { - // let r = unsafe { std::mem::transmute::<u8, Rotation>(i) } - // .mirrored(true, true) - // .rotated(false); - // let mut input = input.clone(); - // input.rotate(r.count()); - // base.overlay(&input); - // } - // } - // } - - base.overlay(&load("units", &format!("{name}-top")).unwrap()); - - if matches!(name, "mech-assembler" | "tank-assembler" | "ship-assembler") { - let side = load("units", &format!("{name}-side")).unwrap(); - if times != 0 { - let mut side = side.clone(); - side.rotate(times); - base.overlay(&side); - } else { - base.overlay(&side); - } - } - Some(ImageHolder::from(base)) +make_simple!(ConstructorBlock, |me: &Self, name, _, _, rot: Rotation| { + let mut base = load(name); + let times = rot.rotated(false).count(); + + let mut out = load(&match name { + "additive-reconstructor" + | "multiplicative-reconstructor" + | "exponential-reconstructor" + | "tetrative-reconstructor" => format!("factory-out-{}", me.size), + _ => format!("factory-out-{}-dark", me.size), + }); + out.rotate(times); + base.overlay(&out); + + let mut input = load(&match name { + "additive-reconstructor" + | "multiplicative-reconstructor" + | "exponential-reconstructor" + | "tetrative-reconstructor" => format!("factory-in-{}", me.size), + _ => format!("factory-in-{}-dark", me.size), + }); + input.rotate(times); + base.overlay(&input); + + // TODO: the context cross is too small + // for i in 0..4u8 { + // if let Some((b, rot)) = dbg!(ctx.cross[i as usize]) { + // if rot.mirrored(true, true) != ctx.rotation && match rot { + // Rotation::Up => i == 3, + // Rotation::Right => i == 4, + // Rotation::Down => i == 0, + // Rotation::Left => i == 2, + // } && is_pay(b.name()) + // { + // let r = unsafe { std::mem::transmute::<u8, Rotation>(i) } + // .mirrored(true, true) + // .rotated(false); + // let mut input = input.clone(); + // input.rotate(r.count()); + // base.overlay(&input); + // } + // } + // } + + base.overlay(&load(&format!("{name}-top"))); + if matches!(name, "mech-assembler" | "tank-assembler" | "ship-assembler") { + let mut side = load(&format!("{name}-side")); + side.rotate(times); + base.overlay(&side); } -); + base +}); make_simple!(UnitBlock); make_simple!(RepairTurret => |_, _, _, buff: &mut DataRead| { buff.skip(4) // rotation: [`f32`] @@ -231,45 +203,25 @@ impl BlockLogic for AssemblerBlock { fn draw( &self, - _: &str, name: &str, _: Option<&State>, _: Option<&RenderingContext>, rot: Rotation, - ) -> Option<ImageHolder> { - let mut base = load("units", name).unwrap().to_owned(); - let out = load( - "payload", - match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", - _ => "factory-out-3-dark", - }, - ) - .unwrap(); - let times = rot.rotated(false).count(); - if times != 0 { - let mut out = out.clone(); - out.rotate(times); - base.overlay(&out); - } else { - base.overlay(&out); - } - base.overlay( - &load( - match name { - "ground-factory" | "air-factory" | "naval-factory" => "payload", - _ => "units", - }, - &match name { - "ground-factory" | "air-factory" | "naval-factory" => { - format!("factory-top-{}", self.size) - } - _ => format!("{name}-top"), - }, - ) - .unwrap() - ); - Some(ImageHolder::from(base)) + ) -> ImageHolder { + let mut base = load(name); + let mut out = load(match name { + "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", + _ => "factory-out-3-dark", + }); + out.rotate(rot.rotated(false).count()); + base.overlay(&out); + base.overlay(&load(&match name { + "ground-factory" | "air-factory" | "naval-factory" => { + format!("factory-top-{}", self.size) + } + _ => format!("{name}-top"), + })); + base } /// format: diff --git a/src/block/walls.rs b/src/block/walls.rs index 59967ac..6b782a7 100644 --- a/src/block/walls.rs +++ b/src/block/walls.rs @@ -2,31 +2,26 @@ use crate::block::simple::*; use crate::block::*; use crate::data::dynamic::DynType; -use crate::data::renderer::{load, read_with, ImageHolder, TOP}; +use crate::data::renderer::{load, read_with}; use tinyrand::{Rand, RandRange, Seeded, StdRand}; use tinyrand_std::clock_seed::ClockSeed; -make_simple!(WallBlock, |_, _, name, _, _, _| { +make_simple!(WallBlock, |_, name, _, _, _| { macro_rules! pick { ($name: literal => load $n: literal) => {{ let mut rand = StdRand::seed(ClockSeed::default().next_u64()); - Some(ImageHolder::from(load( - "walls", - &format!("{}{}", $name, rand.next_range(1usize..$n)), - ))) + load(&format!("{}{}", $name, rand.next_range(1usize..$n))) }}; } match name { "thruster" => { - const SFX: &[&str; 1] = &[TOP]; - Some(ImageHolder::from(read_with( - "turrets", "thruster", SFX, 4u32, - ))) + const SFX: &[&str; 1] = &["-top"]; + read_with("thruster", SFX, 4u32) } "scrap-wall" => pick!("scrap-wall" => load 5), "scrap-wall-large" => pick!("scrap-wall-large" => load 3), "scrap-wall-huge" => pick!("scrap-wall-huge" => load 3), - _ => Some(ImageHolder::from(load("walls", name))), + _ => load(name), } }); @@ -86,6 +81,16 @@ impl DoorBlock { impl BlockLogic for DoorBlock { impl_block!(); + fn draw( + &self, + name: &str, + _: Option<&State>, + _: Option<&RenderingContext>, + _: Rotation, + ) -> ImageHolder { + read(name, self.size) + } + fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { Ok(DynData::Boolean(false)) } diff --git a/src/data/autotile.rs b/src/data/autotile.rs index 3075d4f..76b0e5b 100644 --- a/src/data/autotile.rs +++ b/src/data/autotile.rs @@ -2,7 +2,6 @@ use super::renderer::*; use super::GridPos; use crate::block::{Block, Rotation}; use bobbin_bits::U4; -use image::imageops::{flip_horizontal_in_place as flip_h, flip_vertical_in_place as flip_v}; #[cfg(test)] macro_rules! dir { @@ -93,19 +92,8 @@ fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String { s } -pub fn tile( - ctx: &RenderingContext<'_>, - category: &str, - subcategory: &str, - name: &str, - rot: Rotation, -) -> ImageHolder { - rotations2tile( - mask2rotations(mask(ctx, rot, name), rot), - category, - subcategory, - name, - ) +pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation) -> ImageHolder { + rotations2tile(mask2rotations(mask(ctx, rot, name), rot), name) } pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) { @@ -221,27 +209,19 @@ pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) { pub const FLIP_X: u8 = 1; pub const FLIP_Y: u8 = 2; -pub fn flrot(flip: u8, rot: u8, with: &mut RgbaImage) { +pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder) { if (flip & FLIP_X) != 0 { - flip_h(with); + with.flip_h(); } if (flip & FLIP_Y) != 0 { - flip_v(with); + with.flip_v(); } with.rotate(rot); - // let mut from = from.own(); - // from.rotate(rot); - // ImageHolder::from(from) } /// TODO figure out if a flip is cheaper than a rotate_270 -pub fn rotations2tile( - (index, rot, flip): (u8, u8, u8), - category: &str, - subcategory: &str, - name: &str, -) -> ImageHolder { - let mut p = ImageHolder::from(load(category, &format!("{subcategory}/{name}-{index}"))); +pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str) -> ImageHolder { + let mut p = load(&format!("{name}-{index}")); flrot(flip, rot, p.borrow_mut()); p } diff --git a/src/data/map.rs b/src/data/map.rs index f32b039..7478a66 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -122,14 +122,6 @@ impl<'l> Tile<'l> { self.build.as_ref() } - /// check if this tile contains a building. - pub fn has_building(&self) -> bool { - if let Some(b) = &self.build { - return b.block.has_building(); - } - false - } - /// size of this tile /// /// ._. @@ -143,16 +135,16 @@ impl<'l> Tile<'l> { } pub fn floor_image(&self, context: Option<&RenderingContext>) -> ImageHolder { - let mut i = self.floor.image(None, context, Rotation::Up).own(); + let mut i = self.floor.image(None, context, Rotation::Up); if let Some(ore) = self.ore { i.overlay(ore.image(None, context, Rotation::Up).borrow()); } - ImageHolder::from(i) + i } pub fn build_image(&self, context: Option<&RenderingContext>) -> ImageHolder { // building covers floore - let Some(b)= &self.build else { + let Some(b) = &self.build else { unreachable!(); }; b.image(context) diff --git a/src/data/renderer.rs b/src/data/renderer.rs index e20b75a..cfd9c9b 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -1,13 +1,9 @@ //! schematic drawing use dashmap::mapref::one::Ref; use dashmap::DashMap; -use image::codecs::png::PngDecoder; pub(crate) use image::{DynamicImage, RgbaImage}; -use std::io::{BufReader, Cursor}; use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::sync::OnceLock; -use zip::ZipArchive; +use std::sync::LazyLock; pub(crate) use super::autotile::*; use crate::block::environment::METAL_FLOOR; @@ -20,13 +16,16 @@ pub(crate) use std::borrow::{Borrow, BorrowMut}; use super::schematic::Schematic; use super::GridPos; -type Cache = DashMap<PathBuf, RgbaImage>; -fn cache() -> &'static Cache { - CACHE.get_or_init(Cache::new) -} +type Cache = DashMap<String, RgbaImage>; +include!(concat!(env!("OUT_DIR"), "/asset")); // put function from here +static CACHE: LazyLock<Cache> = LazyLock::new(|| { + let mut map = Cache::new(); + put(&mut map); + map +}); pub enum ImageHolder { - Borrow(Ref<'static, PathBuf, RgbaImage>), + Borrow(Ref<'static, String, RgbaImage>), Own(RgbaImage), } @@ -37,6 +36,14 @@ impl ImageHolder { Self::Borrow(x) => x.clone(), } } + + pub fn rotate(&mut self, times: u8) { + if times == 0 { + return; + } + let p: &mut RgbaImage = self.borrow_mut(); + p.rotate(times); + } } impl Borrow<RgbaImage> for ImageHolder { @@ -73,14 +80,8 @@ impl DerefMut for ImageHolder { } } -impl From<Option<Ref<'static, PathBuf, RgbaImage>>> for ImageHolder { - fn from(value: Option<Ref<'static, PathBuf, RgbaImage>>) -> Self { - Self::Borrow(value.unwrap()) - } -} - -impl From<Ref<'static, PathBuf, RgbaImage>> for ImageHolder { - fn from(value: Ref<'static, PathBuf, RgbaImage>) -> Self { +impl From<Ref<'static, String, RgbaImage>> for ImageHolder { + fn from(value: Ref<'static, String, RgbaImage>) -> Self { Self::Borrow(value) } } @@ -91,69 +92,36 @@ impl From<RgbaImage> for ImageHolder { } } -static CACHE: OnceLock<Cache> = OnceLock::new(); -pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, RgbaImage>> { - let key = Path::new(category).join(name); - use dashmap::mapref::entry::Entry::*; - Some(match cache().entry(key) { - Occupied(v) => v.into_ref().downgrade(), - Vacant(entry) => { - let mut p = Path::new("blocks").join(category).join(name); - p.set_extension("png"); - let Some(i) = load_raw(p) else { - return None; - }; - entry.insert(i).downgrade() - } - }) +pub(crate) fn try_load(name: &str) -> Option<Ref<'static, String, RgbaImage>> { + let key = name.to_string(); + CACHE.get(&key) } -#[cfg(not(unix))] -const P: &str = "target/out"; -#[cfg(unix)] -const P: &str = "/tmp/mindus-tmp"; - -fn load_raw(f: impl AsRef<Path>) -> Option<RgbaImage> { - let f = std::fs::File::open(Path::new(P).join(f)).ok()?; - let r = PngDecoder::new(BufReader::new(f)).unwrap(); - let p = DynamicImage::from_decoder(r).unwrap().into_rgba8(); - assert!(p.width() != 0); - assert!(p.height() != 0); - Some(p) +pub(crate) fn load(name: &str) -> ImageHolder { + ImageHolder::from( + try_load(name) + .ok_or_else(|| format!("failed to load {name}")) + .unwrap(), + ) } -fn load_zip() { - if !Path::new(P).exists() { - let mut zip = ZipArchive::new(Cursor::new( - include_bytes!(concat!(env!("OUT_DIR"), "/asset")).to_vec(), - )) - .unwrap(); - zip.extract(P).unwrap(); - } -} -pub const TOP: &str = "-top"; const SUFFIXES: &[&str; 9] = &[ - "-bottom", "-mid", "-base", "", "-left", "-right", TOP, "-over", "-team", + "-bottom", "-mid", "-base", "", "-left", "-right", "-top", "-over", "-team", ]; -pub(crate) fn read<S>(category: &str, name: &str, size: S) -> RgbaImage +pub(crate) fn read<S>(name: &str, size: S) -> ImageHolder where S: Into<u32> + Copy, { - read_with(category, name, SUFFIXES, size) + read_with(name, SUFFIXES, size) } -pub(crate) fn read_with<S>( - category: &str, - name: &str, - suffixes: &'static [&'static str], - size: S, -) -> RgbaImage +pub(crate) fn read_with<S>(name: &str, suffixes: &'static [&'static str], size: S) -> ImageHolder where S: Into<u32> + Copy, { let mut c = RgbaImage::new(size.into() * 32, size.into() * 32); for suffix in suffixes { - if let Some(p) = load(category, &format!("{name}{suffix}")) { + if let Some(p) = try_load(&format!("{name}{suffix}")) { if suffix == &"-team" { c.overlay(p.clone().tint(SHARDED.color())); continue; @@ -161,7 +129,7 @@ where c.overlay(&p); } } - c + ImageHolder::from(c) } /// trait for renderable objects @@ -180,7 +148,6 @@ impl Renderable for Schematic<'_> { /// let output /*: RgbaImage */ = s.render(); /// ``` fn render(&self) -> RgbaImage { - load_zip(); // fill background let mut bg = RgbaImage::new( ((self.width + 2) * 32) as u32, @@ -225,7 +192,6 @@ impl Renderable for Schematic<'_> { impl Renderable for Map<'_> { fn render(&self) -> RgbaImage { - load_zip(); let scale = if self.width + self.height < 2000 { 8 } else { @@ -291,7 +257,6 @@ impl Renderable for Map<'_> { #[test] fn all_blocks() { - load_zip(); use crate::block::content::Type; use crate::content::Content; let reg = crate::block::build_registry(); @@ -308,7 +273,7 @@ fn all_blocks() { continue; } - let t = reg.get(dbg!(t.get_name())).unwrap(); + let t = reg.get(t.get_name()).unwrap(); t.image( None, Some(&RenderingContext { @@ -1,4 +1,5 @@ //! crate for dealing with mindustry +#![feature(lazy_cell, slice_as_chunks)] mod access; pub mod block; mod content; diff --git a/src/utils/image.rs b/src/utils/image.rs index 1b54d61..6abe314 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -1,5 +1,5 @@ use fast_image_resize as fr; -use image::{Rgb, Rgba, RgbaImage}; +use image::{imageops, Rgb, Rgba, RgbaImage}; use std::num::NonZeroU32; pub trait ImageUtils { @@ -13,6 +13,10 @@ pub trait ImageUtils { fn overlay_at(&mut self, with: &Self, x: u32, y: u32) -> &mut Self; /// rotate fn rotate(&mut self, times: u8) -> &mut Self; + /// flip along the horizontal axis + fn flip_h(&mut self) -> &mut Self; + /// flip along the vertical axis + fn flip_v(&mut self) -> &mut Self; /// shadow #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))] fn shadow(&mut self) -> &mut Self; @@ -78,12 +82,12 @@ impl ImageUtils for RgbaImage { let local = std::mem::take(self); let mut own = local.into_raw(); let other = with.as_raw(); - if own.len() % 4 != 0 || other.len() % 4 != 0 { - unsafe { std::hint::unreachable_unchecked(); } - } - for (i, other_pixels) in other.chunks_exact(4).enumerate() { + for (i, other_pixels) in unsafe { other.as_chunks_unchecked::<4>() } + .iter() + .enumerate() + { if other_pixels[3] > 128 { - let own_pixels = &mut own[i * 4..i * 4 + 4]; + let own_pixels = unsafe { own.get_unchecked_mut(i * 4..i * 4 + 4) }; own_pixels.copy_from_slice(other_pixels); } } @@ -150,4 +154,16 @@ impl ImageUtils for RgbaImage { } self } + + #[inline(always)] + fn flip_h(&mut self) -> &mut Self { + imageops::flip_horizontal_in_place(self); + self + } + + #[inline(always)] + fn flip_v(&mut self) -> &mut Self { + imageops::flip_vertical_in_place(self); + self + } } |