mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-11-16 15:58:14 +00:00
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>
This commit is contained in:
parent
d10b9d3f8b
commit
50894bef89
10 changed files with 1213 additions and 0 deletions
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -841,6 +841,18 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"futures",
|
||||||
|
"libc",
|
||||||
|
"rayon",
|
||||||
|
"tokio",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -1051,6 +1063,16 @@ version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
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]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.20"
|
version = "0.4.20"
|
||||||
|
@ -1327,6 +1349,29 @@ version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
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]]
|
[[package]]
|
||||||
name = "password-hash"
|
name = "password-hash"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -1719,6 +1764,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "sct"
|
name = "sct"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1828,6 +1879,15 @@ dependencies = [
|
||||||
"lazy_static",
|
"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]]
|
[[package]]
|
||||||
name = "simple-request"
|
name = "simple-request"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1995,10 +2055,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
@ -2318,6 +2381,16 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
|
@ -8,6 +8,7 @@ members = [
|
||||||
"cryptonight",
|
"cryptonight",
|
||||||
# "cuprate",
|
# "cuprate",
|
||||||
# "database",
|
# "database",
|
||||||
|
"helper",
|
||||||
"net/levin",
|
"net/levin",
|
||||||
"net/monero-wire",
|
"net/monero-wire",
|
||||||
"p2p/monero-p2p",
|
"p2p/monero-p2p",
|
||||||
|
|
30
helper/Cargo.toml
Normal file
30
helper/Cargo.toml
Normal 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
18
helper/README.md
Normal 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
113
helper/src/asynch.rs
Normal 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
490
helper/src/atomic.rs
Normal 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
50
helper/src/lib.rs
Normal 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
177
helper/src/num.rs
Normal 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
100
helper/src/thread.rs
Normal 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 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 {}
|
161
helper/src/time.rs
Normal file
161
helper/src/time.rs
Normal 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 {}
|
Loading…
Reference in a new issue