From 1a6c23c90d3c806244108fb37c3437c2c8e1a2aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Mon, 17 Feb 2020 16:39:09 +0100
Subject: [PATCH] [server] install state hooks (only) on demand

---
 src/plugins/server/states.ml  | 38 ++++++++++++++++++++++++++++++++---
 src/plugins/server/states.mli | 30 +++++++++++++++++++++++----
 2 files changed, 61 insertions(+), 7 deletions(-)

diff --git a/src/plugins/server/states.ml b/src/plugins/server/states.ml
index 990c4e0b14e..6b811e8a6d5 100644
--- a/src/plugins/server/states.ml
+++ b/src/plugins/server/states.ml
@@ -22,12 +22,31 @@
 
 open Data
 
+type 'a callback = ('a -> unit) -> unit
+
+let install signal hook = function
+  | None -> ()
+  | Some add_hook ->
+    let once = ref false in
+    let install ok =
+      if ok && !once then
+        begin
+          once := false ;
+          add_hook hook ;
+        end
+    in Request.on_signal signal install
+
+let install_emit signal add_hook =
+  install signal (fun () -> Request.emit signal) add_hook
+
 (* -------------------------------------------------------------------------- *)
 (* --- Values                                                             --- *)
 (* -------------------------------------------------------------------------- *)
 
 let register_value (type a) ~page ~name ~descr ?(details=[])
-    ~(output : a Request.output) ~get =
+    ~(output : a Request.output) ~get
+    ?(add_hook : unit callback option) ()
+  =
   let open Markdown in
   let title =  Printf.sprintf "`VALUE` %s" name in
   let index = [ Printf.sprintf "%s (`VALUE`)" name ] in
@@ -38,6 +57,7 @@ let register_value (type a) ~page ~name ~descr ?(details=[])
   Request.register ~page ~kind:`GET ~name:(name ^ ".get")
     ~descr:(plain "Getter for value " @ href h)
     ~input:(module Junit) ~output get ;
+  install_emit signal add_hook ;
   signal
 
 (* -------------------------------------------------------------------------- *)
@@ -45,7 +65,8 @@ let register_value (type a) ~page ~name ~descr ?(details=[])
 (* -------------------------------------------------------------------------- *)
 
 let register_state (type a) ~page ~name ~descr ?(details=[])
-    ~(data : a data) ~get ~set =
+    ~(data : a data) ~get ~set
+    ?(add_hook : unit callback option) () =
   let open Markdown in
   let title =  Printf.sprintf "`STATE` %s" name in
   let index = [ Printf.sprintf "%s (`STATE`)" name ] in
@@ -59,6 +80,7 @@ let register_state (type a) ~page ~name ~descr ?(details=[])
   Request.register ~page ~kind:`SET ~name:(name ^ ".set")
     ~descr:(plain "Setter for state " @ href h)
     ~input:(module (val data)) ~output:(module Junit) set ;
+  install_emit signal add_hook ;
   signal
 
 (* -------------------------------------------------------------------------- *)
@@ -153,6 +175,8 @@ let remove array k =
       Request.emit array.signal ;
     end
 
+let signal array = array.signal
+
 (* -------------------------------------------------------------------------- *)
 (* --- Fetch Model Updates                                                --- *)
 (* -------------------------------------------------------------------------- *)
@@ -220,7 +244,12 @@ let fetch array n =
 (* --- Signature Registry                                                 --- *)
 (* -------------------------------------------------------------------------- *)
 
-let register_array ~page ~name ~descr ?(details=[]) ~key ~iter model =
+let register_array ~page ~name ~descr ?(details=[]) ~key
+    ~(iter : 'a callback)
+    ?(add_update_hook : 'a callback option)
+    ?(add_remove_hook : 'a callback option)
+    ?(add_reload_hook : unit callback option)
+    model =
   let open Markdown in
   let title =  Printf.sprintf "`ARRAY` %s" name in
   let index = [ Printf.sprintf "%s (`ARRAY`)" name ] in
@@ -286,6 +315,9 @@ let register_array ~page ~name ~descr ?(details=[]) ~key ~iter model =
     ~input:(module Junit) ~output:(module Junit)
     (fun () -> reload array) ;
   synchronize array ;
+  install signal (update array) add_update_hook ;
+  install signal (remove array) add_remove_hook ;
+  install signal (fun () -> reload array) add_reload_hook ;
   array
 
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/server/states.mli b/src/plugins/server/states.mli
index 9b7c148dc0b..60f6798add7 100644
--- a/src/plugins/server/states.mli
+++ b/src/plugins/server/states.mli
@@ -20,11 +20,17 @@
 (*                                                                        *)
 (**************************************************************************)
 
+type 'a callback = ('a -> unit) -> unit
+
 (** Register a (projectified) value and generates the associated signal and
     request:
     - Signal [<name>.sig] is emitted on value updates;
     - GET Request [<name>.get] returns the current value.
 
+    If provided, the [~add_hook] option is used to register a hook
+    to notify the server of value updates. The hook will be installed
+    only once the client starts to listen for value updates.
+
     Inside {b Ivette} you can use the [States.useSyncValue(id)] hook to
     synchronize with this value.
 *)
@@ -35,8 +41,8 @@ val register_value :
   ?details:Markdown.block ->
   output:'a Request.output ->
   get:(unit -> 'a) ->
-  Request.signal
-
+  ?add_hook:(unit callback) ->
+  unit -> Request.signal
 
 (** Register a (projectified) state and generates the associated signal and
     requests:
@@ -44,6 +50,10 @@ val register_value :
     - GET Request [<name>.get] returns the current value;
     - SET Request [<name>.set] modifies the server value.
 
+    If provided, the [~add_hook] option is used to register a hook
+    to notify the server of value updates. The hook will be installed
+    only once the client starts to listen for value updates.
+
     Inside {b Ivette} you can use the [States.useSyncState(id)] hook to
     synchronize with this state.
 *)
@@ -55,7 +65,8 @@ val register_state :
   data:'a Data.data ->
   get:(unit -> 'a) ->
   set:('a -> unit) ->
-  Request.signal
+  ?add_hook:(unit callback) ->
+  unit -> Request.signal
 
 type 'a model (** Columns array model *)
 
@@ -80,6 +91,9 @@ val update : 'a array -> 'a -> unit
 (** Mark an array entry as removed. *)
 val remove : 'a array -> 'a -> unit
 
+(** Get the signal associated with the array *)
+val signal : 'a array -> Request.signal
+
 (** Register signals a requests for synchronizing
     an array with the client.
     - Signal [<name>.sig] is emitted on array updates;
@@ -92,6 +106,11 @@ val remove : 'a array -> 'a -> unit
     Columns added to the model after registration are {i not} taken
     into account.
 
+    If provided, the [~add_xxx_hook] options are used to register hooks
+    to notify the server of corresponding array updates.
+    Each hook will be installed only once the client starts to
+    listen for array updates.
+
     Inside {b Ivette} you can obtain the entries in sync by
     using the [States.useSyncArray()] hook.
 *)
@@ -101,7 +120,10 @@ val register_array :
   descr:Markdown.text ->
   ?details:Markdown.block ->
   key:('a -> string) ->
-  iter:(('a -> unit) -> unit) ->
+  iter:('a callback) ->
+  ?add_update_hook:('a callback) ->
+  ?add_remove_hook:('a callback) ->
+  ?add_reload_hook:(unit callback) ->
   'a model -> 'a array
 
 (* -------------------------------------------------------------------------- *)
-- 
GitLab