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:
+55
-27
@@ -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.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user