#![allow(clippy::needless_doctest_main)]
use rand::Rng;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{fmt, thread};
pub const EPOCH: u64 = 1420070400000;
const MAX_5_BITS: u64 = 31;
const MAX_12_BITS: u64 = 4095;
const CLOCK_DRIFT_TOLERANCE_MS: u64 = 10;
#[derive(Debug)]
pub struct Spaceflake {
base_epoch: u64,
pub id: u64,
}
impl fmt::Display for Spaceflake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.id)
}
}
impl Spaceflake {
fn new(id: u64, base_epoch: u64) -> Self {
Spaceflake { base_epoch, id }
}
pub fn time(&self) -> u64 {
(self.id >> 22) + self.base_epoch
}
pub fn node_id(&self) -> u64 {
(self.id & 0x3E0000) >> 17
}
pub fn worker_id(&self) -> u64 {
(self.id & 0x1F000) >> 12
}
pub fn sequence(&self) -> u64 {
self.id & 0xFFF
}
pub fn string_id(&self) -> String {
self.to_string()
}
#[deprecated(since = "1.1.0", note = "Use `to_binary`")]
pub fn binary_id(&self) -> String {
self.to_binary()
}
pub fn to_binary(&self) -> String {
format!("{:064b}", self.id)
}
pub fn decompose(&self) -> HashMap<String, u64> {
HashMap::<String, u64>::from([
("id".to_string(), self.id),
("node_id".to_string(), self.node_id()),
("sequence".to_string(), self.sequence()),
("time".to_string(), self.time()),
("worker_id".to_string(), self.worker_id()),
])
}
pub fn decompose_binary(&self) -> HashMap<String, String> {
HashMap::from([
("id".to_string(), format!("{:064b}", self.id)),
("node_id".to_string(), format!("{:05b}", self.node_id())),
("sequence".to_string(), format!("{:012b}", self.sequence())),
("time".to_string(), format!("{:041b}", self.time())),
("worker_id".to_string(), format!("{:05b}", self.worker_id())),
])
}
}
#[derive(Debug)]
pub struct Node {
pub id: u64,
workers: Vec<Worker>,
}
impl Node {
pub fn new(id: u64) -> Self {
if id > MAX_5_BITS {
panic!("Node ID must be less than {}", MAX_5_BITS);
}
Node {
id,
workers: Vec::new(),
}
}
pub fn new_worker(&mut self) -> Worker {
let worker = Worker::new((self.workers.len() + 1) as u64, self.id);
self.workers.push(worker.clone());
worker
}
pub fn remove_worker(&mut self, id: u64) {
if let Some(index) = self.workers.iter().position(|w| w.id == id) {
self.workers.remove(index);
}
}
pub fn get_workers(&self) -> Vec<Worker> {
self.workers.clone()
}
pub fn bulk_generate(&self, amount: usize) -> Result<Vec<Spaceflake>, String> {
let mut node = Node::new(self.id);
let mut worker = node.new_worker();
let mut spaceflakes = Vec::with_capacity(amount);
for i in 1..=amount {
if i % ((MAX_12_BITS as usize * MAX_5_BITS as usize) + 1) == 0 {
thread::sleep(Duration::from_millis(1));
node.workers.clear();
worker = node.new_worker();
} else if i % MAX_12_BITS as usize == 0
&& i % (MAX_12_BITS as usize * MAX_5_BITS as usize) != 0
{
worker = node.new_worker();
}
spaceflakes.push(generate_on_node_and_worker(node.id, worker.clone(), None)?);
}
Ok(spaceflakes)
}
}
#[derive(Debug, Clone)]
pub struct Worker {
pub id: u64,
pub base_epoch: u64,
pub node_id: u64,
pub sequence: u64,
increment: Arc<Mutex<u64>>,
last_timestamp: u64,
}
impl Worker {
fn new(id: u64, node_id: u64) -> Self {
if id > MAX_12_BITS {
panic!("Worker ID must be less than {}", MAX_12_BITS);
}
Worker {
id,
base_epoch: EPOCH,
node_id,
sequence: 0,
increment: Arc::new(Mutex::new(0)),
last_timestamp: 0,
}
}
pub fn generate(&self) -> Result<Spaceflake, String> {
generate_on_node_and_worker(self.node_id, self.clone(), None)
}
pub fn generate_at(&self, at: u64) -> Result<Spaceflake, String> {
generate_on_node_and_worker(self.node_id, self.clone(), Option::from(at))
}
pub fn bulk_generate(&self, amount: usize) -> Result<Vec<Spaceflake>, String> {
let mut spaceflakes = Vec::with_capacity(amount);
for i in 1..=amount {
if i % (MAX_12_BITS as usize + 1) == 0 {
thread::sleep(Duration::from_millis(1));
}
spaceflakes.push(generate_on_node_and_worker(
self.node_id,
self.clone(),
None,
)?);
}
Ok(spaceflakes)
}
}
#[derive(Debug)]
pub struct BulkGeneratorSettings {
amount: usize,
pub base_epoch: u64,
}
impl BulkGeneratorSettings {
pub fn new(amount: usize) -> Self {
BulkGeneratorSettings {
amount,
base_epoch: EPOCH,
}
}
}
pub fn bulk_generate(settings: BulkGeneratorSettings) -> Result<Vec<Spaceflake>, String> {
let mut node = Node::new(1);
let mut worker = node.new_worker();
worker.base_epoch = settings.base_epoch;
let mut spaceflakes = Vec::<Spaceflake>::new();
for i in 1..=settings.amount {
if i % ((MAX_12_BITS * MAX_5_BITS * MAX_5_BITS) as usize) == 0 {
thread::sleep(Duration::from_millis(1));
let mut new_node = Node::new(1);
let mut new_worker = new_node.new_worker();
new_worker.base_epoch = settings.base_epoch;
node = new_node;
worker = new_worker;
} else if node.workers.len() % MAX_5_BITS as usize == 0
&& i % ((MAX_5_BITS * MAX_12_BITS) as usize) == 0
{
let mut new_node = Node::new(1);
let mut new_worker = new_node.new_worker();
new_worker.base_epoch = settings.base_epoch;
node = new_node;
worker = new_worker;
} else if i % MAX_12_BITS as usize == 0 {
let mut new_worker = node.new_worker();
new_worker.base_epoch = settings.base_epoch;
worker = new_worker;
}
match generate_on_node_and_worker(node.id, worker.clone(), None) {
Ok(spaceflake) => {
spaceflakes.push(spaceflake);
}
Err(error) => return Err(error),
};
}
Ok(spaceflakes)
}
#[derive(Debug, Clone, Copy)]
pub struct GeneratorSettings {
pub base_epoch: u64,
pub node_id: u64,
pub worker_id: u64,
pub sequence: u64,
}
impl GeneratorSettings {
pub fn new(node_id: u64, worker_id: u64) -> Self {
if node_id > MAX_5_BITS {
panic!("Node ID must be less than {}", MAX_5_BITS);
}
if worker_id > MAX_12_BITS {
panic!("Worker ID must be less than {}", MAX_12_BITS);
}
GeneratorSettings {
base_epoch: EPOCH,
node_id,
worker_id,
sequence: 0,
}
}
}
impl Default for GeneratorSettings {
fn default() -> Self {
Self::new(0, 0)
}
}
pub fn generate(settings: GeneratorSettings) -> Result<Spaceflake, String> {
let mut worker = Worker::new(settings.worker_id, settings.node_id);
worker.sequence = if settings.sequence == 0 {
rand::rng().random_range(1..=MAX_12_BITS)
} else {
settings.sequence
};
generate_on_node_and_worker(settings.node_id, worker, None)
}
pub fn generate_at(settings: GeneratorSettings, at: u64) -> Result<Spaceflake, String> {
let mut worker = Worker::new(settings.worker_id, settings.node_id);
worker.sequence = if settings.sequence == 0 {
rand::rng().random_range(1..=MAX_12_BITS)
} else {
settings.sequence
};
generate_on_node_and_worker(settings.node_id, worker, Option::from(at))
}
pub fn parse_time(spaceflake_id: u64, base_epoch: u64) -> u64 {
(spaceflake_id >> 22) + base_epoch
}
pub fn parse_node_id(spaceflake_id: u64) -> u64 {
(spaceflake_id & 0x3E0000) >> 17
}
pub fn parse_worker_id(spaceflake_id: u64) -> u64 {
(spaceflake_id & 0x1F000) >> 12
}
pub fn parse_sequence(spaceflake_id: u64) -> u64 {
spaceflake_id & 0xFFF
}
pub fn decompose(spaceflake_id: u64, base_epoch: u64) -> HashMap<String, u64> {
Spaceflake::new(spaceflake_id, base_epoch).decompose()
}
pub fn decompose_binary(spaceflake_id: u64, base_epoch: u64) -> HashMap<String, String> {
Spaceflake::new(spaceflake_id, base_epoch).decompose_binary()
}
fn generate_on_node_and_worker(
node_id: u64,
mut worker: Worker,
at: Option<u64>,
) -> Result<Spaceflake, String> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards?")
.as_millis() as u64;
let generate_at = at.unwrap_or(now);
if node_id > MAX_5_BITS {
return Err(format!("Node ID must be less than {}", MAX_5_BITS));
}
if worker.id > MAX_12_BITS {
return Err(format!("Worker ID must be less than {}", MAX_12_BITS));
}
if worker.base_epoch > generate_at {
return Err(String::from(
"Base epoch must be less than the time you want to generate the Spaceflake at",
));
}
if worker.base_epoch > now {
return Err(String::from(
"Base epoch must be less than or equals to current epoch time",
));
}
if generate_at > now {
return Err(String::from(
"The current time must be greater than the time you want to generate the Spaceflake at",
));
}
let mut milliseconds = generate_at - worker.base_epoch;
if milliseconds < worker.last_timestamp {
let delta = worker.last_timestamp - milliseconds;
if delta >= CLOCK_DRIFT_TOLERANCE_MS {
return Err(format!("clock moved backwards by {}ms", delta));
}
thread::sleep(Duration::from_millis(delta + 1));
let now_after_sleep = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards?")
.as_millis() as u64;
milliseconds = now_after_sleep - worker.base_epoch;
}
worker.last_timestamp = milliseconds;
let mut increment = worker.increment.lock().unwrap();
if *increment >= MAX_12_BITS {
thread::sleep(Duration::from_millis(1));
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards?")
.as_millis() as u64
- worker.base_epoch;
milliseconds = now_ms;
*increment = 0;
}
*increment += 1;
let sequence = if worker.sequence == 0 {
*increment
} else {
worker.sequence
};
let id = (milliseconds << 22) | (node_id << 17) | (worker.id << 12) | (sequence & 0xFFF);
drop(increment);
Ok(Spaceflake::new(id, worker.base_epoch))
}