Class: Scarpe::CCInstance

Inherits:
Object
  • Object
show all
Includes:
Test::Helpers, Shoes::Log
Defined in:
lib/scarpe/cats_cradle.rb

Overview

This class defines the CatsCradle DSL. It also holds a "bag of fibers" with promises for when they should next resume.

Constant Summary collapse

EVENT_TYPES =

If we add "every" events, that's likely to complicate timing and event_promise handling.

[:init, :next_heartbeat, :next_redraw, :every_heartbeat, :every_redraw]

Constants included from Shoes::Log

Shoes::Log::DEFAULT_COMPONENT, Shoes::Log::DEFAULT_DEBUG_LOG_CONFIG, Shoes::Log::DEFAULT_LOG_CONFIG

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Test::Helpers

#with_env_vars

Methods included from Scarpe::Components::ProcessHelpers

#run_out_err_result

Methods included from Scarpe::Components::FileHelpers

#with_tempfile, #with_tempfiles

Methods included from Shoes::Log

configure_logger, #log_init, logger

Constructor Details

#initializeCCInstance

Returns a new instance of CCInstance.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/scarpe/cats_cradle.rb', line 18

def initialize
  log_init("CatsCradle")

  @waiting_fibers = []
  @event_promises = {}
  @shutdown = false

  @manager_fiber = Fiber.new do
    Fiber[:catscradle] = true

    loop do
      # A fiber can run briefly and then exit. It can run and then block on an API call.
      # These fibers return promises to indicate to CatsCradle when they can run again.
      # A fiber that is no longer #alive? is assumed to be successfully finished.
      @waiting_fibers.each do |fiber_data|
        next if !fiber_data[:promise].fulfilled? || !fiber_data[:fiber].alive? || @shutdown

        @log.debug("Resuming fiber with value #{fiber_data[:promise].returned_value.inspect}")
        result = fiber_data[:fiber].transfer fiber_data[:promise].returned_value

        # Dead fibers will be removed later, just leave it
        next unless fiber_data[:fiber].alive?

        case result
        when ::Scarpe::Promise
          fiber_data[:promise] = result
        else
          raise Scarpe::UnexpectedFiberTransferError, "Unexpected object returned from Fiber#transfer for still-living Fiber! #{result.inspect}"
        end
      end

      # Throw out dead fibers or those that will never wake
      @waiting_fibers.select! do |fiber_data|
        fiber_data[:fiber].alive? && !fiber_data[:promise].rejected?
      end

      # Done with this iteration
      Fiber.yield
    end
  end
end

Class Method Details

.instanceObject



14
15
16
# File 'lib/scarpe/cats_cradle.rb', line 14

def self.instance
  @instance ||= CCInstance.new
end

Instance Method Details

#active_fiber(&block) ⇒ Object



140
141
142
143
144
145
146
# File 'lib/scarpe/cats_cradle.rb', line 140

def active_fiber(&block)
  return if @shutdown

  p = ::Scarpe::Promise.new
  p.fulfilled!
  @waiting_fibers << { promise: p, fiber: cc_fiber(&block), on_event: nil, block: }
end

#dom_html(timeout: 1.0) ⇒ Object



166
167
168
# File 'lib/scarpe/cats_cradle.rb', line 166

def dom_html(timeout: 1.0)
  query_js_value("document.getElementById('wrapper-wvroot').innerHTML", timeout:)
end

#event_initObject

This needs to be called after the basic display service objects exist and we can find the control interface.



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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/scarpe/cats_cradle.rb', line 76

def event_init
  return if @cc_init_done

  @cc_init_done = true

  @control_interface = ::Shoes::DisplayService.display_service.control_interface
  @wrangler = @control_interface.wrangler

  cc_instance = self # ControlInterface#on_event does an instance eval. We'll reset self with another.

  @control_interface.on_event(:every_heartbeat) do
    cc_instance.instance_eval do
      p = @event_promises.delete(:next_heartbeat)
      p&.fulfilled!

      p = @event_promises.delete(:every_heartbeat)
      p&.fulfilled!

      # Reschedule on_every_heartbeat fibers for next heartbeat, too.
      # This fiber won't be called again by a heartbeat, though it may
      # continue if it waits on another promise.
      @waiting_fibers.select { |f| f[:on_event] == :every_heartbeat }.each do |f|
        on_event(:every_heartbeat, &f[:block])
      end

      # Give every ready fiber a chance to run once.
      @manager_fiber.resume unless @shutdown
    end unless @shutdown
  end

  @control_interface.on_event(:every_redraw) do
    cc_instance.instance_eval do
      p = @event_promises.delete(:next_redraw)
      p&.fulfilled!

      p = @event_promises.delete(:every_redraw)
      p&.fulfilled!

      # Reschedule on_every_redraw fibers for next redraw, too.
      @waiting_fibers.select { |f| f[:on_event] == :every_redraw }.each do |f|
        on_event(:every_redraw, &f[:block])
      end

      # Give every ready fiber a chance to run once.
      @manager_fiber.resume unless @shutdown
    end unless @shutdown
  end
end

#event_promise(event) ⇒ Object



129
130
131
# File 'lib/scarpe/cats_cradle.rb', line 129

def event_promise(event)
  @event_promises[event] ||= ::Scarpe::Promise.new
end

#fiber_startObject



125
126
127
# File 'lib/scarpe/cats_cradle.rb', line 125

def fiber_start
  @manager_fiber.resume unless @shutdown
end

#fully_updatedObject

This returns a promise, which can be waited on using wait()



162
163
164
# File 'lib/scarpe/cats_cradle.rb', line 162

def fully_updated
  @wrangler.promise_dom_fully_updated
end

#on_event(event, &block) ⇒ Object



133
134
135
136
137
138
# File 'lib/scarpe/cats_cradle.rb', line 133

def on_event(event, &block)
  raise Scarpe::UnknownEventTypeError, "Unknown event type: #{event.inspect}!" unless EVENT_TYPES.include?(event)
  return if @shutdown

  @waiting_fibers << { promise: event_promise(event), fiber: cc_fiber(&block), on_event: event, block: }
end

#query_js_promise(js_code, timeout: 1.0) ⇒ Object



177
178
179
# File 'lib/scarpe/cats_cradle.rb', line 177

def query_js_promise(js_code, timeout: 1.0)
  @wrangler.eval_js_async(js_code, timeout:)
end

#query_js_value(js_code, timeout: 1.0) ⇒ Object



170
171
172
173
174
175
# File 'lib/scarpe/cats_cradle.rb', line 170

def query_js_value(js_code, timeout: 1.0)
  js_promise = @wrangler.eval_js_async(js_code, timeout:)

  # This promise will return the string, so we can just pass it to #transfer
  @manager_fiber.transfer(js_promise)
end

#shut_down_shoes_codeObject



181
182
183
184
185
186
187
188
# File 'lib/scarpe/cats_cradle.rb', line 181

def shut_down_shoes_code
  if @shutdown
    exit 0
  end

  @shutdown = true
  ::Shoes::DisplayService.dispatch_event("destroy", nil)
end

#wait(promise) ⇒ Object



148
149
150
151
152
153
# File 'lib/scarpe/cats_cradle.rb', line 148

def wait(promise)
  raise(Scarpe::InvalidPromiseError, "Must supply a promise to wait!") unless promise.is_a?(::Scarpe::Promise)

  # Wait until this promise is complete before running again
  @manager_fiber.transfer(promise)
end

#yieldObject



155
156
157
158
159
# File 'lib/scarpe/cats_cradle.rb', line 155

def yield
  p = ::Scarpe::Promise.new
  p.fulfilled!
  @manager_fiber.transfer(p)
end