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.

[View source]

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

[View source]

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

[View source]

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

[View source]

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.

[View source]

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

[View source]

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

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

#fiber_startObject

[View source]

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()

[View source]

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

[View source]

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

[View source]

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

[View source]

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

[View source]

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

[View source]

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

[View source]

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