From e196b694195dfb88ec2b44e83828f6feb1ccf012 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Tue, 31 May 2022 16:07:04 +0200
Subject: [PATCH] [Db] generalization of [on|off|with|while]_progress

---
 src/kernel_services/plugin_entry_points/db.ml | 39 +++++++-----
 .../plugin_entry_points/db.mli                | 60 ++++++++-----------
 2 files changed, 51 insertions(+), 48 deletions(-)

diff --git a/src/kernel_services/plugin_entry_points/db.ml b/src/kernel_services/plugin_entry_points/db.ml
index 4d69ac1a7f4..a091fe04227 100644
--- a/src/kernel_services/plugin_entry_points/db.ml
+++ b/src/kernel_services/plugin_entry_points/db.ml
@@ -930,6 +930,7 @@ end
 type daemon = {
   trigger : unit -> unit ;
   on_delayed : (int -> unit) option ;
+  on_finished : (unit -> unit) option ;
   debounced : float ; (* in ms *)
   mutable next_at : float ; (* next trigger time *)
   mutable last_yield_at : float ; (* last yield time *)
@@ -939,32 +940,33 @@ type daemon = {
 
 let daemons = ref []
 
-let on_progress ?(debounced=0) ?on_delayed trigger =
+let on_progress ?(debounced=0) ?on_delayed ?on_finished trigger =
   let d = {
     trigger ;
     debounced = float debounced *. 0.001 ;
     on_delayed ;
+    on_finished ;
     last_yield_at = 0.0 ;
     next_at = 0.0 ;
   } in
   daemons := List.append !daemons [d] ; d
 
 let off_progress d =
-  daemons := List.filter (fun d0 -> d != d0) !daemons
-
-(* ---- Canceling ---- *)
-
-exception Cancel
-
-(* ---- Processing ---- *)
+  daemons := List.filter (fun d0 -> d != d0) !daemons ;
+  match d.on_finished with
+  | None -> ()
+  | Some f -> f ()
 
-let warn_error exn =
-  Kernel.failure
-    "Unexpected Db.daemon exception:@\n%s"
-    (Printexc.to_string exn)
+let while_progress ?debounced ?on_delayed ?on_finished progress =
+  let d : daemon option ref = ref None in
+  let trigger () =
+    if not @@ progress () then
+      Option.iter off_progress !d
+  in
+  d := Some (on_progress ?debounced ?on_delayed ?on_finished trigger)
 
-let with_progress ?debounced ?on_delayed trigger job data =
-  let d = on_progress ?debounced ?on_delayed trigger in
+let with_progress ?debounced ?on_delayed ?on_finished trigger job data =
+  let d = on_progress ?debounced ?on_delayed ?on_finished trigger in
   let result =
     try job data
     with exn ->
@@ -973,11 +975,20 @@ let with_progress ?debounced ?on_delayed trigger job data =
   in
   off_progress d ; result
 
+(* ---- Canceling ---- *)
+
+exception Cancel
+
 (* ---- Triggering ---- *)
 
 let canceled = ref false
 let cancel () = canceled := true
 
+let warn_error exn =
+  Kernel.failure
+    "Unexpected Db.daemon exception:@\n%s"
+    (Printexc.to_string exn)
+
 let fire ~warn_on_delayed ~forced ~time d =
   if forced || time > d.next_at then
     begin
diff --git a/src/kernel_services/plugin_entry_points/db.mli b/src/kernel_services/plugin_entry_points/db.mli
index 62334a53e2e..2e3d4229914 100644
--- a/src/kernel_services/plugin_entry_points/db.mli
+++ b/src/kernel_services/plugin_entry_points/db.mli
@@ -947,56 +947,48 @@ type daemon
    daemon to be executed on each {!yield}.
    @param debounced the least amount of time between two successive calls to the
    daemon, in milliseconds (default is 0ms).
-   @param on_delayed the callback invoked as soon as the time since the last
+   @param on_delayed callback is invoked as soon as the time since the last
    {!yield} is greater than [debounced] milliseconds (or 100ms at least).
+   @param on_finished callback is invoked when the callback is unregistered.
 *)
 val on_progress :
-  ?debounced:int -> ?on_delayed:(int -> unit) -> (unit -> unit) -> daemon
+  ?debounced:int -> ?on_delayed:(int -> unit) -> ?on_finished:(unit -> unit) ->
+  (unit -> unit) -> daemon
 
 (** Unregister the [daemon]. *)
 val off_progress : daemon -> unit
 
-(** Trigger all daemons immediately. *)
-val flush : unit -> unit
-
-(** Trigger all registered daemons (debounced).
-    This function should be called from time to time by all analysers taking
-    time. In GUI mode, this will make the interface reactive. *)
-val yield : unit -> unit
-
-(** Interrupt the currently running job at the next call to {!yield} as a
-    [Cancel] exception is raised. *)
-val cancel : unit -> unit
+(** [while_progress ?debounced ?on_delayed ?on_finished trigger] is similar to
+    [on_progress] but the daemon is automatically unregistered
+    as soon as [trigger] returns [false].
+    Same optional parameters than [on_progress].
+*)
+val while_progress :
+  ?debounced:int -> ?on_delayed:(int -> unit) -> ?on_finished:(unit -> unit) ->
+  (unit -> bool) -> unit
 
 (**
    [with_progress ?debounced ?on_delayed trigger job data] executes the given
    [job] on [data] while registering [trigger] as temporary (debounced) daemon.
    The daemon is finally unregistered at the end of the computation.
-
-   Illustrative example, where [...] is the debounced time:
-   {[
-     job data :    |<-------------------------------------------------->|<daemon removed>
-                   yields   :       x   x  x x    x             x   x    x   xxx xxx
-         trigger  :       |..........   |..........   |..........  |.........
-     delayed  :                                   !
-         notes    :      (1)           (2)           (3)
-   ]}
-
-   + First yield, normal trigger.
-   + Debounced yields leads to this second trigger.
-   + Delayed warning invoked since there was no yield for more than debounced period.
-
-   Raises every exception raised during the execution of [job] on [data].
-
-   @param debounced the least amount of time, in milliseconds, between two
-   successive calls to the daemon (default is 0ms).
-   @param on_delayed the callback invoked as soon as the time since the last
-   [yield] is greater than [debounced] milliseconds (or 100ms at least).
+   Same optional parameters than [on_progress].
 *)
 val with_progress :
-  ?debounced:int -> ?on_delayed:(int -> unit) ->
+  ?debounced:int -> ?on_delayed:(int -> unit) -> ?on_finished:(unit -> unit) ->
   (unit -> unit) -> ('a -> 'b) -> 'a -> 'b
 
+(** Trigger all daemons immediately. *)
+val flush : unit -> unit
+
+(** Trigger all registered daemons (debounced).
+    This function should be called from time to time by all analysers taking
+    time. In GUI or Server mode, this will make the clients responsive. *)
+val yield : unit -> unit
+
+(** Interrupt the currently running job: the next call to {!yield}
+    will raise a [Cancel] exception. *)
+val cancel : unit -> unit
+
 (**
    Pauses the currently running process for the specified time, in milliseconds.
    Registered daemons, if any, will be regularly triggered during this waiting
-- 
GitLab