byte-engine-utils 0.1.0

Shared allocation, async, synchronization, collection, and geometry utilities for Byte-Engine.
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a>>;
pub type SendSyncBoxedFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + Sync + 'a>>;
pub type SendBoxedFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;

pub mod sync;

pub mod r#async;
pub mod bit_array;
pub mod copy_fn;
pub mod stable_vec;
pub mod stale_map;

use std::ops::Div;

pub type Box<T> = smallbox::SmallBox<T, [u8; 32]>;
pub use copy_fn::{InlineCopyFn, InlineCopyFnError, RefCall1, RefCall2, RefCall3};
pub use gxhash as hash;
pub use sonic_rs as json;
pub use stable_vec::{StableVec, StableVecHandle};
pub struct BufferAllocator<'a> {
	buffer: &'a mut [u8],
	offset: usize,
}

impl<'a> BufferAllocator<'a> {
	pub fn new(buffer: &'a mut [u8]) -> Self {
		Self { buffer, offset: 0 }
	}

	pub fn take(&mut self, size: usize) -> &'a mut [u8] {
		self.take_with_offset(size).1
	}

	pub fn take_with_offset(&mut self, size: usize) -> (usize, &'a mut [u8]) {
		let offset = self.offset;
		let buffer = &mut self.buffer[self.offset..][..size];
		self.offset += size;
		// SAFETY: We know that the buffer is valid for the lifetime of the splitter.
		(offset, unsafe { std::mem::transmute::<&mut [u8], &'a mut [u8]>(buffer) })
	}

	pub fn take_with_offset_aligned(&mut self, size: usize, alignment: usize) -> (usize, &'a mut [u8]) {
		self.offset = self.offset.next_multiple_of(alignment.max(1));
		self.take_with_offset(size)
	}

	pub fn remaining_aligned(&self, alignment: usize) -> usize {
		self.buffer
			.len()
			.saturating_sub(self.offset.next_multiple_of(alignment.max(1)))
	}

	pub fn remaining(&self) -> usize {
		self.buffer.len().saturating_sub(self.offset)
	}
}

pub fn partition<T>(slice: &[T], key_fn: impl Fn(&T) -> usize) -> Vec<(usize, &[T])> {
	let mut partitions = Vec::new();
	let mut slice_start = 0;

	for i in 1..slice.len() {
		if key_fn(&slice[i - 1]) + 1usize != key_fn(&slice[i]) {
			partitions.push((key_fn(&slice[slice_start]), &slice[slice_start..i]));
			slice_start = i;
		}
	}

	if !slice.is_empty() {
		partitions.push((key_fn(&slice[slice_start]), &slice[slice_start..]));
	}

	partitions
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Extent {
	width: u32,
	height: u32,
	depth: u32,
}

impl Extent {
	pub fn new(width: u32, height: u32, depth: u32) -> Self {
		Self { width, height, depth }
	}

	pub fn line(width: u32) -> Self {
		Self {
			width,
			height: 0,
			depth: 0,
		}
	}

	pub fn square(size: u32) -> Self {
		Self {
			width: size,
			height: size,
			depth: 0,
		}
	}

	pub fn rectangle(width: u32, height: u32) -> Self {
		Self { width, height, depth: 0 }
	}

	pub fn cube(width: u32, height: u32, depth: u32) -> Self {
		Self { width, height, depth }
	}

	pub fn as_tuple(&self) -> (u32, u32, u32) {
		(self.width, self.height, self.depth)
	}

	pub fn as_array(&self) -> [u32; 3] {
		[self.width, self.height, self.depth]
	}

	#[inline]
	pub fn width(&self) -> u32 {
		self.width
	}
	#[inline]
	pub fn height(&self) -> u32 {
		self.height
	}
	#[inline]
	pub fn depth(&self) -> u32 {
		self.depth
	}

	pub fn aspect_ratio(&self) -> f32 {
		(self.width as f32) / (self.height as f32)
	}

	pub fn dimensions(&self) -> u32 {
		if self.width == 0 {
			0
		} else if self.depth > 1 {
			3
		} else if self.height > 1 {
			2
		} else {
			1
		}
	}
}

impl From<[u32; 3]> for Extent {
	fn from(array: [u32; 3]) -> Self {
		Self {
			width: array[0],
			height: array[1],
			depth: array[2],
		}
	}
}

impl Div<u32> for Extent {
	type Output = Self;

	fn div(self, rhs: u32) -> Self::Output {
		Self {
			width: if self.width == 1 { 1 } else { self.width / rhs },
			height: if self.height == 1 { 1 } else { self.height / rhs },
			depth: if self.depth == 1 { 1 } else { self.depth / rhs },
		}
	}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RGBA {
	pub r: f32,
	pub g: f32,
	pub b: f32,
	pub a: f32,
}

impl std::ops::Mul for RGBA {
	type Output = Self;

	fn mul(self, rhs: Self) -> Self::Output {
		Self {
			r: self.r * rhs.r,
			g: self.g * rhs.g,
			b: self.b * rhs.b,
			a: self.a * rhs.a,
		}
	}
}

impl std::ops::Mul<f32> for RGBA {
	type Output = Self;

	fn mul(self, rhs: f32) -> Self::Output {
		Self {
			r: self.r * rhs,
			g: self.g * rhs,
			b: self.b * rhs,
			a: self.a * rhs,
		}
	}
}

impl std::hash::Hash for RGBA {
	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
		self.r.to_bits().hash(state);
		self.g.to_bits().hash(state);
		self.b.to_bits().hash(state);
		self.a.to_bits().hash(state);
	}
}

impl RGBA {
	pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
		Self { r, g, b, a }
	}

	pub fn black() -> Self {
		Self {
			r: 0.0,
			g: 0.0,
			b: 0.0,
			a: 1.0,
		}
	}

	pub fn white() -> Self {
		Self {
			r: 1.0,
			g: 1.0,
			b: 1.0,
			a: 1.0,
		}
	}

	pub fn transparent() -> Self {
		Self {
			r: 0.0,
			g: 0.0,
			b: 0.0,
			a: 0.0,
		}
	}
}

impl Default for RGBA {
	fn default() -> Self {
		Self::black()
	}
}

impl From<RGBA> for [f32; 4] {
	fn from(val: RGBA) -> Self {
		[val.r, val.g, val.b, val.a]
	}
}

pub fn insert_return_length<T>(collection: &mut Vec<T>, value: T) -> usize {
	let length = collection.len();
	collection.push(value);
	length
}

pub fn as_byte_slice<T>(slice: &[T]) -> &[u8] {
	unsafe { std::slice::from_raw_parts(slice.as_ptr().cast::<u8>(), std::mem::size_of_val(slice)) }
}

pub fn as_byte_slice_mut<T>(slice: &mut [T]) -> &mut [u8] {
	unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr().cast::<u8>(), std::mem::size_of_val(slice)) }
}

#[cfg(test)]
mod tests {
	use std::hash::{DefaultHasher, Hash as _, Hasher as _};

	use super::{as_byte_slice, as_byte_slice_mut, BufferAllocator, Extent, RGBA};

	#[test]
	fn test_partition() {
		let input = [];
		let expected: Vec<(usize, &[usize])> = vec![];
		assert_eq!(super::partition(&input, |x| *x,), expected);

		let input = [0];
		let expected: Vec<(usize, &[usize])> = vec![(0, &[0])];
		assert_eq!(super::partition(&input, |x| *x,), expected);

		let input = [0, 1];
		let expected: Vec<(usize, &[usize])> = vec![(0, &[0, 1])];
		assert_eq!(super::partition(&input, |x| *x,), expected);

		let input = [0, 2];
		let expected: Vec<(usize, &[usize])> = vec![(0, &[0]), (2, &[2])];
		assert_eq!(super::partition(&input, |x| *x,), expected);

		let input = [1, 2, 3, 5, 6, 7, 9, 10, 11];
		let expected: Vec<(usize, &[usize])> = vec![(1, &[1, 2, 3]), (5, &[5, 6, 7]), (9, &[9, 10, 11])];
		assert_eq!(super::partition(&input, |x| *x,), expected);
	}

	#[test]
	fn buffer_allocator_returns_disjoint_ranges_and_tracks_padding() {
		let mut storage = [0u8; 16];
		{
			let mut allocator = BufferAllocator::new(&mut storage);

			let (first_offset, first) = allocator.take_with_offset(3);
			first.copy_from_slice(&[1, 2, 3]);
			let (second_offset, second) = allocator.take_with_offset_aligned(4, 4);
			second.copy_from_slice(&[4, 5, 6, 7]);

			assert_eq!(first_offset, 0);
			assert_eq!(second_offset, 4);
			assert_eq!(allocator.remaining(), 8);
			assert_eq!(allocator.remaining_aligned(8), 8);
		}
		assert_eq!(&storage[..8], &[1, 2, 3, 0, 4, 5, 6, 7]);
	}

	#[test]
	fn extent_dimensions_and_division_preserve_active_axes() {
		assert_eq!(Extent::line(8).dimensions(), 1);
		assert_eq!(Extent::square(8).dimensions(), 2);
		assert_eq!(Extent::cube(8, 4, 2).dimensions(), 3);
		assert_eq!(Extent::new(8, 1, 1).dimensions(), 1);
		assert_eq!(Extent::new(8, 8, 1).dimensions(), 2);
		assert_eq!(Extent::new(0, 0, 0).dimensions(), 0);
		assert_eq!((Extent::new(8, 1, 1) / 2).as_tuple(), (4, 1, 1));
		assert_eq!((Extent::new(8, 4, 2) / 2).as_array(), [4, 2, 1]);
		assert_eq!(Extent::rectangle(16, 9).aspect_ratio(), 16.0 / 9.0);
	}

	#[test]
	fn rgba_multiplication_is_component_wise_and_hashes_all_channels() {
		let tint = RGBA::new(0.5, 0.25, 1.0, 0.5);
		assert_eq!(tint * RGBA::white(), tint);
		assert_eq!(tint * 2.0, RGBA::new(1.0, 0.5, 2.0, 1.0));
		assert_eq!(<[f32; 4]>::from(RGBA::transparent()), [0.0, 0.0, 0.0, 0.0]);
		assert_eq!(RGBA::default(), RGBA::black());

		let hash = |color: RGBA| {
			let mut hasher = DefaultHasher::new();
			color.hash(&mut hasher);
			hasher.finish()
		};
		assert_ne!(hash(tint), hash(RGBA::new(0.5, 0.25, 1.0, 0.25)));
	}

	#[test]
	fn byte_slice_views_preserve_native_object_representation() {
		let values = [0x1122u16, 0x3344u16];
		let bytes = as_byte_slice(&values);
		assert_eq!(bytes.len(), std::mem::size_of_val(&values));

		let mut mutable = [0u16; 2];
		as_byte_slice_mut(&mut mutable).copy_from_slice(bytes);
		assert_eq!(mutable, values);
	}
}