Spawn read_from_zip on blocking thread

Adds `Send + 'static` bounds to `R` in `read_from_zip` and moves the
file
operations to a Tokio blocking task. This prevents blocking the async
runtime
when reading large zip archives.

Also, updates the `load_symbol_async` function to handle data loading
concurrently and stream chunks of data back to the UI. The `loaded_data`
field is changed from `Option<Arc<Vec<DataPoint<OhlcItem>>>>` to
`Vec<DataPoint<OhlcItem>>`.
The sender and receiver for data updates are also adjusted to handle
streaming
chunks.
This commit is contained in:
Michael Schimmel
2026-02-16 15:12:29 +01:00
parent e59fb661b9
commit 0e2bf7e2aa
+55 -27
View File
@@ -124,8 +124,10 @@ impl DataServer {
Ok(shared_data)
}
async fn read_from_zip<R: bytemuck::Pod>(&self, path: &Path) -> anyhow::Result<Vec<R>> {
let file = std::fs::File::open(path)?;
async fn read_from_zip<R: bytemuck::Pod + Send + 'static>(&self, path: &Path) -> anyhow::Result<Vec<R>> {
let path = path.to_path_buf();
tokio::task::spawn_blocking(move || {
let file = std::fs::File::open(&path)?;
let mut archive = ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
@@ -137,6 +139,8 @@ impl DataServer {
}
}
Err(anyhow::anyhow!("No .bin file found"))
})
.await?
}
}
@@ -147,9 +151,9 @@ struct DataViewerApp {
rt: Runtime,
symbols: Vec<String>,
selected_symbol: Option<String>,
loaded_data: Option<Arc<Vec<DataPoint<OhlcItem>>>>,
tx: std::sync::mpsc::Sender<Arc<Vec<DataPoint<OhlcItem>>>>,
rx: std::sync::mpsc::Receiver<Arc<Vec<DataPoint<OhlcItem>>>>,
loaded_data: Vec<DataPoint<OhlcItem>>,
tx: std::sync::mpsc::Sender<Option<Arc<Vec<DataPoint<OhlcItem>>>>>,
rx: std::sync::mpsc::Receiver<Option<Arc<Vec<DataPoint<OhlcItem>>>>>,
is_loading: bool,
status_msg: String,
}
@@ -173,7 +177,7 @@ impl DataViewerApp {
rt,
symbols,
selected_symbol: None,
loaded_data: None,
loaded_data: Vec::new(),
tx,
rx,
is_loading: false,
@@ -184,27 +188,44 @@ impl DataViewerApp {
fn load_symbol_async(&mut self, symbol: String) {
self.is_loading = true;
self.status_msg = format!("Loading {}...", symbol);
self.loaded_data = None;
self.loaded_data.clear();
let server = Arc::clone(&self.server);
let tx = self.tx.clone();
let ctx = self.rt.handle().clone();
self.rt.spawn(async move {
let files = {
let files: Vec<DataFile> = {
let symbols = server.symbols.read().unwrap();
symbols.get(&symbol).cloned().unwrap_or_default()
};
let mut all_data = Vec::new();
for file in files {
if file.symbol.ends_with(".m1") {
if let Ok(data) = server.load_m1_data(&file).await {
all_data.extend_from_slice(&data);
let mut next_load: Option<tokio::task::JoinHandle<anyhow::Result<Arc<Vec<DataPoint<OhlcItem>>>>>> = None;
for i in 0..files.len() {
let file = &files[i];
if !file.symbol.ends_with(".m1") {
continue;
}
let data_res: anyhow::Result<Arc<Vec<DataPoint<OhlcItem>>>> = if let Some(handle) = next_load.take() {
handle.await.map_err(|e| anyhow::anyhow!("Join error: {}", e)).and_then(|res| res)
} else {
server.load_m1_data(file).await
};
if i + 1 < files.len() {
let next_file = files[i + 1].clone();
let server_clone = Arc::clone(&server);
next_load = Some(tokio::spawn(async move {
server_clone.load_m1_data(&next_file).await
}));
}
if let Ok(data) = data_res {
let _ = tx.send(Some(data));
}
}
}
let _ = tx.send(Arc::new(all_data));
let _ = tx.send(None); // Finished
});
}
}
@@ -215,14 +236,19 @@ impl eframe::App for DataViewerApp {
ctx.request_repaint();
}
// Poll for finished loading tasks
if let Ok(data) = self.rx.try_recv() {
self.loaded_data = Some(data);
// Poll for finished loading tasks (Feed)
let mut received_any = false;
while let Ok(msg) = self.rx.try_recv() {
if let Some(chunk) = msg {
self.loaded_data.extend_from_slice(&chunk);
received_any = true;
} else {
self.is_loading = false;
self.status_msg = format!(
"Loaded {} records",
self.loaded_data.as_ref().unwrap().len()
);
}
}
if received_any {
self.status_msg = format!("Loaded {} records", self.loaded_data.len());
}
let mut symbol_to_load = None;
@@ -248,16 +274,19 @@ impl eframe::App for DataViewerApp {
if let Some(symbol) = &self.selected_symbol {
ui.heading(format!("Data: {}", symbol));
if self.is_loading {
ui.horizontal(|ui| {
ui.add(egui::Spinner::new());
ui.label("Streaming from network share...");
} else if let Some(data) = &self.loaded_data {
ui.label(format!("Successfully loaded {} records.", data.len()));
});
}
ui.label(format!("Successfully loaded {} records.", self.loaded_data.len()));
// Show a small sample
egui::ScrollArea::vertical().show(ui, |ui| {
let data = &self.loaded_data;
for i in 0..data.len().min(10000) {
let d = &data[i];
// Copy fields to local variables to avoid unaligned references
let open = d.data.open;
let high = d.data.high;
let low = d.data.low;
@@ -268,7 +297,6 @@ impl eframe::App for DataViewerApp {
));
}
});
}
} else {
ui.label("Bitte ein Symbol auswählen.");
}