mindustry logic execution, map- and schematic- parsing and rendering
roll my own LazyLock
bendn 2023-08-08
parent 6224367 · commit 23582df
-rw-r--r--Cargo.toml2
-rw-r--r--build.rs10
-rw-r--r--src/block/mod.rs13
-rw-r--r--src/data/map.rs6
-rw-r--r--src/data/renderer.rs54
-rw-r--r--src/data/schematic.rs9
-rw-r--r--src/exe/draw.rs3
-rw-r--r--src/exe/map.rs6
-rw-r--r--src/lib.rs4
-rw-r--r--src/utils/lazy.rs45
-rw-r--r--src/utils/mod.rs2
11 files changed, 107 insertions, 47 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 4843f8d..544ce8a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 = [
diff --git a/build.rs b/build.rs
index 20a6663..35e3d87 100644
--- a/build.rs
+++ b/build.rs
@@ -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(&reg);
@@ -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();
diff --git a/src/lib.rs b/src/lib.rs
index ef1c290..22a510d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;