Skip to content

Commit

Permalink
feat/lazy loading widgets (#118)
Browse files Browse the repository at this point in the history
* lazily load api data for shown widgets

* update CHANGELOG
  • Loading branch information
tarkah authored Mar 15, 2021
1 parent f270fa4 commit 0c24cf4
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 4 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
[#115]: https://github.com/tarkah/tickrs/pull/115
[#118]: https://github.com/tarkah/tickrs/pull/118
19 changes: 19 additions & 0 deletions src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -142,6 +143,15 @@ fn draw_main<B: Backend>(frame: &mut Frame<B>, 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
Expand Down Expand Up @@ -315,6 +325,15 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, 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()
Expand Down
4 changes: 4 additions & 0 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ pub trait Service {
type Update;

fn updates(&self) -> Vec<Self::Update>;

fn pause(&self);

fn resume(&self);
}
8 changes: 8 additions & 0 deletions src/service/default_timestamps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ impl Service for DefaultTimestampService {
fn updates(&self) -> Vec<Self::Update> {
self.handle.response().try_iter().collect()
}

fn pause(&self) {
self.handle.pause();
}

fn resume(&self) {
self.handle.resume();
}
}
14 changes: 14 additions & 0 deletions src/service/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
12 changes: 12 additions & 0 deletions src/service/stock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
31 changes: 28 additions & 3 deletions src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Self::Response> {
let (command_sender, command_receiver) = bounded(1);
let (response_sender, response_receiver) = unbounded::<Self::Response>();
let data_received = DATA_RECEIVED.0.clone();

Expand All @@ -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) = <Self as AsyncTask>::task(input.clone()).await {
let _ = response_sender.send(response);
Expand All @@ -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) = <Self as AsyncTask>::task(input.clone()).await {
let _ = response_sender.send(response);
let _ = data_received.try_send(());
Expand All @@ -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<R> {
response: Receiver<R>,
handle: Option<JoinHandle<()>>,
command_sender: Sender<AsyncTaskCommand>,
}

impl<R> AsyncTaskHandle<R> {
pub fn response(&self) -> &Receiver<R> {
&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<R> Drop for AsyncTaskHandle<R> {
Expand Down

0 comments on commit 0c24cf4

Please sign in to comment.