* 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>
This commit is contained in:
hinto-janai 2024-01-20 19:04:09 -05:00 committed by GitHub
parent d10b9d3f8b
commit 50894bef89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1213 additions and 0 deletions

73
Cargo.lock generated
View file

@ -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"

View file

@ -8,6 +8,7 @@ members = [
"cryptonight",
# "cuprate",
# "database",
"helper",
"net/levin",
"net/monero-wire",
"p2p/monero-p2p",

30
helper/Cargo.toml Normal file
View file

@ -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 }

18
helper/README.md Normal file
View file

@ -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`.

113
helper/src/asynch.rs Normal file
View file

@ -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<T>(oneshot::Receiver<T>);
impl<T> From<oneshot::Receiver<T>> for InfallibleOneshotReceiver<T> {
fn from(value: oneshot::Receiver<T>) -> Self {
InfallibleOneshotReceiver(value)
}
}
impl<T> Future for InfallibleOneshotReceiver<T> {
type Output = T;
#[inline]
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
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, R>(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::<String>();
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");
}
}

490
helper/src/atomic.rs Normal file
View file

@ -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<float>` 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<F>(&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<F>(
&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<f32>` into `f(u32) -> Option<u32>`
// 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);
}
}

50
helper/src/lib.rs Normal file
View file

@ -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
//----------------------------------------------------------------------------------------------------

177
helper/src/num.rs Normal file
View file

@ -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<Self> + 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<T>(a: T, b: T) -> T
where
T: Add<Output = T> + Sub<Output = T> + Div<Output = T> + Mul<Output = T> + Copy + From<u8>,
{
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<T>(array: impl AsRef<[T]>) -> T
where
T: Add<Output = T> + Sub<Output = T> + Div<Output = T> + Mul<Output = T> + Copy + From<u8>,
{
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<F: 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<F: Float>(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 {}

100
helper/src/thread.rs Normal file
View file

@ -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 threads 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 {}

161
helper/src/time.rs Normal file
View file

@ -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 {}