///| A domain-neutral point on a monotonically increasing timeline. The
///|
/// caller chooses the unit (usually UTC minutes or integer ticks).
pub type Tick = Int
///|
pub enum IntervalError {
EmptyOrReversedRange(start~ : Tick, end~ : Tick)
InvalidWindow(start~ : Tick, end~ : Tick)
} derive(Eq, Debug)
///|
/// A half-open interval `[start, end)`. Half-open ranges make adjacent
/// reservations compatible: `[10, 20)` and `[20, 30)` do not overlap.
pub struct Interval {
start : Tick
end : Tick
} derive(Eq, Compare, Debug)
///|
pub fn Interval::new(
start : Tick,
end : Tick,
) -> Result[Interval, IntervalError] {
if start >= end {
Err(EmptyOrReversedRange(start~, end~))
} else {
Ok({ start, end })
}
}
///|
pub fn Interval::start(self : Interval) -> Tick {
self.start
}
///|
pub fn Interval::end(self : Interval) -> Tick {
self.end
}
///|
pub fn Interval::duration(self : Interval) -> Int {
self.end - self.start
}
///|
pub fn Interval::contains(self : Interval, tick : Tick) -> Bool {
self.start <= tick && tick < self.end
}
///|
pub fn Interval::contains_interval(self : Interval, other : Interval) -> Bool {
self.start <= other.start && other.end <= self.end
}
///|
pub fn Interval::overlaps(self : Interval, other : Interval) -> Bool {
self.start < other.end && other.start < self.end
}
///|
pub fn Interval::touches(self : Interval, other : Interval) -> Bool {
self.end == other.start || other.end == self.start
}
///|
pub fn Interval::intersect(self : Interval, other : Interval) -> Interval? {
let start = if self.start > other.start { self.start } else { other.start }
let end = if self.end < other.end { self.end } else { other.end }
if start < end {
Some({ start, end })
} else {
None
}
}
///|
/// Merge overlapping or immediately adjacent ranges. Disjoint ranges return
/// `None` so callers cannot accidentally hide an unavailable gap.
pub fn Interval::merge(self : Interval, other : Interval) -> Interval? {
if !self.overlaps(other) && !self.touches(other) {
return None
}
let start = if self.start < other.start { self.start } else { other.start }
let end = if self.end > other.end { self.end } else { other.end }
Some({ start, end })
}
///|
/// Shift an interval on the abstract timeline. Useful for before/after
/// booking buffers; callers are responsible for choosing a sensible origin.
pub fn Interval::shift(self : Interval, delta : Int) -> Interval {
{ start: self.start + delta, end: self.end + delta }
}
///|
pub fn Interval::with_duration(
start : Tick,
duration : Int,
) -> Result[Interval, IntervalError] {
if duration <= 0 {
Err(InvalidWindow(start~, end=start + duration))
} else {
Interval::new(start, start + duration)
}
}
///|
pub fn Interval::compare_start(a : Interval, b : Interval) -> Int {
if a.start < b.start {
-1
} else if a.start > b.start {
1
} else if a.end < b.end {
-1
} else if a.end > b.end {
1
} else {
0
}
}
///|
pub fn Interval::render(self : Interval) -> String {
"[" + self.start.to_string() + ", " + self.end.to_string() + ")"
}