use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap};
use std::ops::{Bound, Range, RangeBounds};
use std::path::PathBuf;
use crate::bms::command::channel::NoteKind;
use crate::chart::event::{ChartEvent, PlayheadEvent, YCoordinate};
use crate::chart::{Chart, TimeSpan};
use strict_num_extended::NonNegativeF64;
use strict_num_extended::PositiveF64;
pub mod bms;
pub mod bmson;
pub trait Process {
type Error;
fn process(self) -> Result<Chart, Self::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WavId(pub usize);
impl AsRef<usize> for WavId {
fn as_ref(&self) -> &usize {
&self.0
}
}
impl WavId {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn value(self) -> usize {
self.0
}
}
impl From<usize> for WavId {
fn from(value: usize) -> Self {
Self(value)
}
}
impl From<WavId> for usize {
fn from(id: WavId) -> Self {
id.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BmpId(pub usize);
impl AsRef<usize> for BmpId {
fn as_ref(&self) -> &usize {
&self.0
}
}
impl BmpId {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn value(self) -> usize {
self.0
}
}
impl From<usize> for BmpId {
fn from(value: usize) -> Self {
Self(value)
}
}
impl From<BmpId> for usize {
fn from(id: BmpId) -> Self {
id.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChartEventId(pub usize);
impl AsRef<usize> for ChartEventId {
fn as_ref(&self) -> &usize {
&self.0
}
}
impl ChartEventId {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn value(self) -> usize {
self.0
}
}
impl From<usize> for ChartEventId {
fn from(value: usize) -> Self {
Self(value)
}
}
impl From<ChartEventId> for usize {
fn from(id: ChartEventId) -> Self {
id.0
}
}
#[derive(Debug, Clone, Default)]
pub struct ChartEventIdGenerator {
next: usize,
}
impl ChartEventIdGenerator {
#[must_use]
pub const fn new(start: usize) -> Self {
Self { next: start }
}
#[must_use]
pub const fn next_id(&mut self) -> ChartEventId {
let id = ChartEventId(self.next);
self.next += 1;
id
}
#[must_use]
pub const fn peek_next(&self) -> ChartEventId {
ChartEventId::new(self.next)
}
}
#[derive(Debug, Clone)]
pub struct AllEventsIndex {
events: Vec<PlayheadEvent>,
by_y: BTreeMap<YCoordinate, Range<usize>>,
by_time: BTreeMap<TimeSpan, Vec<usize>>,
visible_ln_by_start: BTreeMap<YCoordinate, usize>,
visible_ln_by_end: BTreeMap<YCoordinate, usize>,
}
impl AllEventsIndex {
#[must_use]
pub fn new(map: BTreeMap<YCoordinate, Vec<PlayheadEvent>>) -> Self {
let mut events: Vec<PlayheadEvent> = Vec::new();
let mut by_y: BTreeMap<YCoordinate, Range<usize>> = BTreeMap::new();
for (y_coord, y_events) in map {
let start = events.len();
events.extend(y_events);
let end = events.len();
by_y.insert(y_coord, start..end);
}
let mut by_time: BTreeMap<TimeSpan, Vec<usize>> = BTreeMap::new();
for (idx, ev) in events.iter().enumerate() {
by_time.entry(ev.activate_time).or_default().push(idx);
}
for indices in by_time.values_mut() {
indices.sort_by(|&a, &b| {
let Some(a_ev) = events.get(a) else {
return Ordering::Equal;
};
let Some(b_ev) = events.get(b) else {
return Ordering::Equal;
};
a_ev.position
.cmp(&b_ev.position)
.then_with(|| a_ev.id.cmp(&b_ev.id))
});
}
let mut visible_ln_by_start: BTreeMap<YCoordinate, usize> = BTreeMap::new();
let mut visible_ln_by_end: BTreeMap<YCoordinate, usize> = BTreeMap::new();
for (idx, ev) in events.iter().enumerate() {
if let ChartEvent::Note {
kind: NoteKind::Long,
length: Some(length),
..
} = ev.event()
{
let start_y = *ev.position();
let end_y = YCoordinate::new(
NonNegativeF64::new(start_y.as_f64() + length.as_f64())
.expect("end_y should be non-negative"),
);
visible_ln_by_start.insert(start_y, idx);
visible_ln_by_end.insert(end_y, idx);
}
}
Self {
events,
by_y,
by_time,
visible_ln_by_start,
visible_ln_by_end,
}
}
#[must_use]
pub const fn as_events(&self) -> &Vec<PlayheadEvent> {
&self.events
}
#[must_use]
pub const fn as_by_y(&self) -> &BTreeMap<YCoordinate, Range<usize>> {
&self.by_y
}
#[must_use]
pub fn events_in_y_range<R>(&self, range: R) -> Vec<PlayheadEvent>
where
R: RangeBounds<YCoordinate> + Clone,
{
let view_start = match range.start_bound() {
Bound::Included(start) | Bound::Excluded(start) => *start,
Bound::Unbounded => YCoordinate::ZERO,
};
let view_end = match range.end_bound() {
Bound::Included(end) | Bound::Excluded(end) => *end,
Bound::Unbounded => {
YCoordinate::new(NonNegativeF64::new(f64::MAX).expect("MAX should be non-negative"))
}
};
let start_inclusive = matches!(range.start_bound(), Bound::Included(_));
let estimated_capacity: usize = self
.by_y
.range(range.clone())
.map(|(_, idx_range)| idx_range.len())
.sum();
let mut visible = Vec::with_capacity(estimated_capacity);
for (_, idx_range) in self.by_y.range(range) {
for idx in idx_range.clone() {
let Some(ev) = self.events.get(idx) else {
continue;
};
if matches!(
ev.event(),
ChartEvent::Note {
kind: NoteKind::Long,
..
}
) {
continue;
}
let start_y = ev.position();
let passes_start = if start_inclusive {
*start_y >= view_start
} else {
*start_y > view_start
};
if passes_start && *start_y <= view_end {
visible.push(ev.clone());
}
}
}
let lower_bound = if start_inclusive {
Bound::Included(view_start)
} else {
Bound::Excluded(view_start)
};
for (_, &idx) in self
.visible_ln_by_start
.range((lower_bound, Bound::Included(view_end)))
{
if let Some(ev) = self.events.get(idx) {
visible.push(ev.clone());
}
}
for (_, &idx) in self
.visible_ln_by_end
.range((lower_bound, Bound::Included(view_end)))
{
let Some(ev) = self.events.get(idx) else {
continue;
};
let ln_start = ev.position();
if *ln_start < view_start {
visible.push(ev.clone());
}
}
for (_, &idx) in self
.visible_ln_by_end
.range((Bound::Excluded(view_end), Bound::Unbounded))
{
let Some(ev) = self.events.get(idx) else {
continue;
};
let ln_start = ev.position();
if *ln_start < view_start {
visible.push(ev.clone());
}
}
visible
}
pub fn events_in_time_range<R>(&self, range: R) -> Vec<PlayheadEvent>
where
R: RangeBounds<TimeSpan>,
{
let mut start_bound = range.start_bound().cloned();
let mut end_bound = range.end_bound().cloned();
let start_value = match &start_bound {
Bound::Unbounded => None,
Bound::Included(v) | Bound::Excluded(v) => Some(v),
};
let end_value = match &end_bound {
Bound::Unbounded => None,
Bound::Included(v) | Bound::Excluded(v) => Some(v),
};
if let (Some(start), Some(end)) = (start_value, end_value)
&& start > end
{
std::mem::swap(&mut start_bound, &mut end_bound);
}
self.by_time
.range((start_bound, end_bound))
.flat_map(|(_, indices)| indices.iter().copied())
.filter_map(|idx| self.events.get(idx).cloned())
.collect()
}
pub fn events_in_time_range_offset_from<R>(
&self,
center: TimeSpan,
range: R,
) -> Vec<PlayheadEvent>
where
R: RangeBounds<TimeSpan>,
{
let start_bound = match range.start_bound() {
Bound::Included(offset) => Bound::Included(center + *offset),
Bound::Excluded(offset) => Bound::Excluded(center + *offset),
Bound::Unbounded => Bound::Unbounded,
};
let end_bound = match range.end_bound() {
Bound::Included(offset) => Bound::Included(center + *offset),
Bound::Excluded(offset) => Bound::Excluded(center + *offset),
Bound::Unbounded => Bound::Unbounded,
};
self.events_in_time_range((start_bound, end_bound))
}
}
#[derive(Debug, Clone)]
pub struct ChartResources {
pub(crate) wav_files: HashMap<WavId, PathBuf>,
pub(crate) bmp_files: HashMap<BmpId, PathBuf>,
}
impl ChartResources {
#[must_use]
pub const fn wav_files(&self) -> &HashMap<WavId, PathBuf> {
&self.wav_files
}
#[must_use]
pub const fn bmp_files(&self) -> &HashMap<BmpId, PathBuf> {
&self.bmp_files
}
#[must_use]
pub(crate) const fn new(
wav_files: HashMap<WavId, PathBuf>,
bmp_files: HashMap<BmpId, PathBuf>,
) -> Self {
Self {
wav_files,
bmp_files,
}
}
}
pub fn calculate_cumulative_times<'a, P, B, S>(
points: P,
init_bpm: PositiveF64,
bpm_changes: B,
stops: S,
) -> BTreeMap<YCoordinate, f64>
where
P: IntoIterator<Item = &'a YCoordinate> + Clone,
B: IntoIterator<Item = &'a (YCoordinate, PositiveF64)>,
S: IntoIterator<Item = &'a (YCoordinate, NonNegativeF64)>,
{
let stops: Vec<(YCoordinate, NonNegativeF64)> = stops.into_iter().copied().collect();
let mut cum_map: BTreeMap<YCoordinate, f64> = BTreeMap::new();
cum_map.insert(YCoordinate::ZERO, 0.0);
let mut bpm_map: BTreeMap<YCoordinate, PositiveF64> = BTreeMap::new();
bpm_map.insert(YCoordinate::ZERO, init_bpm);
bpm_map.extend(bpm_changes.into_iter().copied());
let mut stop_idx = 0usize;
let mut total_secs: f64 = 0.0;
let mut prev = YCoordinate::ZERO;
for &curr in points {
if curr <= prev {
continue;
}
let cur_bpm = bpm_map
.range(..curr)
.next_back()
.map_or(init_bpm, |(_, bpm)| *bpm);
let delta_y = curr - prev;
let delta_secs = delta_y.as_f64() * 240.0 / cur_bpm.as_f64();
total_secs = (total_secs + delta_secs).min(f64::MAX);
while let Some((sy, dur)) = stops.get(stop_idx) {
if sy > &curr {
break;
}
if sy > &prev {
let bpm_at_stop = bpm_map
.range(..=sy)
.next_back()
.map_or(init_bpm, |(_, b)| *b);
let dur_secs = dur.as_f64() * 240.0 / bpm_at_stop.as_f64();
total_secs = (total_secs + dur_secs).min(f64::MAX);
}
stop_idx += 1;
}
cum_map.insert(curr, total_secs);
prev = curr;
}
cum_map
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::AllEventsIndex;
use super::ChartEventId;
use crate::bms::command::channel::{Key, NoteKind, PlayerSide};
use crate::chart::TimeSpan;
use crate::chart::event::{ChartEvent, PlayheadEvent, YCoordinate};
use strict_num_extended::NonNegativeF64;
const TEST_LENGTH_3: NonNegativeF64 = NonNegativeF64::new_const(3.0);
const TEST_LENGTH_5: NonNegativeF64 = NonNegativeF64::new_const(5.0);
const TEST_LENGTH_10: NonNegativeF64 = NonNegativeF64::new_const(10.0);
const TEST_LENGTH_20: NonNegativeF64 = NonNegativeF64::new_const(20.0);
const TEST_Y_5: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(5.0));
const TEST_Y_6: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(6.0));
const TEST_Y_7: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(7.0));
const TEST_Y_10: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(10.0));
const TEST_Y_15: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(15.0));
const TEST_Y_20: YCoordinate = YCoordinate::new(NonNegativeF64::new_const(20.0));
fn mk_event(id: usize, y: f64, time_secs: u64) -> PlayheadEvent {
let y_coord = YCoordinate::new(NonNegativeF64::new(y).expect("y should be non-negative"));
PlayheadEvent::new(
ChartEventId::new(id),
y_coord,
ChartEvent::BarLine,
TimeSpan::SECOND * time_secs as i64,
)
}
#[test]
fn events_in_y_range_uses_btreemap_order_and_preserves_group_order() {
let y0 = YCoordinate::ZERO;
let y1 = YCoordinate::ONE;
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
y0,
vec![
mk_event(2, 0.0, 1),
mk_event(1, 0.0, 1),
mk_event(3, 0.0, 2),
],
);
map.insert(y1, vec![mk_event(4, 1.0, 1)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_y_range((std::ops::Bound::Included(y0), std::ops::Bound::Included(y1)))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert_eq!(got_ids, vec![2, 1, 3, 4]);
}
#[test]
fn events_in_time_range_respects_bounds_and_orders_within_same_time() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
YCoordinate::ZERO,
vec![mk_event(2, 0.0, 1), mk_event(1, 0.0, 1)],
);
map.insert(YCoordinate::ONE, vec![mk_event(3, 1.0, 2)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_time_range(TimeSpan::SECOND..TimeSpan::SECOND * 2)
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert_eq!(got_ids, vec![1, 2]);
}
#[test]
fn events_in_time_range_swaps_reversed_bounds() {
use std::ops::Bound::{Included, Unbounded};
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(YCoordinate::ZERO, vec![mk_event(1, 0.0, 1)]);
map.insert(YCoordinate::ONE, vec![mk_event(2, 1.0, 2)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_time_range((Included(TimeSpan::SECOND * 2), Included(TimeSpan::SECOND)))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert_eq!(got_ids, vec![1, 2]);
let got_ids_unbounded: Vec<usize> = idx
.events_in_time_range((Unbounded, Included(TimeSpan::SECOND)))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert_eq!(got_ids_unbounded, vec![1]);
}
#[test]
fn events_in_time_range_offset_from_returns_empty_when_end_is_negative() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(YCoordinate::ZERO, vec![mk_event(1, 0.0, 0)]);
map.insert(YCoordinate::ONE, vec![mk_event(2, 1.0, 1)]);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_time_range_offset_from(
TimeSpan::MILLISECOND * 100,
..=(TimeSpan::ZERO - TimeSpan::MILLISECOND * 200),
)
.into_iter()
.map(|ev| ev.id.value())
.next()
.is_none()
);
}
#[test]
fn events_in_time_range_offset_from_excludes_zero_when_end_is_excluded() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(YCoordinate::ZERO, vec![mk_event(1, 0.0, 0)]);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_time_range_offset_from(TimeSpan::ZERO, ..TimeSpan::ZERO)
.into_iter()
.map(|ev| ev.id.value())
.next()
.is_none()
);
}
#[test]
fn events_in_time_range_offset_from_clamps_negative_start_to_zero() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(YCoordinate::ZERO, vec![mk_event(1, 0.0, 0)]);
map.insert(YCoordinate::ONE, vec![mk_event(2, 1.0, 1)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_time_range_offset_from(
TimeSpan::MILLISECOND * 100,
(TimeSpan::ZERO - TimeSpan::MILLISECOND * 200)..=TimeSpan::ZERO,
)
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert_eq!(got_ids, vec![1]);
}
#[test]
fn events_in_y_range_includes_long_notes_intersecting_view() {
use strict_num_extended::NonNegativeF64;
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
TEST_Y_5,
vec![PlayheadEvent::new(
ChartEventId::new(1),
TEST_Y_5,
ChartEvent::Note {
side: PlayerSide::Player1,
key: Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(NonNegativeF64::new_const(3.0)), continue_play: None,
},
TimeSpan::ZERO,
)],
);
map.insert(TEST_Y_15, vec![mk_event(2, 15.0, 0)]);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_y_range((
std::ops::Bound::Included(YCoordinate::ZERO),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.any(|x| x == 1)
);
}
#[test]
fn events_in_y_range_includes_ln_starting_before_view() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
TEST_Y_5,
vec![PlayheadEvent::new(
ChartEventId::new(1),
TEST_Y_5,
ChartEvent::Note {
side: PlayerSide::Player1,
key: Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_3), continue_play: None,
},
TimeSpan::ZERO,
)],
);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_y_range((
std::ops::Bound::Included(TEST_Y_7),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.any(|x| x == 1)
);
}
#[test]
fn events_in_y_range_includes_ln_ending_after_view() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
TEST_Y_5,
vec![PlayheadEvent::new(
ChartEventId::new(1),
TEST_Y_5,
ChartEvent::Note {
side: PlayerSide::Player1,
key: crate::bms::prelude::Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_10), continue_play: None,
},
TimeSpan::ZERO,
)],
);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_y_range((
std::ops::Bound::Included(YCoordinate::ZERO),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.any(|x| x == 1)
);
}
#[test]
fn events_in_y_range_includes_ln_fully_covering_view() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
YCoordinate::ZERO,
vec![PlayheadEvent::new(
ChartEventId::new(1),
YCoordinate::ZERO,
ChartEvent::Note {
side: PlayerSide::Player1,
key: crate::bms::prelude::Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_20), continue_play: None,
},
TimeSpan::ZERO,
)],
);
let idx = AllEventsIndex::new(map);
assert!(
idx.events_in_y_range((
std::ops::Bound::Included(TEST_Y_5),
std::ops::Bound::Included(TEST_Y_15),
))
.into_iter()
.map(|ev| ev.id.value())
.any(|x| x == 1)
);
}
#[test]
fn events_in_y_range_excludes_ln_before_view() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
YCoordinate::ZERO,
vec![PlayheadEvent::new(
ChartEventId::new(1),
YCoordinate::ZERO,
ChartEvent::Note {
side: PlayerSide::Player1,
key: crate::bms::prelude::Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_3), continue_play: None,
},
TimeSpan::ZERO,
)],
);
map.insert(TEST_Y_10, vec![mk_event(2, 10.0, 0)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_y_range((
std::ops::Bound::Included(TEST_Y_5),
std::ops::Bound::Included(TEST_Y_15),
))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert!(!got_ids.contains(&1));
assert!(got_ids.contains(&2));
}
#[test]
fn events_in_y_range_excludes_ln_after_view() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(YCoordinate::ZERO, vec![mk_event(1, 0.0, 0)]);
map.insert(
TEST_Y_20,
vec![PlayheadEvent::new(
ChartEventId::new(2),
TEST_Y_20,
ChartEvent::Note {
side: PlayerSide::Player1,
key: crate::bms::prelude::Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_5), continue_play: None,
},
TimeSpan::ZERO,
)],
);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_y_range((
std::ops::Bound::Included(YCoordinate::ZERO),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert!(!got_ids.contains(&2));
assert!(got_ids.contains(&1));
}
#[test]
fn events_in_y_range_prevents_duplicate_long_notes() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
TEST_Y_5,
vec![PlayheadEvent::new(
ChartEventId::new(1),
TEST_Y_5,
ChartEvent::Note {
side: PlayerSide::Player1,
key: crate::bms::prelude::Key::Key(1),
kind: NoteKind::Long,
wav_id: None,
length: Some(TEST_LENGTH_3), continue_play: None,
},
TimeSpan::ZERO,
)],
);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_y_range((
std::ops::Bound::Included(YCoordinate::ZERO),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.collect();
let count = got_ids.iter().filter(|&&id| id == 1).count();
assert_eq!(count, 1);
}
#[test]
fn events_in_y_range_respects_excluded_start_bound() {
let mut map: BTreeMap<YCoordinate, Vec<PlayheadEvent>> = BTreeMap::new();
map.insert(
TEST_Y_5,
vec![mk_event(1, 5.0, 0)], );
map.insert(TEST_Y_6, vec![mk_event(2, 6.0, 0)]);
let idx = AllEventsIndex::new(map);
let got_ids: Vec<usize> = idx
.events_in_y_range((
std::ops::Bound::Excluded(TEST_Y_5),
std::ops::Bound::Included(TEST_Y_10),
))
.into_iter()
.map(|ev| ev.id.value())
.collect();
assert!(!got_ids.contains(&1));
assert!(got_ids.contains(&2));
}
}