commit 73943b299dae3e74e8efc4f7c8a44fcc486d2cf8 Author: Michael Hübner Date: Mon Jan 16 10:47:56 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e7caa9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e5b03e2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#551A02", + "titleBar.activeBackground": "#772503", + "titleBar.activeForeground": "#FFF9F6" + } +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1f4aaf5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "utilities" +version = "0.1.0" +authors = ["hodasemi "] +edition = "2021" + +[dependencies] +cgmath = { version = "0.18.0", features = ["swizzle"] } +rand = "0.8.5" +backtrace = "0.3.67" +assetpath = { git = "https://gavania.de/hodasemi/vulkan_lib.git" } +anyhow = { version = "1.0.68", features = ["backtrace"] } + +# networking +serde = { version = "1.0.152", features = ["derive"] } + + diff --git a/src/arc_unique_vec.rs b/src/arc_unique_vec.rs new file mode 100644 index 0000000..ee7614b --- /dev/null +++ b/src/arc_unique_vec.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +/// Wrapper around a `Vec` if `Rc`, +/// that ensures pointer uniqueness of its elements +#[derive(Debug, Clone, Default)] +pub struct ArcUniqueVec { + data: Vec>, +} + +impl ArcUniqueVec { + /// Creates an empty `RcUniqueVec` + pub fn new() -> ArcUniqueVec { + ArcUniqueVec { data: Vec::new() } + } + + /// Checks for pointer collision while inserting. + /// Returns the index for the position of the inserted element. + pub fn insert(&mut self, element: Arc) -> usize { + match self.data.iter().position(|t| Arc::ptr_eq(t, &element)) { + Some(index) => index, + None => { + let index = self.data.len(); + self.data.push(element); + index + } + } + } + + /// Checks if the given element is in the vector, then returns true, + /// otherwise false. + pub fn remove(&mut self, element: &Arc) -> bool { + match self.data.iter().position(|t| Arc::ptr_eq(t, element)) { + Some(index) => { + self.data.remove(index); + true + } + None => false, + } + } + + /// Checks if that element is in this `RcUniqueVec` + /// and returns its index if possible + pub fn get_index(&self, element: &Arc) -> Option { + match self.data.iter().position(|t| Arc::ptr_eq(t, element)) { + Some(index) => Some(index), + None => None, + } + } + + /// Clears all elements from this vector + pub fn clear(&mut self) { + self.data.clear(); + } + + /// Returns the reference to the internal vector + pub fn as_vec(&self) -> &Vec> { + &self.data + } +} diff --git a/src/asyncthread.rs b/src/asyncthread.rs new file mode 100644 index 0000000..88b54c5 --- /dev/null +++ b/src/asyncthread.rs @@ -0,0 +1,92 @@ +//! Asynchronous thread to check if result is returned (non-blocking) + +use std::sync::{mpsc, RwLock}; +use std::thread; + +/// Asynchronous thread handle +pub struct AsyncThread { + receiver: mpsc::Receiver, + result: RwLock>, +} + +impl AsyncThread { + /// Spawns a thread + /// + /// # Arguments + /// + /// `f` is a function to be executed in a separate thread + pub fn spawn(f: F) -> AsyncThread + where + F: FnOnce() -> T, + F: Send + 'static, + { + let (sender, receiver) = mpsc::channel(); + + thread::spawn(move || { + if let Err(err) = sender.send((f)()) { + panic!("sending failed! ({})", err); + } + }); + + AsyncThread { + receiver, + result: RwLock::new(None), + } + } + + /// Checks if the thread is already returned + /// Returns the Some(result) if the thread has finished its work, + /// otherwise None + pub fn check(&self) -> Result { + let mut result = self + .result + .write() + .map_err(|_err| anyhow::Error::msg("Asyncthread: Failed getting write lock"))?; + + match result.as_ref() { + Some(_) => Ok(true), + None => match self.receiver.try_recv() { + Ok(res) => { + *result = Some(res); + + Ok(true) + } + Err(_) => Ok(false), + }, + } + } + + /// consumes the result + pub fn take(&self) -> Result { + let mut result = self + .result + .write() + .map_err(|_err| anyhow::Error::msg("Asyncthread: Failed getting write lock"))?; + + if result.is_some() { + // actually safe to not panic, since we just checked + let res = result.take().unwrap(); + + Ok(res) + } else { + Err(anyhow::Error::msg("Asyncthread: Result not present")) + } + } +} + +impl AsyncThread +where + T: Clone, +{ + pub fn get(&self) -> Result { + let result = self + .result + .read() + .map_err(|_err| anyhow::Error::msg("Asyncthread: Failed getting write lock"))?; + + match result.as_ref() { + Some(res) => Ok(res.clone()), + None => Err(anyhow::Error::msg("Asyncthread: Result not present")), + } + } +} diff --git a/src/closures.rs b/src/closures.rs new file mode 100644 index 0000000..85fd84c --- /dev/null +++ b/src/closures.rs @@ -0,0 +1,83 @@ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!! very unstable, use at your own risk !!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +use super::prelude::*; +use std::ops::Fn; + +#[allow(non_upper_case_globals)] +pub const void: () = (); + +pub struct Callback { + function: FunctionType, +} + +impl Callback { + pub fn new(function: F) -> Callback + where + F: Fn(Args) -> (), + F: 'static, + FunctionType: FunctionTypeCreator, + { + Callback { + function: FunctionType::new(function), + } + } + + pub fn execute(&self, arguments: Args) + where + FunctionType: FunctionTypeExecutorOneArg, + { + display_error!(self.function.execute(arguments)); + } +} + +pub trait FunctionTypeCreator () + 'static, Args> { + fn new(function: F) -> FunctionType; +} + +pub trait FunctionTypeExecutorOneArg { + fn execute(&self, execute: Args) -> VerboseResult<()>; +} + +macro_rules! build_function_type { + ($struct_name: ident, $($ty: ty, $name: ident), *) => { + pub enum $struct_name { + $($name(Box)),* + } + + $( + impl () + 'static> FunctionTypeCreator for $struct_name { + fn new(function: F) -> FunctionType { + $struct_name::$name(Box::new(function)) + } + } + + impl FunctionTypeExecutorOneArg<$ty> for $struct_name { + fn execute(&self, args: $ty) -> VerboseResult<()> { + match self { + FunctionType::$name(ref function) => { + (function)(args); + Ok(()) + } + _ => { + create_error!("wrong argument type for this callback"); + }, + } + } + } + ) * + } +} + +#[rustfmt::skip] +build_function_type!( + FunctionType, + String, String, + i32, I32, + u32, U32, + f32, F32, + (), Void +); diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..08e6fda --- /dev/null +++ b/src/color.rs @@ -0,0 +1,167 @@ +use std::fmt; +use std::ops::MulAssign; +use std::str::FromStr; +use std::{convert::TryFrom, ops::Mul}; + +use serde::{Deserialize, Serialize}; + +/// `TextColor` describes the color of the text +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum Color { + White, + Black, + Red, + Blue, + Green, + Orange, + Yellow, + Custom(u8, u8, u8), +} + +impl Into<[f32; 3]> for Color { + fn into(self) -> [f32; 3] { + match self { + Color::White => [1.0, 1.0, 1.0], + Color::Black => [0.0, 0.0, 0.0], + Color::Red => [1.0, 0.0, 0.0], + Color::Blue => [0.0, 0.0, 1.0], + Color::Green => [0.0, 1.0, 0.0], + Color::Orange => [1.0, 0.65, 0.0], + Color::Yellow => [1.0, 1.0, 0.0], + Color::Custom(r, g, b) => [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0], + } + } +} + +impl Into<[u8; 3]> for Color { + fn into(self) -> [u8; 3] { + match self { + Color::White => [255, 255, 255], + Color::Black => [0, 0, 0], + Color::Red => [255, 0, 0], + Color::Blue => [0, 0, 255], + Color::Green => [0, 255, 0], + Color::Orange => [255, 166, 0], + Color::Yellow => [255, 255, 0], + Color::Custom(r, g, b) => [r, g, b], + } + } +} + +impl Mul for Color { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + let values: [f32; 3] = self.into(); + + let r = (values[0] * rhs).min(1.0); + let g = (values[1] * rhs).min(1.0); + let b = (values[2] * rhs).min(1.0); + + Color::from([r, g, b]) + } +} + +impl MulAssign for Color { + fn mul_assign(&mut self, rhs: f32) { + *self = *self * rhs; + } +} + +impl From<[u8; 3]> for Color { + fn from(array: [u8; 3]) -> Self { + Self::Custom(array[0], array[1], array[2]) + } +} + +impl From<[f32; 3]> for Color { + fn from(array: [f32; 3]) -> Self { + Self::Custom( + (array[0] * 255.0).min(255.0) as u8, + (array[1] * 255.0).min(255.0) as u8, + (array[2] * 255.0).min(255.0) as u8, + ) + } +} + +impl TryFrom<&str> for Color { + type Error = anyhow::Error; + + fn try_from(text: &str) -> Result { + // check if color is a hex value + if text.starts_with("#") { + let without_prefix = text.trim_start_matches("#"); + + if without_prefix.len() != 6 { + return Err(anyhow::Error::msg(format!( + "Hex color format has wrong format: {}", + without_prefix + ))); + } + + let r = u8::from_str_radix(&without_prefix[0..2], 16)?; + let g = u8::from_str_radix(&without_prefix[2..4], 16)?; + let b = u8::from_str_radix(&without_prefix[4..6], 16)?; + + return Ok(Color::Custom(r, g, b)); + } + + match text { + "white" => Ok(Color::White), + "black" => Ok(Color::Black), + "red" => Ok(Color::Red), + "blue" => Ok(Color::Blue), + "green" => Ok(Color::Green), + "orange" => Ok(Color::Orange), + "yellow" => Ok(Color::Yellow), + _ => Err(anyhow::Error::msg(format!("Unknown color: {}", text))), + } + } +} + +impl fmt::Display for Color { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Color::White => write!(f, "white"), + Color::Black => write!(f, "black"), + Color::Red => write!(f, "red"), + Color::Blue => write!(f, "blue"), + Color::Green => write!(f, "green"), + Color::Orange => write!(f, "orange"), + Color::Yellow => write!(f, "yellow"), + Color::Custom(r, g, b) => write!(f, "#{:X?}{:X?}{:X?}", r, g, b), + } + } +} + +impl FromStr for Color { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Self::try_from(s) + } +} + +impl Default for Color { + fn default() -> Self { + Self::Black + } +} + +#[test] +fn simple_color_conversion() { + let color = Color::try_from("white").unwrap(); + + assert_eq!(color, Color::White); +} + +#[test] +fn hex_color_conversion() { + let color = Color::try_from("#FFFFFF").unwrap(); + + assert_eq!(color, Color::Custom(255, 255, 255)); + + let string = format!("{}", color); + + assert_eq!(string, "#FFFFFF"); +} diff --git a/src/future.rs b/src/future.rs new file mode 100644 index 0000000..6a87c48 --- /dev/null +++ b/src/future.rs @@ -0,0 +1,36 @@ +use std::cell::{Ref, RefCell}; +use std::thread::{spawn, JoinHandle}; + +pub struct Future { + thread: RefCell>>, + data: RefCell>, +} + +impl Future { + pub fn new(f: F) -> Future + where + F: Fn() -> T, + F: Send + 'static, + { + let thread = spawn(f); + + Future { + thread: RefCell::new(Some(thread)), + data: RefCell::new(None), + } + } + + pub fn data(&self) -> Ref<'_, Option> { + let mut thread_opt = self.thread.borrow_mut(); + + if thread_opt.is_some() { + let thread_tmp = thread_opt.take(); + let thread = thread_tmp.unwrap(); + if let Ok(data) = thread.join() { + self.data.replace(Some(data)); + } + } + + self.data.borrow() + } +} diff --git a/src/helperfunctions.rs b/src/helperfunctions.rs new file mode 100644 index 0000000..818e4bb --- /dev/null +++ b/src/helperfunctions.rs @@ -0,0 +1,142 @@ +//! Basic helper functions + +use std::fs; +use std::io::ErrorKind; +use std::rc::Rc; +use std::sync::Arc; + +use std::ffi::CStr; +use std::os::raw::c_char; + +use assetpath::AssetPath; +use cgmath; +use cgmath::One; + +pub fn erase_by_ptr(vector: &mut Vec, object: &T) -> bool { + match vector + .iter() + .position(|t| t as *const T == object as *const T) + { + Some(i) => { + vector.remove(i); + true + } + None => false, + } +} + +pub fn erase_arc(vector: &mut Vec>, object: &Arc) -> Option> { + match vector.iter().position(|t| Arc::ptr_eq(t, object)) { + Some(i) => Some(vector.remove(i)), + None => None, + } +} + +pub fn erase_rc(vector: &mut Vec>, object: &Rc) -> Option> { + match vector.iter().position(|t| Rc::ptr_eq(t, object)) { + Some(i) => Some(vector.remove(i)), + None => None, + } +} + +pub fn ortho( + left: f32, + right: f32, + bottom: f32, + top: f32, + z_near: f32, + z_far: f32, +) -> cgmath::Matrix4 { + let mut mat = cgmath::Matrix4::one(); + + mat[0][0] = 2.0 / (right - left); + mat[1][1] = -2.0 / (top - bottom); + mat[2][2] = -2.0 / (z_far - z_near); + mat[3][0] = -(right + left) / (right - left); + mat[3][1] = -(top + bottom) / (top - bottom); + mat[3][2] = -(z_far + z_near) / (z_far - z_near); + + mat +} + +pub fn search_dir_recursively( + base_dir: &str, + suffix: &str, +) -> Result, std::io::Error> { + let mut files = Vec::new(); + + // simply return an empty result when the path could not be found + // or the user has a lack of permissions + let dir_content = match fs::read_dir(base_dir) { + Ok(dir) => dir, + Err(err) => match err.kind() { + ErrorKind::NotFound | ErrorKind::PermissionDenied => return Ok(files), + _ => return Err(err), + }, + }; + + for fs_object in dir_content { + let obj = fs_object?; + + let path = obj.path(); + + if path.is_file() { + if let Some(os_str) = path.file_name() { + if let Some(string) = os_str.to_str() { + if string.ends_with(suffix) { + files.push(AssetPath::from((base_dir, string))); + } + } + } + } else { + if let Some(string) = path.to_str() { + let more_files = search_dir_recursively(string, suffix)?; + files.extend(more_files); + } + } + } + + Ok(files) +} + +pub unsafe fn c_char_to_string(cchar: *const c_char) -> String { + CStr::from_ptr(cchar).to_str().unwrap().to_string() +} + +#[inline] +pub fn perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> cgmath::Matrix4 { + debug_assert!(z_near != 0.0); + + let zero = 0.0; + let one = 1.0; + let two = 2.0; + let q = one / (fov_y / two).tan(); + let a = q / aspect; + let b = (z_near + z_far) / (z_near - z_far); + let c = (two * z_near * z_far) / (z_near - z_far); + + #[cfg_attr(rustfmt, rustfmt_skip)] + cgmath::Matrix4::new( + a, zero, zero, zero, + zero, -q, zero, zero, + zero, zero, b, zero - one, + zero, zero, c, zero, + ) +} + +#[inline] +pub fn rotate_z(v: cgmath::Vector3, angle: f32) -> cgmath::Vector3 { + let mut result = v; + let cos = angle.cos(); + let sin = angle.sin(); + + result.x = v.x * cos - v.y * sin; + result.y = v.x * sin + v.y * cos; + + result +} + +#[inline] +pub fn almost_eq(f1: f32, f2: f32) -> bool { + (f1 - f2).abs() <= std::f32::EPSILON +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..878d738 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,17 @@ +#![deny(rust_2018_idioms)] + +pub mod asyncthread; +pub mod random; +//pub mod hashvector; +pub mod color; +pub mod future; +pub mod helperfunctions; +pub mod prelude; +//pub mod closures; + +pub mod try_lock_guard; + +pub mod arc_unique_vec; +pub mod rc_unique_vec; + +pub mod unsafe_life_time; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..b19f64d --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,29 @@ +// crates +pub use cgmath; + +// functions +pub use crate::helperfunctions::*; + +// async thread +pub use crate::asyncthread::AsyncThread; + +pub use crate::future::Future; + +// Coin +pub use crate::random::{Coin, Random}; + +// Unique vectors +pub use crate::arc_unique_vec::ArcUniqueVec; +pub use crate::rc_unique_vec::RcUniqueVec; + +// rand crate +pub use rand; + +// color +pub use crate::color::*; + +// TryLockGuard +pub use crate::try_lock_guard::TryLockGuard; + +// unsafe life time trickery +pub use crate::unsafe_life_time::*; diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 0000000..ce26f8a --- /dev/null +++ b/src/random.rs @@ -0,0 +1,43 @@ +use rand::Rng; + +pub struct Coin; + +impl Coin { + pub fn flip(probability: f32) -> bool { + if probability >= 1.0 { + return true; + } + + if probability <= 0.0 { + return false; + } + + let random_number = Self::raw(); + + probability > random_number + } + + pub fn raw() -> f32 { + rand::thread_rng().gen_range(0.0..1.0) + } +} + +pub struct Random; + +impl Random { + pub fn range(low: u32, high: u32) -> u32 { + if low == high { + return low; + } + + rand::thread_rng().gen_range(low..high) + } + + pub fn range_f32(low: f32, high: f32) -> f32 { + if low == high { + return low; + } + + rand::thread_rng().gen_range(low..high) + } +} diff --git a/src/rc_unique_vec.rs b/src/rc_unique_vec.rs new file mode 100644 index 0000000..78d70d7 --- /dev/null +++ b/src/rc_unique_vec.rs @@ -0,0 +1,59 @@ +use std::rc::Rc; + +/// Wrapper around a `Vec` if `Rc`, +/// that ensures pointer uniqueness of its elements +#[derive(Debug, Clone, Default)] +pub struct RcUniqueVec { + data: Vec>, +} + +impl RcUniqueVec { + /// Creates an empty `RcUniqueVec` + pub fn new() -> RcUniqueVec { + RcUniqueVec { data: Vec::new() } + } + + /// Checks for pointer collision while inserting. + /// Returns the index for the position of the inserted element. + pub fn insert(&mut self, element: Rc) -> usize { + match self.data.iter().position(|t| Rc::ptr_eq(t, &element)) { + Some(index) => index, + None => { + let index = self.data.len(); + self.data.push(element); + index + } + } + } + + /// Checks if the given element is in the vector, then returns true, + /// otherwise false. + pub fn remove(&mut self, element: &Rc) -> bool { + match self.data.iter().position(|t| Rc::ptr_eq(t, element)) { + Some(index) => { + self.data.remove(index); + true + } + None => false, + } + } + + /// Checks if that element is in this `RcUniqueVec` + /// and returns its index if possible + pub fn get_index(&self, element: &Rc) -> Option { + match self.data.iter().position(|t| Rc::ptr_eq(t, element)) { + Some(index) => Some(index), + None => None, + } + } + + /// Clears all elements from this vector + pub fn clear(&mut self) { + self.data.clear(); + } + + /// Returns the reference to the internal vector + pub fn as_vec(&self) -> &Vec> { + &self.data + } +} diff --git a/src/try_lock_guard.rs b/src/try_lock_guard.rs new file mode 100644 index 0000000..546349c --- /dev/null +++ b/src/try_lock_guard.rs @@ -0,0 +1,41 @@ +use std::sync::{LockResult, Mutex, MutexGuard, PoisonError, TryLockError}; + +pub enum TryLockGuard<'a, T> { + Lock(MutexGuard<'a, T>), + WouldBlock(&'a Mutex), +} + +impl<'a, T> TryLockGuard<'a, T> { + pub fn try_lock( + d: &'a Mutex, + ) -> Result, PoisonError>> { + match d.try_lock() { + Ok(lock) => Ok(TryLockGuard::Lock(lock)), + Err(try_lock_error) => match try_lock_error { + TryLockError::Poisoned(poison_error) => Err(poison_error), + TryLockError::WouldBlock => Ok(TryLockGuard::WouldBlock(d)), + }, + } + } + + pub fn would_block(&self) -> bool { + match self { + Self::Lock(_) => false, + Self::WouldBlock(_) => true, + } + } + + pub fn guard(self) -> Option> { + match self { + Self::Lock(guard) => Some(guard), + Self::WouldBlock(_) => None, + } + } + + pub fn lock(self) -> LockResult> { + match self { + Self::Lock(guard) => Ok(guard), + Self::WouldBlock(mutex) => Ok(mutex.lock()?), + } + } +} diff --git a/src/unsafe_life_time.rs b/src/unsafe_life_time.rs new file mode 100644 index 0000000..6d39837 --- /dev/null +++ b/src/unsafe_life_time.rs @@ -0,0 +1,7 @@ +pub unsafe fn remove_life_time_mut<'a, 'b, T>(t: &'a mut T) -> &'b mut T { + std::mem::transmute(t as *mut T) +} + +pub unsafe fn remove_life_time<'a, 'b, T>(t: &'a T) -> &'b T { + std::mem::transmute(t as *const T) +}