Fix UI hang on audio import
This commit is contained in:
parent
408343094a
commit
12d927ed3d
|
|
@ -272,20 +272,11 @@ impl Engine {
|
|||
// Forward chunk generation events from background threads
|
||||
while let Ok(event) = self.chunk_generation_rx.try_recv() {
|
||||
match event {
|
||||
AudioEvent::WaveformDecodeComplete { pool_index, samples, decoded_frames: df, total_frames: _tf } => {
|
||||
// Update pool entry and forward samples directly to UI
|
||||
if let Some(file) = self.audio_pool.get_file_mut(pool_index) {
|
||||
AudioEvent::WaveformDecodeComplete { pool_index, samples, decoded_frames: _df, total_frames: _tf } => {
|
||||
// Forward samples directly to UI — no clone, just move
|
||||
if let Some(file) = self.audio_pool.get_file(pool_index) {
|
||||
let sr = file.sample_rate;
|
||||
let ch = file.channels;
|
||||
if let crate::audio::pool::AudioStorage::Compressed {
|
||||
ref mut decoded_for_waveform,
|
||||
ref mut decoded_frames,
|
||||
..
|
||||
} = file.storage {
|
||||
*decoded_for_waveform = samples.clone();
|
||||
*decoded_frames = df;
|
||||
}
|
||||
// Send samples inline — UI won't need to query back
|
||||
let _ = self.event_tx.push(AudioEvent::AudioDecodeProgress {
|
||||
pool_index,
|
||||
samples,
|
||||
|
|
@ -1825,6 +1816,22 @@ impl Engine {
|
|||
format: metadata.format,
|
||||
});
|
||||
|
||||
// For PCM files, send samples inline so the UI doesn't need to
|
||||
// do a blocking get_pool_audio_samples() query.
|
||||
if metadata.format == crate::io::AudioFormat::Pcm {
|
||||
if let Some(file) = self.audio_pool.get_file(pool_index) {
|
||||
let samples = file.data().to_vec();
|
||||
if !samples.is_empty() {
|
||||
let _ = self.event_tx.push(AudioEvent::AudioDecodeProgress {
|
||||
pool_index,
|
||||
samples,
|
||||
sample_rate: metadata.sample_rate,
|
||||
channels: metadata.channels,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(pool_index)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -436,10 +436,11 @@ impl AudioFile {
|
|||
}
|
||||
|
||||
// Send progressive update (fast initial, then periodic)
|
||||
// Only send NEW samples since last update (delta) to avoid large copies
|
||||
let interval = if sent_first { steady_interval } else { initial_interval };
|
||||
if audio_data.len() - last_update_len >= interval {
|
||||
let decoded_frames = audio_data.len() as u64 / channels as u64;
|
||||
on_progress(&audio_data, decoded_frames, total_frames);
|
||||
on_progress(&audio_data[last_update_len..], decoded_frames, total_frames);
|
||||
last_update_len = audio_data.len();
|
||||
sent_first = true;
|
||||
}
|
||||
|
|
@ -449,9 +450,9 @@ impl AudioFile {
|
|||
}
|
||||
}
|
||||
|
||||
// Final update with all data
|
||||
// Final update with remaining data (delta since last update)
|
||||
let decoded_frames = audio_data.len() as u64 / channels as u64;
|
||||
on_progress(&audio_data, decoded_frames, decoded_frames.max(total_frames));
|
||||
on_progress(&audio_data[last_update_len..], decoded_frames, decoded_frames.max(total_frames));
|
||||
}
|
||||
|
||||
/// Calculate the duration of the audio file in seconds
|
||||
|
|
|
|||
|
|
@ -761,6 +761,7 @@ impl EditorApp {
|
|||
let current_layout = layouts[0].layout.clone();
|
||||
|
||||
// Disable egui's "Unaligned" debug overlay (on by default in debug builds)
|
||||
#[cfg(debug_assertions)]
|
||||
cc.egui_ctx.style_mut(|style| style.debug.show_unaligned = false);
|
||||
|
||||
// Load application config
|
||||
|
|
@ -1670,6 +1671,7 @@ impl EditorApp {
|
|||
let file = dialog.pick_file();
|
||||
|
||||
if let Some(path) = file {
|
||||
let _import_timer = std::time::Instant::now();
|
||||
// Get extension and detect file type
|
||||
let extension = path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
|
|
@ -1698,12 +1700,16 @@ impl EditorApp {
|
|||
}
|
||||
};
|
||||
|
||||
eprintln!("[TIMING] import took {:.1}ms", _import_timer.elapsed().as_secs_f64() * 1000.0);
|
||||
// Auto-place if this is "Import" (not "Import to Library")
|
||||
if auto_place {
|
||||
if let Some(asset_info) = imported_asset {
|
||||
let _place_timer = std::time::Instant::now();
|
||||
self.auto_place_asset(asset_info);
|
||||
eprintln!("[TIMING] auto_place took {:.1}ms", _place_timer.elapsed().as_secs_f64() * 1000.0);
|
||||
}
|
||||
}
|
||||
eprintln!("[TIMING] total import+place took {:.1}ms", _import_timer.elapsed().as_secs_f64() * 1000.0);
|
||||
}
|
||||
}
|
||||
MenuAction::Export => {
|
||||
|
|
@ -3080,6 +3086,8 @@ impl EditorApp {
|
|||
|
||||
impl eframe::App for EditorApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
let _frame_start = std::time::Instant::now();
|
||||
|
||||
// Disable egui's built-in Ctrl+Plus/Minus zoom behavior
|
||||
// We handle zoom ourselves for the Stage pane
|
||||
ctx.options_mut(|o| {
|
||||
|
|
@ -3111,37 +3119,10 @@ impl eframe::App for EditorApp {
|
|||
// Will switch to editor mode when file finishes loading
|
||||
}
|
||||
|
||||
// Fetch missing raw audio on-demand (for lazy loading after project load)
|
||||
// Collect pool indices that need raw audio data
|
||||
let missing_raw_audio: Vec<usize> = self.action_executor.document()
|
||||
.audio_clips.values()
|
||||
.filter_map(|clip| {
|
||||
if let lightningbeam_core::clip::AudioClipType::Sampled { audio_pool_index } = &clip.clip_type {
|
||||
if !self.raw_audio_cache.contains_key(audio_pool_index) {
|
||||
Some(*audio_pool_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Fetch missing raw audio samples
|
||||
for pool_index in missing_raw_audio {
|
||||
if let Some(ref controller_arc) = self.audio_controller {
|
||||
let mut controller = controller_arc.lock().unwrap();
|
||||
match controller.get_pool_audio_samples(pool_index) {
|
||||
Ok((samples, sr, ch)) => {
|
||||
self.raw_audio_cache.insert(pool_index, (samples, sr, ch));
|
||||
self.waveform_gpu_dirty.insert(pool_index);
|
||||
self.audio_pools_with_new_waveforms.insert(pool_index);
|
||||
}
|
||||
Err(e) => eprintln!("Failed to fetch raw audio for pool {}: {}", pool_index, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: Missing raw audio samples for newly imported files will arrive
|
||||
// via AudioDecodeProgress events (compressed) or inline with AudioFileReady
|
||||
// (PCM). No blocking query needed here.
|
||||
// For project loading, audio files are re-imported which also sends events.
|
||||
|
||||
// Initialize and update effect thumbnail generator (GPU-based effect previews)
|
||||
if let Some(render_state) = frame.wgpu_render_state() {
|
||||
|
|
@ -3296,6 +3277,7 @@ impl eframe::App for EditorApp {
|
|||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
let _pre_events_ms = _frame_start.elapsed().as_secs_f64() * 1000.0;
|
||||
// Check if audio events are pending and request repaint if needed
|
||||
if self.audio_events_pending.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
ctx.request_repaint();
|
||||
|
|
@ -3639,8 +3621,12 @@ impl eframe::App for EditorApp {
|
|||
ctx.request_repaint();
|
||||
}
|
||||
AudioEvent::AudioDecodeProgress { pool_index, samples, sample_rate, channels } => {
|
||||
// Samples arrive inline — no query needed
|
||||
self.raw_audio_cache.insert(pool_index, (samples, sample_rate, channels));
|
||||
// Samples arrive as deltas — append to existing cache
|
||||
if let Some(entry) = self.raw_audio_cache.get_mut(&pool_index) {
|
||||
entry.0.extend_from_slice(&samples);
|
||||
} else {
|
||||
self.raw_audio_cache.insert(pool_index, (samples, sample_rate, channels));
|
||||
}
|
||||
self.waveform_gpu_dirty.insert(pool_index);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
|
@ -3658,6 +3644,8 @@ impl eframe::App for EditorApp {
|
|||
}
|
||||
}
|
||||
|
||||
let _post_events_ms = _frame_start.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
// Request continuous repaints when playing to update time display
|
||||
if self.is_playing {
|
||||
ctx.request_repaint();
|
||||
|
|
@ -4145,6 +4133,12 @@ impl eframe::App for EditorApp {
|
|||
);
|
||||
debug_overlay::render_debug_overlay(ctx, &stats);
|
||||
}
|
||||
|
||||
let frame_ms = _frame_start.elapsed().as_secs_f64() * 1000.0;
|
||||
if frame_ms > 50.0 {
|
||||
eprintln!("[TIMING] SLOW FRAME: {:.1}ms (pre-events={:.1}, events={:.1}, post-events={:.1})",
|
||||
frame_ms, _pre_events_ms, _post_events_ms - _pre_events_ms, frame_ms - _post_events_ms);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue