From 50894bef89a9e08174a316ca06dd90e96f406392 Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Sat, 20 Jan 2024 19:04:09 -0500 Subject: [PATCH] `helper/` (#45) * add `/helper/` * add `num.rs` * add `sys.rs` * add `crypto.rs` * add lints and mod to `lib.rs` * `sys` -> `time`, add more free functions straight from https://docs.rs/readable/latest/readable/time/index.html * num: add `Number/Float` types, `cmp_float()`, `cmp_float_nan()` * `common/src/tower_utils.rs` -> `helper/src/asynch.rs` * gate modules with `#[cfg(feature = "...")]` * add `thread.rs` * cargo fmt * thread: test out of 100 * add `atomic.rs` * atomic: fix `fetch_update()` * atomic: impl `fetch_*()` for atomic floats * `#[no_std]` where possible * asynch: remove `InstaFuture` https://docs.rs/futures/latest/futures/future/fn.ready.html * crypto: remove `check_point()` * thread: return percent computation without static * thread: add `low_priority_thread()` https://docs.rs/lpt * add rayon_spawn_async, remove crypto * remove current_time_try --------- Co-authored-by: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com> --- Cargo.lock | 73 +++++++ Cargo.toml | 1 + helper/Cargo.toml | 30 +++ helper/README.md | 18 ++ helper/src/asynch.rs | 113 ++++++++++ helper/src/atomic.rs | 490 +++++++++++++++++++++++++++++++++++++++++++ helper/src/lib.rs | 50 +++++ helper/src/num.rs | 177 ++++++++++++++++ helper/src/thread.rs | 100 +++++++++ helper/src/time.rs | 161 ++++++++++++++ 10 files changed, 1213 insertions(+) create mode 100644 helper/Cargo.toml create mode 100644 helper/README.md create mode 100644 helper/src/asynch.rs create mode 100644 helper/src/atomic.rs create mode 100644 helper/src/lib.rs create mode 100644 helper/src/num.rs create mode 100644 helper/src/thread.rs create mode 100644 helper/src/time.rs diff --git a/Cargo.lock b/Cargo.lock index b88daf7..713ada0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,6 +841,18 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "helper" +version = "0.1.0" +dependencies = [ + "chrono", + "futures", + "libc", + "rayon", + "tokio", + "windows", +] + [[package]] name = "hermit-abi" version = "0.3.4" @@ -1051,6 +1063,16 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -1327,6 +1349,29 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -1719,6 +1764,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sct" version = "0.7.1" @@ -1828,6 +1879,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simple-request" version = "0.1.0" @@ -1995,10 +2055,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", + "bytes", "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -2318,6 +2381,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index b50328c..f42ca82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "cryptonight", # "cuprate", # "database", + "helper", "net/levin", "net/monero-wire", "p2p/monero-p2p", diff --git a/helper/Cargo.toml b/helper/Cargo.toml new file mode 100644 index 0000000..5520ad8 --- /dev/null +++ b/helper/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "helper" +version = "0.1.0" +edition = "2021" + +[features] +# All features on by default. +default = ["std", "atomic", "asynch", "num", "time", "thread"] +std = [] +atomic = [] +asynch = ["dep:futures", "dep:rayon"] +num = [] +time = ["dep:chrono", "std"] +thread = ["std", "dep:target_os_lib"] + +[dependencies] +chrono = { workspace = true, optional = true } +futures = { workspace = true, optional = true } +rayon = { workspace = true, optional = true } + +# This is kinda a stupid work around. +# [thread] needs to activate one of these libs (windows|libc) +# although it depends on what target we're building for. +[target.'cfg(windows)'.dependencies] +target_os_lib = { package = "windows", version = ">=0.51", features = ["Win32_System_Threading", "Win32_Foundation"], optional = true } +[target.'cfg(unix)'.dependencies] +target_os_lib = { package = "libc", version = "0.2.151", optional = true } + +[dev-dependencies] +tokio = { workspace = true } diff --git a/helper/README.md b/helper/README.md new file mode 100644 index 0000000..42c633a --- /dev/null +++ b/helper/README.md @@ -0,0 +1,18 @@ +## Helper +`helper/` is the kitchen-sink crate for very generic, not necessarily Cuprate specific functions, types, etc. + +This allows all workspace crates to share, and aids compile times. + +If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications. + +## Features +Code can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`. + +All features on by default. + +See [`Cargo.toml`](Cargo.toml)'s `[features]` table to see what features there are and what they enable. + +## `#[no_std]` +Each modules documents whether it requires `std` or not. + +If a module that requires `std` is enabled, `helper` will automatically use `std`. \ No newline at end of file diff --git a/helper/src/asynch.rs b/helper/src/asynch.rs new file mode 100644 index 0000000..ea89dd7 --- /dev/null +++ b/helper/src/asynch.rs @@ -0,0 +1,113 @@ +//! `async` related +//! +//! `#[no_std]` compatible. + +//---------------------------------------------------------------------------------------------------- Use +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use futures::{channel::oneshot, FutureExt}; + +//---------------------------------------------------------------------------------------------------- InfallibleOneshotReceiver +/// A oneshot receiver channel that doesn't return an Error. +/// +/// This requires the sender to always return a response. +pub struct InfallibleOneshotReceiver(oneshot::Receiver); + +impl From> for InfallibleOneshotReceiver { + fn from(value: oneshot::Receiver) -> Self { + InfallibleOneshotReceiver(value) + } +} + +impl Future for InfallibleOneshotReceiver { + type Output = T; + + #[inline] + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + self.0 + .poll_unpin(ctx) + .map(|res| res.expect("Oneshot must not be cancelled before response!")) + } +} + +//---------------------------------------------------------------------------------------------------- rayon_spawn_async +/// Spawns a task for the rayon thread pool and awaits the result without blocking the async runtime. +pub async fn rayon_spawn_async(f: F) -> R +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + let (tx, rx) = oneshot::channel(); + rayon::spawn(move || { + let _ = tx.send(f()); + }); + rx.await.expect("The sender must not be dropped") +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use std::{ + sync::{Arc, Barrier}, + thread, + time::Duration, + }; + + use super::*; + + #[tokio::test] + // Assert that basic channel operations work. + async fn infallible_oneshot_receiver() { + let (tx, rx) = futures::channel::oneshot::channel::(); + let msg = "hello world!".to_string(); + + tx.send(msg.clone()).unwrap(); + + let oneshot = InfallibleOneshotReceiver::from(rx); + assert_eq!(oneshot.await, msg); + } + + #[test] + fn rayon_spawn_async_does_not_block() { + // There must be more than 1 rayon thread for this to work. + rayon::ThreadPoolBuilder::new() + .num_threads(2) + .build_global() + .unwrap(); + + // We use a barrier to make sure both tasks are executed together, we block the rayon thread + // until both rayon threads are blocked. + let barrier = Arc::new(Barrier::new(2)); + let task = |barrier: &Barrier| barrier.wait(); + + let b_2 = barrier.clone(); + + let (tx, rx) = std::sync::mpsc::channel(); + + thread::spawn(move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(async { + tokio::join!( + // This polls them concurrently in the same task, so if the first one blocks the task then + // the second wont run and if the second does not run the first does not unblock. + rayon_spawn_async(move || task(&barrier)), + rayon_spawn_async(move || task(&b_2)), + ) + }); + + // if we managed to get here then rayon_spawn_async didn't block. + tx.send(()).unwrap(); + }); + + rx.recv_timeout(Duration::from_secs(2)) + .expect("rayon_spawn_async blocked"); + } +} diff --git a/helper/src/atomic.rs b/helper/src/atomic.rs new file mode 100644 index 0000000..cfd9609 --- /dev/null +++ b/helper/src/atomic.rs @@ -0,0 +1,490 @@ +//! Atomic related +//! +//! `#[no_std]` compatible. + +//---------------------------------------------------------------------------------------------------- Use +use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +//---------------------------------------------------------------------------------------------------- Atomic Float +// An AtomicF(32|64) implementation. +// +// This internally uses [AtomicU(32|64)], where the +// u(32|64) is the bit pattern of the internal float. +// +// This uses [.to_bits()] and [from_bits()] to +// convert between actual floats, and the bit +// representations for storage. +// +// Using `UnsafeCell` is also viable, +// and would allow for a `const fn new(f: float) -> Self` +// except that becomes problematic with NaN's and infinites: +// - https://github.com/rust-lang/rust/issues/73328 +// - https://github.com/rust-lang/rfcs/pull/3514 +// +// This is most likely safe(?) but... instead of risking UB, +// this just uses the Atomic unsigned integer as the inner +// type instead of transmuting from `UnsafeCell`. +// +// This creates the types: +// - `AtomicF32` +// - `AtomicF64` +// +// Originally taken from: +// https://github.com/hinto-janai/sansan/blob/1f6680b2d08ff5fbf4f090178ea5233d4cf9056f/src/atomic.rs +macro_rules! impl_atomic_f { + ( + $atomic_float:ident, // Name of the new float type + $atomic_float_lit:literal, // Literal name of new float type + $float:ident, // The target float (f32/f64) + $unsigned:ident, // The underlying unsigned type + $atomic_unsigned:ident, // The underlying unsigned atomic type + $bits_0:literal, // Bit pattern for 0.0 + $bits_025:literal, // Bit pattern for 0.25 + $bits_050:literal, // Bit pattern for 0.50 + $bits_075:literal, // Bit pattern for 0.75 + $bits_1:literal, // Bit pattern for 1.0 + ) => { + /// An atomic float. + /// + /// ## Portability + /// [Quoting the std library: ](https://doc.rust-lang.org/1.70.0/std/primitive.f32.html#method.to_bits) + /// "See from_bits for some discussion of the portability of this operation (there are almost no issues)." + /// + /// ## Compile-time failure + /// This internal functions `std` uses will panic _at compile time_ + /// if the bit transmutation operations it uses are not available + /// on the build target, aka, if it compiles we're probably safe. + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] + pub struct $atomic_float($atomic_unsigned); + + impl $atomic_float { + /// Representation of `0.0` as bits, can be inputted into [`Self::from_bits`]. + pub const BITS_0: $unsigned = $bits_0; + /// Representation of `0.25` as bits, can be inputted into [`Self::from_bits`]. + pub const BITS_0_25: $unsigned = $bits_025; + /// Representation of `0.50` as bits, can be inputted into [`Self::from_bits`]. + pub const BITS_0_50: $unsigned = $bits_050; + /// Representation of `0.75` as bits, can be inputted into [`Self::from_bits`]. + pub const BITS_0_75: $unsigned = $bits_075; + /// Representation of `1.0` as bits, can be inputted into [`Self::from_bits`]. + pub const BITS_0_100: $unsigned = $bits_1; + + #[allow(clippy::declare_interior_mutable_const)] + // FIXME: + // Seems like `std` internals has some unstable cfg options that + // allow interior mutable consts to be defined without clippy complaining: + // https://doc.rust-lang.org/1.70.0/src/core/sync/atomic.rs.html#3013. + // + /// `0.0`, returned by [`Self::default`]. + pub const DEFAULT: Self = Self($atomic_unsigned::new($bits_0)); + + #[inline] + /// Create a new atomic float. + /// + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.new + pub fn new(f: $float) -> Self { + // FIXME: Update to const when available. + // https://doc.rust-lang.org/1.70.0/src/core/num/f32.rs.html#998 + // + // `transmute()` here would be safe (`to_bits()` is doing this) + // although checking for NaN's and infinites are non-`const`... + // so we can't can't `transmute()` even though it would allow + // this function to be `const`. + Self($atomic_unsigned::new(f.to_bits())) + } + + #[inline] + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.into_inner + pub fn into_inner(self) -> $float { + $float::from_bits(self.0.into_inner()) + } + + #[inline] + /// Create a new atomic float, from the unsigned bit representation. + pub const fn from_bits(bits: $unsigned) -> Self { + Self($atomic_unsigned::new(bits)) + } + + #[inline] + /// Store a float inside the atomic. + /// + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.store + pub fn store(&self, f: $float, ordering: Ordering) { + self.0.store(f.to_bits(), ordering); + } + + #[inline] + /// Store a bit representation of a float inside the atomic. + pub fn store_bits(&self, bits: $unsigned, ordering: Ordering) { + self.0.store(bits, ordering); + } + + #[inline] + /// Load the internal float from the atomic. + /// + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.load + pub fn load(&self, ordering: Ordering) -> $float { + // FIXME: Update to const when available. + // https://doc.rust-lang.org/1.70.0/src/core/num/f32.rs.html#1088 + $float::from_bits(self.0.load(ordering)) + } + + #[inline] + /// Load the internal bit representation of the float from the atomic. + pub fn load_bits(&self, ordering: Ordering) -> $unsigned { + self.0.load(ordering) + } + + #[inline] + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.swap + pub fn swap(&self, val: $float, ordering: Ordering) -> $float { + $float::from_bits(self.0.swap($float::to_bits(val), ordering)) + } + + #[inline] + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.compare_exchange + pub fn compare_exchange( + &self, + current: $float, + new: $float, + success: Ordering, + failure: Ordering, + ) -> Result<$float, $float> { + match self + .0 + .compare_exchange(current.to_bits(), new.to_bits(), success, failure) + { + Ok(b) => Ok($float::from_bits(b)), + Err(b) => Err($float::from_bits(b)), + } + } + + #[inline] + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.compare_exchange_weak + pub fn compare_exchange_weak( + &self, + current: $float, + new: $float, + success: Ordering, + failure: Ordering, + ) -> Result<$float, $float> { + match self.0.compare_exchange_weak( + current.to_bits(), + new.to_bits(), + success, + failure, + ) { + Ok(b) => Ok($float::from_bits(b)), + Err(b) => Err($float::from_bits(b)), + } + } + + //------------------------------------------------------------------ fetch_*() + // These are tricky to implement because we must + // operate on the _numerical_ value and not the + // bit representations. + // + // This means using some type of CAS, + // which comes with the regular tradeoffs... + + // The (private) function using CAS to implement `fetch_*()` operations. + // + // This is function body used in all the below `fetch_*()` functions. + fn fetch_update_unwrap(&self, ordering: Ordering, mut update: F) -> $float + where + F: FnMut($float) -> $float, + { + // Since it's a CAS, we need a second ordering for failures, + // this will take the user input and return an appropriate order. + let second_order = match ordering { + Ordering::Release | Ordering::Relaxed => Ordering::Relaxed, + Ordering::Acquire | Ordering::AcqRel => Ordering::Acquire, + Ordering::SeqCst => Ordering::SeqCst, + // Ordering is #[non_exhaustive], so we must do this. + ordering => ordering, + }; + + // SAFETY: + // unwrap is safe since `fetch_update()` only panics + // if the closure we pass it returns `None`. + // As seen below, we're passing a `Some`. + // + // https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_update + self.fetch_update(ordering, second_order, |f| Some(update(f))) + .unwrap() + } + + #[inline] + /// This function is implemented with [`Self::fetch_update`], and is not 100% equivalent to + /// https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_add. + /// + /// In particular, this method will not circumvent the [ABA Problem](https://en.wikipedia.org/wiki/ABA_problem). + /// + /// Other than this not actually being atomic, all other behaviors are the same. + pub fn fetch_add(&self, val: $float, order: Ordering) -> $float { + self.fetch_update_unwrap(order, |f| f + val) + } + + #[inline] + /// This function is implemented with [`Self::fetch_update`], and is not 100% equivalent to + /// https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_sub. + /// + /// In particular, this method will not circumvent the [ABA Problem](https://en.wikipedia.org/wiki/ABA_problem). + /// + /// Other than this not actually being atomic, all other behaviors are the same. + pub fn fetch_sub(&self, val: $float, order: Ordering) -> $float { + self.fetch_update_unwrap(order, |f| f - val) + } + + #[inline] + /// This function is implemented with [`Self::fetch_update`], and is not 100% equivalent to + /// https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_max. + /// + /// In particular, this method will not circumvent the [ABA Problem](https://en.wikipedia.org/wiki/ABA_problem). + /// + /// Other than this not actually being atomic, all other behaviors are the same. + pub fn fetch_max(&self, val: $float, order: Ordering) -> $float { + self.fetch_update_unwrap(order, |f| f.max(val)) + } + + #[inline] + /// This function is implemented with [`Self::fetch_update`], and is not 100% equivalent to + /// https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_min. + /// + /// In particular, this method will not circumvent the [ABA Problem](https://en.wikipedia.org/wiki/ABA_problem). + /// + /// Other than this not actually being atomic, all other behaviors are the same. + pub fn fetch_min(&self, val: $float, order: Ordering) -> $float { + self.fetch_update_unwrap(order, |f| f.min(val)) + } + + #[inline] + /// Equivalent to https://doc.rust-lang.org/1.70.0/std/sync/atomic/struct.AtomicUsize.html#method.fetch_update + pub fn fetch_update( + &self, + set_order: Ordering, + fetch_order: Ordering, + mut f: F, + ) -> Result<$float, $float> + where + F: FnMut($float) -> Option<$float>, + { + // Very unreadable closure... + // + // Basically this is converting: + // `f(f32) -> Option` into `f(u32) -> Option` + // so the internal atomic `fetch_update` can work. + let f = |bits: $unsigned| f($float::from_bits(bits)).map(|f| $float::to_bits(f)); + + match self.0.fetch_update(set_order, fetch_order, f) { + Ok(b) => Ok($float::from_bits(b)), + Err(b) => Err($float::from_bits(b)), + } + } + + #[inline] + /// Set the internal float from the atomic, using [`Ordering::Release`]. + pub fn set(&self, f: $float) { + self.store(f, Ordering::Release); + } + + #[inline] + /// Get the internal float from the atomic, using [`Ordering::Acquire`]. + pub fn get(&self) -> $float { + self.load(Ordering::Acquire) + } + } + + impl From<$float> for $atomic_float { + /// Calls [`Self::new`] + fn from(float: $float) -> Self { + Self::new(float) + } + } + + impl Default for $atomic_float { + /// Returns `0.0`. + fn default() -> Self { + Self::DEFAULT + } + } + + impl std::fmt::Debug for $atomic_float { + /// This prints the internal float value, using [`Ordering::Acquire`]. + /// + /// # Panics + /// This panics on NaN or subnormal float inputs. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple($atomic_float_lit) + .field(&self.0.load(Ordering::Acquire)) + .finish() + } + } + }; +} + +impl_atomic_f! { + AtomicF64, + "AtomicF64", + f64, + u64, + AtomicU64, + 0, + 4598175219545276416, + 4602678819172646912, + 4604930618986332160, + 4607182418800017408, +} + +impl_atomic_f! { + AtomicF32, + "AtomicF32", + f32, + u32, + AtomicU32, + 0, + 1048576000, + 1056964608, + 1061158912, + 1065353216, +} + +//---------------------------------------------------------------------------------------------------- TESTS +#[cfg(test)] +mod tests { + use super::*; + + // These tests come in pairs, `f32|f64`. + // + // If changing one, update the other as well. + // + // `macro_rules!()` + `paste!()` could do this automatically, + // but that might be more trouble than it's worth... + + #[test] + // Tests the varying fetch, swap, and compare functions. + fn f32_functions() { + let float = AtomicF32::new(5.0); + let ordering = Ordering::SeqCst; + + // Loads/Stores + assert_eq!(float.swap(1.0, ordering), 5.0); + assert_eq!(float.load(ordering), 1.0); + float.store(2.0, ordering); + assert_eq!(float.load(ordering), 2.0); + + // CAS + assert_eq!( + float.compare_exchange(2.0, 5.0, ordering, ordering), + Ok(2.0) + ); + assert_eq!( + float.fetch_update(ordering, ordering, |f| Some(f * 3.0)), + Ok(5.0) + ); + assert_eq!(float.get(), 15.0); + loop { + if let Ok(float) = float.compare_exchange_weak(15.0, 2.0, ordering, ordering) { + assert_eq!(float, 15.0); + break; + } + } + + // `fetch_*()` functions + assert_eq!(float.fetch_add(1.0, ordering), 2.0); + assert_eq!(float.fetch_sub(1.0, ordering), 3.0); + assert_eq!(float.fetch_max(5.0, ordering), 2.0); + assert_eq!(float.fetch_min(0.0, ordering), 5.0); + } + + #[test] + fn f64_functions() { + let float = AtomicF64::new(5.0); + let ordering = Ordering::SeqCst; + + assert_eq!(float.swap(1.0, ordering), 5.0); + assert_eq!(float.load(ordering), 1.0); + float.store(2.0, ordering); + assert_eq!(float.load(ordering), 2.0); + + assert_eq!( + float.compare_exchange(2.0, 5.0, ordering, ordering), + Ok(2.0) + ); + assert_eq!( + float.fetch_update(ordering, ordering, |f| Some(f * 3.0)), + Ok(5.0) + ); + assert_eq!(float.get(), 15.0); + + loop { + if let Ok(float) = float.compare_exchange_weak(15.0, 2.0, ordering, ordering) { + assert_eq!(float, 15.0); + break; + } + } + + assert_eq!(float.fetch_add(1.0, ordering), 2.0); + assert_eq!(float.fetch_sub(1.0, ordering), 3.0); + assert_eq!(float.fetch_max(5.0, ordering), 2.0); + assert_eq!(float.fetch_min(0.0, ordering), 5.0); + } + + #[test] + fn f32_bits() { + assert_eq!(AtomicF32::default().get(), 0.00); + assert_eq!(AtomicF32::from_bits(AtomicF32::BITS_0).get(), 0.00); + assert_eq!(AtomicF32::from_bits(AtomicF32::BITS_0_25).get(), 0.25); + assert_eq!(AtomicF32::from_bits(AtomicF32::BITS_0_50).get(), 0.50); + assert_eq!(AtomicF32::from_bits(AtomicF32::BITS_0_75).get(), 0.75); + assert_eq!(AtomicF32::from_bits(AtomicF32::BITS_0_100).get(), 1.00); + } + + #[test] + fn f64_bits() { + assert_eq!(AtomicF64::default().get(), 0.00); + assert_eq!(AtomicF64::from_bits(AtomicF64::BITS_0).get(), 0.00); + assert_eq!(AtomicF64::from_bits(AtomicF64::BITS_0_25).get(), 0.25); + assert_eq!(AtomicF64::from_bits(AtomicF64::BITS_0_50).get(), 0.50); + assert_eq!(AtomicF64::from_bits(AtomicF64::BITS_0_75).get(), 0.75); + assert_eq!(AtomicF64::from_bits(AtomicF64::BITS_0_100).get(), 1.00); + } + + #[test] + fn f32_0_to_100() { + let mut i = 0.0; + let f = AtomicF32::new(0.0); + while i < 100.0 { + f.set(i); + assert_eq!(f.get(), i); + i += 0.1; + } + } + + #[test] + fn f64_0_to_100() { + let mut i = 0.0; + let f = AtomicF64::new(0.0); + while i < 100.0 { + f.set(i); + assert_eq!(f.get(), i); + i += 0.1; + } + } + + #[test] + fn f32_irregular() { + assert!(AtomicF32::new(f32::NAN).get().is_nan()); + assert_eq!(AtomicF32::new(f32::INFINITY).get(), f32::INFINITY); + assert_eq!(AtomicF32::new(f32::NEG_INFINITY).get(), f32::NEG_INFINITY); + } + + #[test] + fn f64_irregular() { + assert!(AtomicF64::new(f64::NAN).get().is_nan()); + assert_eq!(AtomicF64::new(f64::INFINITY).get(), f64::INFINITY); + assert_eq!(AtomicF64::new(f64::NEG_INFINITY).get(), f64::NEG_INFINITY); + } +} diff --git a/helper/src/lib.rs b/helper/src/lib.rs new file mode 100644 index 0000000..e52e0c6 --- /dev/null +++ b/helper/src/lib.rs @@ -0,0 +1,50 @@ +#![doc = include_str!("../README.md")] +//---------------------------------------------------------------------------------------------------- Lints +#![allow(clippy::len_zero, clippy::type_complexity, clippy::module_inception)] +#![deny(nonstandard_style, deprecated, missing_docs, unused_mut)] +#![forbid( + unused_unsafe, + future_incompatible, + break_with_label_and_loop, + coherence_leak_check, + duplicate_macro_attributes, + exported_private_dependencies, + for_loops_over_fallibles, + large_assignments, + overlapping_range_endpoints, + // private_in_public, + semicolon_in_expressions_from_macros, + redundant_semicolons, + unconditional_recursion, + unreachable_patterns, + unused_allocation, + unused_braces, + unused_comparisons, + unused_doc_comments, + unused_parens, + unused_labels, + while_true, + keyword_idents, + non_ascii_idents, + noop_method_call, + unreachable_pub, + single_use_lifetimes, + // variant_size_differences, +)] +#![cfg_attr(not(feature = "std"), no_std)] + +//---------------------------------------------------------------------------------------------------- Public API +#[cfg(feature = "asynch")] +pub mod asynch; // async collides +#[cfg(feature = "atomic")] +pub mod atomic; +#[cfg(feature = "num")] +pub mod num; +#[cfg(feature = "thread")] +pub mod thread; +#[cfg(feature = "time")] +pub mod time; + +//---------------------------------------------------------------------------------------------------- Private Usage + +//---------------------------------------------------------------------------------------------------- diff --git a/helper/src/num.rs b/helper/src/num.rs new file mode 100644 index 0000000..a4aedcf --- /dev/null +++ b/helper/src/num.rs @@ -0,0 +1,177 @@ +//! Number related +//! +//! `#[no_std]` compatible. + +//---------------------------------------------------------------------------------------------------- Use +use core::{ + cmp::Ordering, + ops::{Add, Div, Mul, Sub}, +}; + +//---------------------------------------------------------------------------------------------------- Types +// INVARIANT: must be private. +// Protects against outside-crate implementations. +mod private { + pub trait Sealed: Copy + PartialOrd + core::fmt::Display {} +} + +/// Non-floating point numbers +/// +/// This trait is sealed and is only implemented on: +/// - [`u8`] to [`u128`] and [`usize`] +/// - [`i8`] to [`i128`] and [`isize`] +pub trait Number: private::Sealed {} +macro_rules! impl_number { + ($($num:ty),* $(,)?) => { + $( + impl Number for $num {} + impl private::Sealed for $num {} + )* + }; +} +impl_number!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); + +/// Floating point numbers +/// +/// This trait is sealed and is only implemented on: +/// - [`f32`] +/// - [`f64`] +pub trait Float: private::Sealed {} +macro_rules! impl_float { + ($($num:ty),* $(,)?) => { + $( + impl Float for $num {} + impl private::Sealed for $num {} + )* + }; +} +impl_float!(f32, f64); + +//---------------------------------------------------------------------------------------------------- Free Functions +#[inline] +/// Returns the average of two numbers; works with at least all integral and floating point types +/// +/// ```rust +/// # use helper::num::*; +/// assert_eq!(get_mid(0, 10), 5); +/// assert_eq!(get_mid(0.0, 10.0), 5.0); +/// assert_eq!(get_mid(-10.0, 10.0), 0.0); +/// assert_eq!(get_mid(i16::MIN, i16::MAX), -1); +/// assert_eq!(get_mid(u8::MIN, u8::MAX), 127); +/// +/// assert!(get_mid(f32::NAN, f32::NAN).is_nan()); +/// assert!(get_mid(f32::NEG_INFINITY, f32::INFINITY).is_nan()); +/// ``` +pub fn get_mid(a: T, b: T) -> T +where + T: Add + Sub + Div + Mul + Copy + From, +{ + let two: T = 2_u8.into(); + + // https://github.com/monero-project/monero/blob/90294f09ae34ef96f3dea5fea544816786df87c8/contrib/epee/include/misc_language.h#L43 + (a / two) + (b / two) + ((a - two * (a / two)) + (b - two * (b / two))) / two +} + +#[inline] +/// Gets the median from a sorted slice. +/// +/// ```rust +/// # use helper::num::*; +/// let mut vec = vec![10, 5, 1, 4, 2, 8, 9, 7, 3, 6]; +/// vec.sort(); +/// +/// assert_eq!(median(vec), 5); +/// ``` +/// +/// # Safety +/// If not sorted the output will be invalid. +pub fn median(array: impl AsRef<[T]>) -> T +where + T: Add + Sub + Div + Mul + Copy + From, +{ + let array = array.as_ref(); + let len = array.len(); + + let mid = len / 2; + + if len == 1 { + return array[0]; + } + + if len % 2 == 0 { + get_mid(array[mid - 1], array[mid]) + } else { + array[mid] + } +} + +#[inline] +/// Compare 2 non-`NaN` floats. +/// +/// ```rust +/// # use helper::num::*; +/// # use core::cmp::Ordering; +/// assert_eq!(cmp_float(0.0, 1.0), Ordering::Less); +/// assert_eq!(cmp_float(1.0, 1.0), Ordering::Equal); +/// assert_eq!(cmp_float(2.0, 1.0), Ordering::Greater); +/// +/// assert_eq!(cmp_float(1.0, f32::INFINITY), Ordering::Less); +/// assert_eq!(cmp_float(f32::INFINITY, f32::INFINITY), Ordering::Equal); +/// assert_eq!(cmp_float(f32::INFINITY, 1.0), Ordering::Greater); +/// +/// assert_eq!(cmp_float(f32::NEG_INFINITY, f32::INFINITY), Ordering::Less); +/// assert_eq!(cmp_float(f32::NEG_INFINITY, f32::NEG_INFINITY), Ordering::Equal); +/// assert_eq!(cmp_float(f32::INFINITY, f32::NEG_INFINITY), Ordering::Greater); +/// ``` +/// +/// # Panic +/// This function panics if either floats are NaNs. +/// +/// ```rust,should_panic +/// # use helper::num::*; +/// cmp_float(0.0, f32::NAN); +/// ``` +pub fn cmp_float(a: F, b: F) -> Ordering { + match (a <= b, a >= b) { + (false, true) => Ordering::Greater, + (true, false) => Ordering::Less, + (true, true) => Ordering::Equal, + _ => panic!("cmp_float() has failed, input: {a} - {b}"), + } +} + +#[inline] +/// Compare 2 floats, `NaN`'s will always return [`Ordering::Equal`]. +/// +/// ```rust +/// # use helper::num::*; +/// # use core::cmp::Ordering; +/// assert_eq!(cmp_float_nan(0.0, 1.0), Ordering::Less); +/// assert_eq!(cmp_float_nan(1.0, 1.0), Ordering::Equal); +/// assert_eq!(cmp_float_nan(2.0, 1.0), Ordering::Greater); +/// +/// assert_eq!(cmp_float_nan(1.0, f32::INFINITY), Ordering::Less); +/// assert_eq!(cmp_float_nan(f32::INFINITY, f32::INFINITY), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::INFINITY, 1.0), Ordering::Greater); +/// +/// assert_eq!(cmp_float_nan(f32::NEG_INFINITY, f32::INFINITY), Ordering::Less); +/// assert_eq!(cmp_float_nan(f32::NEG_INFINITY, f32::NEG_INFINITY), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::INFINITY, f32::NEG_INFINITY), Ordering::Greater); +/// +/// assert_eq!(cmp_float_nan(f32::NAN, -0.0), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::NAN, 0.0), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::NAN, f32::NAN), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::NAN, f32::INFINITY), Ordering::Equal); +/// assert_eq!(cmp_float_nan(f32::NAN, f32::NEG_INFINITY), Ordering::Equal); +/// ``` +pub fn cmp_float_nan(a: F, b: F) -> Ordering { + match (a <= b, a >= b) { + (false, true) => Ordering::Greater, + (true, false) => Ordering::Less, + _ => Ordering::Equal, + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test {} diff --git a/helper/src/thread.rs b/helper/src/thread.rs new file mode 100644 index 0000000..ed1ea29 --- /dev/null +++ b/helper/src/thread.rs @@ -0,0 +1,100 @@ +//! System thread related +//! +//! Requires `std`. + +//---------------------------------------------------------------------------------------------------- Use +use std::{cmp::max, num::NonZeroUsize}; + +//---------------------------------------------------------------------------------------------------- Constants +// FIXME: switch to `.unwrap()` when const stablized +const NON_ZERO_USIZE_1: NonZeroUsize = match NonZeroUsize::new(1) { + Some(t) => t, + _ => panic!(), +}; + +//---------------------------------------------------------------------------------------------------- Thread Count & Percent +#[allow(non_snake_case)] +/// Get the total amount of system threads. +/// +/// ```rust +/// # use helper::thread::*; +/// assert!(threads().get() >= 1); +/// ``` +pub fn threads() -> NonZeroUsize { + std::thread::available_parallelism().unwrap_or(NON_ZERO_USIZE_1) +} + +// Implement a function for the various +// `x` thread-percent functions below. +macro_rules! impl_thread_percent { + ($( + $(#[$doc:meta])* + $fn_name:ident => // Name of the function + $percent:literal // The target percent of threads + ),* $(,)?) => { + $( + $(#[$doc])* + pub fn $fn_name() -> NonZeroUsize { + // SAFETY: + // unwrap here is okay because: + // - THREADS().get() is always non-zero + // - max() guards against 0 + NonZeroUsize::new(max(1, (threads().get() as f64 * $percent).floor() as usize)).unwrap() + } + )* + } +} +impl_thread_percent! { + /// Get 90% (rounded down) of available amount of system threads. + threads_90 => 0.90, + /// Get 75% (rounded down) of available amount of system threads. + threads_75 => 0.75, + /// Get 50% (rounded down) of available amount of system threads. + threads_50 => 0.50, + /// Get 25% (rounded down) of available amount of system threads. + threads_25 => 0.25, + /// Get 10% (rounded down) of available amount of system threads. + threads_10 => 0.10, +} + +//---------------------------------------------------------------------------------------------------- Thread Priority +/// Low Priority Thread +/// +/// Sets the calling thread’s priority to the lowest platform-specific value possible. +/// +/// https://docs.rs/lpt +/// +/// # Windows +/// Uses SetThreadPriority() with THREAD_PRIORITY_IDLE (-15). +/// +/// # Unix +/// Uses libc::nice() with the max nice level. +/// +/// On macOS and *BSD: +20 +/// On Linux: +19 +pub fn low_priority_thread() { + #[cfg(target_os = "windows")] + { + use target_os_lib as windows; + use windows::Win32::System::Threading::*; + + // SAFETY: calling C. + // We are _lowering_ our priority, not increasing, so this function should never fail. + unsafe { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE) }; + } + + #[cfg(target_family = "unix")] + { + use target_os_lib as libc; + + const NICE_MAX: libc::c_int = if cfg!(target_os = "linux") { 19 } else { 20 }; + + // SAFETY: calling C. + // We are _lowering_ our priority, not increasing, so this function should never fail. + unsafe { libc::nice(NICE_MAX) }; + } +} + +//---------------------------------------------------------------------------------------------------- TESTS +#[cfg(test)] +mod tests {} diff --git a/helper/src/time.rs b/helper/src/time.rs new file mode 100644 index 0000000..390fe6c --- /dev/null +++ b/helper/src/time.rs @@ -0,0 +1,161 @@ +//! System related +//! +//! Requires `std`. + +//---------------------------------------------------------------------------------------------------- Use +use std::time::{SystemTime, UNIX_EPOCH}; + +//---------------------------------------------------------------------------------------------------- Public API +#[inline] +/// Returns the current system time as a UNIX timestamp. +/// +/// ```rust +/// # use helper::time::*; +/// assert!(current_unix_timestamp() > 0); +/// ``` +/// +/// # Panics +/// This function panics if the call to get the system time fails. +pub fn current_unix_timestamp() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} + +#[inline] +/// Get the clock time of a UNIX timestamp +/// +/// The input must be a UNIX timestamp. +/// +/// The returned `u64` will represent how many seconds has +/// passed on the day corresponding to that timestamp. +/// +/// The output is guaranteed to be in the range of `0..=86399`. +/// +/// ```rust +/// # use helper::time::*; +/// // October 20th 2023 - 10:18:30 PM +/// const TIME: u64 = 1697840310; +/// +/// let seconds = unix_clock(TIME); +/// assert_eq!(seconds, 80310); +/// +/// let (h, m, s) = secs_to_clock(seconds); +/// // 10:18:30 PM. +/// assert_eq!((h, m, s), (22, 18, 30)) +/// ``` +pub const fn unix_clock(seconds_after_unix_epoch: u64) -> u32 { + (seconds_after_unix_epoch % 86400) as _ +} + +#[inline] +/// Convert seconds to `hours`, `minutes` and `seconds`. +/// +/// - The seconds returned is guaranteed to be `0..=59` +/// - The minutes returned is guaranteed to be `0..=59` +/// - The hours returned can be over `23`, as this is not a clock function, +/// see [`secs_to_clock`] for clock-like behavior that wraps around on `24` +/// +/// ```rust +/// # use helper::time::*; +/// // 59 seconds. +/// assert_eq!(secs_to_hms(59), (0, 0, 59)); +/// +/// // 1 minute. +/// assert_eq!(secs_to_hms(60), (0, 1, 0)); +/// +/// // 59 minutes, 59 seconds. +/// assert_eq!(secs_to_hms(3599), (0, 59, 59)); +/// +/// // 1 hour. +/// assert_eq!(secs_to_hms(3600), (1, 0, 0)); +/// +/// // 23 hours, 59 minutes, 59 seconds. +/// assert_eq!(secs_to_hms(86399), (23, 59, 59)); +/// +/// // 24 hours. +/// assert_eq!(secs_to_hms(86400), (24, 0, 0)); +/// ``` +pub const fn secs_to_hms(seconds: u64) -> (u64, u8, u8) { + let hours = seconds / 3600; + let minutes = (seconds % 3600) / 60; + let seconds = (seconds % 3600) % 60; + + debug_assert!(minutes < 60); + debug_assert!(seconds < 60); + + (hours, minutes as u8, seconds as u8) +} + +#[inline] +/// Convert seconds to clock time, `hours`, `minutes` and `seconds`. +/// +/// This is the same as [`secs_to_hms`] except it will wrap around, +/// e.g, `24:00:00` would turn into `00:00:00`. +/// +/// - The seconds returned is guaranteed to be `0..=59` +/// - The minutes returned is guaranteed to be `0..=59` +/// - The hours returned is guaranteed to be `0..=23` +/// +/// ```rust +/// # use helper::time::*; +/// // 59 seconds. +/// assert_eq!(secs_to_clock(59), (0, 0, 59)); +/// +/// // 1 minute. +/// assert_eq!(secs_to_clock(60), (0, 1, 0)); +/// +/// // 59 minutes, 59 seconds. +/// assert_eq!(secs_to_clock(3599), (0, 59, 59)); +/// +/// // 1 hour. +/// assert_eq!(secs_to_clock(3600), (1, 0, 0)); +/// +/// // 23 hours, 59 minutes, 59 seconds. +/// assert_eq!(secs_to_clock(86399), (23, 59, 59)); +/// +/// // 24 hours (wraps back) +/// assert_eq!(secs_to_clock(86400), (0, 0, 0)); +/// +/// // 24 hours, 59 minutes, 59 seconds (wraps back) +/// assert_eq!(secs_to_clock(89999), (0, 59, 59)); +/// ``` +pub const fn secs_to_clock(seconds: u32) -> (u8, u8, u8) { + let seconds = seconds % 86400; + let (h, m, s) = secs_to_hms(seconds as u64); + + debug_assert!(h < 24); + debug_assert!(m < 60); + debug_assert!(s < 60); + + (h as u8, m, s) +} + +#[inline] +/// Get the current system time in the system's timezone +/// +/// The returned value is the total amount of seconds passed in the current day. +/// +/// This is guaranteed to return a value between `0..=86399` +/// +/// This will return `0` if the underlying system call fails. +pub fn time() -> u32 { + use chrono::Timelike; + let now = chrono::offset::Local::now().time(); + (now.hour() * 3600) + (now.minute() * 60) + now.second() +} + +#[inline] +/// Get the current system time in the UTC timezone +/// +/// The returned value is the total amount of seconds passed in the current day. +/// +/// This is guaranteed to return a value between `0..=86399` +pub fn time_utc() -> u32 { + unix_clock(chrono::offset::Local::now().timestamp() as u64) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test {}