use std::sync::atomic::{AtomicU64, Ordering};
const BYTE_OFFSET_BITS: u32 = 27;
static LAST_CURSOR: AtomicU64 = AtomicU64::new(0);
#[must_use]
pub fn generate(next_offset: &crate::protocol::offset::Offset) -> String {
if let Some((read_seq, byte_offset)) = next_offset.parse_components() {
let offset_value = (read_seq << BYTE_OFFSET_BITS) | byte_offset;
loop {
let last = LAST_CURSOR.load(Ordering::Relaxed);
let next = offset_value.max(last + 1);
if LAST_CURSOR
.compare_exchange_weak(last, next, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
return next.to_string();
}
}
}
"0".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::offset::Offset;
#[test]
fn test_cursor_is_digits_only() {
let offset = Offset::new(3, 26);
let cursor = generate(&offset);
assert!(cursor.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_cursor_increases_for_same_offset() {
let offset = Offset::new(7, 42);
let c1: u64 = generate(&offset).parse().unwrap();
let c2: u64 = generate(&offset).parse().unwrap();
assert!(c2 > c1);
}
#[test]
fn test_cursor_changes_with_offset() {
let a = Offset::new(1, 2);
let b = Offset::new(1, 3);
assert_ne!(generate(&a), generate(&b));
}
#[test]
fn test_cursor_is_monotonic() {
let a = Offset::new(1, 5);
let b = Offset::new(1, 10);
let c = Offset::new(2, 0);
let ca: u64 = generate(&a).parse().unwrap();
let cb: u64 = generate(&b).parse().unwrap();
let cc: u64 = generate(&c).parse().unwrap();
assert!(cb > ca);
assert!(cc > cb);
}
#[test]
fn test_cursor_fits_in_js_max_safe_integer() {
let offset = Offset::new(67_000_000, 10_485_760);
let cursor: u64 = generate(&offset).parse().unwrap();
assert!(cursor < (1_u64 << 53));
}
}