Skip to content

Commit 05b3d9a

Browse files
committed
add a bunch of nonsense
1 parent fcbed65 commit 05b3d9a

File tree

22 files changed

+5674
-0
lines changed

22 files changed

+5674
-0
lines changed

large-scale-viz/high_perf_character_render/Cargo.lock

Lines changed: 2898 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "sprite-video-renderer"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
# Parquet reading
8+
arrow = "54"
9+
parquet = "54"
10+
11+
# WGPU rendering
12+
wgpu = "23"
13+
pollster = "0.4"
14+
bytemuck = { version = "1", features = ["derive"] }
15+
16+
# Image loading
17+
image = "0.25"
18+
19+
# JSON parsing for map_data.json
20+
serde = { version = "1", features = ["derive"] }
21+
serde_json = "1"
22+
23+
# CLI
24+
clap = { version = "4", features = ["derive"] }
25+
regex = "1"
26+
27+
# Utilities
28+
anyhow = "1"
29+
chrono = "0.4"
30+
rayon = "1"
31+
32+
# Logging
33+
env_logger = "0.11"
34+
log = "0.4"
35+
36+
# Async
37+
futures-intrusive = "0.5"
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use crate::data::{AnimationState, CoordinateMapper, Direction, SpriteSequence, SpriteInstance};
2+
3+
pub struct AnimationInterpolator {
4+
coordinate_mapper: CoordinateMapper,
5+
interval_ms: f32,
6+
fps: f32,
7+
}
8+
9+
impl AnimationInterpolator {
10+
pub fn new(coordinate_mapper: CoordinateMapper, interval_ms: f32, fps: f32) -> Self {
11+
Self {
12+
coordinate_mapper,
13+
interval_ms,
14+
fps,
15+
}
16+
}
17+
18+
/// Calculate animation state for a sprite sequence at a given time
19+
pub fn get_animation_state(&self, sequence: &SpriteSequence, time_ms: f32) -> Option<AnimationState> {
20+
if sequence.frames.is_empty() {
21+
return None;
22+
}
23+
24+
// Calculate which point we're at based on fixed interval
25+
let point_index_f = time_ms / self.interval_ms;
26+
let current_frame_index = point_index_f.floor() as usize;
27+
28+
if current_frame_index >= sequence.frames.len() {
29+
return None; // Animation finished
30+
}
31+
32+
let next_frame_index = (current_frame_index + 1).min(sequence.frames.len() - 1);
33+
let interpolation_t = point_index_f.fract();
34+
35+
Some(AnimationState {
36+
current_frame_index,
37+
next_frame_index,
38+
interpolation_t,
39+
})
40+
}
41+
42+
/// Get interpolated position and direction for a sprite at a given animation state
43+
pub fn interpolate_sprite(
44+
&self,
45+
sequence: &SpriteSequence,
46+
state: &AnimationState,
47+
) -> Option<SpriteInstance> {
48+
if state.current_frame_index >= sequence.frames.len() {
49+
return None;
50+
}
51+
52+
let current_frame = &sequence.frames[state.current_frame_index];
53+
let next_frame = &sequence.frames[state.next_frame_index];
54+
55+
// Convert coordinates to pixel positions
56+
let current_pos = self.coordinate_mapper.convert_coords(&current_frame.coords);
57+
let next_pos = self.coordinate_mapper.convert_coords(&next_frame.coords);
58+
59+
// Linear interpolation
60+
let position = [
61+
current_pos[0] + (next_pos[0] - current_pos[0]) * state.interpolation_t,
62+
current_pos[1] + (next_pos[1] - current_pos[1]) * state.interpolation_t,
63+
];
64+
65+
// Determine direction based on movement
66+
let dx = next_pos[0] - current_pos[0];
67+
let dy = next_pos[1] - current_pos[1];
68+
let direction = Direction::from_movement(dx, dy);
69+
70+
Some(SpriteInstance {
71+
position,
72+
sprite_id: sequence.sprite_id,
73+
direction,
74+
})
75+
}
76+
77+
/// Calculate total animation duration in milliseconds
78+
pub fn calculate_duration(&self, sequences: &[SpriteSequence]) -> f32 {
79+
sequences
80+
.iter()
81+
.map(|seq| seq.frames.len() as f32 * self.interval_ms)
82+
.max_by(|a, b| a.partial_cmp(b).unwrap())
83+
.unwrap_or(0.0)
84+
}
85+
86+
/// Get frame time in milliseconds for a given frame number
87+
pub fn frame_to_time(&self, frame_number: usize) -> f32 {
88+
frame_number as f32 * (1000.0 / self.fps)
89+
}
90+
91+
/// Calculate total number of frames for the animation
92+
pub fn calculate_frame_count(&self, sequences: &[SpriteSequence]) -> usize {
93+
let duration_ms = self.calculate_duration(sequences);
94+
(duration_ms * self.fps / 1000.0).ceil() as usize
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use super::*;
101+
use crate::data::{SpriteFrame, SpriteSequence};
102+
use chrono::Utc;
103+
use std::collections::HashMap;
104+
105+
#[test]
106+
fn test_animation_state_calculation() {
107+
let mapper = CoordinateMapper {
108+
regions: HashMap::new(),
109+
};
110+
let interpolator = AnimationInterpolator::new(mapper, 500.0, 60.0);
111+
112+
let sequence = SpriteSequence {
113+
user: "test".to_string(),
114+
env_id: "1".to_string(),
115+
sprite_id: 0,
116+
color: "#000000".to_string(),
117+
frames: vec![
118+
SpriteFrame {
119+
timestamp: Utc::now(),
120+
user: "test".to_string(),
121+
env_id: "1".to_string(),
122+
sprite_id: 0,
123+
color: "#000000".to_string(),
124+
extra: String::new(),
125+
coords: [0, 0, 1],
126+
},
127+
SpriteFrame {
128+
timestamp: Utc::now(),
129+
user: "test".to_string(),
130+
env_id: "1".to_string(),
131+
sprite_id: 0,
132+
color: "#000000".to_string(),
133+
extra: String::new(),
134+
coords: [10, 0, 1],
135+
},
136+
],
137+
};
138+
139+
// At time 0, should be at first frame
140+
let state = interpolator.get_animation_state(&sequence, 0.0).unwrap();
141+
assert_eq!(state.current_frame_index, 0);
142+
assert_eq!(state.next_frame_index, 1);
143+
assert!((state.interpolation_t - 0.0).abs() < 0.01);
144+
145+
// At time 250ms (halfway between frames), should be interpolating
146+
let state = interpolator.get_animation_state(&sequence, 250.0).unwrap();
147+
assert_eq!(state.current_frame_index, 0);
148+
assert_eq!(state.next_frame_index, 1);
149+
assert!((state.interpolation_t - 0.5).abs() < 0.01);
150+
151+
// At time 500ms, should be at second frame
152+
let state = interpolator.get_animation_state(&sequence, 500.0).unwrap();
153+
assert_eq!(state.current_frame_index, 1);
154+
assert_eq!(state.next_frame_index, 1);
155+
}
156+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod interpolator;
2+
3+
pub use interpolator::AnimationInterpolator;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use anyhow::Result;
2+
use clap::Parser;
3+
use sprite_video_renderer::data::{ParquetFilter, ParquetReader, SpriteFrame};
4+
use std::collections::HashMap;
5+
use std::path::PathBuf;
6+
7+
#[derive(Parser, Debug)]
8+
#[command(author, version, about = "Analyze user+env_id runs in parquet file", long_about = None)]
9+
struct Args {
10+
/// Path to parquet file
11+
#[arg(long)]
12+
parquet_file: PathBuf,
13+
}
14+
15+
#[derive(Debug)]
16+
struct Run {
17+
start_timestamp: chrono::DateTime<chrono::Utc>,
18+
end_timestamp: chrono::DateTime<chrono::Utc>,
19+
coord_count: usize,
20+
}
21+
22+
#[derive(Debug)]
23+
struct UserEnvStats {
24+
user: String,
25+
env_id: String,
26+
runs: Vec<Run>,
27+
total_coords: usize,
28+
}
29+
30+
fn main() -> Result<()> {
31+
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
32+
33+
let args = Args::parse();
34+
35+
log::info!("=== Analyzing runs in parquet file ===");
36+
log::info!("Parquet file: {:?}", args.parquet_file);
37+
38+
// Read all frames from parquet
39+
log::info!("Reading parquet file...");
40+
let reader = ParquetReader::new(ParquetFilter::default());
41+
let frames = reader.read_file(&args.parquet_file)?;
42+
43+
log::info!("Total frames read: {}", frames.len());
44+
45+
// Group by user+env_id directly and build run splits in one pass
46+
log::info!("Grouping and detecting runs...");
47+
let mut user_env_data: HashMap<String, Vec<SpriteFrame>> = HashMap::new();
48+
49+
for frame in frames {
50+
let key = format!("{}-{}", frame.user, frame.env_id);
51+
user_env_data.entry(key).or_insert_with(Vec::new).push(frame);
52+
}
53+
54+
log::info!("Found {} unique user+env_id pairs", user_env_data.len());
55+
56+
// Process each user+env_id to identify runs with reset detection
57+
let mut stats: Vec<UserEnvStats> = Vec::new();
58+
let reset_maps = vec![0i64, 37, 40];
59+
60+
for (_key, mut frames_list) in user_env_data {
61+
if frames_list.is_empty() {
62+
continue;
63+
}
64+
65+
// Sort by timestamp
66+
frames_list.sort_by_key(|f| (f.timestamp, f.path_index));
67+
68+
let user = frames_list[0].user.clone();
69+
let env_id = frames_list[0].env_id.clone();
70+
let total_coords = frames_list.len();
71+
72+
// Detect runs with 2-minute gaps and reset events
73+
let runs = detect_runs_with_resets(&frames_list, &reset_maps);
74+
75+
stats.push(UserEnvStats {
76+
user,
77+
env_id,
78+
runs,
79+
total_coords,
80+
});
81+
}
82+
83+
// Filter out runs < 60 seconds
84+
let min_duration = chrono::Duration::seconds(60);
85+
let mut total_runs_before = 0;
86+
for stat in &mut stats {
87+
total_runs_before += stat.runs.len();
88+
stat.runs.retain(|run| {
89+
(run.end_timestamp - run.start_timestamp) >= min_duration
90+
});
91+
}
92+
93+
log::info!("Runs before 60s filter: {}", total_runs_before);
94+
log::info!("Runs after 60s filter: {}", stats.iter().map(|s| s.runs.len()).sum::<usize>());
95+
96+
// Remove user+env_id pairs with no runs left after filtering
97+
stats.retain(|s| !s.runs.is_empty());
98+
99+
// Recalculate total coords after filtering
100+
for stat in &mut stats {
101+
stat.total_coords = stat.runs.iter().map(|r| r.coord_count).sum();
102+
}
103+
104+
// Sort by total coords descending for easier reading
105+
stats.sort_by_key(|s| std::cmp::Reverse(s.total_coords));
106+
107+
// Print summary
108+
println!("\n=== SUMMARY (after filtering runs < 60s) ===");
109+
println!("Total unique user+env_id pairs: {}", stats.len());
110+
println!("Total runs across all pairs: {}", stats.iter().map(|s| s.runs.len()).sum::<usize>());
111+
println!();
112+
113+
// Print detailed stats (limit to first 50 to avoid huge output)
114+
println!("=== DETAILED BREAKDOWN (top 50) ===\n");
115+
116+
for stat in stats.iter().take(50) {
117+
println!("User: {}, Env ID: {}", stat.user, stat.env_id);
118+
println!(" Total coords: {}", stat.total_coords);
119+
println!(" Number of runs: {}", stat.runs.len());
120+
121+
for (i, run) in stat.runs.iter().enumerate() {
122+
let duration = run.end_timestamp - run.start_timestamp;
123+
println!(" Run {}: {} coords, duration: {:.1}s ({} to {})",
124+
i + 1,
125+
run.coord_count,
126+
duration.num_milliseconds() as f64 / 1000.0,
127+
run.start_timestamp.format("%Y-%m-%d %H:%M:%S"),
128+
run.end_timestamp.format("%Y-%m-%d %H:%M:%S"));
129+
}
130+
println!();
131+
}
132+
133+
Ok(())
134+
}
135+
136+
/// Detect runs based on 2-minute gaps and reset events
137+
/// This processes frames in a single pass - O(n)
138+
fn detect_runs_with_resets(
139+
frames: &[SpriteFrame],
140+
reset_maps: &[i64],
141+
) -> Vec<Run> {
142+
if frames.is_empty() {
143+
return Vec::new();
144+
}
145+
146+
let mut runs = Vec::new();
147+
let gap_threshold = chrono::Duration::minutes(2);
148+
149+
let mut run_start_idx = 0;
150+
151+
for i in 1..frames.len() {
152+
let time_gap = frames[i].timestamp - frames[i-1].timestamp;
153+
let curr_map = frames[i].coords[2];
154+
let prev_map = frames[i-1].coords[2];
155+
156+
let mut should_split = false;
157+
158+
// Split on 2-minute gaps
159+
if time_gap >= gap_threshold {
160+
should_split = true;
161+
}
162+
163+
// Split when jumping TO a reset map (0, 37, 40) from a different map
164+
if reset_maps.contains(&curr_map) && !reset_maps.contains(&prev_map) {
165+
should_split = true;
166+
}
167+
168+
if should_split {
169+
let run = Run {
170+
start_timestamp: frames[run_start_idx].timestamp,
171+
end_timestamp: frames[i-1].timestamp,
172+
coord_count: i - run_start_idx,
173+
};
174+
runs.push(run);
175+
run_start_idx = i;
176+
}
177+
}
178+
179+
// Add the final run
180+
let final_run = Run {
181+
start_timestamp: frames[run_start_idx].timestamp,
182+
end_timestamp: frames[frames.len() - 1].timestamp,
183+
coord_count: frames.len() - run_start_idx,
184+
};
185+
runs.push(final_run);
186+
187+
runs
188+
}

0 commit comments

Comments
 (0)