123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183open!Core_kernelopen!ImportmoduleNode=Types.Nodetype'aedge='aTypes.Expert.edge={child:'aNode.t;on_change:'a->unit;(* [index] is defined whenever the [edge] is in the [children] of some [t]. Then it is
the index of this [edge] in that [children] array. It might seem redundant with all
the other indexes we have, but it is necessary to remove children. The index may
change as sibling children are removed. *)mutableindex:intUopt.t}[@@derivingsexp_of]typepacked_edge=Types.Expert.packed_edge=E:'aedge->packed_edge[@@unboxed][@@derivingsexp_of]type'at='aTypes.Expert.t={f:unit->'a;on_observability_change:is_now_observable:bool->unit;mutablechildren:packed_edgeUopt.tArray.t;mutablenum_children:int;(* When set, makes the node of [t] stale. It is set when the set of children changes.
Otherwise the normal check of staleness (comparing the [changed_at] field of
children and the [recomputed_at] field for the node of [t]) would not be enough.
This plays a role similar to the cutoff of [Never] for the lhs-change of binds, but
we don't have a special child. *)mutableforce_stale:bool;(* The number of invalid children that point to us. Used to determine whether the node
of [t] needs to invalidated, without iterating over all the children. This is not
needed for other nodes, because there are no other nodes that have a potentially
large and dynamic set of children. *)mutablenum_invalid_children:int;(* Whether we will fire the [on_change] callbacks for all children when the node of [t]
itself runs. Used to make sure we rerun everything after [t] switches from
unobservable and back to observable. *)mutablewill_fire_all_callbacks:bool}[@@derivingsexp_of]letinvariant_invariant_a{f=_;children;num_children;force_stale=_;num_invalid_children;on_observability_change=_;will_fire_all_callbacks=_}=assert(num_children<=Array.lengthchildren);ignorenum_invalid_children;(* invariant is below, because we need some context *)Array.iterichildren~f:(funiuopt->matchi<num_childrenwith|true->let(Er)=Uopt.value_exnuoptin[%test_result:int](Uopt.value_exnr.index)~expect:i|false->assert(Uopt.is_noneuopt));;letinvariant_about_num_invalid_children{children;num_children;num_invalid_children;_}~is_necessary=ifnotis_necessarythen[%test_result:int]num_invalid_children~expect:0else(letcount_invalid_children=ref0infori=0tonum_children-1dolet(Er)=Uopt.value_exnchildren.(i)inifnot(Node.is_validr.child)thenincrcount_invalid_childrendone;[%test_result:int]num_invalid_children~expect:!count_invalid_children);;letcreate~f~on_observability_change={f;on_observability_change;children=[||];num_children=0;force_stale=false;num_invalid_children=0;will_fire_all_callbacks=true};;letmake_stalet=ift.force_stalethen`Already_staleelse(t.force_stale<-true;`Ok);;letincr_invalid_childrent=t.num_invalid_children<-t.num_invalid_children+1letdecr_invalid_childrent=t.num_invalid_children<-t.num_invalid_children-1letmake_space_for_child_if_necessaryt=ift.num_children>=Array.lengtht.childrenthen(ifdebugthenassert(t.num_children=Array.lengtht.children);letnew_max=Int.max2(2*Array.lengtht.children)int.children<-Array.realloct.children~len:new_maxUopt.none);;letadd_child_edgetpacked_edge=let(Eedge)=packed_edgeinassert(Uopt.is_noneedge.index);make_space_for_child_if_necessaryt;letnew_child_index=t.num_childreninedge.index<-Uopt.somenew_child_index;t.children.(new_child_index)<-Uopt.somepacked_edge;t.num_children<-t.num_children+1;t.force_stale<-true;(* We will bump the number of invalid children if necessary when connecting child and
parent. Same thing for running the [on_change] callbacks. *)new_child_index;;letswap_childrent~child_index1~child_index2=let(Eedge1)=Uopt.value_exnt.children.(child_index1)inlet(Eedge2)=Uopt.value_exnt.children.(child_index2)inedge1.index<-Uopt.somechild_index2;edge2.index<-Uopt.somechild_index1;Array.swapt.childrenchild_index1child_index2;;letlast_child_edge_exnt=letlast_index=t.num_children-1inUopt.value_exnt.children.(last_index);;letremove_last_child_edge_exnt=letlast_index=t.num_children-1inletpacked_edge_opt=t.children.(last_index)int.children.(last_index)<-Uopt.none;t.num_children<-last_index;t.force_stale<-true;assert(Uopt.is_somepacked_edge_opt);let(Eedge)=Uopt.unsafe_valuepacked_edge_optinedge.index<-Uopt.none;;letbefore_main_computationt=ift.num_invalid_children>0then`Invalidelse(t.force_stale<-false;letwill_fire_all_callbacks=t.will_fire_all_callbacksint.will_fire_all_callbacks<-false;ifwill_fire_all_callbacksthenfori=0tot.num_children-1dolet(Er)=Uopt.value_exnt.children.(i)inr.on_change(Uopt.value_exnr.child.value_opt)done;`Ok);;letobservability_changet~is_now_observable=t.on_observability_change~is_now_observable;ifnotis_now_observablethen(t.will_fire_all_callbacks<-true;(* If we don't reset num_invalid_children, we would double count them: just imagine
what happens we if reconnect/disconnect/reconnect/disconnect with an invalid
child. *)t.num_invalid_children<-0);;letrun_edge_callbackt~child_index=ifnott.will_fire_all_callbacksthen(let(Er)=Uopt.value_exnt.children.(child_index)in(* This value is not necessarily set, because we try to run this when connecting the
node to its children, which could be before they have run even once. Also the node
could be invalid. *)ifUopt.is_somer.child.value_optthenr.on_change(Uopt.unsafe_valuer.child.value_opt));;