Skip to main content

teaql_runtime/
id.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::{Mutex, OnceLock};
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use crate::RuntimeError;
6
7pub trait InternalIdGenerator: Send + Sync {
8    fn generate_id(&self, entity: &str) -> Result<u64, RuntimeError>;
9
10    fn ensure_floor(&self, entity: &str, floor: u64) -> Result<(), RuntimeError> {
11        Err(RuntimeError::IdGeneration(format!(
12            "ID generator cannot reserve fixed bootstrap ID floor {floor} for {entity}"
13        )))
14    }
15}
16
17/// Normalize generated Rust type names and model entity names to the same
18/// stable key used by generated `ENTITY_NAME` constants.
19pub fn canonical_id_space_entity(entity: &str) -> String {
20    let mut result = String::with_capacity(entity.len() + 4);
21    for (index, character) in entity.chars().enumerate() {
22        if character.is_ascii_uppercase() {
23            if index > 0 {
24                result.push('_');
25            }
26            result.push(character.to_ascii_lowercase());
27        } else {
28            result.push(character);
29        }
30    }
31    result
32}
33
34// ---------------------------------------------------------------------------
35// AtomicCounterIdGenerator — process-level counter, suitable for in-memory use
36// ---------------------------------------------------------------------------
37
38/// A simple atomic counter that produces sequential IDs starting from a
39/// configurable base value (default 1000).
40///
41/// Suitable for in-memory / test / single-process scenarios where readable,
42/// compact IDs are preferred over globally unique snowflake IDs.
43#[derive(Debug)]
44pub struct AtomicCounterIdGenerator {
45    counter: AtomicU64,
46}
47
48impl Default for AtomicCounterIdGenerator {
49    fn default() -> Self {
50        Self::new(1000)
51    }
52}
53
54impl AtomicCounterIdGenerator {
55    /// Create a new counter starting from `start`.
56    /// The first call to `generate_id` will return `start + 1`.
57    pub fn new(start: u64) -> Self {
58        Self {
59            counter: AtomicU64::new(start),
60        }
61    }
62}
63
64impl InternalIdGenerator for AtomicCounterIdGenerator {
65    fn generate_id(&self, _entity: &str) -> Result<u64, RuntimeError> {
66        Ok(self.counter.fetch_add(1, Ordering::Relaxed) + 1)
67    }
68
69    fn ensure_floor(&self, _entity: &str, floor: u64) -> Result<(), RuntimeError> {
70        self.counter.fetch_max(floor, Ordering::Relaxed);
71        Ok(())
72    }
73}
74
75// ---------------------------------------------------------------------------
76// SnowflakeIdGenerator — distributed-friendly, timestamp-based
77// ---------------------------------------------------------------------------
78
79#[derive(Debug)]
80pub struct SnowflakeIdGenerator {
81    epoch_millis: u64,
82    worker_id: u64,
83    datacenter_id: u64,
84    state: Mutex<SnowflakeState>,
85}
86
87#[derive(Debug, Default)]
88struct SnowflakeState {
89    last_timestamp: u64,
90    sequence: u64,
91}
92
93impl Default for SnowflakeIdGenerator {
94    fn default() -> Self {
95        Self::new(0, 0)
96    }
97}
98
99impl SnowflakeIdGenerator {
100    const DEFAULT_EPOCH_MILLIS: u64 = 1_288_834_974_657;
101    const WORKER_ID_BITS: u64 = 5;
102    const DATACENTER_ID_BITS: u64 = 5;
103    const SEQUENCE_BITS: u64 = 12;
104    const MAX_WORKER_ID: u64 = (1 << Self::WORKER_ID_BITS) - 1;
105    const MAX_DATACENTER_ID: u64 = (1 << Self::DATACENTER_ID_BITS) - 1;
106    const SEQUENCE_MASK: u64 = (1 << Self::SEQUENCE_BITS) - 1;
107    const WORKER_ID_SHIFT: u64 = Self::SEQUENCE_BITS;
108    const DATACENTER_ID_SHIFT: u64 = Self::SEQUENCE_BITS + Self::WORKER_ID_BITS;
109    const TIMESTAMP_SHIFT: u64 =
110        Self::SEQUENCE_BITS + Self::WORKER_ID_BITS + Self::DATACENTER_ID_BITS;
111
112    pub fn new(worker_id: u64, datacenter_id: u64) -> Self {
113        assert!(worker_id <= Self::MAX_WORKER_ID, "worker id out of range");
114        assert!(
115            datacenter_id <= Self::MAX_DATACENTER_ID,
116            "datacenter id out of range"
117        );
118
119        Self {
120            epoch_millis: Self::DEFAULT_EPOCH_MILLIS,
121            worker_id,
122            datacenter_id,
123            state: Mutex::new(SnowflakeState::default()),
124        }
125    }
126
127    fn current_millis() -> Result<u64, RuntimeError> {
128        let now = SystemTime::now()
129            .duration_since(UNIX_EPOCH)
130            .map_err(|err| RuntimeError::IdGeneration(err.to_string()))?;
131        Ok(now.as_millis() as u64)
132    }
133
134    fn wait_until_next_millis(last_timestamp: u64) -> Result<u64, RuntimeError> {
135        loop {
136            let timestamp = Self::current_millis()?;
137            if timestamp > last_timestamp {
138                return Ok(timestamp);
139            }
140            std::thread::sleep(Duration::from_millis(1));
141        }
142    }
143}
144
145impl InternalIdGenerator for SnowflakeIdGenerator {
146    fn generate_id(&self, _entity: &str) -> Result<u64, RuntimeError> {
147        let mut state = self
148            .state
149            .lock()
150            .map_err(|_| RuntimeError::IdGeneration("snowflake state poisoned".to_owned()))?;
151        let mut timestamp = Self::current_millis()?;
152
153        if timestamp < state.last_timestamp {
154            timestamp = Self::wait_until_next_millis(state.last_timestamp)?;
155        }
156
157        match timestamp == state.last_timestamp {
158            true => {
159                state.sequence = (state.sequence + 1) & Self::SEQUENCE_MASK;
160                if state.sequence == 0 {
161                    timestamp = Self::wait_until_next_millis(state.last_timestamp)?;
162                }
163            }
164            false => state.sequence = 0,
165        }
166
167        state.last_timestamp = timestamp;
168
169        let relative_timestamp = timestamp.checked_sub(self.epoch_millis).ok_or_else(|| {
170            RuntimeError::IdGeneration("system clock is before snowflake epoch".to_owned())
171        })?;
172
173        Ok((relative_timestamp << Self::TIMESTAMP_SHIFT)
174            | (self.datacenter_id << Self::DATACENTER_ID_SHIFT)
175            | (self.worker_id << Self::WORKER_ID_SHIFT)
176            | state.sequence)
177    }
178
179    fn ensure_floor(&self, _entity: &str, _floor: u64) -> Result<(), RuntimeError> {
180        Ok(())
181    }
182}
183
184pub(crate) fn local_id_generator() -> &'static AtomicCounterIdGenerator {
185    static LOCAL_ID_GENERATOR: OnceLock<AtomicCounterIdGenerator> = OnceLock::new();
186    LOCAL_ID_GENERATOR.get_or_init(AtomicCounterIdGenerator::default)
187}