stabilize

In this section we will explain how stabilization works.

At the very beginning, if current state is not Not_stabilizing, then raise an exception.

let stabilize t =
  ensure_not_stabilizing t ~name:"stabilize" ~allow_in_update_handler:false;

then try to stabilize the state t. if an exception raised then set the status to Stabilize_previously_raised and raise the exception.

The first job to stabilize is setting the status of state t to Stabilizing.

t.status <- Stabilizing;

By default, an observer has a finalizer that calls disallow_future_use when the observer is no longer referenced. If an observer has not finalizer, the observer will live until disallow_future_use is explicitly called.

TODO

disallow_finalized_observers t;

Then handle all new observers(created via create_observer) whose type are ensured to be Created.

add_new_observers t;
  Var
+---------+
|  width  | ----\        Map2
+---------+      \    +-----------+
  Var             +-->| base_area |
+---------+      /    +-----------+
|  height |-----/
+---------+

Provided that base_area node is Created.

The state of base_area will be set to In_use.

internal_observer.state <- In_use;

If there are some observers(old_all_observers) in t, then we set

if Uopt.is_some old_all_observers
then (
    internal_observer.next_in_all <- old_all_observers;
    Packed.set_prev_in_all (Uopt.unsafe_value old_all_observers) (Uopt.some packed));
t.all_observers <- Uopt.some packed;

If the node of internal_observer was not necessary, then we set it and its children necessary. and the node will be added to the recompute_heap.

let observing = internal_observer.observing in
let was_necessary = Node.is_necessary observing in
if not was_necessary then became_necessary observing

Then add num_on_update_handlers of observer to observing.

observing.num_on_update_handlers
<- observing.num_on_update_handlers
    + List.length internal_observer.on_update_handlers;
      let old_observers = observing.observers in

Add the observer to observing's observers.

if Uopt.is_some old_observers
then (
    internal_observer.next_in_observing <- old_observers;
    (Uopt.unsafe_value old_observers).prev_in_observing
    <- Uopt.some internal_observer);
observing.observers <- Uopt.some internal_observer;

By adding internal_observer to observing.observers, we may have added on-update handlers to observing. We need to handle observing after this stabilization to give those handlers a chance to run.

let handle_after_stabilization : type a. a Node.t -> unit =
  fun node ->
  (* Make sure we will not add a repeat node *)
  if not node.is_in_handle_after_stabilization
  then (
    let t = node.state in
    node.is_in_handle_after_stabilization <- true;
    Stack.push t.handle_after_stabilization (T node))
;;

handle_after_stabilization observing;

Now we can go back to stabilize. Next step is unlink_disallowed_observers.

TODO

This function will pop all disallowed_observers out and the state of them to Disallowed.

let unlink_disallowed_observers t =
  while Stack.length t.disallowed_observers > 0 do
    let packed = Stack.pop_exn t.disallowed_observers in
    let module Packed = Internal_observer.Packed in
    let (T internal_observer) = packed in
    if debug
    then
      assert (
        match internal_observer.state with
        | Disallowed -> true
        | _ -> false);
    internal_observer.state <- Unlinked;
    let (T all_observers) = Uopt.value_exn t.all_observers in
    if Internal_observer.same internal_observer all_observers
    then t.all_observers <- internal_observer.next_in_all;
    Internal_observer.unlink internal_observer;
    check_if_unnecessary internal_observer.observing
  done
;;

unlink_disallowed_observers t;

Then compute recompute_heap via recompute_everything_that_is_necessary.

recompute_everything_that_is_necessary t;

and update stabilization_num

t.stabilization_num <- Stabilization_num.add1 t.stabilization_num;

then update lazy updated value

while not (Stack.is_empty t.set_during_stabilization) do
  let (T var) = Stack.pop_exn t.set_during_stabilization in
  let value = Uopt.value_exn var.value_set_during_stabilization in
  var.value_set_during_stabilization <- Uopt.none;
  set_var_while_not_stabilizing var value
done;

Then push all nodes that in handle after stabilization into the stack run_on_update_handlers of state t. And them run the registed update function via run_on_update_handlers.

while not (Stack.is_empty t.handle_after_stabilization) do
  let (T node) = Stack.pop_exn t.handle_after_stabilization in
  node.is_in_handle_after_stabilization <- false;
  let old_value = node.old_value_opt in
  node.old_value_opt <- Uopt.none;
  let node_update : _ Node_update.t =
    if not (Node.is_valid node)
    then Invalidated
    else if not (Node.is_necessary node)
    then Unnecessary
    else (
      let new_value = Uopt.value_exn node.value_opt in
      if Uopt.is_none old_value
      then Necessary new_value
      else Changed (Uopt.unsafe_value old_value, new_value))
  in
  Stack.push t.run_on_update_handlers (T (node, node_update))
done;
t.status <- Running_on_update_handlers;
let now = t.stabilization_num in
while not (Stack.is_empty t.run_on_update_handlers) do
  let (T (node, node_update)) = Stack.pop_exn t.run_on_update_handlers in
  Node.run_on_update_handlers node node_update ~now
done;
t.status <- Not_stabilizing;
reclaim_space_in_weak_hashtbls t