use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct PlayerStateSpan<S> {
pub time: f32,
pub frame: usize,
pub end_time: f32,
pub end_frame: usize,
pub duration: f32,
#[ts(as = "crate::interop::ts_bindings::RemoteIdTs")]
pub player: PlayerId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub player_position: Option<[f32; 3]>,
pub is_team_0: bool,
pub state: S,
}
fn player_sort_key(player: &PlayerId) -> String {
format!("{player:?}")
}
#[derive(Debug, Clone)]
pub struct PlayerSpanTracker<S> {
open: HashMap<PlayerId, PlayerStateSpan<S>>,
closed: EventStream<PlayerStateSpan<S>>,
}
impl<S> Default for PlayerSpanTracker<S> {
fn default() -> Self {
Self {
open: HashMap::new(),
closed: EventStream::default(),
}
}
}
impl<S: Clone + PartialEq> PlayerSpanTracker<S> {
pub fn begin_update(&mut self) {
self.closed.begin_update();
}
#[allow(clippy::too_many_arguments)]
pub fn record(
&mut self,
frame_number: usize,
start_time: f32,
end_time: f32,
duration: f32,
player: &PlayerId,
player_position: Option<[f32; 3]>,
is_team_0: bool,
state: S,
) {
if let Some(open) = self.open.get_mut(player) {
if open.state == state {
open.end_time = end_time;
open.end_frame = frame_number;
open.duration += duration;
open.player_position = player_position;
return;
}
}
let span = PlayerStateSpan {
time: start_time,
frame: frame_number,
end_time,
end_frame: frame_number,
duration,
player: player.clone(),
player_position,
is_team_0,
state,
};
if let Some(previous) = self.open.insert(player.clone(), span) {
self.closed.push(previous);
}
}
pub fn close(&mut self, player: &PlayerId) {
if let Some(span) = self.open.remove(player) {
self.closed.push(span);
}
}
pub fn close_all(&mut self) {
let mut spans: Vec<_> = self.open.drain().map(|(_, span)| span).collect();
spans.sort_by_key(|span| player_sort_key(&span.player));
self.closed.extend(spans);
}
pub fn events(&self) -> &[PlayerStateSpan<S>] {
self.closed.all()
}
pub fn new_events(&self) -> &[PlayerStateSpan<S>] {
self.closed.new_events()
}
pub fn projected_events(&self) -> Vec<PlayerStateSpan<S>> {
let mut events = self.closed.all().to_vec();
let mut open: Vec<_> = self.open.values().cloned().collect();
open.sort_by_key(|span| player_sort_key(&span.player));
events.extend(open);
events
}
}
pub(crate) fn scalar_state_segments<S: Copy>(
start: f32,
end: f32,
thresholds: &[f32],
states: &[S],
) -> Vec<(S, f32)> {
debug_assert_eq!(states.len(), thresholds.len() + 1);
let region = |value: f32| -> usize { thresholds.iter().take_while(|&&t| value >= t).count() };
let start_region = region(start);
let end_region = region(end);
if (end - start).abs() <= f32::EPSILON || start_region == end_region {
return vec![(states[start_region], 1.0)];
}
let direction: isize = if end > start { 1 } else { -1 };
let mut segments = Vec::new();
let mut current_region = start_region;
let mut previous_t = 0.0f32;
while current_region != end_region {
let crossing = if direction > 0 {
thresholds[current_region]
} else {
thresholds[current_region - 1]
};
let t = ((crossing - start) / (end - start)).clamp(0.0, 1.0);
segments.push((states[current_region], (t - previous_t).max(0.0)));
previous_t = t;
current_region = (current_region as isize + direction) as usize;
}
segments.push((states[end_region], (1.0 - previous_t).max(0.0)));
segments
}
#[cfg(test)]
#[path = "player_state_span_tests.rs"]
mod tests;