vizcore 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +66 -648
- data/docs/assets/playground-worker.js +373 -0
- data/docs/assets/playground.css +440 -0
- data/docs/assets/playground.js +652 -0
- data/docs/index.html +2 -1
- data/docs/playground.html +81 -0
- data/docs/shape_dsl.md +269 -0
- data/frontend/index.html +50 -2
- data/frontend/src/audio-inspector.js +9 -0
- data/frontend/src/custom-shape-param-controls.js +106 -0
- data/frontend/src/live-controls.js +219 -7
- data/frontend/src/main.js +703 -45
- data/frontend/src/mapping-target-selector.js +109 -0
- data/frontend/src/midi-learn.js +22 -2
- data/frontend/src/performance-monitor.js +137 -1
- data/frontend/src/renderer/engine.js +401 -11
- data/frontend/src/renderer/layer-manager.js +490 -75
- data/frontend/src/runtime-control-preset.js +44 -0
- data/frontend/src/scene-patches.js +159 -0
- data/frontend/src/shader-error-overlay.js +1 -0
- data/frontend/src/shape-editor-controls.js +157 -0
- data/frontend/src/visuals/geometry.js +425 -27
- data/frontend/src/visuals/image-renderer.js +19 -0
- data/frontend/src/visuals/particle-system.js +10 -0
- data/frontend/src/visuals/shape-renderer.js +488 -0
- data/frontend/src/visuals/spectrogram-renderer.js +14 -0
- data/frontend/src/visuals/svg-arc.js +104 -0
- data/frontend/src/visuals/text-renderer.js +13 -0
- data/frontend/src/websocket-client.js +6 -0
- data/lib/vizcore/analysis/adaptive_normalizer.rb +20 -2
- data/lib/vizcore/analysis/bpm_estimator.rb +18 -8
- data/lib/vizcore/analysis/feature_recorder.rb +117 -7
- data/lib/vizcore/analysis/feature_replay.rb +48 -9
- data/lib/vizcore/analysis/pipeline.rb +258 -9
- data/lib/vizcore/analysis/tap_tempo.rb +17 -2
- data/lib/vizcore/audio/calibration.rb +156 -0
- data/lib/vizcore/audio/file_input.rb +28 -0
- data/lib/vizcore/audio/input_manager.rb +36 -1
- data/lib/vizcore/audio/midi_input.rb +5 -0
- data/lib/vizcore/audio/ring_buffer.rb +22 -0
- data/lib/vizcore/audio.rb +1 -0
- data/lib/vizcore/cli/dsl_reference.rb +65 -9
- data/lib/vizcore/cli/plugin_checker.rb +93 -0
- data/lib/vizcore/cli/scene_diagnostics.rb +2 -2
- data/lib/vizcore/cli/scene_inspector.rb +35 -1
- data/lib/vizcore/cli/scene_validator.rb +573 -33
- data/lib/vizcore/cli/shader_template.rb +7 -2
- data/lib/vizcore/cli/shader_uniform_docs.rb +11 -0
- data/lib/vizcore/cli.rb +268 -15
- data/lib/vizcore/config.rb +40 -3
- data/lib/vizcore/control_preset.rb +29 -0
- data/lib/vizcore/deep_copy.rb +21 -0
- data/lib/vizcore/dsl/color_helpers.rb +155 -0
- data/lib/vizcore/dsl/engine.rb +219 -23
- data/lib/vizcore/dsl/layer_builder.rb +1072 -21
- data/lib/vizcore/dsl/layer_group_builder.rb +10 -12
- data/lib/vizcore/dsl/layout_helpers.rb +290 -0
- data/lib/vizcore/dsl/mapping_preset_builder.rb +41 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +549 -13
- data/lib/vizcore/dsl/mapping_transform_builder.rb +50 -0
- data/lib/vizcore/dsl/midi_map_executor.rb +219 -23
- data/lib/vizcore/dsl/reaction_builder.rb +1 -0
- data/lib/vizcore/dsl/scene_builder.rb +83 -13
- data/lib/vizcore/dsl/shader_source_resolver.rb +1 -10
- data/lib/vizcore/dsl/style_builder.rb +3 -0
- data/lib/vizcore/dsl/timeline_builder.rb +91 -8
- data/lib/vizcore/dsl/transition_controller.rb +157 -18
- data/lib/vizcore/dsl.rb +2 -0
- data/lib/vizcore/layer_catalog.rb +5 -2
- data/lib/vizcore/plugin_asset_policy.rb +55 -0
- data/lib/vizcore/project_manifest.rb +12 -2
- data/lib/vizcore/renderer/render_sequence.rb +104 -13
- data/lib/vizcore/renderer/scene_frame_source.rb +190 -12
- data/lib/vizcore/renderer/scene_serializer.rb +38 -0
- data/lib/vizcore/renderer/snapshot.rb +4 -3
- data/lib/vizcore/renderer/snapshot_renderer.rb +641 -23
- data/lib/vizcore/scene_trust.rb +31 -0
- data/lib/vizcore/server/frame_broadcaster.rb +513 -18
- data/lib/vizcore/server/rack_app.rb +151 -4
- data/lib/vizcore/server/runner.rb +697 -82
- data/lib/vizcore/server/websocket_handler.rb +236 -14
- data/lib/vizcore/server.rb +21 -0
- data/lib/vizcore/shape.rb +742 -0
- data/lib/vizcore/sync/osc_message.rb +66 -9
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +34 -0
- data/scripts/browser_capture.mjs +31 -2
- data/sig/vizcore.rbs +154 -4
- metadata +29 -3
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "set"
|
|
5
5
|
require "thread"
|
|
6
|
+
require "uri"
|
|
6
7
|
require_relative "../errors"
|
|
7
8
|
|
|
8
9
|
module Vizcore
|
|
@@ -12,6 +13,14 @@ module Vizcore
|
|
|
12
13
|
PROTOCOL_VERSION = "vizcore.frame.v1"
|
|
13
14
|
MAX_BUFFERED_FRAME_BYTES = 1_000_000
|
|
14
15
|
DROPPABLE_MESSAGE_TYPES = Set["audio_frame"].freeze
|
|
16
|
+
VALID_CLIENT_ROLES = Set["projector", "control", "monitor"].freeze
|
|
17
|
+
CONTROL_ROLE = "control".freeze
|
|
18
|
+
PROJECTOR_ROLE = "projector".freeze
|
|
19
|
+
MONITOR_ROLE = "monitor".freeze
|
|
20
|
+
READ_ONLY_ROLES = Set[PROJECTOR_ROLE, MONITOR_ROLE].freeze
|
|
21
|
+
READ_ONLY_ALLOWED_MESSAGE_TYPES = Set["latency_probe", "client_runtime_error"].freeze
|
|
22
|
+
LOW_BANDWIDTH_ROLES = Set[CONTROL_ROLE, MONITOR_ROLE].freeze
|
|
23
|
+
CONTROL_AUDIO_FRAME_INTERVAL = 4
|
|
15
24
|
|
|
16
25
|
class << self
|
|
17
26
|
# Rack endpoint for WebSocket upgrade handling.
|
|
@@ -25,7 +34,7 @@ module Vizcore
|
|
|
25
34
|
|
|
26
35
|
socket = websocket_klass.new(env, nil, ping: 15)
|
|
27
36
|
|
|
28
|
-
socket.on(:open) { register(socket) }
|
|
37
|
+
socket.on(:open) { register(socket, role: websocket_role_for_env(env)) }
|
|
29
38
|
socket.on(:close) { unregister(socket) }
|
|
30
39
|
socket.on(:message) { |event| handle_message(socket, event.data) }
|
|
31
40
|
|
|
@@ -78,6 +87,28 @@ module Vizcore
|
|
|
78
87
|
mutex.synchronize { @dropped_frame_count || 0 }
|
|
79
88
|
end
|
|
80
89
|
|
|
90
|
+
# @return [Hash] current websocket backpressure metrics for control/status surfaces.
|
|
91
|
+
def backpressure_status
|
|
92
|
+
mutex.synchronize do
|
|
93
|
+
clients = sockets.map { |socket| backpressure_client_status(socket) }
|
|
94
|
+
{
|
|
95
|
+
threshold_bytes: MAX_BUFFERED_FRAME_BYTES,
|
|
96
|
+
active_clients: sockets.size,
|
|
97
|
+
total: {
|
|
98
|
+
dropped_frames: socket_backpressure_totals[:dropped_frames],
|
|
99
|
+
dropped_payload_bytes: socket_backpressure_totals[:dropped_payload_bytes],
|
|
100
|
+
sent_frames: socket_backpressure_totals[:sent_frames],
|
|
101
|
+
sent_payload_bytes: socket_backpressure_totals[:sent_payload_bytes],
|
|
102
|
+
avg_payload_bytes: average(
|
|
103
|
+
socket_backpressure_totals[:sent_payload_bytes],
|
|
104
|
+
socket_backpressure_totals[:sent_frames]
|
|
105
|
+
)
|
|
106
|
+
},
|
|
107
|
+
clients: clients
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
81
112
|
# Register one inbound message handler for client -> server control messages.
|
|
82
113
|
#
|
|
83
114
|
# @yieldparam message [Hash]
|
|
@@ -103,32 +134,52 @@ module Vizcore
|
|
|
103
134
|
end
|
|
104
135
|
|
|
105
136
|
def send_message(socket, message, type:)
|
|
106
|
-
return
|
|
137
|
+
return unless should_send_to_socket?(socket, type: type)
|
|
138
|
+
|
|
139
|
+
message_bytes = message.bytesize
|
|
140
|
+
return if drop_for_backpressure?(socket, type, payload_bytes: message_bytes)
|
|
107
141
|
|
|
108
142
|
if event_machine_reactor_running?
|
|
109
|
-
EventMachine.schedule { safe_send(socket, message, type: type) }
|
|
143
|
+
EventMachine.schedule { safe_send(socket, message, type: type, payload_bytes: message_bytes) }
|
|
110
144
|
else
|
|
111
|
-
safe_send(socket, message, type: type)
|
|
145
|
+
safe_send(socket, message, type: type, payload_bytes: message_bytes)
|
|
112
146
|
end
|
|
113
147
|
end
|
|
114
148
|
|
|
115
|
-
def safe_send(socket, message, type:)
|
|
116
|
-
return if drop_for_backpressure?(socket, type)
|
|
149
|
+
def safe_send(socket, message, type:, payload_bytes:)
|
|
150
|
+
return if drop_for_backpressure?(socket, type, payload_bytes: payload_bytes)
|
|
117
151
|
|
|
118
152
|
socket.send(message)
|
|
153
|
+
record_message_sent(socket, payload_bytes)
|
|
119
154
|
rescue StandardError => e
|
|
120
155
|
set_last_error(e)
|
|
121
156
|
unregister(socket)
|
|
122
157
|
end
|
|
123
158
|
|
|
124
|
-
def drop_for_backpressure?(socket, type)
|
|
159
|
+
def drop_for_backpressure?(socket, type, payload_bytes: nil)
|
|
125
160
|
return false unless DROPPABLE_MESSAGE_TYPES.include?(type.to_s)
|
|
126
161
|
|
|
127
162
|
buffered_amount = socket_buffered_amount(socket)
|
|
128
|
-
|
|
163
|
+
begin
|
|
164
|
+
buffered_amount = Integer(buffered_amount) if buffered_amount
|
|
165
|
+
rescue ArgumentError, TypeError
|
|
166
|
+
buffered_amount = nil
|
|
167
|
+
end
|
|
129
168
|
|
|
130
|
-
|
|
131
|
-
|
|
169
|
+
if buffered_amount && buffered_amount > MAX_BUFFERED_FRAME_BYTES
|
|
170
|
+
mutex.synchronize do
|
|
171
|
+
refresh_socket_backpressure_metrics(socket, buffered_amount: buffered_amount)
|
|
172
|
+
increment_dropped_frame_count
|
|
173
|
+
increment_client_drop(socket, payload_bytes: payload_bytes)
|
|
174
|
+
end
|
|
175
|
+
return true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if buffered_amount
|
|
179
|
+
mutex.synchronize { refresh_socket_backpressure_metrics(socket, buffered_amount: buffered_amount) }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
false
|
|
132
183
|
end
|
|
133
184
|
|
|
134
185
|
def socket_buffered_amount(socket)
|
|
@@ -160,12 +211,21 @@ module Vizcore
|
|
|
160
211
|
nil
|
|
161
212
|
end
|
|
162
213
|
|
|
163
|
-
def register(socket)
|
|
164
|
-
mutex.synchronize
|
|
214
|
+
def register(socket, role: PROJECTOR_ROLE)
|
|
215
|
+
mutex.synchronize do
|
|
216
|
+
sockets << socket
|
|
217
|
+
socket_backpressure_metrics[socket_id(socket)] = default_backpressure_metrics
|
|
218
|
+
client_backpressure_metrics(socket)[:role] = normalize_client_role(role)
|
|
219
|
+
client_backpressure_metrics(socket)[:control_audio_frame_index] = 0
|
|
220
|
+
socket_backpressure_metrics[socket_id(socket)] = client_backpressure_metrics(socket)
|
|
221
|
+
end
|
|
165
222
|
end
|
|
166
223
|
|
|
167
224
|
def unregister(socket)
|
|
168
|
-
mutex.synchronize
|
|
225
|
+
mutex.synchronize do
|
|
226
|
+
sockets.delete(socket)
|
|
227
|
+
socket_backpressure_metrics.delete(socket_id(socket))
|
|
228
|
+
end
|
|
169
229
|
end
|
|
170
230
|
|
|
171
231
|
def each_socket(&block)
|
|
@@ -177,6 +237,158 @@ module Vizcore
|
|
|
177
237
|
@sockets ||= Set.new
|
|
178
238
|
end
|
|
179
239
|
|
|
240
|
+
def socket_backpressure_metrics
|
|
241
|
+
@socket_backpressure_metrics ||= {}
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def socket_backpressure_totals
|
|
245
|
+
@socket_backpressure_totals ||= {
|
|
246
|
+
dropped_frames: 0,
|
|
247
|
+
dropped_payload_bytes: 0,
|
|
248
|
+
sent_frames: 0,
|
|
249
|
+
sent_payload_bytes: 0
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def backpressure_client_status(socket)
|
|
254
|
+
metrics = client_backpressure_metrics(socket)
|
|
255
|
+
{
|
|
256
|
+
id: socket_id(socket).to_s,
|
|
257
|
+
role: metrics[:role],
|
|
258
|
+
buffered_amount: metrics[:buffered_amount],
|
|
259
|
+
peak_buffered_amount: metrics[:peak_buffered_amount],
|
|
260
|
+
dropped_frames: metrics[:dropped_frames],
|
|
261
|
+
dropped_payload_bytes: metrics[:dropped_payload_bytes],
|
|
262
|
+
sent_frames: metrics[:sent_frames],
|
|
263
|
+
sent_payload_bytes: metrics[:sent_payload_bytes],
|
|
264
|
+
avg_payload_bytes: average(metrics[:sent_payload_bytes], metrics[:sent_frames]),
|
|
265
|
+
estimated_lag_frames: estimated_lag_frames(
|
|
266
|
+
metrics[:buffered_amount],
|
|
267
|
+
metrics[:sent_payload_bytes],
|
|
268
|
+
metrics[:sent_frames]
|
|
269
|
+
),
|
|
270
|
+
last_payload_bytes: metrics[:last_payload_bytes]
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def socket_id(socket)
|
|
275
|
+
socket.object_id
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def client_backpressure_metrics(socket)
|
|
279
|
+
socket_backpressure_metrics.fetch(socket_id(socket)) do
|
|
280
|
+
socket_backpressure_metrics[socket_id(socket)] = default_backpressure_metrics
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def socket_role(socket)
|
|
285
|
+
client_backpressure_metrics(socket)[:role]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def should_send_to_socket?(socket, type:)
|
|
289
|
+
return true unless LOW_BANDWIDTH_ROLES.include?(socket_role(socket))
|
|
290
|
+
return true unless type.to_s == "audio_frame"
|
|
291
|
+
|
|
292
|
+
control_audio_frame_due?(socket)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def control_audio_frame_due?(socket)
|
|
296
|
+
metrics = client_backpressure_metrics(socket)
|
|
297
|
+
metrics[:control_audio_frame_index] = (metrics[:control_audio_frame_index] || 0) + 1
|
|
298
|
+
count = metrics[:control_audio_frame_index]
|
|
299
|
+
|
|
300
|
+
return true if count == 1
|
|
301
|
+
return true if (count % CONTROL_AUDIO_FRAME_INTERVAL).zero?
|
|
302
|
+
|
|
303
|
+
false
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def refresh_socket_backpressure_metrics(socket, buffered_amount: nil)
|
|
307
|
+
metrics = client_backpressure_metrics(socket)
|
|
308
|
+
amount = buffered_amount.nil? ? socket_buffered_amount(socket) : buffered_amount
|
|
309
|
+
return metrics unless amount
|
|
310
|
+
|
|
311
|
+
integer_amount = amount.to_i
|
|
312
|
+
metrics[:buffered_amount] = integer_amount
|
|
313
|
+
metrics[:peak_buffered_amount] = [metrics[:peak_buffered_amount], integer_amount].max
|
|
314
|
+
metrics
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def increment_client_drop(socket, payload_bytes: nil)
|
|
318
|
+
payload_bytes = Integer(payload_bytes || 0)
|
|
319
|
+
metrics = client_backpressure_metrics(socket)
|
|
320
|
+
metrics[:dropped_frames] += 1
|
|
321
|
+
metrics[:dropped_payload_bytes] += payload_bytes
|
|
322
|
+
socket_backpressure_totals[:dropped_frames] += 1
|
|
323
|
+
socket_backpressure_totals[:dropped_payload_bytes] += payload_bytes
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def record_message_sent(socket, payload_bytes)
|
|
327
|
+
payload_bytes = Integer(payload_bytes || 0)
|
|
328
|
+
buffered_amount = socket_buffered_amount(socket)
|
|
329
|
+
buffered_amount = Integer(buffered_amount) if buffered_amount
|
|
330
|
+
|
|
331
|
+
mutex.synchronize do
|
|
332
|
+
metrics = client_backpressure_metrics(socket)
|
|
333
|
+
metrics[:sent_frames] += 1
|
|
334
|
+
metrics[:sent_payload_bytes] += payload_bytes
|
|
335
|
+
metrics[:last_payload_bytes] = payload_bytes
|
|
336
|
+
socket_backpressure_totals[:sent_frames] += 1
|
|
337
|
+
socket_backpressure_totals[:sent_payload_bytes] += payload_bytes
|
|
338
|
+
refresh_socket_backpressure_metrics(socket, buffered_amount: buffered_amount) if buffered_amount
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def average(numerator, denominator)
|
|
343
|
+
return 0.0 if denominator.to_i <= 0
|
|
344
|
+
|
|
345
|
+
numerator.to_f / denominator.to_f
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def estimated_lag_frames(buffered_amount, payload_bytes, sent_frames)
|
|
349
|
+
avg_payload_bytes = average(payload_bytes, sent_frames)
|
|
350
|
+
return 0.0 if avg_payload_bytes <= 0.0
|
|
351
|
+
|
|
352
|
+
buffered_amount.to_f / avg_payload_bytes
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def default_backpressure_metrics
|
|
356
|
+
{
|
|
357
|
+
role: PROJECTOR_ROLE,
|
|
358
|
+
control_audio_frame_index: 0,
|
|
359
|
+
buffered_amount: 0,
|
|
360
|
+
peak_buffered_amount: 0,
|
|
361
|
+
dropped_frames: 0,
|
|
362
|
+
dropped_payload_bytes: 0,
|
|
363
|
+
sent_frames: 0,
|
|
364
|
+
sent_payload_bytes: 0,
|
|
365
|
+
last_payload_bytes: 0
|
|
366
|
+
}
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def websocket_role_for_env(env)
|
|
370
|
+
return PROJECTOR_ROLE unless env.is_a?(Hash)
|
|
371
|
+
|
|
372
|
+
role = query_param(String(env["QUERY_STRING"] || ""), "role")
|
|
373
|
+
normalize_client_role(role)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def query_param(query_string, key)
|
|
377
|
+
URI.decode_www_form(query_string).each do |entry_key, value|
|
|
378
|
+
return value if entry_key == key
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
nil
|
|
382
|
+
rescue StandardError
|
|
383
|
+
nil
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def normalize_client_role(role)
|
|
387
|
+
return PROJECTOR_ROLE unless VALID_CLIENT_ROLES.include?(role.to_s)
|
|
388
|
+
|
|
389
|
+
role.to_s
|
|
390
|
+
end
|
|
391
|
+
|
|
180
392
|
def mutex
|
|
181
393
|
@mutex ||= Mutex.new
|
|
182
394
|
end
|
|
@@ -186,7 +398,7 @@ module Vizcore
|
|
|
186
398
|
end
|
|
187
399
|
|
|
188
400
|
def increment_dropped_frame_count
|
|
189
|
-
|
|
401
|
+
@dropped_frame_count = (@dropped_frame_count || 0) + 1
|
|
190
402
|
end
|
|
191
403
|
|
|
192
404
|
def dispatch_message(message, socket)
|
|
@@ -194,12 +406,22 @@ module Vizcore
|
|
|
194
406
|
return unless handler
|
|
195
407
|
return unless message.is_a?(Hash)
|
|
196
408
|
|
|
409
|
+
type = message["type"] || message[:type]
|
|
410
|
+
role = socket_role(socket)
|
|
411
|
+
return unless client_message_allowed?(role: role, type: type)
|
|
412
|
+
|
|
197
413
|
handler.call(message, socket)
|
|
198
414
|
rescue StandardError => e
|
|
199
415
|
set_last_error(e)
|
|
200
416
|
nil
|
|
201
417
|
end
|
|
202
418
|
|
|
419
|
+
def client_message_allowed?(role:, type:)
|
|
420
|
+
return true unless READ_ONLY_ROLES.include?(role.to_s)
|
|
421
|
+
|
|
422
|
+
READ_ONLY_ALLOWED_MESSAGE_TYPES.include?(type.to_s)
|
|
423
|
+
end
|
|
424
|
+
|
|
203
425
|
def text_headers
|
|
204
426
|
{ "content-type" => "text/plain; charset=utf-8" }
|
|
205
427
|
end
|
data/lib/vizcore/server.rb
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "config"
|
|
4
|
+
|
|
3
5
|
module Vizcore
|
|
4
6
|
# Rack/WebSocket server runtime namespace.
|
|
5
7
|
module Server
|
|
8
|
+
# Start a Vizcore server from Ruby code.
|
|
9
|
+
#
|
|
10
|
+
# @param config [Vizcore::Config, Hash, nil] runtime config or Config keyword options
|
|
11
|
+
# @param output [#puts] stream used by the runner
|
|
12
|
+
# @param options [Hash] Config keyword options when `config` is nil
|
|
13
|
+
# @return [void]
|
|
14
|
+
def self.start(config = nil, output: $stdout, **options)
|
|
15
|
+
runtime_config = start_config(config, options)
|
|
16
|
+
Runner.new(runtime_config, output: output).run
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.start_config(config, options)
|
|
20
|
+
return Config.new(**options) if config.nil?
|
|
21
|
+
return config if config.is_a?(Config) && options.empty?
|
|
22
|
+
return Config.new(**config.merge(options)) if config.is_a?(Hash)
|
|
23
|
+
|
|
24
|
+
raise ArgumentError, "server start requires a Vizcore::Config or config options"
|
|
25
|
+
end
|
|
26
|
+
private_class_method :start_config
|
|
6
27
|
end
|
|
7
28
|
end
|
|
8
29
|
|