piotrmurach/tty-reader

getting double/triple/etc events via Reader#on on subsequent Reader#read_keypress

pjvleeuwen opened this issue · 4 comments

It seems that after calling Reader#read_keypress a new Reader needs to be created, otherwise we get double events on second read_keypress, triple events on third read_keypress, etc. This does not seem intended to me. With events I mean events via the Reader#on listeners.

So my question is: is this intentional? And: did I miss this part in the ReadMe.md?

Example where I ran into this (not cleaned up for this case specific, sorry, have to run and wanted to share what I saw anyway):

require 'tty-screen'
require 'tty-box'
require 'tty-reader'

class Test
  def initialize
    @reader = ::TTY::Reader.new
    @x = 10
    @y = 10
  end

  def redraw
    print ::TTY::Box.frame(width: ::TTY::Screen.width, height: ::TTY::Screen.height, left: 0, top: 0)
    print ::TTY::Box.frame("x = #{@x}\ny = #{@y}", width: 10, height: 10, left: @x, top: @y)
  end

  def draw
    redraw
    loop do
      # @reader = ::TTY::Reader.new
      @reader.on(:keyleft)   { |event| @x -= 1 }
      @reader.on(:keyright)  { |event| @x += 1 }
      @reader.on(:keyup)     { |event| @y -= 1 }
      @reader.on(:keydown)   { |event| @y += 1 }
      @reader.on(:keyescape) { |event| break }
      @reader.read_keypress
      @reader.cursor.clear_screen
      redraw
    end
  end
end

Test.new.draw

Describe your environment

  • OS version: Ubuntu 18.04.3 LTS
  • Ruby version: ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux] (in docker, FROM ruby:2.6.5)
  • TTY version:
gem list tty

*** LOCAL GEMS ***

tty-box (0.5.0)
tty-color (0.5.0)
tty-cursor (0.7.0)
tty-pager (0.12.1)
tty-prompt (0.20.0)
tty-reader (0.7.0)
tty-screen (0.7.0)
tty-which (0.4.1)

The above described behavior can be seen here (screencast):
https://gofile.io/?c=h9yHhT

It was uploaded without account. We can remove it with this code:
lcFCbnjtgykQntXeOzok

I don't believe this is an issue with tty-reader here.

The reason for your issue is that you register to listen for an event in an infinite loop. Each time you run through the loop, you yet another event to listen for and on and on. So, for example, the :keyleft internal stores all registered listeners in an array, which in your case are { |event| @x -= 1 } procs.

I'd suggest to change your code to register each key event listener only once:

class Test
  def initialize
    @reader = ::TTY::Reader.new
    @reader.on(:keyleft)   { |event| @x -= 1 }
    @reader.on(:keyright)  { |event| @x += 1 }
    @reader.on(:keyup)     { |event| @y -= 1 }
    @reader.on(:keydown)   { |event| @y += 1 }
    @reader.on(:keyescape) { |event| break }
    @x = 10
    @y = 10
  end

  def draw
    redraw
    loop do
      @reader.read_keypress
      @reader.cursor.clear_screen
      redraw
    end
  end
end

I should have seen that... doh (><)
Thanks, and lets pretend I never reported this...
Cheers