Class: Brut::TUI::EventLoop

Inherits:
Object
  • Object
show all
Defined in:
lib/brut/tui/event_loop.rb

Overview

An event loop used to power any TUI, including those that just print out messages. This is intended to be used across multiple threads, with this running on the "main" thread that is allowed to write to the screen.

Instance Method Summary collapse

Constructor Details

#initialize(tick: true) ⇒ EventLoop

Create a new EventLoop.

Parameters:

  • tick (true|false) (defaults to: true)

    if true, a "tick" event is fired every 50ms to allow progress spinners to animate.



9
10
11
12
13
14
15
16
17
# File 'lib/brut/tui/event_loop.rb', line 9

def initialize(tick: true)

  @queue = Deque.new

  @queue << Brut::TUI::Events::EventLoopStarted.new

  @event_bus = Brut::TUI::Events::EventBus.new
  @tick = tick
end

Instance Method Details

#<<(event) ⇒ Object

Queue an event for later processing. This is safe to do from another thread.

Parameters:



22
23
24
# File 'lib/brut/tui/event_loop.rb', line 22

def <<(event)
  @queue << event
end

#runObject

Start the event loop. Don't call this more than once. It will block and continue running until an event is received that returns true for Brut::TUI::Events::BaseEvent#exit? or Brut::TUI::Events::BaseEvent#drain_then_exit?.

If Brut::TUI::Events::BaseEvent#exit? returns true, the loop is exited and any events left in the queue are unprocessed, essentially ignored/discarded.

If Brut::TUI::Events::BaseEvent#drain_then_exit? returns true, anything currently in the queue is processed before exiting. If any subscriber adds events to the queue they will not be processed. If no event handler produces errors, the CLI should exit cleanly. If, however, any of the event handlers themselves produce errors, those errors will be handled, but the script will exit nonzero.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/brut/tui/event_loop.rb', line 67

def run
  debug "EventLoop: starting"
  start = Time.now
  loop do
    event = @queue.pop(timeout: 0.05)
    debug "EventLoop: got event #{event.class.name}\n                    #{event.inspect}"
    if event
      errors = @event_bus.notify(event)
      debug "EventLoop: notified subscribers of #{event.class.name}, got #{errors.length} errors"

      # future screen rendering here

      handle_errors_from_notify(errors)

      if event.drain_then_exit?
        debug "EventLoop: exiting"
        all_errors = []
        @queue.size.times do
          event = @queue.pop(timeout: 0.05)
          if event
            debug "EventLoop (exiting): got event #{event.class.name}\n                    #{event.inspect}"
            errors = @event_bus.notify(event)
            all_errors = all_errors + errors
            # future screen rendering here
          end
        end
        handle_errors_from_notify(all_errors, immediate: true)
        break
      elsif event.exit?
        debug "EventLoop: exiting"
        break
      end
    end
    if @tick
      errors = @event_bus.notify(Brut::TUI::Events::Tick.new(Time.now - start))
      handle_errors_from_notify(errors)
    end
  end
end

#subscribe(event_class, subscriber) ⇒ Object

Subscribe to a specific event. This requires that subscriber implement the handler method exposed by Brut::TUI::Events::BaseEvent.handler_method_name. The method's arguments must be one of three forms:

  • no-args (or a single :rest arg, like (*)) - the method is called when the event occurs, no arguments are passed, thus no information about the event is available.
  • single required arg (e.g. (event)) - the event instance is passed.
  • keyword args (e.g. (description:, command:)) - the event is splatted via deconstruct_keys and passed in for any keyword arg. If required keyword args aren't available from the event, an exception is raised. If optional keyword args aren't available from the event, their default values are provided. Each event should document what keyword args are available.

In all cases, if the method raises an exception, it is captured and sent as a Brut::TUI::Events::Exception event, potentially to be handled by other subscribers. See #run for how this interacts with the loop.

Parameters:

  • event_class (Class)

    the event subscriber should be notified about. This should be a subclass of Brut::TUI::Events::BaseEvent.

  • subscriber (Object)

    object to be notified about the given event.



41
42
43
# File 'lib/brut/tui/event_loop.rb', line 41

def subscribe(event_class, subscriber)
  @event_bus.subscribe(event_class, subscriber)
end

#subscribe_to_all(subscriber) ⇒ Object

Subscribe to all events. subscriber will only be notified if it implements an event's Brut::TUI::Events::BaseEvent.handler_method_name or if the subscriber implements on_any_event. If both are implemented, only the more specific method is called. See #subscribe for a description of how the method is invoked. If a specific method is not provided, on_any_event is invoked with the event instance. There is no keyword splatting in this case.

Parameters:

  • subscriber (Object)

    object to be notified about the given event.



52
53
54
# File 'lib/brut/tui/event_loop.rb', line 52

def subscribe_to_all(subscriber)
  @event_bus.subscribe_to_all(subscriber)
end