Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] - termion event listener is actually blocking #77

Open
hasezoey opened this issue May 4, 2024 · 1 comment
Open

[BUG] - termion event listener is actually blocking #77

hasezoey opened this issue May 4, 2024 · 1 comment
Assignees
Labels
bug Something isn't working

Comments

@hasezoey
Copy link
Contributor

hasezoey commented May 4, 2024

Description

As i discovered in #76, the current termion key / event listener is blocking, even though the Poll trait explicitly says the function should not be blocking.

This effectively means that anyone wanting to use the termion backend and one custom port OR rely on a tick is basically unable to do so reliably.

Steps to reproduce

  1. use the termion backend
  2. enable the ticker
  3. fetch all events for a while
  4. observe no events being generated (including no ticks)
Reproduction script
extern crate tuirealm;

use std::thread;
use std::time::Duration;

use tui::layout::Rect;
use tuirealm::application::PollStrategy;
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::terminal::TerminalBridge;
use tuirealm::{Application, AttrValue, Attribute, Component, Event, EventListenerCfg, Frame, MockComponent, NoUserEvent, Props, State, Sub, SubClause, SubEventClause, Update};

#[derive(Debug, PartialEq)]
pub enum Msg {}

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Id {
    FIRST
}

pub struct Model {
    pub app: Application<Id, Msg, NoUserEvent>,
    pub terminal: TerminalBridge,
}

impl Default for Model {
    fn default() -> Self {
        Self {
            app: Self::init_app(),
            terminal: TerminalBridge::new().expect("Cannot initialize terminal"),
        }
    }
}

impl Model {
    fn init_app() -> Application<Id, Msg, NoUserEvent> {
        let app: Application<Id, Msg, NoUserEvent> = Application::init(
            EventListenerCfg::default()
                .default_input_listener(Duration::from_millis(20))
                .poll_timeout(Duration::from_millis(10))
                .tick_interval(Duration::from_secs(1)),
        );
        app
    }
}

impl Update<Msg> for Model {
    fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
        None
    }
}

pub struct Label {
    props: Props,
}

impl Default for Label {
    fn default() -> Self {
        Self {
            props: Props::default(),
        }
    }
}

impl MockComponent for Label {
    fn view(&mut self, frame: &mut Frame, area: Rect) {
        
    }

    fn query(&self, attr: Attribute) -> Option<AttrValue> {
        self.props.get(attr)
    }

    fn attr(&mut self, attr: Attribute, value: AttrValue) {
        self.props.set(attr, value);
    }

    fn state(&self) -> State {
        State::None
    }

    fn perform(&mut self, _: Cmd) -> CmdResult {
        CmdResult::None
    }
}

impl Component<Msg, NoUserEvent> for Label {
    fn on(&mut self, msg: Event<NoUserEvent>) -> Option<Msg> {
        println!("Message: {:#?}", msg);

        None
    }
}

fn main() {
    let mut model = Model::default();
    // let _ = model.terminal.enter_alternate_screen();
    // let _ = model.terminal.enable_raw_mode(); // does nothing on termion

    let _ = model.app.mount(Id::FIRST, Box::new(Label::default()), vec![
        Sub::new(
            SubEventClause::Any,
            SubClause::Always,
        ),
    ]);

    loop {
        // Tick
        match model.app.tick(PollStrategy::Once) {
            Err(err) => {
                eprintln!("ERROR: {:#?}", err);
                break;
            }
            Ok(messages) => {
                eprintln!("After Tick");
            }
        }

        thread::sleep(Duration::from_secs(1));
    }
    // let _ = model.terminal.leave_alternate_screen();
    // let _ = model.terminal.disable_raw_mode();
    // let _ = model.terminal.clear_screen();
}
Log output + some tuirealm inner logs

Note that this is the output over multiple seconds, where a tick should happen every second

LOOP
TERMION WAIT
After Tick
After Tick
After Tick
After Tick
After Tick
After Tick
After Tick
After Tick
After Tick
After Tick

Note that there is no Message: Tick

Git Diff
diff --git a/Cargo.toml b/Cargo.toml
index 17cd154..a4ea2f3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,3 +48,7 @@ path = "examples/demo/demo.rs"
 [[example]]
 name = "user-events"
 path = "examples/user_events/user_events.rs"
+
+[[example]]
+name = "termion-test"
+path = "examples/demo copy/termion-test.rs"
diff --git a/examples/demo copy/termion-test.rs b/examples/demo copy/termion-test.rs
new file mode 100644
index 0000000..2d815a0
--- /dev/null
+++ b/examples/demo copy/termion-test.rs	
@@ -0,0 +1,127 @@
+extern crate tuirealm;
+
+use std::thread;
+use std::time::Duration;
+
+use tui::layout::Rect;
+use tuirealm::application::PollStrategy;
+use tuirealm::command::{Cmd, CmdResult};
+use tuirealm::terminal::TerminalBridge;
+use tuirealm::{Application, AttrValue, Attribute, Component, Event, EventListenerCfg, Frame, MockComponent, NoUserEvent, Props, State, Sub, SubClause, SubEventClause, Update};
+
+#[derive(Debug, PartialEq)]
+pub enum Msg {}
+
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+pub enum Id {
+    FIRST
+}
+
+pub struct Model {
+    pub app: Application<Id, Msg, NoUserEvent>,
+    pub terminal: TerminalBridge,
+}
+
+impl Default for Model {
+    fn default() -> Self {
+        Self {
+            app: Self::init_app(),
+            terminal: TerminalBridge::new().expect("Cannot initialize terminal"),
+        }
+    }
+}
+
+impl Model {
+    fn init_app() -> Application<Id, Msg, NoUserEvent> {
+        let app: Application<Id, Msg, NoUserEvent> = Application::init(
+            EventListenerCfg::default()
+                .default_input_listener(Duration::from_millis(20))
+                .poll_timeout(Duration::from_millis(10))
+                .tick_interval(Duration::from_secs(1)),
+        );
+        app
+    }
+}
+
+impl Update<Msg> for Model {
+    fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
+        None
+    }
+}
+
+pub struct Label {
+    props: Props,
+}
+
+impl Default for Label {
+    fn default() -> Self {
+        Self {
+            props: Props::default(),
+        }
+    }
+}
+
+impl MockComponent for Label {
+    fn view(&mut self, frame: &mut Frame, area: Rect) {
+        
+    }
+
+    fn query(&self, attr: Attribute) -> Option<AttrValue> {
+        self.props.get(attr)
+    }
+
+    fn attr(&mut self, attr: Attribute, value: AttrValue) {
+        self.props.set(attr, value);
+    }
+
+    fn state(&self) -> State {
+        State::None
+    }
+
+    fn perform(&mut self, _: Cmd) -> CmdResult {
+        CmdResult::None
+    }
+}
+
+impl Component<Msg, NoUserEvent> for Label {
+    fn on(&mut self, msg: Event<NoUserEvent>) -> Option<Msg> {
+        println!("Message: {:#?}", msg);
+
+        None
+    }
+}
+
+fn main() {
+    let mut model = Model::default();
+    // let _ = model.terminal.enter_alternate_screen();
+    // let _ = model.terminal.enable_raw_mode(); // does nothing on termion
+
+    let _ = model.app.mount(Id::FIRST, Box::new(Label::default()), vec![
+        Sub::new(
+            SubEventClause::Any,
+            SubClause::Always,
+        ),
+    ]);
+
+    loop {
+        // Tick
+        match model.app.tick(PollStrategy::Once) {
+            Err(err) => {
+                eprintln!("ERROR: {:#?}", err);
+                break;
+            }
+            Ok(messages) => {
+                eprintln!("After Tick");
+            }
+        }
+
+        thread::sleep(Duration::from_secs(1));
+    }
+    // let _ = model.terminal.leave_alternate_screen();
+    // let _ = model.terminal.disable_raw_mode();
+    // let _ = model.terminal.clear_screen();
+}
diff --git a/src/adapter/termion/listener.rs b/src/adapter/termion/listener.rs
index 117b930..18f5504 100644
--- a/src/adapter/termion/listener.rs
+++ b/src/adapter/termion/listener.rs
@@ -35,6 +35,7 @@ where
     U: Eq + PartialEq + Clone + PartialOrd + Send + 'static,
 {
     fn poll(&mut self) -> ListenerResult<Option<Event<U>>> {
+        eprintln!("TERMION WAIT");
         match stdin().events().next() {
             Some(Ok(ev)) => Ok(Some(Event::from(ev))),
             Some(Err(_)) => Err(ListenerError::PollFailed),
diff --git a/src/listener/worker.rs b/src/listener/worker.rs
index 165f76c..aea6bf6 100644
--- a/src/listener/worker.rs
+++ b/src/listener/worker.rs
@@ -152,6 +152,7 @@ where
     /// thread run method
     pub(super) fn run(&mut self) {
         loop {
+            eprintln!("LOOP");
             // Check if running or send_error has occurred
             if !self.running() {
                 break;
@@ -165,6 +166,7 @@ where
             if self.poll().is_err() {
                 break;
             }
+            eprintln!("AFTER POLL");
             // Tick
             if self.should_tick() && self.send_tick().is_err() {
                 break;

cmd: cargo run --example=termion-test --no-default-features --features=tui,termion

Expected behaviour

termion to not be blocking

Environment

  • OS: Linux Manjaro 24.1.4
  • Architecture x86_64
  • Rust version: 1.78
  • tui-realm version: 40ee05c

Additional information

Also from what i can tell, termion does not provide any way to do this non-blockingly, either requiring to be completely removed or be put into a separate thread

@hasezoey
Copy link
Contributor Author

hasezoey commented May 5, 2024

slight update a day later: updated the script as i had noticed that the key events were only going to the components directly, not returned from .poll; this does not change result though that termion is still blocking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants