mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | src/block/distribution.rs | 39 | ||||
| -rw-r--r-- | src/block/drills.rs | 10 | ||||
| -rw-r--r-- | src/block/payload.rs | 49 | ||||
| -rw-r--r-- | src/block/power.rs | 21 | ||||
| -rw-r--r-- | src/block/production.rs | 64 | ||||
| -rw-r--r-- | src/block/units.rs | 73 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/utils/image/affine.rs | 77 | ||||
| -rw-r--r-- | src/utils/image/holder.rs | 7 | ||||
| -rw-r--r-- | src/utils/image/mod.rs | 70 | ||||
| -rw-r--r-- | src/utils/image/overlay.rs | 69 |
11 files changed, 242 insertions, 241 deletions
diff --git a/src/block/distribution.rs b/src/block/distribution.rs index 489d230..97308fe 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -39,12 +39,10 @@ make_simple!(JunctionBlock => |_, buff| { read_directional_item_buffer(buff) }); make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation, s| { let mut base = load!("duct-base", s); let mut top = load!(from name which is ["overflow-duct" "underflow-duct"], s); - unsafe { - // SAFETY: any load() is square - top.rotate(rot.rotated(false).count()); - // SAFETY: same size - base.overlay(&top); - } + // SAFETY: any load!() is square + unsafe { top.rotate(rot.rotated(false).count()) }; + // SAFETY: same size + unsafe { base.overlay(&top) }; base }); @@ -124,7 +122,9 @@ make_simple!( SurgeRouter, |_, _, _, _, r: Rotation, s| { let mut base = load!("surge-router", s); - unsafe { base.overlay(load!("top", s).rotate(r.rotated(false).count())) }; + let mut top = load!("top", s); + unsafe { top.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }, |_, buff: &mut DataRead| buff.skip(2) @@ -206,17 +206,14 @@ impl BlockLogic for ItemBlock { unsafe { p.overlay(top.tint(item.color())) }; return p; } - match name { - "duct-router" => { - unsafe { p.overlay(load!("top", s).rotate(rot.rotated(false).count())) }; - } - "duct-unloader" => { - unsafe { - p.overlay(load!("duct-unloader-top", s).rotate(rot.rotated(false).count())) - }; - } - _ => {} - }; + if matches!(name, "duct-router" | "duct-unloader") { + let mut top = load!(s -> match name { + "duct-router" => "top", + "duct-unloader" => "duct-unloader-top", + }); + unsafe { top.rotate(rot.rotated(false).count()) }; + unsafe { p.overlay(&top) }; + } p } @@ -442,10 +439,8 @@ impl BlockLogic for BridgeBlock { _ => "reinforced-bridge-conduit-dir", } ); - unsafe { - arrow.rotate(r.rotated(false).count()); - base.overlay(&arrow) - }; + unsafe { arrow.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&arrow) }; base } // "bridge-conveyor" | "phase-conveyor" | "bridge-conduit" | "phase-conduit" | "payload-mass-driver" | "large-payload-mass-driver" diff --git a/src/block/drills.rs b/src/block/drills.rs index 6952157..b042ceb 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -7,16 +7,18 @@ make_simple!( |_, name, _, _, rot: Rotation, s| { let mut base = load!(from name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s); - unsafe { - base.overlay(load!(concat "top" => name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s).rotate(rot.rotated(false).count()) ) - }; + let mut top = load!(concat "top" => name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s); + unsafe { top.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }, |_, buff: &mut DataRead| read_drill(buff) ); make_simple!(WallDrillBlock, |_, _, _, _, rot: Rotation, scl| { let mut base = load!("cliff-crusher", scl); - unsafe { base.overlay(load!("cliff-crusher-top", scl).rotate(rot.rotated(false).count())) }; + let mut top = load!("cliff-crusher-top", scl); + unsafe { top.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }); diff --git a/src/block/payload.rs b/src/block/payload.rs index 9b1970b..f68f8af 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -18,28 +18,30 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| { "small-deconstructor" => "factory-in-3", _ => "factory-in-5", }); - unsafe { - base.overlay(r#in.rotate(r.rotated(false).count())) - .overlay(&load!(scl -> match n { - "small-deconstructor" => "small-deconstructor-top", - "deconstructor" => "deconstructor-top", - _ => "payload-void-top", - })) - }; + unsafe { r#in.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&r#in) }; + let top = load!(scl -> match n { + "small-deconstructor" => "small-deconstructor-top", + "deconstructor" => "deconstructor-top", + _ => "payload-void-top", + }); + unsafe { base.overlay(&top) }; base } // "payload-loader" | "payload-unloader" _ => { let mut base = load!(from n which is ["payload-loader" | "payload-unloader"], scl); let mut input = load!("factory-in-3-dark", scl); + unsafe { input.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&input) }; + let mut output = load!("factory-out-3-dark", scl); - unsafe { - base.overlay(input.rotate(r.rotated(false).count())) - .overlay(output.rotate(r.rotated(false).count())) - .overlay( - &load!(concat "top" => n which is ["payload-loader" | "payload-unloader"], scl), - ) - }; + unsafe { output.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&output) }; + + let top = + load!(concat "top" => n which is ["payload-loader" | "payload-unloader"], scl); + unsafe { base.overlay(&top) }; base } } @@ -59,10 +61,10 @@ make_simple!( PayloadRouter, |_, n, _, _, r: Rotation, s| { let mut base = load!(from n which is ["payload-router" | "reinforced-payload-router"], s); - unsafe { - base.rotate(r.rotated(false).count()); - base.overlay(& load!(concat "over" => n which is ["payload-router" | "reinforced-payload-router"], s)); - } + unsafe { base.rotate(r.rotated(false).count()) }; + let over = + load!(concat "over" => n which is ["payload-router" | "reinforced-payload-router"], s); + unsafe { base.overlay(&over) }; base }, read_payload_router @@ -115,11 +117,10 @@ impl BlockLogic for PayloadBlock { "large-constructor" => "factory-out-5-dark", _ => "factory-out-5", }); - unsafe { - out.rotate(r.rotated(false).count()); - base.overlay(&out); - base.overlay(&load!(concat "top" => name which is ["constructor" | "large-constructor" | "payload-source"], s)) - }; + unsafe { out.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&out) }; + let top = load!(concat "top" => name which is ["constructor" | "large-constructor" | "payload-source"], s); + unsafe { base.overlay(&top) }; base } diff --git a/src/block/power.rs b/src/block/power.rs index 7b04135..d891a8f 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -12,15 +12,12 @@ make_simple!( Neoplasia, |_, _, _, _, rot: Rotation, scl| { let mut base = load!("neoplasia-reactor", scl); - unsafe { - base.overlay( - load!(scl -> match rot { - Rotation::Up | Rotation::Right => "neoplasia-reactor-top1", - Rotation::Down | Rotation::Left => "neoplasia-reactor-top2", - }) - .rotate(rot.rotated(false).count()), - ) - }; + let mut top = load!(scl -> match rot { + Rotation::Up | Rotation::Right => "neoplasia-reactor-top1", + Rotation::Down | Rotation::Left => "neoplasia-reactor-top2", + }); + unsafe { top.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }, |_, buff: &mut DataRead| read_heater(buff) @@ -31,10 +28,8 @@ make_simple!(DiodeBlock, |_, _, _, _, rot: Rotation, s| { return base; } let mut top = load!("diode-arrow", s); - unsafe { - top.rotate(rot.rotated(false).count()); - base.overlay(&top) - }; + unsafe { top.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }); diff --git a/src/block/production.rs b/src/block/production.rs index bd44518..9e97bf2 100644 --- a/src/block/production.rs +++ b/src/block/production.rs @@ -12,22 +12,19 @@ make_simple!( // electrolyzer exclusive // ozone <- e(^) -> hydrogen let mut base = load!("electrolyzer", s); - unsafe { - base.overlay( - load!(s -> match r { - Rotation::Up | Rotation::Left => "electrolyzer-hydrogen-output1" - Rotation::Down | Rotation::Right => "electrolyzer-hydrogen-output2" - }) - .rotate(r.count()), - ); - base.overlay( - load!(s -> match r { - Rotation::Down | Rotation::Right => "electrolyzer-ozone-output1" - Rotation::Up | Rotation::Left => "electrolyzer-ozone-output2" - }) - .rotate(r.mirrored(true, true).count()), - ); - } + let mut hydro = load!(s -> match r { + Rotation::Up | Rotation::Left => "electrolyzer-hydrogen-output1" + Rotation::Down | Rotation::Right => "electrolyzer-hydrogen-output2" + }); + unsafe { hydro.rotate(r.count()) }; + unsafe { base.overlay(&hydro) }; + + let mut ozone = load!(s -> match r { + Rotation::Down | Rotation::Right => "electrolyzer-ozone-output1" + Rotation::Up | Rotation::Left => "electrolyzer-ozone-output2" + }); + unsafe { ozone.rotate(r.mirrored(true, true).count()) }; + unsafe { base.overlay(&ozone) }; base }, |b: &mut Build<'_>, buff: &mut DataRead| { @@ -44,12 +41,16 @@ make_simple!( HeatCrafter, |_, n, _, _, r: Rotation, s| { let mut base = load!(from n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s); - unsafe { - base.overlay(match r { - Rotation::Up | Rotation::Right => load!(concat "top1" => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s), - Rotation::Down | Rotation::Left => load!(concat "top2" => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s) - }.rotate(r.rotated(false).count())) + let mut top = match r { + Rotation::Up | Rotation::Right => { + load!(concat "top1" => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s) + } + Rotation::Down | Rotation::Left => { + load!(concat "top2" => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s) + } }; + unsafe { top.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }, |_, buff: &mut DataRead| { @@ -63,18 +64,15 @@ make_simple!( ); make_simple!(HeatConduit, |_, n, _, _, r: Rotation, s| { let mut base = load!(from n which is ["heat-router" | "heat-redirector"], s); - unsafe { - base.overlay( - match r { - Rotation::Up | Rotation::Right => { - load!(concat "top1" => n which is ["heat-router" | "heat-redirector"], s) - } - Rotation::Down | Rotation::Left => { - load!(concat "top2" => n which is ["heat-router" | "heat-redirector"], s) - } - } - .rotate(r.rotated(false).count()), - ) + let mut top = match r { + Rotation::Up | Rotation::Right => { + load!(concat "top1" => n which is ["heat-router" | "heat-redirector"], s) + } + Rotation::Down | Rotation::Left => { + load!(concat "top2" => n which is ["heat-router" | "heat-redirector"], s) + } }; + unsafe { top.rotate(r.rotated(false).count()) }; + unsafe { base.overlay(&top) }; base }); diff --git a/src/block/units.rs b/src/block/units.rs index 1fe0ade..97e3b98 100644 --- a/src/block/units.rs +++ b/src/block/units.rs @@ -39,16 +39,18 @@ make_simple!( |_, name, _, _, rot: Rotation, s| { let mut base = load!(from name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s); - unsafe { - base.overlay( - match rot { - Rotation::Up | Rotation::Right => load!(concat "side1" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s), - Rotation::Down | Rotation::Left => load!(concat "side2" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s) + let mut side = match rot { + Rotation::Up | Rotation::Right => { + load!(concat "side1" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s) + } + Rotation::Down | Rotation::Left => { + load!(concat "side2" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s) } - .rotate(rot.rotated(false).count()) - ); - base.overlay(&load!(concat "top" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s)) }; + unsafe { side.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&side) }; + let top = load!(concat "top" => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s); + unsafe { base.overlay(&top) }; base }, |_, buff| read_assembler(buff) @@ -74,15 +76,12 @@ make_simple!( AssemblerModule, |_, _, _, _, rot: Rotation, scl| { let mut base = load!("basic-assembler-module", scl); - unsafe { - base.overlay( - load!(scl -> match rot { - Rotation::Up | Rotation::Right => "basic-assembler-module-side1", - _ => "basic-assembler-module-side2", - }) - .rotate(rot.rotated(false).count()), - ) - }; + let mut side = load!(scl -> match rot { + Rotation::Up | Rotation::Right => "basic-assembler-module-side1", + _ => "basic-assembler-module-side2", + }); + unsafe { side.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&side) }; base }, |_, buff| read_payload_block(buff) @@ -160,7 +159,8 @@ impl BlockLogic for ConstructorBlock { "prime-refabricator" => "factory-out-5-dark", "tetrative-reconstructor" => "factory-out-9", }); - unsafe { base.overlay(out.rotate(times)) }; + unsafe { out.rotate(times) }; + unsafe { base.overlay(&out) }; let mut r#in = load!(s -> match name { "additive-reconstructor" => "factory-in-3", @@ -171,7 +171,8 @@ impl BlockLogic for ConstructorBlock { "prime-refabricator" => "factory-in-5-dark", "tetrative-reconstructor" => "factory-in-9", }); - unsafe { base.overlay(r#in.rotate(times)) }; + unsafe { r#in.rotate(times) }; + unsafe { base.overlay(&r#in) }; // TODO: the context cross is too small // for i in 0..4u8 { @@ -192,10 +193,8 @@ impl BlockLogic for ConstructorBlock { // } // } // } - - unsafe { - base.overlay(&load!(concat "top" => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s)) - }; + let top = load!(concat "top" => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s); + unsafe { base.overlay(&top) }; base } @@ -297,21 +296,19 @@ impl BlockLogic for UnitFactory { s: Scale, ) -> ImageHolder<4> { let mut base = load!(from name which is ["ground-factory" | "air-factory" | "naval-factory" | "tank-fabricator" | "ship-fabricator" | "mech-fabricator"], s); - unsafe { - base.overlay( - load!(s -> match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", - _ => "factory-out-3-dark", - }) - .rotate(rot.rotated(false).count()), - ) - .overlay(&load!(s -> match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3", - "tank-fabricator" => "tank-fabricator-top", - "ship-fabricator" => "ship-fabricator-top", - "mech-fabricator" => "mech-fabricator-top", - })) - }; + let mut out = load!(s -> match name { + "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", + _ => "factory-out-3-dark", + }); + unsafe { out.rotate(rot.rotated(false).count()) }; + unsafe { base.overlay(&out) }; + let top = load!(s -> match name { + "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3", + "tank-fabricator" => "tank-fabricator-top", + "ship-fabricator" => "ship-fabricator-top", + "mech-fabricator" => "mech-fabricator-top", + }); + unsafe { base.overlay(&top) }; base } @@ -11,8 +11,10 @@ test )] #![warn( - clippy::missing_safety_doc, + clippy::multiple_unsafe_ops_per_block, clippy::missing_const_for_fn, + clippy::missing_safety_doc, + unsafe_op_in_unsafe_fn, clippy::dbg_macro, clippy::perf )] diff --git a/src/utils/image/affine.rs b/src/utils/image/affine.rs index 04a6180..6ca78cd 100644 --- a/src/utils/image/affine.rs +++ b/src/utils/image/affine.rs @@ -4,15 +4,12 @@ use super::Image; pub fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { for y in 0..img.height() / 2 { for x in 0..img.width() { - // SAFETY: this is safe because it cannot be out of bounds - unsafe { - let p = img.pixel(x, y); - let x2 = img.width() - x - 1; - let y2 = img.height() - y - 1; - let p2 = img.pixel(x2, y2); - img.set_pixel(x, y, p2); - img.set_pixel(x2, y2, p); - } + let p = unsafe { img.pixel(x, y) }; + let x2 = img.width() - x - 1; + let y2 = img.height() - y - 1; + let p2 = unsafe { img.pixel(x2, y2) }; + unsafe { img.set_pixel(x, y, p2) }; + unsafe { img.set_pixel(x2, y2, p) }; } } @@ -20,15 +17,11 @@ pub fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { let middle = img.height() / 2; for x in 0..img.width() / 2 { - // SAFETY: this is safe because it cannot be out of bounds - unsafe { - let p = img.pixel(x, middle); - let x2 = img.width() - x - 1; - - let p2 = img.pixel(x2, middle); - img.set_pixel(x, middle, p2); - img.set_pixel(x2, middle, p); - } + let p = unsafe { img.pixel(x, middle) }; + let x2 = img.width() - x - 1; + let p2 = unsafe { img.pixel(x2, middle) }; + unsafe { img.set_pixel(x, middle, p2) }; + unsafe { img.set_pixel(x2, middle, p) }; } } } @@ -42,10 +35,13 @@ unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) for i in 0..size { for j in i..size { for c in 0..CHANNELS { - img.buffer.swap_unchecked( - (i * size + j) as usize * CHANNELS + c, - (j * size + i) as usize * CHANNELS + c, - ); + // SAFETY: caller gurantees rectangularity + unsafe { + img.buffer.swap_unchecked( + (i * size + j) as usize * CHANNELS + c, + (j * size + i) as usize * CHANNELS + c, + ) + }; } } } @@ -60,7 +56,8 @@ unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) #[inline] pub unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { flip_v(img); - transpose(img); + // SAFETY: caller ensures rectangularity + unsafe { transpose(img) }; } /// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise. @@ -72,22 +69,22 @@ pub unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS> #[inline] pub unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { flip_h(img); - transpose(img); + // SAFETY: caller ensures rectangularity + unsafe { transpose(img) }; } /// Flip a image vertically. pub fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { for y in 0..img.height() / 2 { for x in 0..img.width() { - // SAFETY: cant overflow - unsafe { - let y2 = img.height().unchecked_sub(y).unchecked_sub(1); - // SAFETY: within bounds - let p2 = img.pixel(x, y2); - let p = img.pixel(x, y); - img.set_pixel(x, y2, p); - img.set_pixel(x, y, p2); - } + // SAFETY: i think this is unsound if the image is big enough + let y2 = unsafe { img.height().unchecked_sub(y) }; + let y2 = unsafe { y2.unchecked_sub(1) }; + // SAFETY: within bounds + let p2 = unsafe { img.pixel(x, y2) }; + let p = unsafe { img.pixel(x, y) }; + unsafe { img.set_pixel(x, y2, p) }; + unsafe { img.set_pixel(x, y, p2) }; } } } @@ -96,14 +93,12 @@ pub fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { pub fn flip_h<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { for y in 0..img.height() { for x in 0..img.width() / 2 { - // SAFETY: This cannot be out of bounds - unsafe { - let x2 = img.width().unchecked_sub(x).unchecked_sub(1); - let p2 = img.pixel(x2, y); - let p = img.pixel(x, y); - img.set_pixel(x2, y, p); - img.set_pixel(x, y, p2); - } + let x2 = unsafe { img.width().unchecked_sub(x) }; + let x2 = unsafe { x2.unchecked_sub(1) }; + let p2 = unsafe { img.pixel(x2, y) }; + let p = unsafe { img.pixel(x, y) }; + unsafe { img.set_pixel(x2, y, p) }; + unsafe { img.set_pixel(x, y, p2) }; } } } diff --git a/src/utils/image/holder.rs b/src/utils/image/holder.rs index 02c9240..4c1b6a5 100644 --- a/src/utils/image/holder.rs +++ b/src/utils/image/holder.rs @@ -40,14 +40,15 @@ impl<const CHANNELS: usize> ImageHolder<CHANNELS> { impl OverlayAt<ImageHolder<4>> for ImageHolder<4> { unsafe fn overlay_at(&mut self, with: &ImageHolder<4>, x: u32, y: u32) -> &mut Self { - self.borrow_mut().overlay_at(&with.borrow(), x, y); + // SAFETY: this is basically a deref impl, the caller upholds the safety invariants + unsafe { self.borrow_mut().overlay_at(&with.borrow(), x, y) }; self } } impl Overlay<ImageHolder<4>> for ImageHolder<4> { unsafe fn overlay(&mut self, with: &Self) -> &mut Self { - self.borrow_mut().overlay(&with.borrow()); + unsafe { self.borrow_mut().overlay(&with.borrow()) }; self } } @@ -63,7 +64,7 @@ impl ImageUtils for ImageHolder<4> { return self; } // borrow mut may clone, so try to avoid - self.borrow_mut().rotate(times); + unsafe { self.borrow_mut().rotate(times) }; self } diff --git a/src/utils/image/mod.rs b/src/utils/image/mod.rs index bbe9a3e..0ef2c69 100644 --- a/src/utils/image/mod.rs +++ b/src/utils/image/mod.rs @@ -35,19 +35,19 @@ pub trait ImageUtils { fn scale(self, to: u32) -> Image<Vec<u8>, 4>; } -macro_rules! unsafe_assert { +macro_rules! assert_unchecked { ($cond:expr) => {{ if !$cond { #[cfg(debug_assertions)] + let _ = ::core::ptr::NonNull::<()>::dangling().as_ref(); // force unsafe wrapping block + #[cfg(debug_assertions)] panic!("assertion failed: {} returned false", stringify!($cond)); #[cfg(not(debug_assertions))] - unsafe { - std::hint::unreachable_unchecked() - }; + std::hint::unreachable_unchecked() } }}; } -pub(self) use unsafe_assert; +use assert_unchecked; impl RepeatNew for Image<&[u8], 4> { type Output = Image<Vec<u8>, 4>; @@ -56,7 +56,8 @@ impl RepeatNew for Image<&[u8], 4> { for x in 0..(x / self.width()) { for y in 0..(y / self.height()) { let a: &mut Image<&mut [u8], 4> = &mut img.as_mut(); - a.overlay_at(self, x * self.width(), y * self.height()); + // SAFETY: caller upholds + unsafe { a.overlay_at(self, x * self.width(), y * self.height()) }; } } img @@ -65,20 +66,17 @@ impl RepeatNew for Image<&[u8], 4> { impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> { unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { - unsafe_assert!(self.width() == with.width()); - unsafe_assert!(self.height() == with.height()); + // SAFETY: caller upholds these + unsafe { assert_unchecked!(self.width() == with.width()) }; + unsafe { assert_unchecked!(self.height() == with.height()) }; for (i, other_pixels) in with.chunked().enumerate() { if other_pixels[3] >= 128 { + let idx_begin = unsafe { i.unchecked_mul(4) }; + let idx_end = unsafe { idx_begin.unchecked_add(4) }; + let own_pixels = unsafe { self.buffer.get_unchecked_mut(idx_begin..idx_end) }; unsafe { - let own_pixels = self - .buffer - .get_unchecked_mut(i.unchecked_mul(4)..i.unchecked_mul(4).unchecked_add(4)); - std::ptr::copy_nonoverlapping( - other_pixels.as_ptr(), - own_pixels.as_mut_ptr(), - 4, - ); - } + std::ptr::copy_nonoverlapping(other_pixels.as_ptr(), own_pixels.as_mut_ptr(), 4) + }; } } self @@ -164,9 +162,8 @@ impl ImageUtils for Image<&mut [u8], 4> { #[inline] unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize { // y * w + x - (y as usize) - .unchecked_mul(w as usize) - .unchecked_add(x as usize) + let tmp = unsafe { (y as usize).unchecked_mul(w as usize) }; + unsafe { tmp.unchecked_add(x as usize) } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -227,15 +224,18 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS pub unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { debug_assert!(x < self.width(), "x out of bounds"); debug_assert!(y < self.height(), "y out of bounds"); - let index = really_unsafe_index(x, y, self.width()).unchecked_mul(CHANNELS); + let index = unsafe { really_unsafe_index(x, y, self.width()) }; + let index = unsafe { index.unchecked_mul(CHANNELS) }; debug_assert!(self.buffer.len() > index); - index..index.unchecked_add(CHANNELS) + index..unsafe { index.unchecked_add(CHANNELS) } } #[inline] pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> { - unsafe_assert!(self.buffer.len() > CHANNELS); - unsafe_assert!(self.buffer.len() % CHANNELS == 0); + // SAFETY: 0 sized images illegal + unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; + // SAFETY: no half pixels! + unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) }; self.buffer.array_chunks::<CHANNELS>() } @@ -245,7 +245,9 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS /// Refer to [`slice`] #[inline] pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] { - *(self.buffer.get_unchecked(self.slice(x, y)).as_ptr().cast()) + let idx = unsafe { self.slice(x, y) }; + let ptr = unsafe { self.buffer.get_unchecked(idx).as_ptr().cast() }; + unsafe { *ptr } } } @@ -256,18 +258,30 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN /// Refer to [`slice`] #[inline] pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] { - let idx = self.slice(x, y); - self.buffer.get_unchecked_mut(idx) + let idx = unsafe { self.slice(x, y) }; + unsafe { self.buffer.get_unchecked_mut(idx) } } #[inline] pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> { + // SAFETY: 0 sized images are not allowed + unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; + // SAFETY: buffer cannot have half pixels + unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) }; self.buffer.array_chunks_mut::<CHANNELS>() } + /// Set the pixel at x, y + /// + /// # Safety + /// + /// UB if x, y is out of bounds. #[inline] pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) { - std::ptr::copy_nonoverlapping(px.as_ptr(), self.pixel_mut(x, y).as_mut_ptr(), CHANNELS); + // SAFETY: Caller says that x, y is in bounds + let out = unsafe { self.pixel_mut(x, y) }; + // SAFETY: px must be CHANNELS long + unsafe { std::ptr::copy_nonoverlapping(px.as_ptr(), out.as_mut_ptr(), CHANNELS) }; } } diff --git a/src/utils/image/overlay.rs b/src/utils/image/overlay.rs index 9cad52e..4406a9b 100644 --- a/src/utils/image/overlay.rs +++ b/src/utils/image/overlay.rs @@ -1,4 +1,4 @@ -use super::{really_unsafe_index, unsafe_assert, Image}; +use super::{assert_unchecked, really_unsafe_index, Image}; use std::simd::SimdInt; use std::simd::SimdPartialOrd; use std::simd::{simd_swizzle, Simd}; @@ -28,8 +28,8 @@ pub unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { let mut srci = 0; let mut dsti = 0; while dsti + 16 <= rgb.len() { - let old: Simd<u8, 16> = Simd::from_slice(rgb.get_unchecked(dsti..dsti + 16)); - let new: Simd<u8, 16> = Simd::from_slice(rgba.get_unchecked(srci..srci + 16)); + let old: Simd<u8, 16> = Simd::from_slice(unsafe { rgb.get_unchecked(dsti..dsti + 16) }); + let new: Simd<u8, 16> = Simd::from_slice(unsafe { rgba.get_unchecked(srci..srci + 16) }); let threshold = new.simd_ge(Simd::splat(128)).to_int().cast::<u8>(); let mut mask = simd_swizzle!( @@ -40,19 +40,17 @@ pub unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { let new_rgb = simd_swizzle!(new, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0, 0, 0, 0]); let blended = (new_rgb & mask) | (old & !mask); - blended.copy_to_slice(rgb.get_unchecked_mut(dsti..dsti + 16)); + blended.copy_to_slice(unsafe { rgb.get_unchecked_mut(dsti..dsti + 16) }); srci += 16; dsti += 12; } while dsti + 3 <= rgb.len() { - if *rgba.get_unchecked(srci + 3) >= 128 { - std::ptr::copy_nonoverlapping( - rgba.get_unchecked(srci..srci + 3).as_ptr(), - rgb.get_unchecked_mut(dsti..dsti + 3).as_mut_ptr(), - 3, - ); + if unsafe { *rgba.get_unchecked(srci + 3) } >= 128 { + let src = unsafe { rgba.get_unchecked(srci..srci + 3) }; + let end = unsafe { rgb.get_unchecked_mut(dsti..dsti + 3) }; + unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), end.as_mut_ptr(), 3) }; } srci += 4; @@ -62,8 +60,9 @@ pub unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> { unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { - unsafe_assert!(x + with.width() <= self.width()); - unsafe_assert!(y + with.height() <= self.height()); + // SAFETY: caller upholds these + unsafe { assert_unchecked!(x + with.width() <= self.width()) }; + unsafe { assert_unchecked!(y + with.height() <= self.height()) }; for j in 0..with.height() { let i_x = j as usize * with.width() as usize * 4 ..(j as usize + 1) * with.width() as usize * 4; @@ -72,10 +71,9 @@ impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> { + x as usize + with.width() as usize) * 3; - blit( - self.buffer.get_unchecked_mut(o_x), - with.buffer.get_unchecked(i_x), - ) + let rgb = unsafe { self.buffer.get_unchecked_mut(o_x) }; + let rgba = unsafe { with.buffer.get_unchecked(i_x) }; + unsafe { blit(rgb, rgba) } } self } @@ -83,19 +81,19 @@ impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> { impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> { unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { - unsafe_assert!(self.width() == with.width()); - unsafe_assert!(self.height() == with.height()); + unsafe { assert_unchecked!(self.width() == with.width()) }; + unsafe { assert_unchecked!(self.height() == with.height()) }; for (i, chunk) in with .buffer .chunks_exact(with.width() as usize * 4) .enumerate() { - blit( + let rgb = unsafe { self.buffer.get_unchecked_mut( i * with.width() as usize * 3..(i + 1) * with.width() as usize * 3, - ), - chunk, - ); + ) + }; + unsafe { blit(rgb, chunk) }; } self } @@ -105,18 +103,21 @@ impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> { unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { for j in 0..with.height() { for i in 0..with.width() { - let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4); - let their_px = with - .buffer - .get_unchecked(with_index..with_index.unchecked_add(4)); - if *their_px.get_unchecked(3) >= 128 { - let our_index = - really_unsafe_index(i.unchecked_add(x), j.unchecked_add(y), self.width()) - .unchecked_mul(4); - let our_px = self - .buffer - .get_unchecked_mut(our_index..our_index.unchecked_add(4)); - std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 4); + // TODO: see how many unchecked maths i can get rid of + let index_begin = unsafe { really_unsafe_index(i, j, with.width()) }; + let index_begin = unsafe { index_begin.unchecked_mul(4) }; + let index_end = unsafe { index_begin.unchecked_add(4) }; + let their_px = unsafe { with.buffer.get_unchecked(index_begin..index_end) }; + if unsafe { *their_px.get_unchecked(3) } >= 128 { + let x = unsafe { i.unchecked_add(x) }; + let y = unsafe { j.unchecked_add(y) }; + let index_begin = unsafe { really_unsafe_index(x, y, self.width()) }; + let index_begin = unsafe { index_begin.unchecked_mul(4) }; + let index_end = unsafe { index_begin.unchecked_add(4) }; + let our_px = unsafe { self.buffer.get_unchecked_mut(index_begin..index_end) }; + unsafe { + std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 4) + }; } } } |