mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-11-16 07:47:49 +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"
|
||||
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"
|
||||
|
|
|
@ -8,6 +8,7 @@ members = [
|
|||
"cryptonight",
|
||||
# "cuprate",
|
||||
# "database",
|
||||
"helper",
|
||||
"net/levin",
|
||||
"net/monero-wire",
|
||||
"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