123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334open!Coreopen!ImportopenProcopenBonsai.For_openletcreate_handlecomponent=Handle.create(modulestructtypet=int->intBonsai.Effect_throttling.Poll_result.tEffect.ttypeincoming=intletview_=""letincomingfquery=let%bind.Effectresult=fqueryinEffect.print_s[%message(query:int)(result:intBonsai.Effect_throttling.Poll_result.t)];;end)component;;moduleCommon(M:sigvalpoll:('a->'bEffect.t)Value.t->('a->'bBonsai.Effect_throttling.Poll_result.tEffect.t)Computation.tend)=structlet%expect_test{| Effect_throttling.poll only runs one instance of an effect at a time |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inletcomponent=M.poll(Value.return(Effect.For_testing.of_query_response_trackerqrt))inlethandle=create_handlecomponentinHandle.do_actionshandle[0;1;2];Handle.recompute_viewhandle;(* query 1 gets aborted because only 1 request can be enqueued at a time *)[%expect{| ((query 1) (result Aborted)) |}];respond1;[%expect{| ((query 0) (result (Finished 1))) |}];respond2;(* We call [recompute_view] after [respond 2] and before [respond 3] to
demonstrate that the effect being responded to doesn't begin until the
next time the state machine effects get run. This isn't necessarily
desirable behavior, but it is the way this computation works, so it's
worth showing in this test. *)Handle.recompute_viewhandle;[%expect{| |}];respond3;[%expect{| ((query 2) (result (Finished 3))) |}];;let%expect_test{| Effect_throttling.poll resetting |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inleteffect_var=Bonsai.Var.create(Effect.For_testing.of_query_response_trackerqrt)inletcomponent=Bonsai.with_model_resetter(M.poll(Bonsai.Var.valueeffect_var))inlethandle=Handle.create(modulestructtypet=(int->intBonsai.Effect_throttling.Poll_result.tEffect.t)*unitEffect.ttypeincoming=[`Runofint|`Reset]letview_=""letincoming(f,reset)query=matchquerywith|`Runquery->let%bind.Effectresult=fqueryinEffect.print_s[%message(query:int)(result:intBonsai.Effect_throttling.Poll_result.t)]|`Reset->reset;;end)componentinHandle.do_actionshandle[`Run0;`Run1;`Run2];Handle.recompute_viewhandle;[%expect{| ((query 1) (result Aborted)) |}];Handle.do_actionshandle[`Reset];Handle.recompute_viewhandle;respond1;Handle.recompute_viewhandle;[%expect{| ((query 0) (result (Finished 1))) |}];Handle.do_actionshandle[`Reset];respond2;[%expect{| ((query 2) (result (Finished 2))) |}];;endmodule_=Common(structletpoll=Bonsai.Effect_throttling.pollend)module_=Common(structletpolleffect=letopenBonsai.Let_syntaxinlet%subeffect=Bonsai.Effect_throttling.polleffectinlet%subeffect=Bonsai.Effect_throttling.polleffectinlet%arreffect=effectinfunint->match%map.Effecteffectintwith|Aborted->Bonsai.Effect_throttling.Poll_result.Aborted|Finished(Finishedresult)->Finishedresult|FinishedAborted->raise_s[%message"Unexpected finished of aborted"];;end)let%expect_test{| Effect_throttling.poll deactivation |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inleteffect_var=Bonsai.Var.create(Effect.For_testing.of_query_response_trackerqrt)inletmatch_var=Bonsai.Var.createtrueinletpoll_effect=Bonsai.Effect_throttling.poll(Bonsai.Var.valueeffect_var)inletcomponent=letopenBonsai.Let_syntaxinif%subBonsai.Var.valuematch_varthenpoll_effectelsepoll_effectinlethandle=create_handlecomponentin(* The actions [ 0; 1; 2 ] are associated with the [true] branch, but don't get run
until we flush/recompute_view. *)Handle.do_actionshandle[0;1;2];Bonsai.Var.setmatch_varfalse;Handle.recompute_viewhandle;(* Since the [true] branch is inactive, no action is running, and they all count as
"up_next". We will drop 0 and 1 because they will be replaced by 1 and 2,
respectively, since we only allow one effect to be in the pending queue at a time *)[%expect{|
((query 0) (result Aborted))
((query 1) (result Aborted)) |}];(* The actions [ 3; 4; 5 ] are associated with the [false] branch*)Handle.do_actionshandle[3;4;5];Handle.recompute_viewhandle;[%expect{| ((query 4) (result Aborted)) |}];respond1;[%expect{| ((query 3) (result (Finished 1))) |}];(* The outstanding request doesn't get run until we flush/recompute_view so nothing is
waiting for a response *)respond2;[%expect{| |}];Handle.recompute_viewhandle;respond3;[%expect{| ((query 5) (result (Finished 3))) |}];Handle.recompute_viewhandle;Bonsai.Var.setmatch_vartrue;Handle.recompute_viewhandle;Handle.recompute_viewhandle;respond4;Handle.recompute_viewhandle;[%expect{| ((query 2) (result (Finished 4))) |}];;let%expect_test{| Effect_throttling.poll gets activated and de-activated the next frame |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inleteffect_var=Bonsai.Var.create(Effect.For_testing.of_query_response_trackerqrt)inletmatch_var=Bonsai.Var.createtrueinletpoll_effect=Bonsai.Effect_throttling.poll(Bonsai.Var.valueeffect_var)inletcomponent=letopenBonsai.Let_syntaxinif%subBonsai.Var.valuematch_varthenpoll_effectelsepoll_effectinlethandle=create_handlecomponentinHandle.do_actionshandle[0];Bonsai.Var.setmatch_varfalse;Handle.recompute_viewhandle;[%expect{| |}];respond1;Bonsai.Var.setmatch_vartrue;Handle.recompute_viewhandle;[%expect{| |}];respond2;Bonsai.Var.setmatch_varfalse;Handle.recompute_viewhandle;[%expect{| |}];respond3;Bonsai.Var.setmatch_vartrue;Handle.recompute_viewhandle;[%expect{| |}];respond4;Handle.recompute_viewhandle;respond5;[%expect{| ((query 0) (result (Finished 5))) |}];;let%expect_test{| Effect_throttling.poll effect finishes while inactive and effect is queued |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inleteffect_var=Bonsai.Var.create(Effect.For_testing.of_query_response_trackerqrt)inletmatch_var=Bonsai.Var.createtrueinletpoll_effect=Bonsai.Effect_throttling.poll(Bonsai.Var.valueeffect_var)inletcomponent=letopenBonsai.Let_syntaxinif%subBonsai.Var.valuematch_varthenpoll_effectelsepoll_effectinlethandle=create_handlecomponentinHandle.do_actionshandle[0;1];Handle.recompute_viewhandle;Handle.recompute_viewhandle;Handle.recompute_viewhandle;Bonsai.Var.setmatch_varfalse;[%expect{| |}];respond1;Handle.recompute_viewhandle;[%expect{| ((query 0) (result (Finished 1))) |}];Bonsai.Var.setmatch_vartrue;Handle.recompute_viewhandle;[%expect{| |}];Handle.recompute_viewhandle;respond2;[%expect{| ((query 1) (result (Finished 2))) |}];;let%expect_test{| Effect_throttling.poll in an assoc |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondq=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondq)inletmap_var=Bonsai.Var.create(Int.Map.of_alist_exn[1,();2,();3,()])inleteffect_var=Bonsai.Var.create(Effect.For_testing.of_query_response_trackerqrt)inletcomponent=letopenBonsai.Let_syntaxinBonsai.assoc(moduleInt)(Bonsai.Var.valuemap_var)~f:(funkey_data->let%subpoll_effect=Bonsai.Effect_throttling.poll(Bonsai.Var.valueeffect_var)inlet%arrkey=keyandpoll_effect=poll_effectinpoll_effectkey)inlethandle=Handle.create(modulestructtypet=intBonsai.Effect_throttling.Poll_result.tEffect.tInt.Map.ttypeincoming=intletview_=""letincomingmapquery=matchMap.findmapquerywith|Someeffect->let%bind.Effectresult=effectinEffect.print_s[%message(query:int)(result:intBonsai.Effect_throttling.Poll_result.t)]|None->Effect.print_s[%message"Key not in map"(query:int)];;end)componentinHandle.do_actionshandle[1;2;3;1;2;3;2;1;3];Handle.recompute_viewhandle;[%expect{|
((query 2) (result Aborted))
((query 1) (result Aborted))
((query 3) (result Aborted)) |}];Handle.recompute_viewhandle;respond1;[%expect{|
((query 3) (result (Finished 1)))
((query 2) (result (Finished 1)))
((query 1) (result (Finished 1))) |}];respond2;Handle.recompute_viewhandle;respond3;[%expect{|
((query 1) (result (Finished 3)))
((query 2) (result (Finished 3)))
((query 3) (result (Finished 3))) |}];Handle.do_actionshandle[1;2;3;1;2;3];Bonsai.Var.updatemap_var~f:(funmap->Map.removemap1);Handle.recompute_viewhandle;[%expect{| ((query 1) (result Aborted)) |}];respond4;[%expect{|
((query 3) (result (Finished 4)))
((query 2) (result (Finished 4))) |}];;let%expect_test"collapse functions"=letprint?tag_sinput=letoutput=Bonsai.Effect_throttling.Poll_result.collapse_to_or_error?tag_sinputinprint_s[%message""~_:(input:unitOr_error.tBonsai.Effect_throttling.Poll_result.t)"=>"~_:(output:unitOr_error.t)]inprintAborted;print(Finished(Error(Error.of_string"oh no!")));print(Finished(Ok()));[%expect{|
(Aborted => (Error "request was aborted"))
((Finished (Error "oh no!")) => (Error "oh no!"))
((Finished (Ok ())) => (Ok ())) |}];print~tag_s:(lazy(Sexp.Atom"my tag"))Aborted;print~tag_s:(lazy(Sexp.Atom"my tag"))(Finished(Error(Error.of_string"oh no!")));[%expect{|
(Aborted => (Error ("my tag" "request was aborted")))
((Finished (Error "oh no!")) => (Error ("my tag" "oh no!"))) |}];;