Source file bonsai_driver.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
open! Core
module Incr = Ui_incr
module Stabilization_tracker = Bonsai.Private.Stabilization_tracker
module Action = Bonsai.Private.Action
module Instrumentation = Instrumentation
module Timer = Bonsai.Private.Timer
type ('m, 'action, 'action_input, 'r, 'timer) unpacked =
{ model_var : 'm Incr.Var.t
; default_model : 'm
; time_source : Bonsai.Time_source.t
; inject : 'action Action.t -> unit Ui_effect.t
; sexp_of_model : 'm -> Sexp.t
; sexp_of_action : 'action Action.t -> Sexp.t
; action_input_incr : 'action_input Incr.t
; action_input : 'action_input Incr.Observer.t
; apply_action :
schedule_event:(unit Ui_effect.t -> unit)
-> 'action_input Bonsai.Computation_status.t
-> 'm
-> 'action Action.t
-> 'm
; result : 'r Incr.Observer.t
; result_incr : 'r Incr.t
; lifecycle : Bonsai.Private.Lifecycle.Collection.t Incr.Observer.t
; lifecycle_incr : Bonsai.Private.Lifecycle.Collection.t Incr.t
; queue : 'action Action.t Queue.t
; mutable last_lifecycle : Bonsai.Private.Lifecycle.Collection.t
; mutable print_actions : bool
; stabilization_tracker : 'action Stabilization_tracker.t
; running_computation : 'r Bonsai.Private.Computation.t
; instrumentation :
(Instrumentation.Timeable_event.t, 'timer) Bonsai.Private.Instrumentation.Config.t
;
timer : 'a. Instrumentation.Timeable_event.t -> f:(unit -> 'a) -> 'a
}
type 'r t = T : (_, _, _, 'r, _) unpacked -> 'r t
let assert_type_equalities
(T a : _ Bonsai.Private.Computation.packed_info)
(T b : _ Bonsai.Private.Computation.packed_info)
=
let T =
Bonsai.Private.Meta.Model.Type_id.same_witness_exn a.model.type_id b.model.type_id
in
let T = Bonsai.Private.Action.Type_id.same_witness_exn a.action b.action in
()
;;
let assert_unoptimized_type_equalities ~time_source computation =
let unoptimized_info =
Bonsai.Private.gather
~recursive_scopes:Bonsai.Private.Computation.Recursive_scopes.empty
~time_source
computation
in
assert_type_equalities unoptimized_info unoptimized_info
;;
let[@inline never] define_ui_effect (type action) () =
let queue = Queue.create () in
let module A =
Ui_effect.Define (struct
module Action = struct
type t = action Action.t
end
let handle value ~on_exn:_ = Queue.enqueue queue value
end)
in
let inject = A.inject in
inject, queue
;;
let create_direct
(type r)
~(here : [%call_pos])
?(optimize = true)
~(instrumentation :
(Instrumentation.Timeable_event.t, _) Bonsai.Private.Instrumentation.Config.t)
~time_source
(unoptimized_computation : r Bonsai.Private.Computation.t)
: r t
=
let { Timer.time } =
Timer.create
~start_timer:instrumentation.start_timer
~stop_timer:instrumentation.stop_timer
in
let am_running_bonsai_test =
am_running_test
&& (String.is_prefix here.pos_fname ~prefix:{|lib/bonsai/test/of_bonsai_itself|}
|| String.is_prefix
here.pos_fname
~prefix:{|lib/bonsai/web_test/of_bonsai_itself|})
in
if am_running_bonsai_test
then assert_unoptimized_type_equalities ~time_source unoptimized_computation;
let running_computation =
time Preprocess ~f:(fun () ->
if optimize
then Bonsai.Private.pre_process unoptimized_computation
else unoptimized_computation)
in
let optimized_info =
time Gather ~f:(fun () ->
Bonsai.Private.gather
running_computation
~recursive_scopes:Bonsai.Private.Computation.Recursive_scopes.empty
~time_source)
in
let (T
({ model =
{ default = default_model; sexp_of = sexp_of_model; equal = _; type_id = _ }
; input = _
; apply_action
; action = _
; run = _
; reset = _
; may_contain = _
} as computation_info))
=
optimized_info
in
if am_running_bonsai_test then assert_type_equalities optimized_info optimized_info;
let environment = Bonsai.Private.Environment.empty in
let starting_model = default_model in
let model_var = Incr.Var.create starting_model in
let create_polymorphic
(type action action_input)
(computation : r Bonsai.Private.Computation.t)
(computation_info :
(_, action, action_input, r, unit) Bonsai.Private.Computation.info)
apply_action
: r t
=
let _ : _ = define_ui_effect () in
let inject, queue = define_ui_effect () in
let sexp_of_action = Action.Type_id.to_sexp computation_info.action in
let result_incr, result, action_input_incr, action_input, lifecycle_incr, lifecycle =
time Run_eval_fun ~f:(fun () ->
let%pattern_bind.Ui_incr result_incr, action_input_incr, lifecycle_incr =
let instrumentation =
{ instrumentation with
start_timer = (fun s -> instrumentation.start_timer (Profiling_entry s))
}
in
Bonsai.Private.Instrumentation.create_computation_with_instrumentation
instrumentation
~f:(fun run ->
let snapshot, () =
run
~environment
~fix_envs:Bonsai.Private.Environment.Recursive.empty
~path:Bonsai.Private.Path.empty
~model:(Incr.Var.watch model_var)
~inject
|> Bonsai.Private.Trampoline.run
in
Ui_incr.map3
(Bonsai.Private.Snapshot.result snapshot)
(Bonsai.Private.Input.to_incremental
(Bonsai.Private.Snapshot.input snapshot))
(Bonsai.Private.Snapshot.lifecycle_or_empty ~here:[%here] snapshot)
~f:(fun result_incr action_input_incr lifecycle_incr ->
result_incr, action_input_incr, lifecycle_incr))
~recursive_scopes:Bonsai.Private.Computation.Recursive_scopes.empty
~time_source
~computation
computation_info
in
let result = Incr.observe result_incr in
let action_input = Incr.observe action_input_incr in
let lifecycle = Incr.observe lifecycle_incr in
result_incr, result, action_input_incr, action_input, lifecycle_incr, lifecycle)
in
let stabilization_tracker = Stabilization_tracker.empty () in
time First_stabilization ~f:(fun () ->
Incr.stabilize ();
Stabilization_tracker.mark_stabilization stabilization_tracker);
T
{ model_var
; default_model
; time_source
; inject
; action_input
; action_input_incr
; apply_action = apply_action ~inject
; result
; result_incr
; sexp_of_model
; sexp_of_action
; lifecycle
; lifecycle_incr
; queue
; last_lifecycle = Bonsai.Private.Lifecycle.Collection.empty
; print_actions = false
; stabilization_tracker
; running_computation
; instrumentation
; timer = time
}
in
create_polymorphic running_computation computation_info apply_action
;;
let create
(type r)
~(here : [%call_pos])
?(optimize = true)
~(instrumentation : _ Bonsai.Private.Instrumentation.Config.t)
~time_source
(computation : local_ Bonsai.graph -> r Bonsai.t)
=
let timer =
Timer.create
~start_timer:(fun event ->
let event =
match event with
| `Graph_application -> Instrumentation.Timeable_event.Graph_application
| `Preprocess -> Preprocess
| `Gather -> Gather
in
instrumentation.start_timer event)
~stop_timer:instrumentation.stop_timer
in
Timer.set_timer ~timer;
let graph_applied =
timer.time `Graph_application ~f:(fun () ->
Bonsai.Private.top_level_handle computation)
in
create_direct ~here ~optimize ~instrumentation ~time_source graph_applied
;;
let schedule_event _ t =
Ui_effect.Expert.handle t ~on_exn:(fun exn ->
Exn.reraise exn "Unhandled exception raised in effect")
;;
let has_before_display_events (T t) =
let lifecycle = t.lifecycle |> Incr.Observer.value_exn in
Bonsai.Private.Lifecycle.Collection.get_before_display
~old:Bonsai.Private.Lifecycle.Collection.empty
~new_:lifecycle
|> Option.is_some
|| Bonsai.Time_source.Private.has_before_display_events t.time_source
;;
let trigger_before_display (T t) ~apply_actions =
let old = ref Bonsai.Private.Lifecycle.Collection.empty in
let rec loop () =
let new_ = t.lifecycle |> Incr.Observer.value_exn in
match Bonsai.Private.Lifecycle.Collection.get_before_display ~old:!old ~new_ with
| None -> ()
| Some before_displays ->
schedule_event () before_displays;
old := Map.merge_skewed !old new_ ~combine:(fun ~key:_ l _r -> l);
apply_actions ();
loop ()
in
loop ();
if Bonsai.Time_source.Private.has_before_display_events t.time_source
then (
Bonsai.Time_source.Private.trigger_before_display t.time_source;
apply_actions ())
;;
let flush
?(log_before_action_application = fun ~action_sexp:_ -> ())
?(log_on_skipped_stabilization = fun ~action_sexp:_ -> ())
(T
({ model_var
; apply_action
; action_input
; queue
; time_source
; stabilization_tracker
; sexp_of_action
; timer
; _
} as t))
=
Bonsai.Time_source.Private.flush time_source;
timer Stabilize_for_clock ~f:(fun () ->
Incr.stabilize ();
Stabilization_tracker.mark_stabilization stabilization_tracker);
let process_event action model =
log_before_action_application ~action_sexp:(lazy (sexp_of_action action));
if Stabilization_tracker.requires_stabilization stabilization_tracker action
then (
Incr.Var.set model_var model;
timer Stabilize_for_action ~f:(fun () ->
Incr.stabilize ();
Stabilization_tracker.mark_stabilization stabilization_tracker))
else log_on_skipped_stabilization ~action_sexp:(lazy (sexp_of_action action));
let new_model =
let action_input = Incr.Observer.value_exn action_input in
apply_action
~schedule_event:
(Ui_effect.Expert.handle ~on_exn:(fun exn ->
Exn.reraise exn "Unhandled exception raised in effect"))
(Bonsai.Computation_status.Active action_input)
model
action
in
Stabilization_tracker.insert stabilization_tracker action;
if t.print_actions then print_s [%message "Processed action" (action : action)];
new_model
in
let rec apply_actions model =
match Queue.dequeue queue with
| None ->
Incr.Var.set model_var model;
timer Stabilize_after_all_apply_actions ~f:(fun () ->
Incr.stabilize ();
Stabilization_tracker.mark_stabilization stabilization_tracker)
| Some action ->
let new_model = process_event action model in
apply_actions new_model
in
let apply_actions () =
timer Apply_actions ~f:(fun () -> apply_actions (Incr.Var.latest_value model_var))
in
apply_actions ();
trigger_before_display (T t) ~apply_actions
;;
let result (T { result; _ }) = Incr.Observer.value_exn result
let has_after_display_events (T t) =
let lifecycle = t.lifecycle |> Incr.Observer.value_exn in
Bonsai.Private.Lifecycle.Collection.has_after_display lifecycle
|| Bonsai.Time_source.Private.has_after_display_events t.time_source
;;
let trigger_lifecycles (T t) =
let old = t.last_lifecycle in
let new_ = t.lifecycle |> Incr.Observer.value_exn in
t.last_lifecycle <- new_;
schedule_event () (Bonsai.Private.Lifecycle.Collection.get_after_display ~old ~new_);
Bonsai.Time_source.Private.trigger_after_display t.time_source
;;
module Expert = struct
let sexp_of_model (T { sexp_of_model; model_var; _ }) =
sexp_of_model (Incr.Var.value model_var)
;;
let result_incr (T { result_incr; _ }) = result_incr
let action_input_incr (T { action_input_incr; _ }) = Ui_incr.pack action_input_incr
let lifecycle_incr (T { lifecycle_incr; _ }) = Ui_incr.pack lifecycle_incr
let time_source (T { time_source; _ }) = time_source
let invalidate_observers (T { action_input; result; lifecycle; _ }) =
Incr.Observer.disallow_future_use action_input;
Incr.Observer.disallow_future_use result;
Incr.Observer.disallow_future_use lifecycle
;;
let reset_model_to_default (T { default_model; model_var; _ }) =
Incr.Var.set model_var default_model
;;
let print_actions (T t) = t.print_actions <- true
let print_stabilizations (T t) =
Stabilization_tracker.For_testing.start_debugging t.stabilization_tracker
;;
let print_stabilization_tracker_stats (T t) =
Stabilization_tracker.For_testing.display_stats t.stabilization_tracker
;;
end
module For_testing = struct
let dump_dot (T { result_incr; lifecycle_incr; action_input_incr; _ }) =
let buffer = Buffer.create 10 in
let formatter = Format.formatter_of_buffer buffer in
Ui_incr.Packed.save_dot
formatter
[ Incr.pack result_incr; Incr.pack lifecycle_incr; Incr.pack action_input_incr ];
Buffer.contents buffer
;;
let running_computation (T { running_computation; _ }) = running_computation
end