Class: Plushie::Runtime
- Inherits:
-
Object
- Object
- Plushie::Runtime
- Includes:
- Commands, Subscriptions
- Defined in:
- lib/plushie/runtime.rb,
lib/plushie/runtime/windows.rb,
lib/plushie/runtime/commands.rb,
lib/plushie/runtime/subscriptions.rb
Overview
Core event loop for Plushie applications.
Owns the Elm-style update cycle: event -> model -> view -> diff -> patch. Processes events sequentially from a thread-safe queue. All state is owned by the runtime thread: no shared mutable state.
Defined Under Namespace
Modules: Commands, Subscriptions, Windows
Constant Summary collapse
- SDK_LOG_LEVELS =
{ off: Logger::UNKNOWN, error: Logger::ERROR, warning: Logger::WARN, warn: Logger::WARN, info: Logger::INFO, debug: Logger::DEBUG, trace: Logger::DEBUG }.freeze
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Accessors for Runtime submodules (Windows, etc.).
-
#logger ⇒ Object
readonly
Accessors for Runtime submodules (Windows, etc.).
-
#model ⇒ Object
readonly
Accessors for Runtime submodules (Windows, etc.).
Instance Method Summary collapse
-
#await_async(tag, timeout: 5) ⇒ :ok
Waits for an async task with the given tag to complete.
-
#bridge_send_window_op(op, window_id, settings = {}) ⇒ Object
private
Send a window operation to the renderer via the bridge.
-
#get_diagnostics ⇒ Array<Event::System>
Returns and clears accumulated prop validation diagnostics.
-
#get_focused ⇒ String?
Returns the ID of the currently focused widget, or nil.
-
#initialize(app:, transport: :spawn, format: :msgpack, daemon: false, binary: nil, log_level: DEFAULT_LOG_LEVEL, token: nil, dev: false, dev_dirs: nil) ⇒ Runtime
constructor
A new instance of Runtime.
-
#interact(action, selector = nil, payload = {}, timeout: 5) ⇒ Array<Object>
Simulate a user interaction with a widget.
-
#register_effect_stub(kind, response, timeout: 5) ⇒ Object
Register an effect stub with the renderer.
-
#run ⇒ Object
Run the event loop in the calling thread (blocking).
-
#start ⇒ Runtime
Start the event loop in a background thread.
-
#stop ⇒ Object
Stop a background runtime.
-
#unregister_effect_stub(kind, timeout: 5) ⇒ Object
Remove a previously registered effect stub.
-
#view_error? ⇒ Boolean
Returns true if the most recent view/render call failed, meaning the tree is stale and does not reflect the current model state.
Constructor Details
#initialize(app:, transport: :spawn, format: :msgpack, daemon: false, binary: nil, log_level: DEFAULT_LOG_LEVEL, token: nil, dev: false, dev_dirs: nil) ⇒ Runtime
Returns a new instance of Runtime.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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 106 |
# File 'lib/plushie/runtime.rb', line 47 def initialize(app:, transport: :spawn, format: :msgpack, daemon: false, binary: nil, log_level: DEFAULT_LOG_LEVEL, token: nil, dev: false, dev_dirs: nil) validate_app!(app) validate_transport!(transport) @app = app @transport = transport @format = format @daemon = daemon @binary = binary @log_level_explicit = !log_level.equal?(DEFAULT_LOG_LEVEL) @log_level = renderer_log_level(log_level) @token = token @dev = dev @dev_dirs = dev_dirs @event_queue = BoundedQueue.new @model = nil @previous_tree = nil @bridge = nil @dev_server = nil @running = false @timer_scheduler = TimerScheduler.new @async_tasks = {} # tag -> {thread:, nonce:} @pending_effects = {} # wire_id -> timer_thread @effect_tags = {} # tag -> wire_id @effect_ids = {} # wire_id -> tag @effect_kinds = {} # wire_id -> kind string # Coalescable event buffer. High-frequency events (move, scroll, # scrolled, resize) are stored here, keyed by (window_id, id, type), # and flushed at the next event_queue iteration. Last-wins so # bursts collapse to the latest value, except scroll events which # accumulate their delta_x / delta_y. Keeps update() from drowning # in pointer-move events when the host's update() is slow. @pending_coalesce = {} # [window_id, id, type] -> event @coalesce_order = [] # insertion order for deterministic flush @pending_timers = {} # event_key -> {thread:, nonce:} @subscriptions = {} # sub_key -> {sub_type:, ...} @subscription_keys = [] # sorted keys for short-circuit @canvas_widgets = {} # scoped_id -> CanvasWidget::RegistryEntry @consecutive_errors = 0 @consecutive_view_errors = 0 @widget_statuses = {} # id -> status string @focused_widget_id = nil # currently focused widget ID @memo_cache = {} # : Hash[untyped, untyped] @diagnostics = [] # accumulated prop validation diagnostics @diagnostics_mutex = Mutex.new @dispatch_depth = 0 # Command.dispatch chain position @pending_runtime_events = [] # : Array[untyped] @pending_stub_acks = {} # kind -> Queue (for sync ack round-trip) @pending_await_async = {} # tag -> Queue (for sync await) @pending_interact = nil # {id:, action:, selector:, result_queue:, timeout_timer:} for current interact @tracked_windows = Set.new # active window IDs @restarting = false @runtime_thread = nil @logger = Logger.new($stderr, level: sdk_log_level(log_level), progname: "plushie") end |
Instance Attribute Details
#app ⇒ Object (readonly)
Accessors for Runtime submodules (Windows, etc.).
35 36 37 |
# File 'lib/plushie/runtime.rb', line 35 def app @app end |
#logger ⇒ Object (readonly)
Accessors for Runtime submodules (Windows, etc.).
35 36 37 |
# File 'lib/plushie/runtime.rb', line 35 def logger @logger end |
#model ⇒ Object (readonly)
Accessors for Runtime submodules (Windows, etc.).
35 36 37 |
# File 'lib/plushie/runtime.rb', line 35 def model @model end |
Instance Method Details
#await_async(tag, timeout: 5) ⇒ :ok
Waits for an async task with the given tag to complete.
If the task has already completed, returns immediately. Otherwise blocks until the task finishes and its result has been processed through update.
251 252 253 254 255 256 257 258 259 260 |
# File 'lib/plushie/runtime.rb', line 251 def await_async(tag, timeout: 5) ack_queue = Thread::Queue.new enqueued = BoundedQueue.push(@event_queue, [:await_async, tag, ack_queue], timeout: Float(timeout)) raise Plushie::Error, format_await_async_timeout(tag) if enqueued.nil? result = ack_queue.pop(timeout: Float(timeout)) raise Plushie::Error, format_await_async_timeout(tag) if result.nil? :ok end |
#bridge_send_window_op(op, window_id, settings = {}) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Send a window operation to the renderer via the bridge.
213 214 215 216 |
# File 'lib/plushie/runtime.rb', line 213 def bridge_send_window_op(op, window_id, settings = {}) bridge = @bridge or return bridge.send_encoded(Protocol::Encode.encode_window_op(op, window_id, Encode.encode_props(settings), @format)) end |
#get_diagnostics ⇒ Array<Event::System>
Returns and clears accumulated prop validation diagnostics.
The renderer emits diagnostic events when validate_props is enabled. These are intercepted by the runtime (never delivered to update) and accumulated. This method atomically retrieves and clears the list.
187 188 189 190 191 192 193 |
# File 'lib/plushie/runtime.rb', line 187 def get_diagnostics @diagnostics_mutex.synchronize do result = @diagnostics.dup @diagnostics.clear result end end |
#get_focused ⇒ String?
Returns the ID of the currently focused widget, or nil. Focus is tracked automatically from renderer status events.
199 200 201 |
# File 'lib/plushie/runtime.rb', line 199 def get_focused @focused_widget_id end |
#interact(action, selector = nil, payload = {}, timeout: 5) ⇒ Array<Object>
Simulate a user interaction with a widget.
Sends an interact message through the bridge and blocks until the renderer responds. Used by scripting and automation: the test session has its own interact that runs synchronously within the test process.
230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/plushie/runtime.rb', line 230 def interact(action, selector = nil, payload = {}, timeout: 5) result_queue = Thread::Queue.new enqueued = BoundedQueue.push(@event_queue, [:interact, action, selector, payload, result_queue], timeout: Float(timeout)) raise Plushie::Error, format_interact_timeout(action, selector) if enqueued.nil? result = result_queue.pop(timeout: Float(timeout)) raise Plushie::Error, format_interact_timeout(action, selector) if result.nil? raise Plushie::Error, result[:error] if result.is_a?(Hash) && result[:error] result.is_a?(Hash) ? result.fetch(:events, []) : [] end |
#register_effect_stub(kind, response, timeout: 5) ⇒ Object
Register an effect stub with the renderer. Blocks until the renderer confirms the stub is stored.
153 154 155 156 157 158 159 160 161 162 |
# File 'lib/plushie/runtime.rb', line 153 def register_effect_stub(kind, response, timeout: 5) ack_queue = Thread::Queue.new enqueued = BoundedQueue.push(@event_queue, [:register_effect_stub, kind, response, ack_queue], timeout: Float(timeout)) raise Plushie::Error, "effect stub registration timed out for #{kind}" if enqueued.nil? result = ack_queue.pop(timeout: Float(timeout)) raise Plushie::Error, "effect stub registration timed out for #{kind}" if result.nil? :ok end |
#run ⇒ Object
Run the event loop in the calling thread (blocking).
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/plushie/runtime.rb', line 109 def run @runtime_thread = Thread.current start_bridge start_dev_server if @dev initialize_app event_loop ensure shutdown @runtime_thread = nil end |
#start ⇒ Runtime
Start the event loop in a background thread.
122 123 124 125 126 127 |
# File 'lib/plushie/runtime.rb', line 122 def start thread = Thread.new { run } thread.name = "plushie-runtime" @loop_thread = thread self end |
#stop ⇒ Object
Stop a background runtime.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/plushie/runtime.rb', line 130 def stop @running = false enqueued = BoundedQueue.push(@event_queue, :shutdown, timeout: 1) thread = @loop_thread return unless thread return if thread == Thread.current unless enqueued @event_queue.close if @event_queue.respond_to?(:close) stop_thread(thread, timeout: 5) return end joined = thread.join(5) stop_thread(thread, timeout: 1) if joined.nil? end |
#unregister_effect_stub(kind, timeout: 5) ⇒ Object
Remove a previously registered effect stub. Blocks until the renderer confirms the stub is removed.
169 170 171 172 173 174 175 176 177 178 |
# File 'lib/plushie/runtime.rb', line 169 def unregister_effect_stub(kind, timeout: 5) ack_queue = Thread::Queue.new enqueued = BoundedQueue.push(@event_queue, [:unregister_effect_stub, kind, ack_queue], timeout: Float(timeout)) raise Plushie::Error, "effect stub unregistration timed out for #{kind}" if enqueued.nil? result = ack_queue.pop(timeout: Float(timeout)) raise Plushie::Error, "effect stub unregistration timed out for #{kind}" if result.nil? :ok end |
#view_error? ⇒ Boolean
Returns true if the most recent view/render call failed, meaning the tree is stale and does not reflect the current model state.
207 208 209 |
# File 'lib/plushie/runtime.rb', line 207 def view_error? @consecutive_view_errors > 0 end |