@@ -22,6 +22,43 @@ class CounterActor < SolidObjects::Actor
2222 end
2323 end
2424
25+ class BenchmarkConnection
26+ attr_reader :session_id
27+
28+ def initialize ( session_id )
29+ @session_id = session_id
30+ end
31+ end
32+
33+ class PlaymatActor < SolidObjects ::Actor
34+ actor_type "benchmark-playmat"
35+
36+ attribute :player , default : "unseated"
37+ attribute :player_controls , default : -> { [ ] }
38+ attribute :library_search , default : -> { [ ] }
39+ attribute :hands , default : -> { { } }
40+
41+ observable :player
42+ observable :player_controls
43+ observable :library_search
44+
45+ broadcast_payload :playmat_state do |actor , context |
46+ {
47+ "player" => actor . player ,
48+ "controls" => actor . player_controls ,
49+ "library" => actor . library_search ,
50+ "hand" => actor . hands . fetch ( context . session_id , [ ] )
51+ }
52+ end
53+
54+ def seat ( player :)
55+ self . player = player
56+ self . player_controls = %w[ untap draw ]
57+ self . library_search = %w[ Island Forest ]
58+ self . hands = hands . merge ( player => %w[ Island ] )
59+ end
60+ end
61+
2562 class << self
2663 # @rbs () -> Integer
2764 def count
@@ -189,6 +226,69 @@ def activation_cache
189226 worker &.stop
190227 end
191228
229+ # Compares how many browser requests one actor mutation costs across the
230+ # three delivery paths, and how long the server spends producing them.
231+ # @rbs () -> void
232+ def component_delivery
233+ require "action_controller"
234+ require "action_view"
235+ require "action_view/testing/resolvers"
236+
237+ SolidObjects . configuration . stream_signing_secret = "benchmark-secret"
238+ SolidObjects . configuration . authorize_query = -> ( **) { true }
239+ reference = PlaymatActor . ref ( "table" )
240+ reference . seat ( player : "alice" )
241+
242+ view_context = benchmark_view_context
243+ snapshot = SolidObjects ::ActorSnapshot . new ( reference )
244+ registrations = %w[ player player_controls library_search ] . map do |name |
245+ SolidObjects ::ComponentRegistration . issue (
246+ reference :,
247+ component_name : name ,
248+ component_key : nil ,
249+ dependencies : [ name ] ,
250+ locals : { } ,
251+ refresh_method : "morph" ,
252+ snapshot :,
253+ refresh_path : "/solid_objects/components" ,
254+ batch : "playmat"
255+ )
256+ end
257+
258+ individual = measure_delivery ( count ) do
259+ registrations . each do |registration |
260+ render_component ( registration , view_context )
261+ end
262+ end
263+ batched = measure_delivery ( count ) do
264+ current = SolidObjects ::ActorSnapshot . new ( reference )
265+ registrations . each do |registration |
266+ render_component ( registration , view_context , snapshot : current )
267+ end
268+ end
269+ payload = measure_delivery ( count ) do
270+ SolidObjects ::PayloadBroadcast . new (
271+ snapshot : SolidObjects ::ActorSnapshot . new ( reference ) ,
272+ name : "playmat_state" ,
273+ authorization_context : BenchmarkConnection . new ( "alice" )
274+ ) . call
275+ end
276+
277+ puts "three components changing in one mutation, #{ count } iterations"
278+ puts format (
279+ " individual refreshes: 3 requests, %.3fms per mutation" ,
280+ individual
281+ )
282+ puts format (
283+ " batched refresh: 1 request, %.3fms per mutation" ,
284+ batched
285+ )
286+ puts format (
287+ " state payload: 0 requests, %.3fms per mutation" ,
288+ payload
289+ )
290+ end
291+
192292 # @rbs () -> void
193293 def query_count
194294 CounterActor . ref ( "queries" ) . async ( :increment )
@@ -209,6 +309,37 @@ def query_count
209309
210310 private
211311
312+ # @rbs (ComponentRegistration, untyped, ?snapshot: ActorSnapshot?) -> untyped
313+ def render_component ( registration , view_context , snapshot : nil )
314+ SolidObjects ::ComponentRenderer . new (
315+ snapshot : snapshot || SolidObjects ::ActorSnapshot . new ( registration . reference ) ,
316+ registration :,
317+ view_context :,
318+ authorization_context : nil
319+ ) . call
320+ end
321+
322+ # @rbs (Integer) { () -> untyped } -> Float
323+ def measure_delivery ( iterations )
324+ yield
325+ elapsed = Benchmark . realtime { iterations . times { yield } }
326+ ( elapsed / iterations ) * 1_000
327+ end
328+
329+ # @rbs () -> untyped
330+ def benchmark_view_context
331+ resolver = ActionView ::FixtureResolver . new (
332+ "actors/solid_objects_benchmark/playmat_actor/_player.html.erb" => "<p><%= actor.player %></p>" ,
333+ "actors/solid_objects_benchmark/playmat_actor/_player_controls.html.erb" => "<ul><% actor.player_controls.each do |c| %><li><%= c %></li><% end %></ul>" ,
334+ "actors/solid_objects_benchmark/playmat_actor/_library_search.html.erb" => "<ul><% actor.library_search.each do |c| %><li><%= c %></li><% end %></ul>"
335+ )
336+ ActionView ::Base . with_empty_template_cache . new (
337+ ActionView ::LookupContext . new ( [ resolver ] ) ,
338+ { } ,
339+ nil
340+ )
341+ end
342+
212343 # @rbs () -> void
213344 def establish_connection
214345 database_url = ENV [ "SOLID_OBJECTS_DATABASE_URL" ]
0 commit comments