use chrono::prelude::*;
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
use crate::{builder::Builder, error::Error};
pub(crate) const BIT_LEN_TIME: u64 = 39;
pub(crate) const BIT_LEN_SEQUENCE: u64 = 8;
pub(crate) const BIT_LEN_MACHINE_ID: u64 = 63 - BIT_LEN_TIME - BIT_LEN_SEQUENCE;
const GENERATE_MASK_SEQUENCE: u16 = (1 << BIT_LEN_SEQUENCE) - 1;
#[derive(Debug)]
pub(crate) struct Internals {
pub(crate) elapsed_time: i64,
pub(crate) sequence: u16,
}
#[derive(Debug)]
pub(crate) struct SharedSonyflake {
pub(crate) start_time: i64,
pub(crate) machine_id: u16,
pub(crate) internals: Mutex<Internals>,
}
#[derive(Debug, Clone)]
pub struct Sonyflake(pub(crate) Arc<SharedSonyflake>);
impl Sonyflake {
pub fn new() -> Result<Self, Error> {
Builder::new().finalize()
}
#[must_use]
pub fn builder<'a>() -> Builder<'a> {
Builder::new()
}
pub(crate) fn new_inner(shared: Arc<SharedSonyflake>) -> Self {
Self(shared)
}
#[allow(clippy::cast_sign_loss)]
pub fn next_id(&self) -> Result<u64, Error> {
let mut internals = self.0.internals.lock().map_err(|_| Error::MutexPoisoned)?;
let current = current_elapsed_time(self.0.start_time)?;
if internals.elapsed_time < current {
internals.elapsed_time = current;
internals.sequence = 0;
} else {
internals.sequence = (internals.sequence + 1) & GENERATE_MASK_SEQUENCE;
if internals.sequence == 0 {
internals.elapsed_time += 1;
let overtime = internals.elapsed_time - current;
thread::sleep(sleep_time(overtime)?);
}
}
if internals.elapsed_time >= 1 << BIT_LEN_TIME {
return Err(Error::OverTimeLimit);
}
Ok(
(internals.elapsed_time as u64) << (BIT_LEN_SEQUENCE + BIT_LEN_MACHINE_ID)
| u64::from(internals.sequence) << BIT_LEN_MACHINE_ID
| u64::from(self.0.machine_id),
)
}
}
const SONYFLAKE_TIME_UNIT: i64 = 10_000_000;
pub(crate) fn to_sonyflake_time(time: DateTime<Utc>) -> Result<i64, Error> {
Ok(time
.timestamp_nanos_opt()
.ok_or(Error::FailedToGetCurrentTime)?
/ SONYFLAKE_TIME_UNIT)
}
fn current_elapsed_time(start_time: i64) -> Result<i64, Error> {
Ok(to_sonyflake_time(Utc::now())? - start_time)
}
#[allow(clippy::cast_sign_loss)]
fn sleep_time(overtime: i64) -> Result<Duration, Error> {
Ok(Duration::from_millis(overtime as u64 * 10)
- Duration::from_nanos(
(Utc::now()
.timestamp_nanos_opt()
.ok_or(Error::FailedToGetCurrentTime)?
% SONYFLAKE_TIME_UNIT) as u64,
))
}
pub struct DecomposedSonyflake {
pub id: u64,
pub msb: u64,
pub time: u64,
pub sequence: u64,
pub machine_id: u64,
}
impl DecomposedSonyflake {
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn nanos_time(&self) -> i64 {
(self.time as i64) * SONYFLAKE_TIME_UNIT
}
}
const DECOMPOSE_MASK_SEQUENCE: u64 = ((1 << BIT_LEN_SEQUENCE) - 1) << BIT_LEN_MACHINE_ID;
const MASK_MACHINE_ID: u64 = (1 << BIT_LEN_MACHINE_ID) - 1;
#[must_use]
pub fn decompose(id: u64) -> DecomposedSonyflake {
DecomposedSonyflake {
id,
msb: id >> 63,
time: id >> (BIT_LEN_SEQUENCE + BIT_LEN_MACHINE_ID),
sequence: (id & DECOMPOSE_MASK_SEQUENCE) >> BIT_LEN_MACHINE_ID,
machine_id: id & MASK_MACHINE_ID,
}
}