use std::hash::{BuildHasher as _, RandomState};
use std::sync::OnceLock;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Run(u64);
impl Run {
#[must_use]
pub fn mint() -> Self {
static MINE: OnceLock<u64> = OnceLock::new();
Self(*MINE.get_or_init(|| RandomState::new().hash_one(std::process::id())))
}
#[must_use]
pub const fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Compat(u64);
impl Compat {
#[must_use]
pub const fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
impl From<u64> for Compat {
fn from(raw: u64) -> Self {
Self(raw)
}
}
impl From<u32> for Compat {
fn from(raw: u32) -> Self {
Self(u64::from(raw))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Identity {
pub run: Run,
pub compat: Compat,
}
impl Identity {
#[must_use]
pub const fn new(run: Run, compat: Compat) -> Self {
Self { run, compat }
}
#[must_use]
pub fn mine(compat: Compat) -> Self {
Self::new(Run::mint(), compat)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_process_keeps_one_run_token() {
assert_eq!(Run::mint(), Run::mint());
}
#[test]
fn a_token_survives_a_round_trip_through_its_raw_form() {
let minted = Run::mint();
assert_eq!(Run::from_raw(minted.get()), minted);
}
#[test]
fn identities_differ_when_either_half_differs() {
let run = Run::from_raw(1);
let other = Run::from_raw(2);
let compat = Compat::from(7_u32);
assert_ne!(Identity::new(run, compat), Identity::new(other, compat));
assert_ne!(
Identity::new(run, compat),
Identity::new(run, Compat::from(8_u32))
);
}
}