123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325open!CoreopenBonsai.Let_syntaxletwith_inject_fixed_pointf=let%subr,_=Bonsai.wrap(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event(_result,inject)()action->(* speedy thing go in, speedy thing come out *)schedule_event(injectaction))~f:(fun_modelinject->finject)inreturnr;;letyoinka=let%sub_,result=Bonsai.actor1[%here](moduleUnit)(moduleUnit)~recv:(fun~schedule_event:_a()()->(),a)~default_model:()ainreturn@@let%mapresult=resultinresult();;letscope_model(typeacmp)(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)~on:vcomputation=letv=Bonsai.Value.mapv~f:(funk->Map.singleton(moduleM)k())inlet%submap=Bonsai.assoc(moduleM)v~f:(fun__->computation)inlet%arrmap=mapin(* This _exn is ok because we know that the map is a singleton *)let_k,r=Map.max_elt_exnmapinr;;letstate_machine1_dynamic_model(typema)here(moduleM:Bonsai.Modelwithtypet=m)(moduleA:Bonsai.Actionwithtypet=a)~model~apply_actioninput=letmodel_creator=matchmodelwith|`Givenm->Bonsai.Value.mapm~f:(funm->function|None->m|Somea->a)|`Computedf->finletmoduleM_actual=structtypet=M.toption[@@derivingsexp,equal]endinletapply_action~inject~schedule_event(input,model_creator)modelaction=letmodel=model_creatormodelinSome(apply_action~inject~schedule_eventinputmodelaction)inlet%submodel_and_inject=Bonsai.state_machine1here(moduleM_actual)(moduleA)~default_model:None~apply_action(Bonsai.Value.bothinputmodel_creator)inreturn@@let%mapmodel,inject=model_and_injectandmodel_creator=model_creatorinmodel_creatormodel,inject;;letstate_machine0_dynamic_modelheremodel_modaction_mod~model~apply_action=letapply_action~inject~schedule_event()modelaction=apply_action~inject~schedule_eventmodelactioninstate_machine1_dynamic_modelheremodel_modaction_mod~model~apply_action(Bonsai.Value.return());;letstate_dynamic_model(typem)here(moduleM:Bonsai.Modelwithtypet=m)~model=letapply_action~inject:_~schedule_event:__old_modelnew_model=new_modelinstate_machine0_dynamic_modelhere(moduleM)(moduleM)~model~apply_action;;letexactly_oncehereeffect=let%subhas_run,set_has_run=Bonsai.statehere(moduleBool)~default_model:falseinif%subhas_runthenBonsai.const()elseBonsai.Edge.lifecycle~on_activate:(let%mapset_has_run=set_has_runandevent=effectinUi_effect.Many[set_has_runtrue;event])();;letexactly_once_with_valueheremoduleffect=let%subvalue,set_value=Bonsai.state_optheremodulinlet%sub()=match%subvaluewith|None->Bonsai.Edge.lifecycle~on_activate:(let%mapset_value=set_valueandeffect=effectinlet%bind.Bonsai.Effectr=effectinset_value(Somer))()|Some_->Bonsai.const()inreturnvalue;;letfreezeheremodelvalue=let%substate,set_state=Bonsai.state_optheremodelinmatch%substatewith|Somestate->returnstate|None->let%sub()=Bonsai.Edge.lifecycle~on_activate:(let%mapset_state=set_stateandvalue=valueinset_state(Somevalue))()inreturnvalue;;letthunk(typea)(f:unit->a)=let%subout=returnBonsai.Value.(map(return())~f)infreeze[%here](modulestructtypet=(a[@sexp.opaque])[@@derivingsexp]letequal=phys_equalend)out;;lettogglehere~default_model=let%substate=Bonsai.state_machine0here(moduleBool)(moduleUnit)~apply_action:(fun~inject:_~schedule_event:_b()->notb)~default_modelinlet%arrstate,inject=stateinstate,inject();;letpipe(typea)here(moduleA:Bonsai.Modelwithtypet=a)=letmoduleModel=structtypet={queued_actions:A.tFdeque.t;queued_receivers:(unit,a)Bonsai.Effect.Private.Callback.tFdeque.t}letequal=phys_equalletdefault={queued_actions=Fdeque.empty;queued_receivers=Fdeque.empty}letsexp_of_t{queued_actions;_}=[%sexp_of:A.tFdeque.t]queued_actionslett_of_sexpsexp=letqueued_actions=[%of_sexp:A.tFdeque.t]sexpin{defaultwithqueued_actions};;endinletmoduleAction=structtypet=|Add_actionofa|Add_receiverof(unit,a)Bonsai.Effect.Private.Callback.tletsexp_of_t=function|Add_actiona->A.sexp_of_ta|Add_receiverr->sexp_of_opaquer;;endinlet%sub_,inject=Bonsai.state_machine0here(moduleModel)(moduleAction)~default_model:Model.default~apply_action:(fun~inject:_~schedule_eventmodel->function|Add_actiona->(matchFdeque.dequeue_frontmodel.queued_receiverswith|None->letqueued_actions=Fdeque.enqueue_backmodel.queued_actionsain{modelwithqueued_actions}|Some(hd,queued_receivers)->schedule_event(Bonsai.Effect.Private.Callback.respond_tohda);{modelwithqueued_receivers})|Add_receiverr->(matchFdeque.dequeue_frontmodel.queued_actionswith|None->letqueued_receivers=Fdeque.enqueue_backmodel.queued_receiversrin{modelwithqueued_receivers}|Some(hd,queued_actions)->schedule_event(Bonsai.Effect.Private.Callback.respond_torhd);{modelwithqueued_actions}))inreturn(let%mapinject=injectinletrequest=Bonsai.Effect.Private.make~request:()~evaluator:(funr->inject(Add_receiverr))in(funa->inject(Add_actiona)),request);;letmap_of_set=Bonsai.Incr.compute~f:Ui_incr.Map.of_setletmap_keys=Bonsai.Incr.compute~f:Ui_incr.Map.keysletmap_mergeab~f=Bonsai.Incr.compute(Bonsai.Value.bothab)~f:(funa_and_b->let%pattern_bind.Ui_incra,b=a_and_binIncr_map.mergeab~f);;moduleId_gen(T:Int_intf.S)()=structincludeTletcomponenthere=let%map.Bonsai.Computation_,fetch=Bonsai.actor0here(moduleT)(moduleUnit)~default_model:T.zero~recv:(fun~schedule_event:_i()->T.(+)iT.one,i)infetch();;endletmirror(typem)here(moduleM:Bonsai.Modelwithtypet=m)~store_set~store_value~interactive_set~interactive_value=letmoduleM2=structtypet={store:M.t;interactive:M.t}[@@derivingsexp,equal]endinletcallback=let%mapstore_set=store_setandinteractive_set=interactive_setinfunold_pair{M2.store=store_value;interactive=interactive_value}->letstability=if[%equal:M.t]store_valueinteractive_valuethen`Stableelse`Unstableinmatchstabilitywith|`Stable->(* if both of the new values are the same, then we're done! Stability
has already been reached. *)Ui_effect.Ignore|`Unstable->(matchold_pairwith|None->(* on_change' is triggered when the values flow through this node
for the first time. In this scenario, we prioritize the
value in the store. *)interactive_setstore_value|Some{M2.store=old_store_value;interactive=old_interactive_value}->letstore_changed=not([%equal:M.t]old_store_valuestore_value)inletinteractive_changed=not([%equal:M.t]old_interactive_valueinteractive_value)in(matchinteractive_changed,store_changedwith(* if the interactive-value has changed, forward that on to the store.
we intentionally prioritize the interactive value here, so changes to
the store that happened at the same instant are dropped. *)|true,_->store_setinteractive_value(* finally, if the store changed but interactive did not, update the
interactive value. *)|false,true->interactive_setstore_value(* this final case should never happen. Error message explains why.*)|false,false->eprint_s[%message"BUG"[%here]"on_change triggered when nothing actually changed?"];Ui_effect.Ignore))inBonsai.Edge.on_change'here(moduleM2)(let%mapstore=store_valueandinteractive=interactive_valuein{M2.store;interactive})~callback;;