Make buffers return their items lazily from the end instead of materializing them

This commit is contained in:
2023-11-04 20:14:51 +01:00
parent d5e5639ba7
commit 46b703ba45
4 changed files with 37 additions and 28 deletions

View File

@ -24,7 +24,7 @@ use tokio::sync::mpsc::UnboundedReceiver;
use tracing_error::ErrorLayer; use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*; use tracing_subscriber::prelude::*;
use super::Buffer; use super::{Buffer, BufferItem};
/// Maximum number of log lines to be stored in memory /// Maximum number of log lines to be stored in memory
const MAX_MEM_LOG_LINES: usize = 100; const MAX_MEM_LOG_LINES: usize = 100;
@ -61,19 +61,21 @@ impl Buffer for LogBuffer {
self.lines.push_back(line); self.lines.push_back(line);
} }
fn content(&self) -> Vec<Text> { fn content<'a>(&'a self) -> Box<dyn Iterator<Item = BufferItem<'a>> + 'a> {
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
let (slice1, slice2) = self.lines.as_slices(); let (slice1, slice2) = self.lines.as_slices();
slice1 Box::new(
.into_iter() slice1
.chain(slice2.into_iter()) .into_iter()
.cloned() .chain(slice2.into_iter())
.map(|line| { .rev()
line.into_text().unwrap_or_else(|e| { .cloned()
tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e); .map(|line| BufferItem {
Text::raw(line) text: line.into_text().unwrap_or_else(|e| {
}) tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e);
}) Text::raw(line)
.collect() }),
}),
)
} }
} }

View File

@ -18,12 +18,17 @@ use futures::stream::FuturesUnordered;
use futures::StreamExt; use futures::StreamExt;
use matrix_sdk::async_trait; use matrix_sdk::async_trait;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use ratatui::text::Text;
mod log; mod log;
pub use log::LogBuffer; pub use log::LogBuffer;
mod room; mod room;
pub use room::RoomBuffer; pub use room::RoomBuffer;
pub struct BufferItem<'a> {
pub text: Text<'a>,
}
#[async_trait] #[async_trait]
pub trait Buffer: Send + Sync { pub trait Buffer: Send + Sync {
/// A short human-readable name for the room, eg. to show in compact buflist /// A short human-readable name for the room, eg. to show in compact buflist
@ -34,7 +39,7 @@ pub trait Buffer: Send + Sync {
} }
/// Returns if there are any updates to apply. /// Returns if there are any updates to apply.
async fn poll_updates(&mut self); async fn poll_updates(&mut self);
fn content(&self) -> Vec<ratatui::text::Text>; // TODO: make this lazy, only the last few are used fn content<'a>(&'a self) -> Box<dyn Iterator<Item = BufferItem<'a>> + 'a>;
/// Called when the user is being showned the oldest items this buffer returned. /// Called when the user is being showned the oldest items this buffer returned.
/// ///
/// This should return immediately, not waiting for anything to be loaded. /// This should return immediately, not waiting for anything to be loaded.

View File

@ -32,7 +32,7 @@ use matrix_sdk_ui::timeline::{
use ratatui::text::Text; use ratatui::text::Text;
use smallvec::SmallVec; use smallvec::SmallVec;
use super::Buffer; use super::{Buffer, BufferItem};
pub struct SingleClientRoomBuffer { pub struct SingleClientRoomBuffer {
room_id: OwnedRoomId, room_id: OwnedRoomId,
@ -304,16 +304,20 @@ impl Buffer for RoomBuffer {
.await; .await;
} }
fn content(&self) -> Vec<Text> { fn content<'a>(&'a self) -> Box<dyn Iterator<Item = BufferItem<'a>> + 'a> {
// TODO: merge buffers, etc. // TODO: merge buffers, etc.
self Box::new(
.buffers self
.first() .buffers
.unwrap_or_else(|| panic!("No sub-buffer for {}", self.room_id)) .first()
.items .unwrap_or_else(|| panic!("No sub-buffer for {}", self.room_id))
.iter() .items
.map(|line| Text::raw(line)) .iter()
.collect() .rev()
.map(|line| BufferItem {
text: Text::raw(line),
}),
)
} }
fn request_back_pagination(&self, num: u16) { fn request_back_pagination(&self, num: u16) {

View File

@ -64,8 +64,6 @@ impl Component for Backlog {
let active_buffer = buffers.active_buffer(); let active_buffer = buffers.active_buffer();
let mut items = active_buffer.content(); let mut items = active_buffer.content();
items.reverse();
let mut items = items.into_iter();
let mut scroll = self.scroll; let mut scroll = self.scroll;
// Skip widgets at the bottom (if scrolled up), and render the first visible one // Skip widgets at the bottom (if scrolled up), and render the first visible one
@ -73,7 +71,7 @@ impl Component for Backlog {
let Some(item) = items.next() else { let Some(item) = items.next() else {
break; break;
}; };
let widget = BottomAlignedParagraph::new(item); let widget = BottomAlignedParagraph::new(item.text);
let expected_height = widget.height(text_area.width); let expected_height = widget.height(text_area.width);
if scroll.saturating_sub(expected_height) > text_area.height.into() { if scroll.saturating_sub(expected_height) > text_area.height.into() {
@ -95,7 +93,7 @@ impl Component for Backlog {
// Render other widgets // Render other widgets
for item in items { for item in items {
let widget = BottomAlignedParagraph::new(item); let widget = BottomAlignedParagraph::new(item.text);
let height = widget.render_overlap(text_area, frame.buffer_mut()); let height = widget.render_overlap(text_area, frame.buffer_mut());
assert!(area.height >= height, "{:?} {}", area, height); assert!(area.height >= height, "{:?} {}", area, height);
text_area.height = text_area.height.saturating_sub(height); // Remove lines at the bottom used by this paragraph text_area.height = text_area.height.saturating_sub(height); // Remove lines at the bottom used by this paragraph