mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 03:55:23 +00:00
refactor(startup): improve overall startup time and remove first frames artifacts (#408)
Fixes #406
This commit is contained in:
parent
5ee891230c
commit
3a5b5ec0cc
@ -272,6 +272,16 @@ fn impl_tv_channel(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
#(
|
||||||
|
#enum_name::#variant_names(ref channel) => {
|
||||||
|
channel.supports_preview()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,6 +146,10 @@ impl OnAir for Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&self) {}
|
fn shutdown(&self) {}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
|
@ -213,6 +213,10 @@ impl OnAir for Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&self) {}
|
fn shutdown(&self) {}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
self.preview_kind != PreviewKind::None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
|
#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
|
||||||
|
@ -142,6 +142,10 @@ impl OnAir for Channel {
|
|||||||
fn shutdown(&self) {
|
fn shutdown(&self) {
|
||||||
self.crawl_handle.abort();
|
self.crawl_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
|
@ -131,4 +131,8 @@ impl OnAir for Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&self) {}
|
fn shutdown(&self) {}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,10 @@ impl OnAir for Channel {
|
|||||||
fn shutdown(&self) {
|
fn shutdown(&self) {
|
||||||
self.crawl_handle.abort();
|
self.crawl_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
|
@ -113,6 +113,10 @@ impl OnAir for Channel {
|
|||||||
debug!("Shutting down git repos channel");
|
debug!("Shutting down git repos channel");
|
||||||
self.crawl_handle.abort();
|
self.crawl_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ignored_paths() -> Vec<PathBuf> {
|
fn get_ignored_paths() -> Vec<PathBuf> {
|
||||||
|
@ -84,6 +84,9 @@ pub trait OnAir: Send {
|
|||||||
|
|
||||||
/// Turn off
|
/// Turn off
|
||||||
fn shutdown(&self);
|
fn shutdown(&self);
|
||||||
|
|
||||||
|
/// Whether this channel supports previewing entries.
|
||||||
|
fn supports_preview(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The available television channels.
|
/// The available television channels.
|
||||||
|
@ -181,4 +181,8 @@ impl OnAir for RemoteControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&self) {}
|
fn shutdown(&self) {}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,4 +120,8 @@ impl OnAir for Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&self) {}
|
fn shutdown(&self) {}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
self.preview_type != PreviewType::None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,6 +245,10 @@ impl OnAir for Channel {
|
|||||||
fn shutdown(&self) {
|
fn shutdown(&self) {
|
||||||
self.crawl_handle.abort();
|
self.crawl_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_preview(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum file size we're willing to search in.
|
/// The maximum file size we're willing to search in.
|
||||||
|
@ -6,7 +6,7 @@ use rustc_hash::FxHashSet;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
channels::entry::{Entry, PreviewType, ENTRY_PLACEHOLDER},
|
channels::entry::Entry,
|
||||||
config::Config,
|
config::Config,
|
||||||
picker::Picker,
|
picker::Picker,
|
||||||
preview::PreviewState,
|
preview::PreviewState,
|
||||||
@ -22,6 +22,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// The state of the current television channel.
|
||||||
|
///
|
||||||
|
/// This struct is passed along to the UI thread as part of the `TvState` struct.
|
||||||
pub struct ChannelState {
|
pub struct ChannelState {
|
||||||
pub current_channel_name: String,
|
pub current_channel_name: String,
|
||||||
pub selected_entries: FxHashSet<Entry>,
|
pub selected_entries: FxHashSet<Entry>,
|
||||||
@ -57,6 +60,9 @@ impl Hash for ChannelState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
/// The state of the main thread `Television` struct.
|
||||||
|
///
|
||||||
|
/// This struct is passed along to the UI thread as part of the `Ctx` struct.
|
||||||
pub struct TvState {
|
pub struct TvState {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub selected_entry: Option<Entry>,
|
pub selected_entry: Option<Entry>,
|
||||||
@ -91,6 +97,11 @@ impl TvState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// A drawing context that holds the current state of the application.
|
||||||
|
///
|
||||||
|
/// This is used as a message passing object between the main thread
|
||||||
|
/// and the UI thread and should contain all the information needed to
|
||||||
|
/// draw a frame.
|
||||||
pub struct Ctx {
|
pub struct Ctx {
|
||||||
pub tv_state: TvState,
|
pub tv_state: TvState,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
@ -152,15 +163,24 @@ impl Ord for Ctx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the current UI frame based on the given context.
|
||||||
|
///
|
||||||
|
/// This function is responsible for drawing the entire UI frame based on the given context by
|
||||||
|
/// ultimately flushing buffers down to the underlying terminal.
|
||||||
|
///
|
||||||
|
/// This function is executed by the UI thread whenever it receives a render message from the main
|
||||||
|
/// thread.
|
||||||
|
///
|
||||||
|
/// It will draw the help bar, the results list, the input box, the preview content block, and the
|
||||||
|
/// remote control.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A `Result` containing the layout of the current frame if the drawing was successful.
|
||||||
|
/// This layout can then be sent back to the main thread to serve for tasks where having that
|
||||||
|
/// information can be useful or lead to optimizations.
|
||||||
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||||
let selected_entry = ctx
|
let show_preview =
|
||||||
.tv_state
|
ctx.config.ui.show_preview_panel && ctx.tv_state.preview_state.enabled;
|
||||||
.selected_entry
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(ENTRY_PLACEHOLDER);
|
|
||||||
|
|
||||||
let show_preview = ctx.config.ui.show_preview_panel
|
|
||||||
&& !matches!(selected_entry.preview_type, PreviewType::None);
|
|
||||||
let show_remote = !matches!(ctx.tv_state.mode, Mode::Channel);
|
let show_remote = !matches!(ctx.tv_state.mode, Mode::Channel);
|
||||||
|
|
||||||
let layout =
|
let layout =
|
||||||
@ -219,7 +239,7 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
|||||||
&ctx.colorscheme,
|
&ctx.colorscheme,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if show_preview {
|
if layout.preview_window.is_some() {
|
||||||
draw_preview_content_block(
|
draw_preview_content_block(
|
||||||
f,
|
f,
|
||||||
layout.preview_window.unwrap(),
|
layout.preview_window.unwrap(),
|
||||||
|
@ -109,6 +109,7 @@ impl Preview {
|
|||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||||
pub struct PreviewState {
|
pub struct PreviewState {
|
||||||
|
pub enabled: bool,
|
||||||
pub preview: Arc<Preview>,
|
pub preview: Arc<Preview>,
|
||||||
pub scroll: u16,
|
pub scroll: u16,
|
||||||
pub target_line: Option<u16>,
|
pub target_line: Option<u16>,
|
||||||
@ -118,11 +119,13 @@ const PREVIEW_MIN_SCROLL_LINES: u16 = 3;
|
|||||||
|
|
||||||
impl PreviewState {
|
impl PreviewState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
enabled: bool,
|
||||||
preview: Arc<Preview>,
|
preview: Arc<Preview>,
|
||||||
scroll: u16,
|
scroll: u16,
|
||||||
target_line: Option<u16>,
|
target_line: Option<u16>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PreviewState {
|
PreviewState {
|
||||||
|
enabled,
|
||||||
preview,
|
preview,
|
||||||
scroll,
|
scroll,
|
||||||
target_line,
|
target_line,
|
||||||
|
@ -37,6 +37,11 @@ impl IoStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
/// The state of the UI after rendering.
|
||||||
|
///
|
||||||
|
/// This struct is returned by the UI thread to the main thread after each rendering cycle.
|
||||||
|
/// It contains information that the main thread might be able to exploit to make certain
|
||||||
|
/// decisions and optimizations.
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
pub layout: Layout,
|
pub layout: Layout,
|
||||||
}
|
}
|
||||||
@ -47,6 +52,18 @@ impl UiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main UI rendering task loop.
|
||||||
|
///
|
||||||
|
/// This function is responsible for rendering the UI based on the rendering tasks it receives from
|
||||||
|
/// the main thread via `render_rx`.
|
||||||
|
///
|
||||||
|
/// This has a handle to the main action queue `action_tx` (for things like self-triggering
|
||||||
|
/// subsequent rendering instructions) and the UI state queue `ui_state_tx` to send back the layout
|
||||||
|
/// of the UI after each rendering cycle to the main thread to help make decisions and
|
||||||
|
/// optimizations.
|
||||||
|
///
|
||||||
|
/// When starting the rendering loop, a choice is made to either render to stdout or stderr based
|
||||||
|
/// on if the output is believed to be a TTY or not.
|
||||||
pub async fn render(
|
pub async fn render(
|
||||||
mut render_rx: mpsc::UnboundedReceiver<RenderingTask>,
|
mut render_rx: mpsc::UnboundedReceiver<RenderingTask>,
|
||||||
action_tx: mpsc::UnboundedSender<Action>,
|
action_tx: mpsc::UnboundedSender<Action>,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::action::Action;
|
use crate::action::Action;
|
||||||
use crate::cable::load_cable_channels;
|
use crate::cable::load_cable_channels;
|
||||||
use crate::channels::entry::{Entry, PreviewType, ENTRY_PLACEHOLDER};
|
use crate::channels::entry::{Entry, ENTRY_PLACEHOLDER};
|
||||||
use crate::channels::{
|
use crate::channels::{
|
||||||
remote_control::{load_builtin_channels, RemoteControl},
|
remote_control::{load_builtin_channels, RemoteControl},
|
||||||
OnAir, TelevisionChannel, UnitChannel,
|
OnAir, TelevisionChannel, UnitChannel,
|
||||||
@ -9,7 +9,7 @@ use crate::config::{Config, Theme};
|
|||||||
use crate::draw::{ChannelState, Ctx, TvState};
|
use crate::draw::{ChannelState, Ctx, TvState};
|
||||||
use crate::input::convert_action_to_input_request;
|
use crate::input::convert_action_to_input_request;
|
||||||
use crate::picker::Picker;
|
use crate::picker::Picker;
|
||||||
use crate::preview::{PreviewState, Previewer};
|
use crate::preview::{Preview, PreviewState, Previewer};
|
||||||
use crate::render::UiState;
|
use crate::render::UiState;
|
||||||
use crate::screen::colors::Colorscheme;
|
use crate::screen::colors::Colorscheme;
|
||||||
use crate::screen::layout::InputPosition;
|
use crate::screen::layout::InputPosition;
|
||||||
@ -21,6 +21,7 @@ use anyhow::Result;
|
|||||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
|
||||||
@ -79,6 +80,13 @@ impl Television {
|
|||||||
channel.find(&input.unwrap_or(EMPTY_STRING.to_string()));
|
channel.find(&input.unwrap_or(EMPTY_STRING.to_string()));
|
||||||
let spinner = Spinner::default();
|
let spinner = Spinner::default();
|
||||||
|
|
||||||
|
let preview_state = PreviewState::new(
|
||||||
|
channel.supports_preview(),
|
||||||
|
Arc::new(Preview::default()),
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
action_tx,
|
action_tx,
|
||||||
config,
|
config,
|
||||||
@ -91,7 +99,7 @@ impl Television {
|
|||||||
results_picker,
|
results_picker,
|
||||||
rc_picker: Picker::default(),
|
rc_picker: Picker::default(),
|
||||||
previewer,
|
previewer,
|
||||||
preview_state: PreviewState::default(),
|
preview_state,
|
||||||
spinner,
|
spinner,
|
||||||
spinner_state: SpinnerState::from(&spinner),
|
spinner_state: SpinnerState::from(&spinner),
|
||||||
app_metadata,
|
app_metadata,
|
||||||
@ -261,11 +269,13 @@ impl Television {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RENDER_FIRST_N_TICKS: u64 = 20;
|
||||||
const RENDER_EVERY_N_TICKS: u64 = 10;
|
const RENDER_EVERY_N_TICKS: u64 = 10;
|
||||||
|
|
||||||
impl Television {
|
impl Television {
|
||||||
fn should_render(&self, action: &Action) -> bool {
|
fn should_render(&self, action: &Action) -> bool {
|
||||||
self.ticks == RENDER_EVERY_N_TICKS
|
self.ticks < RENDER_FIRST_N_TICKS
|
||||||
|
|| self.ticks % RENDER_EVERY_N_TICKS == 0
|
||||||
|| matches!(
|
|| matches!(
|
||||||
action,
|
action,
|
||||||
Action::AddInputChar(_)
|
Action::AddInputChar(_)
|
||||||
@ -300,8 +310,7 @@ impl Television {
|
|||||||
&mut self,
|
&mut self,
|
||||||
selected_entry: &Entry,
|
selected_entry: &Entry,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.config.ui.show_preview_panel
|
if self.config.ui.show_preview_panel && self.channel.supports_preview()
|
||||||
&& !matches!(selected_entry.preview_type, PreviewType::None)
|
|
||||||
{
|
{
|
||||||
// preview content
|
// preview content
|
||||||
if let Some(preview) = self
|
if let Some(preview) = self
|
||||||
@ -591,7 +600,6 @@ impl Television {
|
|||||||
if self.channel.running() {
|
if self.channel.running() {
|
||||||
self.spinner.tick();
|
self.spinner.tick();
|
||||||
}
|
}
|
||||||
self.ticks = 0;
|
|
||||||
|
|
||||||
Some(Action::Render)
|
Some(Action::Render)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
|
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
|
||||||
|
/// Global application metadata like version and current directory.
|
||||||
pub struct AppMetadata {
|
pub struct AppMetadata {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub current_directory: String,
|
pub current_directory: String,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user