mindustry logic execution, map- and schematic- parsing and rendering
roll my own LazyLock
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | build.rs | 10 | ||||
| -rw-r--r-- | src/block/mod.rs | 13 | ||||
| -rw-r--r-- | src/data/map.rs | 6 | ||||
| -rw-r--r-- | src/data/renderer.rs | 54 | ||||
| -rw-r--r-- | src/data/schematic.rs | 9 | ||||
| -rw-r--r-- | src/exe/draw.rs | 3 | ||||
| -rw-r--r-- | src/exe/map.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/utils/lazy.rs | 45 | ||||
| -rw-r--r-- | src/utils/mod.rs | 2 |
11 files changed, 107 insertions, 47 deletions
@@ -1,6 +1,6 @@ [package] name = "mindus" -version = "2.0.3" +version = "3.0.0" edition = "2021" description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)" authors = [ @@ -35,7 +35,7 @@ fn main() { // let mut half = File::create(o.join("half.rs")).unwrap(); let mut quar = File::create(o.join("quar.rs")).unwrap(); let mut eigh = File::create(o.join("eigh.rs")).unwrap(); - let mut n = 4usize; + let mut n = 5usize; wr!(full => "pub mod full {{"); wr!(full => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(32, 32));"); @@ -48,7 +48,8 @@ fn main() { for mut file in [&full, &quar, &eigh] { file.write_all(b"macro_rules!img{($v:expr)=>{{static TMP:LazyLock<RgbaImage>=LazyLock::new(||$v);&TMP}};}\n").unwrap(); - wr!(file => "use ::{{image::RgbaImage, std::sync::LazyLock}};"); + wr!(file => "use image::RgbaImage;"); + wr!(file => "use crate::utils::Lock as LazyLock;"); } for i in 1..=16 { n += 1; @@ -58,7 +59,8 @@ fn main() { } let mut warmup = File::create(o.join("warmup.rs")).unwrap(); - wr!(warmup => "pub fn warmup() {{"); + wr!(warmup => "/// SAFETY: this function must only be called once."); + wr!(warmup => "pub unsafe fn warmup() {{"); 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" { @@ -104,7 +106,7 @@ fn main() { // writ!(half + 0.5); writ!(quar / 4); writ!(eigh / 8); - wr!(warmup => "LazyLock::force({path});"); + wr!(warmup => "LazyLock::load({path});"); n += 1; } } diff --git a/src/block/mod.rs b/src/block/mod.rs index 8f27516..238f28b 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -7,7 +7,6 @@ use bobbin_bits::U4::{self, *}; use std::any::Any; use std::error::Error; use std::fmt; -use std::sync::LazyLock; use crate::data::dynamic::{DynData, DynType}; use crate::data::map::{Build, EntityMapping}; @@ -15,6 +14,7 @@ use crate::data::{self, renderer::*, CompressError}; use crate::data::{DataRead, GridPos, ReadError as DataReadError}; use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; +use crate::utils::Lock; macro_rules! mods { ($($mod:ident)*) => { @@ -239,7 +239,7 @@ impl SerializeError { /// a block. put it in stuff! pub struct Block { - image: Option<[&'static LazyLock<RgbaImage>; 3]>, + image: Option<[&'static Lock<RgbaImage>; 3]>, name: &'static str, logic: BlockLogicEnum, } @@ -256,7 +256,7 @@ impl Block { pub(crate) const fn new( name: &'static str, logic: BlockLogicEnum, - image: Option<[&'static LazyLock<RgbaImage>; 3]>, + image: Option<[&'static Lock<RgbaImage>; 3]>, ) -> Self { Self { name, logic, image } } @@ -275,7 +275,8 @@ impl Block { } /// draw this block, with this state - pub fn image( + /// SAFETY: call [`warmup`](crate::warmup) first + pub unsafe fn image( &self, state: Option<&State>, context: Option<&RenderingContext>, @@ -283,9 +284,7 @@ impl Block { scale: Scale, ) -> ImageHolder { if let Some(imgs) = self.image { - return ImageHolder::from(LazyLock::force(unsafe { - imgs.get_unchecked(scale as usize) - })); + return ImageHolder::from(unsafe { Lock::get(imgs.get_unchecked(scale as usize)) }); } self.logic.draw(self.name, state, context, rot, scale) } diff --git a/src/data/map.rs b/src/data/map.rs index 911f522..5ee644d 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -134,7 +134,7 @@ impl<'l> Tile<'l> { 1 } - pub fn floor_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + 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()); @@ -142,7 +142,7 @@ impl<'l> Tile<'l> { i } - pub fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + pub unsafe fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { // building covers floore let Some(b) = &self.build else { unreachable!(); @@ -243,7 +243,7 @@ impl<'l> Build<'l> { } } - pub fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + pub unsafe fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { self.block .image(self.state.as_ref(), context, self.rotation, s) } diff --git a/src/data/renderer.rs b/src/data/renderer.rs index 05067cf..8423d98 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -113,11 +113,11 @@ impl std::ops::Mul<u32> for Scale { #[macro_export] macro_rules! load { ($name:literal, $scale:ident) => { paste::paste! { - ImageHolder::from(std::sync::LazyLock::force(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>]] @@ -138,11 +138,11 @@ macro_rules! load { (concat $x:expr => $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { paste::paste! { match $v { $($k => - ImageHolder::from(std::sync::LazyLock::force(match $scale { + 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)] n => unreachable!("{n:?}"), @@ -154,7 +154,8 @@ pub(crate) use load; /// trait for renderable objects pub trait Renderable { /// create a picture - fn render(&self) -> RgbImage; + /// SAFETY: call the [warmup] function first. + unsafe fn render(&self) -> RgbImage; } impl Renderable for Schematic<'_> { @@ -165,9 +166,12 @@ impl Renderable for Schematic<'_> { /// s.put(0, 0, &block::distribution::DISTRIBUTOR); /// s.put(0, 2, &block::distribution::ROUTER); /// s.put(1, 2, &block::walls::COPPER_WALL); - /// let output /*: RgbImage */ = s.render(); + /// // warm up the images for the first time + /// unsafe { warmup(); } + /// // this is now safe, because we have warmed up + /// let output /*: RgbImage */ = unsafe { s.render() }; /// ``` - fn render(&self) -> RgbImage { + unsafe fn render(&self) -> RgbImage { // fill background let mut bg = RgbImage::repeated( &DynamicImage::from( @@ -232,7 +236,7 @@ impl Renderable for Schematic<'_> { } impl Renderable for Map<'_> { - fn render(&self) -> RgbImage { + unsafe fn render(&self) -> RgbImage { let scale = if self.width + self.height < 2000 { Scale::Quarter } else { @@ -286,7 +290,8 @@ impl Renderable for Map<'_> { } /// Loads all the images into memory (about 300mb) -pub fn warmup() { +/// SAFETY: only call once. or else. +pub unsafe fn warmup() { full::warmup(); quar::warmup(); eigh::warmup(); @@ -296,6 +301,7 @@ pub fn warmup() { fn all_blocks() { use crate::block::content::Type; use crate::content::Content; + unsafe { warmup() }; let reg = crate::block::build_registry(); for t in 19..Type::WorldMessage as u16 { let t = Type::try_from(t).unwrap(); @@ -309,20 +315,22 @@ fn all_blocks() { { continue; } - let name = t.get_name(); + let name = dbg!(t.get_name()); let t = reg.get(name).unwrap(); - t.image( - None, - Some(&RenderingContext { - cross: [None; 4], - position: PositionContext { - position: GridPos(0, 0), - width: 5, - height: 5, - }, - }), - Rotation::Up, - Scale::Quarter, - ); + unsafe { + t.image( + None, + Some(&RenderingContext { + cross: [None; 4], + position: PositionContext { + position: GridPos(0, 0), + width: 5, + height: 5, + }, + }), + Rotation::Up, + Scale::Quarter, + ) + }; } } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 6d00dcc..de606ca 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -59,7 +59,8 @@ impl<'l> Placement<'l> { } /// draws this placement in particular - pub fn image( + /// SAFETY: call [`warmup`](crate::warmup) first + pub unsafe fn image( &self, context: Option<&RenderingContext>, rot: Rotation, @@ -731,8 +732,10 @@ mod test { let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed)); println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap()); if parsed != parsed2 { - parsed2.render().save("p2.png").unwrap(); - parsed.render().save("p1.png").unwrap(); + unsafe { crate::warmup() }; + // SAFETY: we just warmed up, its fine + unsafe { parsed2.render() }.save("p2.png").unwrap(); + unsafe { parsed.render() }.save("p1.png").unwrap(); panic!("DIFFERENT! see `p1.png` != `p2.png`") } )* diff --git a/src/exe/draw.rs b/src/exe/draw.rs index 3352af0..c7de323 100644 --- a/src/exe/draw.rs +++ b/src/exe/draw.rs @@ -6,6 +6,7 @@ use std::env::Args; use crate::print_err; pub fn main(args: Args) { + unsafe { mindus::warmup() }; let reg = build_registry(); let mut ss = SchematicSerializer(®); @@ -13,7 +14,7 @@ pub fn main(args: Args) { for curr in args { match ss.deserialize_base64(&curr) { Ok(s) => { - s.render().save("x.png").unwrap(); + unsafe { s.render() }.save("x.png").unwrap(); } // continue processing literals & maybe interactive mode Err(e) => { diff --git a/src/exe/map.rs b/src/exe/map.rs index fb55e07..c976696 100644 --- a/src/exe/map.rs +++ b/src/exe/map.rs @@ -13,7 +13,7 @@ pub fn main(args: Args) { // process schematics from command line println!("starting timing"); let then = Instant::now(); - warmup(); + unsafe { warmup() }; let warmup_took = then.elapsed(); for curr in args { let Ok(s) = std::fs::read(curr) else { @@ -26,13 +26,13 @@ pub fn main(args: Args) { let deser_took = starting_deser.elapsed(); if let Ok(v) = std::env::var("SAVE") { if v == "1" { - m.render().save("x.png").unwrap(); + unsafe { m.render() }.save("x.png").unwrap(); continue; } } let starting_render = Instant::now(); for _ in 0..10 { - m.render(); + unsafe { m.render() }; } let renders_took = starting_render.elapsed(); let took = then.elapsed(); @@ -1,5 +1,5 @@ //! crate for dealing with mindustry -#![feature(lazy_cell, array_chunks, const_trait_impl)] +#![feature(array_chunks, const_trait_impl)] pub mod block; mod content; pub mod data; @@ -17,7 +17,7 @@ pub use { data::{ dynamic::DynData, map::{Map, MapSerializer}, - renderer::Renderable, + renderer::{warmup, Renderable}, schematic::{Schematic, SchematicSerializer}, Serializer, }, diff --git a/src/utils/lazy.rs b/src/utils/lazy.rs new file mode 100644 index 0000000..268a81c --- /dev/null +++ b/src/utils/lazy.rs @@ -0,0 +1,45 @@ +//! [LazyLock] copy +use std::cell::UnsafeCell; +use std::mem::ManuallyDrop; +use std::panic::{RefUnwindSafe, UnwindSafe}; + +union Data<T, F> { + value: ManuallyDrop<T>, + f: ManuallyDrop<F>, +} + +pub struct Lock<T, F = fn() -> T> { + data: UnsafeCell<Data<T, F>>, +} + +impl<T, F: FnOnce() -> T> Lock<T, F> { + #[inline] + pub const fn new(f: F) -> Lock<T, F> { + Lock { + data: UnsafeCell::new(Data { + f: ManuallyDrop::new(f), + }), + } + } + + #[inline] + // SAFETY: CALL ONLY ONCE! NOT CHECKED + pub unsafe fn load(this: &Lock<T, F>) { + let data = &mut *this.data.get(); + let f = ManuallyDrop::take(&mut data.f); + let value = f(); + data.value = ManuallyDrop::new(value); + } +} + +impl<T, F> Lock<T, F> { + #[inline] + // SAFETY: CALL [load] FIRST! + pub unsafe fn get(&self) -> &T { + &*(*self.data.get()).value + } +} + +unsafe impl<T: Sync + Send, F: Send> Sync for Lock<T, F> {} +impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for Lock<T, F> {} +impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for Lock<T, F> {} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4018850..7314caa 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,5 @@ pub mod array; pub mod image; pub use self::image::{ImageUtils, Overlay, RepeatNew as Repeat}; +pub mod lazy; +pub use lazy::Lock; |