const LOGGED_AHEAD: i128 = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SequenceValuePosition {
pub current: i64,
pub called: bool,
pub log_count: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SequenceValueReservation {
pub first_value: i64,
pub last_value: i64,
pub count: i64,
pub log_count: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SequenceValueAllocation {
pub reservation: SequenceValueReservation,
pub logged_value: Option<i64>,
}
impl SequenceValueAllocation {
#[must_use]
pub fn record_value(
&self,
position: SequenceValuePosition,
covering: Option<i64>,
increment: i64,
min_value: i64,
max_value: i64,
) -> Option<i64> {
if self.logged_value.is_some() {
return self.logged_value;
}
let reservation = self.reservation;
let ascending = increment > 0;
let wrapped = position.called
&& if ascending {
reservation.first_value <= position.current
} else {
reservation.first_value >= position.current
};
let covered = covering.is_some_and(|covering| {
!wrapped
&& if ascending {
reservation.last_value <= covering
} else {
reservation.last_value >= covering
}
});
if covered {
return None;
}
let ahead = i128::from(reservation.last_value)
+ i128::from(increment) * i128::from(reservation.log_count);
let bounded = ahead.clamp(i128::from(min_value), i128::from(max_value));
Some(i64::try_from(bounded).expect("a value within the sequence bounds"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceReservationResult {
Missing,
DefinitionChanged,
Exhausted,
Reserved(SequenceValueReservation),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceSetValueResult {
Missing,
DefinitionChanged,
Set(i64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceLogResult {
Missing,
DefinitionChanged,
Changed(SequenceValuePosition),
Logged,
}
#[must_use]
pub fn sequence_value_reservation(
position: SequenceValuePosition,
increment: i64,
min_value: i64,
max_value: i64,
cycle: bool,
cache_size: i64,
) -> Option<SequenceValueReservation> {
sequence_value_allocation(position, increment, min_value, max_value, cycle, cache_size)
.map(|allocation| allocation.reservation)
}
#[must_use]
pub fn sequence_value_allocation(
position: SequenceValuePosition,
increment: i64,
min_value: i64,
max_value: i64,
cycle: bool,
cache_size: i64,
) -> Option<SequenceValueAllocation> {
let SequenceValuePosition {
current,
called,
log_count,
} = position;
debug_assert_ne!(increment, 0);
debug_assert!(cache_size > 0);
let first_value = if called {
match current
.checked_add(increment)
.filter(|value| (min_value..=max_value).contains(value))
{
Some(value) => value,
None if cycle && increment > 0 => min_value,
None if cycle => max_value,
None => return None,
}
} else {
current
};
let distance = if increment > 0 {
i128::from(max_value) - i128::from(first_value)
} else {
i128::from(first_value) - i128::from(min_value)
};
let step = i128::from(increment).abs();
let available = distance / step + 1;
let count = available.min(i128::from(cache_size));
let last_value = i128::from(first_value) + i128::from(increment) * (count - 1);
let initial_count = i128::from(!called);
let cache_fetch = i128::from(cache_size) - initial_count;
let mut fetch = cache_fetch;
let mut next_log_count = i128::from(log_count);
let logged = i128::from(log_count) < cache_fetch || !called;
if logged {
fetch += LOGGED_AHEAD;
next_log_count = fetch;
}
let fetched = fetch.min(available - initial_count);
next_log_count -= fetched.min(cache_fetch);
next_log_count -= fetch - fetched;
let logged_value = logged.then(|| {
let ahead = i128::from(first_value) + i128::from(increment) * (fetched - 1 + initial_count);
i64::try_from(ahead).expect("logged sequence value stays in bounds")
});
Some(SequenceValueAllocation {
reservation: SequenceValueReservation {
first_value,
last_value: i64::try_from(last_value).expect("reserved sequence value stays in bounds"),
count: i64::try_from(count).expect("reservation count cannot exceed cache size"),
log_count: i64::try_from(next_log_count)
.expect("persisted sequence log count cannot exceed the cache request"),
},
logged_value,
})
}
#[cfg(test)]
mod tests;