diff --git a/CHANGELOG.md b/CHANGELOG.md index 219d189..32f1f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ and `Removed`. ## [Unreleased] +### Changed + +- API data is only fetched for widgets that are actively shown + - This greatly reduces the number of active API requests when many tickers are + added. Data is lazily fetched & updated once a widget is in view ([#118]) + ## [0.14.2] - 2021-03-05 ### Fixed @@ -174,4 +180,5 @@ and `Removed`. [#93]: https://github.com/tarkah/tickrs/pull/93 [#110]: https://github.com/tarkah/tickrs/pull/110 [#112]: https://github.com/tarkah/tickrs/pull/112 -[#115]: https://github.com/tarkah/tickrs/pull/115 \ No newline at end of file +[#115]: https://github.com/tarkah/tickrs/pull/115 +[#118]: https://github.com/tarkah/tickrs/pull/118 \ No newline at end of file diff --git a/src/draw.rs b/src/draw.rs index 682ca06..65b38fe 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -6,6 +6,7 @@ use tui::{Frame, Terminal}; use crate::app::{App, Mode, ScrollDirection}; use crate::common::{ChartType, TimeFrame}; +use crate::service::Service; use crate::theme::style; use crate::widget::{ block, AddStockWidget, ChartConfigurationWidget, OptionsWidget, StockSummaryWidget, @@ -142,6 +143,15 @@ fn draw_main(frame: &mut Frame, app: &mut App, area: Rect) { } } + // Make sure only displayed stock has network activity + app.stocks.iter().enumerate().for_each(|(idx, s)| { + if idx == app.current_tab { + s.stock_service.resume(); + } else { + s.stock_service.pause(); + } + }); + // Draw main widget if let Some(stock) = app.stocks.get_mut(app.current_tab) { // main_chunks[0] - Stock widget @@ -315,6 +325,15 @@ fn draw_summary(frame: &mut Frame, app: &mut App, mut area: Rect) let stock_layout = Layout::default().constraints(contraints).split(layout[1]); + // Make sure only displayed stocks have network activity + app.stocks.iter().enumerate().for_each(|(idx, s)| { + if idx >= scroll_offset && idx < num_to_render + scroll_offset { + s.stock_service.resume(); + } else { + s.stock_service.pause(); + } + }); + for (idx, stock) in app.stocks[scroll_offset..num_to_render + scroll_offset] .iter_mut() .enumerate() diff --git a/src/service.rs b/src/service.rs index 8b1dd27..48cc330 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,4 +8,8 @@ pub trait Service { type Update; fn updates(&self) -> Vec; + + fn pause(&self); + + fn resume(&self); } diff --git a/src/service/default_timestamps.rs b/src/service/default_timestamps.rs index fd0afbb..debf978 100644 --- a/src/service/default_timestamps.rs +++ b/src/service/default_timestamps.rs @@ -23,4 +23,12 @@ impl Service for DefaultTimestampService { fn updates(&self) -> Vec { self.handle.response().try_iter().collect() } + + fn pause(&self) { + self.handle.pause(); + } + + fn resume(&self) { + self.handle.resume(); + } } diff --git a/src/service/options.rs b/src/service/options.rs index 5f86c7d..586cbb7 100644 --- a/src/service/options.rs +++ b/src/service/options.rs @@ -57,4 +57,18 @@ impl Service for OptionsService { updates } + + fn pause(&self) { + self.expiration_dates_handle.pause(); + if let Some(handle) = self.options_data_handle.as_ref() { + handle.pause(); + } + } + + fn resume(&self) { + self.expiration_dates_handle.resume(); + if let Some(handle) = self.options_data_handle.as_ref() { + handle.resume(); + } + } } diff --git a/src/service/stock.rs b/src/service/stock.rs index e78a530..6ac55b5 100644 --- a/src/service/stock.rs +++ b/src/service/stock.rs @@ -70,4 +70,16 @@ impl Service for StockService { updates } + + fn pause(&self) { + self.current_price_handle.pause(); + self.prices_handle.pause(); + self.company_handle.pause(); + } + + fn resume(&self) { + self.current_price_handle.resume(); + self.prices_handle.resume(); + self.company_handle.resume(); + } } diff --git a/src/task.rs b/src/task.rs index 36ecc97..d3d90e5 100644 --- a/src/task.rs +++ b/src/task.rs @@ -2,7 +2,7 @@ use std::time::{Duration, Instant}; use async_std::sync::Arc; use async_std::task; -use crossbeam_channel::{unbounded, Receiver}; +use crossbeam_channel::{bounded, unbounded, Receiver, Sender}; use futures::future::BoxFuture; use task::JoinHandle; @@ -40,6 +40,7 @@ pub trait AsyncTask: 'static { /// Runs the task on the async runtime and returns a handle to query updates from fn connect(&self) -> AsyncTaskHandle { + let (command_sender, command_receiver) = bounded(1); let (response_sender, response_receiver) = unbounded::(); let data_received = DATA_RECEIVED.0.clone(); @@ -49,6 +50,8 @@ pub trait AsyncTask: 'static { let handle = task::spawn(async move { let mut last_updated = Instant::now(); + let mut paused = false; + // Execute the task initially and request a redraw to display this data if let Some(response) = ::task(input.clone()).await { let _ = response_sender.send(response); @@ -64,7 +67,14 @@ pub trait AsyncTask: 'static { // Execute task every update interval loop { - if last_updated.elapsed() >= update_interval { + if let Ok(command) = command_receiver.try_recv() { + match command { + AsyncTaskCommand::Resume => paused = false, + AsyncTaskCommand::Pause => paused = true, + } + } + + if last_updated.elapsed() >= update_interval && !paused { if let Some(response) = ::task(input.clone()).await { let _ = response_sender.send(response); let _ = data_received.try_send(()); @@ -74,26 +84,41 @@ pub trait AsyncTask: 'static { } // Free up some cycles - task::sleep(Duration::from_secs(1)).await; + task::sleep(Duration::from_millis(500)).await; } }); AsyncTaskHandle { response: response_receiver, handle: Some(handle), + command_sender, } } } +enum AsyncTaskCommand { + Pause, + Resume, +} + pub struct AsyncTaskHandle { response: Receiver, handle: Option>, + command_sender: Sender, } impl AsyncTaskHandle { pub fn response(&self) -> &Receiver { &self.response } + + pub fn pause(&self) { + let _ = self.command_sender.try_send(AsyncTaskCommand::Pause); + } + + pub fn resume(&self) { + let _ = self.command_sender.try_send(AsyncTaskCommand::Resume); + } } impl Drop for AsyncTaskHandle {