diff --git a/gui.opam b/gui.opam new file mode 100644 index 0000000000000000000000000000000000000000..6935db2b3ea3eb0b444edfa647bb6a40b3f367f3 --- /dev/null +++ b/gui.opam @@ -0,0 +1,21 @@ +opam-version: "2.0" +synopsis: "A Graphical User Interface for XXX" +maintainer: "XXX" +description: """ +sasa is a *Self-stabilizing Algorithms SimulAtor*. XXX +""" +authors: [ "XXX" ] +license: "CeCILL" +homepage: "https://gricad-gitlab.univ-grenoble-alpes.fr/verimag/synchrone/sasa" +dev-repo: "https://gricad-gitlab.univ-grenoble-alpes.fr/verimag/synchrone/sasa.git" +bug-reports: "https://gricad-gitlab.univ-grenoble-alpes.fr/verimag/synchrone/sasa/issues" +build: [make "build"] +install: [make "install"] +depends: [ + "lablgtk3" + "sasa" +] +depexts: [ + ["graphviz" "emacs" "gnuplot" "zathura"] +] +post-messages: ["The last version can be obtained via (opam repo add) http://www-verimag.imag.fr/DIST-TOOLS/SYNCHRONE/opam-repository/ "] diff --git a/lib/sasacore/daemon.ml b/lib/sasacore/daemon.ml index e4f2a3ec8d79aa7cc684d0e74f0cbd6407ea4d9d..ffa1ebc12b38ffc29d390d5368b569250d56e5d5 100644 --- a/lib/sasacore/daemon.ml +++ b/lib/sasacore/daemon.ml @@ -1,4 +1,4 @@ -(* Time-stamp: *) +(* Time-stamp: *) type t = | Synchronous (* select all actions *) @@ -54,7 +54,7 @@ let (synchrone: 'a list list -> 'a list) = fun all -> XXX this daemon is not fair: it is biased by the degree of nodes. *) -let (locally_central: 'v pna list list -> 'v pna list) = +let (locally_central_pna: 'v pna list list -> 'v pna list) = fun all -> let remove_one_conflict al = let _a, al = random_list2 al in @@ -74,6 +74,27 @@ let (locally_central: 'v pna list list -> 'v pna list) = let al = distributed all in remove_conflicts al +(* Somewhat duplicate the previous one. Hard to avoid... *) +let (locally_central: ('v * 'v list) list list -> 'v list) = + fun all -> + let remove_one_conflict al = + let _a, al = random_list2 al in + al + in + let rec remove_conflicts al = + let activated_pids = List.map (fun (pid,_) -> pid) al in + let conflicts, ok = List.partition (fun (_p,nl) -> + List.exists (fun n -> List.mem n activated_pids) nl + ) al + in + if conflicts = [] then ok else + let conflicts = remove_one_conflict conflicts in + ok @ (remove_conflicts conflicts) + in + if all = [] then [] else + let al = distributed all in + fst (List.split (remove_conflicts al)) + let rec map3 f l1 l2 l3 = match (l1, l2, l3) with ([], [], []) -> [] @@ -137,7 +158,7 @@ let (f: bool -> bool -> t -> 'v Process.t list -> let al = central nall in get_activate_val al pl, al | LocallyCentral -> - let al = locally_central nall in + let al = locally_central_pna nall in get_activate_val al pl, al | Distributed -> let al = distributed nall in diff --git a/lib/sasacore/daemon.mli b/lib/sasacore/daemon.mli index 1c5d94fbb5b2028049fe576154f6418c87709c24..6a71adba5292e96df88dd4227101348a9daa6991 100644 --- a/lib/sasacore/daemon.mli +++ b/lib/sasacore/daemon.mli @@ -1,4 +1,4 @@ -(* Time-stamp: *) +(* Time-stamp: *) type t = | Synchronous (* select all actions *) @@ -49,3 +49,10 @@ val f : bool -> bool -> t -> 'v Process.t list -> 'v Env.t -> 'v pna list list -> bool list list -> (string -> string -> bool) -> bool list list * 'v pna list + +(** Used in gtkgui.ml *) +val central: 'a list list -> 'a list +val distributed: 'a list list -> 'a list + +(* pid + its neighbors in input *) +val locally_central: ('v * 'v list) list list -> 'v list diff --git a/sasa.opam b/sasa.opam index 911122630d200b21266d0175e1e5cf9ccfcd49ed..c08c14d0da047ecd0f95cbb228e2cc7238605537 100644 --- a/sasa.opam +++ b/sasa.opam @@ -23,7 +23,7 @@ depends: [ "dune" { >= "1.11" } "ocamlgraph" "lutils" - "rdbg" { >= "1.184" } + "rdbg" { >= "1.190" } ] depopts: [ "lustre-v6" diff --git a/test/Makefile.dot b/test/Makefile.dot index e67483c7cf3f3706cf3029929444627fcfacf2e5..0e01d5ab0fc1bb0383826909c0002b03bbc6be54 100644 --- a/test/Makefile.dot +++ b/test/Makefile.dot @@ -1,5 +1,4 @@ -# Time-stamp: - +# Time-stamp: # Rules to generate various dot files. # The DECO_PATTERN variable should be defined diff --git a/test/coloring/Makefile b/test/coloring/Makefile index 40cb20b8ff346bfa74ad81eb446a9a215c253e7d..92758405a62ff8c2b85738ed81b76d0c6bcebe95 100644 --- a/test/coloring/Makefile +++ b/test/coloring/Makefile @@ -1,4 +1,4 @@ -# Time-stamp: +# Time-stamp: sasa=$(DIR)/bin/sasa -l 100 @@ -58,11 +58,11 @@ rdbgui-demo: grid10.ml rdbg-luciole: grid4.ml echo "" > include.ml - rdbg --luciole -sut "sasa -rif grid4.dot --custom-daemon" --missing-vars-last + rdbg --luciole -sut "sasa -rif grid4.dot --custom-daemon" -rdbgui-luciole: grid4.ml +rdbgui-custd: grid4.ml echo "" > include.ml - rdbgui4sasa --luciole -sut "sasa -rif grid4.dot --custom-daemon" --missing-vars-last + rdbgui4sasa -sut "sasa -rif grid4.dot --custom-daemon" rdbg3: ring.ml diff --git a/test/coloring/my-rdbg-tuning.ml b/test/coloring/my-rdbg-tuning.ml index e1e921562303f128ba40412edb048f79050c32b7..b7eaf7035e343852109bd0a8848d81f34cb1d788 100644 --- a/test/coloring/my-rdbg-tuning.ml +++ b/test/coloring/my-rdbg-tuning.ml @@ -3,7 +3,7 @@ #use "rdbg-cmds.ml";; #use "sasa-rdbg-cmds.ml";; -#use "include.ml";; +#use "include.ml";; let _ = del_hook "print_event"; diff --git a/tools/daemongui/.gitignore b/tools/daemongui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a7f2cfceea1b4b78dd5b5bb11122f06876e5f9f8 --- /dev/null +++ b/tools/daemongui/.gitignore @@ -0,0 +1,2 @@ +gui +!*.sh diff --git a/tools/daemongui/build.sh b/tools/daemongui/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..ee5aacc4f3598f0655c0c639fcbfe089b6df5dbc --- /dev/null +++ b/tools/daemongui/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +ocamlfind ocamlc str.cma -g -thread -package lablgtk3 -linkpkg gui.ml -o gui diff --git a/tools/daemongui/dune b/tools/daemongui/dune new file mode 100644 index 0000000000000000000000000000000000000000..1a63067d7fe2077a1ef81c8121798a15ff72d59d --- /dev/null +++ b/tools/daemongui/dune @@ -0,0 +1,14 @@ + + +(executables + (names gui ) + (flags :standard -w -3-6-7-10-24-26-27-33-35 -no-strict-sequence) + (libraries lablgtk3 str lutils)) + +(install + (section bin) + (package gui) + (files + (gui.exe as daemongui) + ) +) diff --git a/tools/daemongui/gui.ml b/tools/daemongui/gui.ml new file mode 100644 index 0000000000000000000000000000000000000000..191f82481e90cfad3955ba0d86ef263cb089b908 --- /dev/null +++ b/tools/daemongui/gui.ml @@ -0,0 +1,319 @@ + +open GMain +open GdkKeysyms + +(* Lance rdbg avec les arguments passés au gui *) +let from_rdbg, to_rdbg = + let quote str = if String.contains str ' ' then ("\""^str^"\"") else str in + let rdbg_cmd = String.concat " " ("rdbg"::(List.tl (List.map quote (Array.to_list Sys.argv)))) in + Unix.open_process rdbg_cmd + +let _ = Unix.set_nonblock (Unix.descr_of_in_channel from_rdbg) + +(** Lit la sortie d'un processus. + Renvoie None si la fin du flux est atteinte *) +let read_stdout (ic: in_channel): string option = + let buff = Bytes.create 512 in + let res = ref "" in + let cond = ref true in + let eof = ref false in + Unix.sleepf 0.5; + while !cond do + try + let n = Stdlib.input ic buff 0 512 in + res := !res ^ (Bytes.sub_string buff 0 n); + if n < 512 then cond := false; + if n == 0 then eof := true; + with Sys_blocked_io -> cond := false + done; + if !eof then None else Some !res + +(* Fonctions pour travailler avec les commandes rdbg *) +type command_state = + | Normal + | Save + +let cmd_loop = ref false +let cmd_state = ref Normal +let cmd_output: string option ref = ref None + +let read_rdbg_out (): bool = + let res = read_stdout from_rdbg in + match res with + | None -> + if !cmd_state == Save then + cmd_output := Some ""; (* set cmd_output to exit the loop in rdbg_get *) + false + | Some str -> + let str = String.trim str in + if String.length str > 0 then Printf.printf "%s" str; + match !cmd_state with + | Normal -> (); + | Save -> + cmd_output := res; + cmd_state := Normal; + ; + true + +(** Envoie une commande à rdbg *) +let rdbg (str: string) = + Printf.fprintf to_rdbg "%s\n%!" str; + Printf.printf "%s\n%!" str; + () + +(** Envoie une commande à rdbg et récupère son résultat *) +let rdbg_get (cmd: string): string = + cmd_state := Save; + cmd_output := None; + rdbg cmd; + while !cmd_output == None do + (* + Si cmd_loop vaut true c'est qu'il y a une boucle en court qui lit la sortie de rdbg + Sinon, il faut lire la sortie ici. + *) + if not !cmd_loop then ignore(read_rdbg_out ()) else (); + done; + cmd_state := Normal; + match !cmd_output with + | Some str -> + (* supprime le prompt rdbg du résultat *) + Str.global_replace (Str.regexp_string "(rdbg) ") "" str + | None -> assert false + +(** Compte le nombre de noeuds *) +let rdbg_count_nodes (): int = + let cmd = " List.length (List.filter (fun (n,v) -> String.length n > 5 && String.sub n 0 5 = \"Enab_\") !e.outputs);;" in + let res = rdbg_get cmd in + (* rdbg renvoie une réponse qui ressemble à "- : int = 7" *) + (* on prend ce qui est après le '=' *) + let count_str = List.hd (List.tl (String.split_on_char '=' res)) in + int_of_string (String.trim count_str) + +(** +Extrait le nom et l'état des noeuds +@return liste de tuples (nom, etat, activable) +*) +let rdbg_nodes_info (): (string * string * bool) list = + (* récupère une liste qui dit si chaque état de chaque noeud est activable/pas activable *) + + let cmd = " String.concat \";\" (List.map (fun (n,v) -> Printf.sprintf \"%s=%s\" (String.sub n 5 (String.length n - 5)) (val_to_string string_of_float v)) (List.filter (fun (n,v) -> String.length n > 5 && String.sub n 0 5 = \"Enab_\") !e.data));;" in + let res = + let res = rdbg_get cmd in + if not (Str.string_partial_match (Str.regexp "[ \n]*- : string =") res 0) then + (* On recommence si on a obtenu un résultat incohérent (bug suite à la vérification que + !e.data a été chargé) *) + rdbg_get " ;;" + else + res + in + + (* rdbg renvoie - : string = "p1_conflict=f;p2_conflict=t" etc. *) + (* "p1" est le nom du noeud, "conflict" le nom de l'état, f=false donc non activable, t=true donc activable *) + let data = String.trim (String.sub res 13 (String.length res - 13)) in + let data = String.trim (Str.global_replace (Str.regexp_string "\"") "" data) in + List.map (fun str -> + let parts = String.split_on_char '=' str in + match parts with + | a::b::[] -> + (match String.split_on_char '_' a with + | node::state::[] -> + let node_enabled = match b with + | "t" | "true" -> true + | _ -> false + in + (node, state, node_enabled) + | _ -> + Printf.printf "\nERROR: %s" a; + assert false;) + | _ -> + Printf.printf "\nERROR: %s" str; + assert false; + ) (String.split_on_char ';' data) + +(** Hashtable qui dit pour chaque noeud s'il est activable (càd s'il a un état activable) ou non. +On considère que les états sont mutuellement exclusifs. *) +let rdbg_nodes_enabled () = + let table = Hashtbl.create 8 in + List.iter (fun (node, state, enabled) -> + let prev_enab = + match Hashtbl.find_opt table node with + | None -> false + | Some e -> e + in + Hashtbl.replace table node (prev_enab || enabled) + ) (rdbg_nodes_info ()); + table + +(** Nom de la table utilisée par le hook dans rdbg *) +let hook_hashtbl_name = "daemongui_activate" + +(** Met en place le hook *) +let init_rdbg_hook () = + let value = "Hashtbl.create 1" in + let cmd = Printf.sprintf " let %s = %s;;" hook_hashtbl_name value in + assert (String.length (rdbg_get cmd) > 0); + + let cmd = Printf.sprintf " let daemongui sl = + let sl = List.filter (fun (n,v) -> String.length n > 5 && String.sub n 0 5 = \"Enab_\") sl in + let res = List.map (fun (n,enabled) -> + (* n est de la forme Enab_node_state, enabled est un Data.v *) + let str = String.sub n 5 ((String.length n)-5) in + let node_name = List.hd (String.split_on_char '_' str) in + let to_activate = match Hashtbl.find_opt %s node_name with + | None -> false + | Some x -> x + in + let activate = match enabled with + | B true -> B to_activate + | _ -> B false + in + (str, activate) + ) sl in + Some res;;" hook_hashtbl_name in + assert (String.length (rdbg_get cmd) > 0); + + let cmd = " rdbg_mv_hook := Some daemongui;;" in + assert (String.length (rdbg_get cmd) > 0); + () + +(** Met à jour le hook pour un noeud *) +let update_rdbg_hook node activate = + let cmd = Printf.sprintf " Hashtbl.replace %s \"%s\" %B;;" + hook_hashtbl_name node activate + in + rdbg cmd; + ignore(read_rdbg_out ()); + () + +(* GTK3 *) +let locale = GtkMain.Main.init () +let thread = GtkThread.start() +let main () = + let window = GWindow.window ~width:320 ~height:240 + ~title:"Daemon GUI" + ~show:true () in + let vbox = GPack.vbox ~packing:window#add () in + ignore (window#connect#destroy ~callback: ( + fun () -> + rdbg "q"; (* quit rdbg, this will stop the readloop below *) + Main.quit () (* terminate gtk *) + )); + + (* attend le chargement de la session rdbg *) + (* On attend après avoir lancé l'UI pour que l'utilisateur ne s'impatiente pas *) + let rec wait_for_rdbg () = + match read_stdout from_rdbg with + | None -> (); + | Some str -> + Printf.printf "%s%!" str; + let tr = String.trim str in + if not (Str.string_match (Str.regexp ".*\\(rdbg\\).*") tr 0) then wait_for_rdbg () + in + wait_for_rdbg (); + + (* création du rdbg_mv_hook et de tout ce qu'il faut autour *) + init_rdbg_hook (); + + (* attend que !e.data soit dispo *) + (* + BUG - la sortie de la commande ci-dessous ne sera visible qu'à la commande suivante, + ce qui décale tout... + Cet assert échoue mais ne devrait pas : + + assert (String.length (String.trim (rdbg_get " !e.data;;")) > 0); + *) + rdbg " !e.data;;"; + Unix.sleepf 1.5; + ignore(read_rdbg_out ()); + + (* 1 case par noeud : activer/pas activer *) + (* NB : lablgtk3 ne propose pas le FlowBox (pourtant dispo dans GTK >= 3.12) *) + let container = GPack.hbox ~packing:vbox#add () in + let nodes_table = rdbg_nodes_enabled () in + let nodes_enabled = Hashtbl.to_seq nodes_table in + let n = Hashtbl.length nodes_table in + + let checkboxes_map = Hashtbl.create n in + Seq.iter (fun (name, enabled) -> + (* cf. classe toggle_button de lablgtk3 *) + let checkbox = GButton.check_button ~label:name ~packing:container#add () in + (* Quand on coche/décoche, met à jour le rdbg_mv_hook *) + ignore(checkbox#connect#toggled ~callback: (fun () -> + update_rdbg_hook name checkbox#active + )); + checkbox#set_sensitive enabled; (* désactive la box si le noeud n'est pas activable *) + checkbox#set_active false; (* décoche la case *) + Hashtbl.add checkboxes_map name checkbox + ) nodes_enabled; + + let update_checkbox node enabled = + let checkbox = Hashtbl.find checkboxes_map node in + checkbox#set_sensitive enabled + in + + (* Affichage d'informations *) + let scrolled = GBin.scrolled_window ~border_width:10 + ~shadow_type:`OUT ~height:250 ~packing:vbox#add () + in + let gtext = GText.view ~wrap_mode:`CHAR ~height:50 ~editable:false ~width:50 + ~packing: scrolled#add () ~cursor_visible:true + in + + let gtext_content = ref "Noeuds activables :" in + gtext#buffer#set_text !gtext_content; + + let print_gui str = + let txt = Printf.sprintf "%s\n%s" !gtext_content str in + gtext#buffer#set_text txt; + gtext_content := txt; + in + + Seq.iter (fun (name, enabled) -> + print_gui (Printf.sprintf "%s : %B" name enabled); + ) nodes_enabled; + + (* Boutons de contrôle de la simulation *) + let hbox = GPack.hbox ~packing:vbox#add () in + + let update_all_checkboxes () = + print_gui "Nouveaux noeuds activables :"; + Seq.iter (fun (name, enabled) -> + update_checkbox name enabled; + print_gui (Printf.sprintf "%s : %B" name enabled); + ) (Hashtbl.to_seq (rdbg_nodes_enabled ())) + in + + let rdbg_btn label cmd = + let btn = GButton.button ~label:label ~packing:hbox#add () in + btn#misc#set_tooltip_text "tooltip"; + ignore (btn#connect#clicked ~callback: ( + fun () -> + rdbg cmd; + ignore (read_rdbg_out ()); + print_gui (Printf.sprintf "> %s" cmd); + update_all_checkboxes (); + ) + ); + btn + in + + let _ = rdbg_btn "<<" "pr" in + let _ = rdbg_btn "<" "bd" in + let _ = rdbg_btn "G" "graph_view" in + let _ = rdbg_btn ">" "sd" in + let _ = rdbg_btn ">>" "nr" in + + (* Read rdbg output until rdbg is closed *) + let rec read_stdout_loop () = + if read_rdbg_out () then read_stdout_loop () else () + in + cmd_loop := true; + read_stdout_loop () + +let () = + (* choix de session *) + ignore (read_rdbg_out ()); + ignore (rdbg "\n"); + + (* lance l'interface graphique *) + main () diff --git a/tools/daemongui/run.sh b/tools/daemongui/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..1e5e1c96bc68eda91af880a70cb7892be11efee1 --- /dev/null +++ b/tools/daemongui/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e +cd $(dirname "$0") +exe=$(readlink -f ./gui) + +./build.sh +cd ../../test/coloring +"$exe" -sut "sasa -custd ring.dot" --missing-vars-last +#"$exe" -sut "sasa ring.dot" diff --git a/tools/rdbg4sasa/dune b/tools/rdbg4sasa/dune index 2fcfde61cc280f022b1b0b57200f6fc1a95336bd..c128797af7171adf4d9752ddef5d2ba45fde4c2f 100644 --- a/tools/rdbg4sasa/dune +++ b/tools/rdbg4sasa/dune @@ -1,6 +1,6 @@ (install - (files sasa-rdbg-cmds.ml dot4sasa.ml) + (files sasa-rdbg-cmds.ml dot4sasa.ml gtkgui.ml) (section lib) (package sasa) ) diff --git a/tools/rdbg4sasa/gtkgui.ml b/tools/rdbg4sasa/gtkgui.ml new file mode 100644 index 0000000000000000000000000000000000000000..3b9f5a016ac85b1f7765785d7ff0c973cfd9c985 --- /dev/null +++ b/tools/rdbg4sasa/gtkgui.ml @@ -0,0 +1,615 @@ +(* Time-stamp: *) + +#thread +#require "lablgtk3" + +open GMain +open GdkKeysyms +open RdbgEvent +open Data + +(** Extrait le nom et l'état des noeuds +@return liste de tuples (nom, etat, activable) +*) +let rdbg_nodes_info e: (string * string * bool) list = + (* récupère une liste qui dit si chaque état de chaque noeud est activable/pas activable *) + let enabled = + List.filter (fun (n,v) -> String.length n > 5 && String.sub n 0 5 = "Enab_") e.data + in + let split_var (str, value) = + let v = match value with B v -> v | _ -> assert false in + let p, label = + match String.split_on_char '_' str with + | [] | _::[] | _::_::[] -> assert false + | _::x::y::_ -> x, y + in + p, label,v + in + List.map split_var enabled + +(** Liste qui dit pour chaque noeud s'il est activable. On suppose + qu'ils sont groupés. + + XXX On suppose aussi qu'au plus une action par process est + enabled. Les demons dans sasa, font mieux. Mais ici, c'est + raisonnable si on ne veut pas avoir nb_actions fois plus de boutons + ! (alors que ca n'arrive jamais) *) +let rdbg_nodes_enabled e = + match rdbg_nodes_info e with + | [] -> assert false + | (node, _action, enab)::l -> + let last, res = + List.fold_left + (fun ((p_node, p_enab), res) (node, _action, enab) -> + if p_node = node then + (node, p_enab || enab), res + else + (node, enab), (p_node, p_enab)::res + ) + ((node, enab), []) + l + in + List.rev (last::res) + + +type daemon_kind = Distributed | Synchronous | Central | LocCentral | ManualCentral | Manual +let daemon_kind = ref ManualCentral + +let refresh_fun_tbl = Hashtbl.create 1 +let _ = Hashtbl.add refresh_fun_tbl "update dot" d +let refresh () = + Hashtbl.iter (fun str f -> f()) refresh_fun_tbl + +(** Met en place le hook *) +let daemongui_activate = Hashtbl.create 1 +let init_rdbg_hook () = + let guidaemon sl = + let sl = List.filter (fun (n,v) -> String.length n > 5 && String.sub n 0 5 = "Enab_") sl in + let res = List.map (fun (n,enabled) -> + (* n est de la forme Enab_node_state, enabled est un Data.v *) + let str = String.sub n 5 ((String.length n)-5) in + let node_name = List.hd (String.split_on_char '_' str) in + let to_activate = match Hashtbl.find_opt daemongui_activate node_name with + | None -> false + | Some x -> x + in + let activate = match enabled with + | B true -> B to_activate + | _ -> B false + in + (str, activate) + ) sl + in + Some res + in + rdbg_mv_hook := Some guidaemon + +let set_tooltip b = b#misc#set_tooltip_text + +let custom_daemon p gtext vbox step_button round_button = + (* création du rdbg_mv_hook et de tout ce qu'il faut autour *) + init_rdbg_hook (); + + let daemon_box = GPack.hbox ~packing:vbox#add () ~homogeneous:true ~height:15 in + let dk_dd = GButton.radio_button ~active:(!daemon_kind=Distributed) + ~label:"Distributed" ~packing:daemon_box#add () + in + let make_but act lbl = GButton.radio_button ~active:act ~label:lbl + ~group:dk_dd#group ~packing:daemon_box#add () + in + let dk_cd = make_but (!daemon_kind=Central) "Central" in + let dk_lcd = make_but (!daemon_kind=LocCentral) "Locally Central" in + let dk_sd = make_but (!daemon_kind=Synchronous) "Synchronous" in + let dk_manual = make_but (!daemon_kind=Manual) "Manual" in + let dk_manual_central = make_but (!daemon_kind=ManualCentral) "Manual Central" in + set_tooltip dk_dd (Printf.sprintf "Set the automatic distributed mode"); + set_tooltip dk_sd (Printf.sprintf "Set the automatic synchronous mode"); + set_tooltip dk_cd (Printf.sprintf "Set the automatic central mode"); + set_tooltip dk_lcd (Printf.sprintf "Set the automatic locally central mode"); + set_tooltip dk_manual (Printf.sprintf "Set the manual mode"); + set_tooltip dk_manual_central (Printf.sprintf "Set the manual central mode"); + + let nodes_enabled = rdbg_nodes_enabled !e in + + (** Met à jour le hook pour node quand le bouton ou une checkbox correspondant est activé *) + let update_rdbg_hook node activate = + (match !daemon_kind with + | Distributed | Synchronous | Central | LocCentral -> + assert false (* SNO *) + | ManualCentral -> ( + let txt = Printf.sprintf "ManualCentral step: %s\n%s" node (str_of_sasa_event false !e) in + gtext#buffer#set_text txt; + Hashtbl.iter + (fun n status -> Hashtbl.replace daemongui_activate n (n = node && status)) + daemongui_activate; + ) + | Manual -> + let txt = Printf.sprintf "Manual step: \n%s" (str_of_sasa_event false !e) in + gtext#buffer#set_text txt; + Hashtbl.replace daemongui_activate node activate + ); + + in + + (* 1 case par noeud : activer/pas activer *) + (* NB : lablgtk3 ne propose pas le FlowBox (pourtant dispo dans GTK >= 3.12) *) + let n = List.length nodes_enabled in + let m = int_of_float (sqrt (float_of_int n)) in + + (* Des checkbox pour le mode Manuel *) + (* build manually a m x m grid *) + let i = ref 0 in + let checkbox_grid = GPack.vbox ~packing:vbox#add () in + let checkbox_scrolled_grid = GBin.scrolled_window ~border_width:10 ~hpolicy:`AUTOMATIC + ~vpolicy:`AUTOMATIC + ~height:300 + ~shadow_type:`OUT ~packing:checkbox_grid#add () + in + let checkbox_scrolled_grid_box = GPack.vbox ~packing:checkbox_scrolled_grid#add () in + let checkbox_line = GPack.hbox ~packing:checkbox_scrolled_grid_box#add () in + + let checkbox_line_ref = ref checkbox_line in + let checkbox_map = Hashtbl.create n in + List.iter (fun (name, enabled) -> + incr i; + if !i > m then ( + i := 1; + let new_checkbox_line = GPack.hbox ~packing:checkbox_scrolled_grid_box#add () in + checkbox_line_ref := new_checkbox_line; + ); + (* cf. classe toggle_button de lablgtk3 *) + let checkbox = GButton.check_button ~label:name ~packing:!checkbox_line_ref#add () in + (* Quand on coche/décoche, met à jour le rdbg_mv_hook *) + set_tooltip checkbox (Printf.sprintf "check to activate %s at the next step" name); + ignore(checkbox#connect#toggled + ~callback: (fun () -> update_rdbg_hook name checkbox#active)); + checkbox#set_sensitive enabled; (* désactive la box si le noeud n'est pas activable *) + checkbox#set_active false; (* décoche la case *) + Hashtbl.add checkbox_map name checkbox; + ) + nodes_enabled; + + (* Des boutons pour le mode Manuel Central *) + let pushbox_grid = GPack.vbox ~packing:vbox#add () ~homogeneous:true in + let pushbox_scrolled_grid = GBin.scrolled_window ~border_width:10 ~hpolicy:`AUTOMATIC + ~vpolicy:`AUTOMATIC + ~height:300 + ~shadow_type:`OUT ~packing:pushbox_grid#add () + in + let pushbox_scrolled_grid_box = GPack.vbox ~homogeneous:true ~packing:pushbox_scrolled_grid#add () in + let pushbox_line = GPack.hbox ~packing:pushbox_scrolled_grid_box#add () in + let pushbox_line_ref = ref pushbox_line in + let pushbox_map = Hashtbl.create n in + i := 0; + List.iter (fun (name, enabled) -> + incr i; + if !i > m then ( + i := 1; + let new_pushbox_line = GPack.hbox ~packing:pushbox_scrolled_grid_box#add () in + pushbox_line_ref := new_pushbox_line + ); + (* cf. classe toggle_button de lablgtk3 *) + let pushbox = GButton.button ~label:name ~packing:!pushbox_line_ref#add () in + set_tooltip pushbox (Printf.sprintf "Press to activate %s" name); + (* Quand on appuie, met à jour le rdbg_mv_hook *) + ignore(pushbox#event#connect#button_press + ~callback: (fun _ -> + update_rdbg_hook name true; + sd(); + refresh (); + false)); + Hashtbl.add pushbox_map name pushbox + ) + nodes_enabled; + + (* Des compteurs pour les modes automatiques *) + let counter_grid = GPack.vbox ~packing:vbox#add () in + let counter_scrolled_grid = GBin.scrolled_window ~border_width:10 ~hpolicy:`AUTOMATIC + ~vpolicy:`AUTOMATIC ~height:400 + ~shadow_type:`OUT ~packing:counter_grid#add () + in + let counter_scrolled_grid_box = GPack.vbox ~packing:counter_scrolled_grid#add () in + let counter_line = GPack.hbox ~packing:counter_scrolled_grid_box#add () in + let counter_line_ref = ref counter_line in + let counter_map = Hashtbl.create n in + i := 0; + List.iter (fun (name, enabled) -> + incr i; + if !i > m then ( + i := 1; + let new_counter_line = GPack.hbox ~packing:counter_scrolled_grid_box#add () in + counter_line_ref := new_counter_line + ); + + let counter_container_frame = GBin.frame ~label:name ~packing:!counter_line_ref#add () in + let counter_container = + GPack.hbox ~homogeneous:true ~border_width: 2 ~spacing:0 + ~packing:counter_container_frame#add () + in + let incr_container = GPack.vbox ~packing:counter_container#add () in + + let counter = new GUtil.variable 0 in + let decB = GButton.button ~label:"-" ~packing:incr_container#add () in + let counter_lbl = GMisc.label ~packing:counter_container#pack () in + let incB = GButton.button ~label:"+" ~packing:incr_container#add () in + let adj = GData.adjustment ~lower:0. ~upper:100. ~step_incr:1. ~page_incr:10. () in + ignore (decB#connect#clicked ~callback:(fun () -> adj#set_value (float(counter#get-1)))); + ignore (incB#connect#clicked ~callback:(fun () -> adj#set_value (float(counter#get+1)))); + ignore (adj#connect#value_changed ~callback:(fun () -> counter#set (truncate adj#value))); + ignore (counter#connect#changed ~callback:(fun n -> counter_lbl#set_text (string_of_int n))); + counter#set 1; + set_tooltip counter_container (Printf.sprintf "Set the priority of %s" name); + Hashtbl.add counter_map name counter + ) + nodes_enabled; + + let hide b = b#misc#hide() in + let show b = b#misc#show() in + let update_checkbox node enabled = + match !daemon_kind with + | Manual -> + show step_button; show checkbox_grid; + hide round_button; hide pushbox_grid; hide counter_grid; + let checkbox = Hashtbl.find checkbox_map node in + if enabled then + show checkbox + else ( + checkbox#set_active false; (* on decoche *) + hide checkbox + ); + checkbox#set_sensitive enabled + | ManualCentral -> + hide step_button; hide round_button; hide checkbox_grid; hide counter_grid; + show pushbox_grid; + let pushbox = Hashtbl.find pushbox_map node in + if enabled then show pushbox else hide pushbox; + pushbox#set_sensitive enabled + | Distributed | Synchronous | Central | LocCentral -> + show step_button; show round_button; show counter_grid; + hide checkbox_grid; hide pushbox_grid; + in + let update_all_checkboxes () = + let nodes_enabled = rdbg_nodes_enabled !e in + List.iter (fun (name, enabled) -> + Hashtbl.replace daemongui_activate name enabled; + update_checkbox name enabled + ) + nodes_enabled + in + Hashtbl.add refresh_fun_tbl "" update_all_checkboxes; + + let set_dd_mode () = daemon_kind := Distributed; refresh () in + let set_sd_mode () = daemon_kind := Synchronous; refresh () in + let set_cd_mode () = daemon_kind := Central; refresh () in + let set_lcd_mode () = daemon_kind := LocCentral; refresh () in + let set_manual_mode () = daemon_kind := Manual; refresh () in + let set_manual_central_mode () = daemon_kind := ManualCentral; refresh () in + ignore(dk_dd#connect#clicked ~callback:set_dd_mode); + ignore(dk_sd#connect#clicked ~callback:set_sd_mode); + ignore(dk_cd#connect#clicked ~callback:set_cd_mode); + ignore(dk_lcd#connect#clicked ~callback:set_lcd_mode); + ignore(dk_manual#connect#clicked ~callback:set_manual_mode); + ignore(dk_manual_central#connect#clicked ~callback:set_manual_central_mode); + (* Affichage d'informations *) + (* gtext#buffer#set_text !gtext_content; *) + + let rec get_higher_prioriry nl = + let prio n = + let counter = Hashtbl.find counter_map n in + counter#get + in + let rec aux p acc = function + | [] -> acc + | (n, false)::t -> aux p acc t + | (n, true)::t -> + let pn = prio n in + if p > pn then aux p acc t else + if p = pn then aux p (n::acc) t else + aux pn [n] t + in + aux 0 [] nl + in + let step () = + let nodes_enabled = rdbg_nodes_enabled !e in + let nodes = List.filter (fun (_,b) -> b) nodes_enabled in + let nodes = get_higher_prioriry nodes in + match !daemon_kind with + | Distributed -> + let nodes = List.map (fun x -> [x]) nodes in + let to_activate = Daemon.distributed nodes in + Hashtbl.clear daemongui_activate; + List.iter (fun n -> Hashtbl.replace daemongui_activate n true) to_activate; + sd (); + p ("Distributed step : " ^ (String.concat "," to_activate)) + | Synchronous -> ( + Hashtbl.clear daemongui_activate; + List.iter (fun n -> Hashtbl.replace daemongui_activate n true) nodes; + sd (); + p ("Synchronous step : " ^ (String.concat "," nodes)) + ) + | Central -> + let nodes = List.map (fun x -> [x]) nodes in + let to_activate = Daemon.central nodes in + Hashtbl.clear daemongui_activate; + List.iter (fun n -> Hashtbl.replace daemongui_activate n true) to_activate; + sd (); + p ("Central step : " ^ (String.concat "," to_activate)) + + | LocCentral -> + let get_neigbors x = + let succ = snd (List.split (topology.succ x)) in + let pred = topology.pred x in + let res = List.fold_left (fun acc x -> if List.mem x acc then acc else x::acc) succ pred in + (* p (Printf.sprintf "voisins(%s)=%s\n" x (String.concat "," res)); *) + res + in + let nodes = List.map (fun x -> [x, get_neigbors x]) nodes in + let to_activate = Daemon.locally_central nodes in + Hashtbl.clear daemongui_activate; + List.iter (fun n -> Hashtbl.replace daemongui_activate n true) to_activate; + sd (); + p "Locally central step: finish me" + + | ManualCentral -> () (* SNO *) + | Manual -> sd () + in + step + +let prefix = + try + let opam_dir = Unix.getenv "OPAM_SWITCH_PREFIX" in + opam_dir + with Not_found -> "$HOME/sasa/" + +let lib_prefix = prefix ^ "/lib/sasa" +let libui_prefix = prefix ^ "/lib/rdbgui4sasa" + +let oc_stdin = stdout +let ic_stdout = stdin + +open GButton + (* GTK3 *) +let main () = + let _locale = GtkMain.Main.init () in + let _thread = GtkThread.start () in + let window = GWindow.window + (* ~width:320 ~height:240 *) + ~title:"A rdbg GUI for sasa" + ~show:true () + in + let w = GPack.vbox ~packing:window#add () ~homogeneous:false in + let box = GPack.vbox ~packing: w#add () in + let gbox = GPack.hbox ~packing: box#add () in + let gbox2 = GPack.hbox ~packing: box#add () in + let sw2 = GBin.scrolled_window ~border_width:10 ~shadow_type:`OUT ~height:250 + ~packing:box#add () + in + set_tooltip sw2 "This window displays commands outputs"; + let text_out = GText.view ~wrap_mode:`CHAR ~height:250 ~editable:false + ~packing: sw2#add () ~cursor_visible:true + in + let p str = + text_out#set_buffer (GText.buffer ~text:str ()); + Printf.fprintf oc_stdin "%s\n%!" str; + Printf.printf "%s\n%!" str; + in + (* It should be better to rely on the gtk event handler + + let sw1 = GBin.scrolled_window ~border_width:10 ~shadow_type:`IN ~height:30 ~width:50 + ~packing:box#add () + in + sw1#misc#set_tooltip_text "This window displays the commands sent to the rdbg cli"; + let text_in = GText.view ~wrap_mode:`CHAR ~height:250 ~editable:true ~width:50 + ~packing: sw1#add () ~cursor_visible:true + in + let rec read_text_in () = + let buff = text_in#buffer#get_text () in + let size = String.length buff in + if size >0 then ( + let last = String.get buff (size - 1) in + if last = '\n' then ( + Printf.fprintf oc_stdin "%s\n%!" buff; + Printf.printf "%s\n%!" buff; + text_in#set_buffer (GText.buffer ~text:"(rdbg) " ()) + ) else () + ); + Unix.sleepf 0.1; + read_text_in () + in + let _ = Thread.create read_text_in () in + *) + (* Printf.fprintf oc_stdin "#use \"sasa-rdbg-cmds.ml\";;\n%!"; *) + (* Printf.fprintf oc_stdin "print_sasa_event false !e;;\n%!"; (* print the first event *) *) + let bbox = GPack.hbox ~packing: box#add () in + + let change_label button str = + let icon = button#image in + button#set_label str; + button#set_image icon; + refresh () + in + let button_cb cmd () = + cmd (); + let txt = Printf.sprintf "%s" (str_of_sasa_event false !e) in + text_out#buffer#set_text txt; + refresh () + in + let button_cb_string cmd () = + let txt = Printf.sprintf "%s" (cmd ()) in + text_out#buffer#set_text txt + in + + let back_step_button = button ~use_mnemonic:true ~stock:`GO_BACK ~packing:bbox#add () in + set_tooltip back_step_button "Move BACKWARD to the previous STEP"; + change_label back_step_button "Ste_p"; + ignore (back_step_button#connect#clicked ~callback:(button_cb bd)); + + let step_button = button ~use_mnemonic:true ~packing:bbox#add ~stock:`GO_FORWARD () in + let back_round_button = + button ~packing:bbox#add ~stock:`MEDIA_PREVIOUS ~use_mnemonic:true ~label:"back round" () + in + let round_button = + button ~use_mnemonic:true ~stock:`MEDIA_FORWARD ~packing:bbox#add ~label:"round" () + in + let ze_step = + if custom_mode then + custom_daemon p text_out w step_button round_button + else + s + in + let step () = + ze_step(); + d() + in + + set_tooltip step_button "Move FORWARD to the next STEP"; + change_label step_button "_Step"; + ignore (step_button#connect#clicked ~callback:(button_cb step)); + set_tooltip round_button "Move FORWARD to the next ROUND"; + change_label round_button "_Round"; + ignore (round_button#connect#clicked ~callback:(button_cb nr)); + set_tooltip back_round_button "Move BACKWARD to the previous ROUND"; + change_label back_round_button "Roun_d"; + ignore (back_round_button#connect#clicked ~callback:(button_cb pr)); + + let legitimate () = + let legitimate_button = button ~use_mnemonic:true ~packing:bbox#add () in + set_tooltip legitimate_button + "Move FORWARD until a legitimate configuration is reached (silence by default)"; + let image = GMisc.image ~file:(libui_prefix^"/chut_small.svg") () in + legitimate_button#set_image image#coerce; + (* change_label legitimate_button "Silen_t"; *) + ignore (legitimate_button#connect#clicked ~callback:(button_cb legitimate)) + + in + legitimate (); + + let graph () = + let graph_button = button ~use_mnemonic:true ~packing:bbox#add () in + set_tooltip graph_button "Visualize the Topology states: Green=Enabled ; Gold=Active"; + let image = GMisc.image ~file:(libui_prefix^"/graph_small.png") () in + graph_button#set_image image#coerce; + ignore (graph_button#connect#clicked ~callback:(button_cb graph_view)); + + in + graph (); + + let make_button stock lbl msg cmd = + let butt = button ~use_mnemonic:true ~stock:stock ~packing:bbox#add ~label:lbl () in + set_tooltip butt msg; + change_label butt lbl; + ignore (butt#connect#clicked ~callback:cmd); + butt + in + if args.oracles <> [] then ( + ignore (make_button `OK "_Oracle" "Move FORWARD until an oracle is violated" + (* let image = GMisc.image ~file:"../rdbg-utils/oracle_small.jpg" () in *) + (* viol_button#set_image image#coerce; *) + (button_cb_string viol_string)) + ); + let _ = make_button `UNDO "_Undo" "Undo the last move" (button_cb (fun ()->u();d())) in + let _ = make_button `REFRESH "Restar_t" "Restart from the beginning" + (button_cb (fun ()-> r();d())) + in + let _ = make_button `MEDIA_PLAY "_Sim2chro" "Launch sim2chro on the generated data (so far)" + (button_cb (fun ()->sim2chro ())) + in + let _ = make_button `MEDIA_PLAY "_Gnuplot" "Launch gnuplot-rif on the generated data (so far)" + (button_cb (fun ()->gnuplot ())) + in + let _ = make_button `INFO "_Info" "Get information about the current session" + (button_cb_string info_string) + in + + let _ = make_button `QUIT "_Quit" "Quit RDBGUI" (fun() -> p "bye"; Stdlib.exit 0) in + + + let dot_button = radio_button ~packing:gbox#add ~label:"dot" () in + let make_but active lbl = radio_button ~packing:gbox#add + ~active:active ~group:dot_button#group ~label:lbl () + in + let fd_button = make_but false "fdp" in + let sf_button = make_but false "sfdp" in + let ne_button = make_but true "neato" in + let tw_button = make_but false "twopi" in + let ci_button = make_but false "circo" in + let pa_button = make_but false "patchwork" in + let os_button = make_but false "osage" in + + let connect button str cmd = + ignore (button#connect#clicked + ~callback:(fun () -> p ((button#misc#tooltip_text)^"\n"^(help_string str)); + dot_view := cmd; !dot_view())) + in + let have_parent () = (* is there a parent field in the state ? *) + List.exists (fun (v,_) -> Str.string_match (Str.regexp ".*_par.*") v 0) !e.data + in + if have_parent () then ( + let make_but lbl = GButton.radio_button ~packing:gbox2#add + ~group:dot_button#group ~label:lbl () + in + let par_dot_button = make_but "dot*" in + let par_fd_button = make_but "fdp*" in + let par_sf_button = make_but "sfdp*" in + let par_ne_button = make_but "neato*" in + let par_tw_button = make_but "twopi*" in + let par_ci_button = make_but "circo*" in + let par_pa_button = make_but "patchwork*" in + let par_os_button = make_but "osage*" in + set_tooltip par_dot_button "Use dot, but show only links to the parent"; + set_tooltip par_fd_button "Use fdp, but show only links to the parent"; + set_tooltip par_sf_button "Use sfdp, but show only links to the parent"; + set_tooltip par_ne_button "Use neato, but show only links to the parent"; + set_tooltip par_tw_button "Use twopi, but show only links to the parent"; + set_tooltip par_ci_button "Use circo, but show only links to the parent"; + set_tooltip par_pa_button "Use patchwork, but show only links to the parent"; + set_tooltip par_os_button "Use osage, but show only links to the parent"; + connect par_dot_button "d_par" d_par; + connect par_fd_button "fd_par" fd_par; + connect par_sf_button "sf_par" sf_par; + connect par_ne_button "ne_par" ne_par; + connect par_tw_button "tw_par" tw_par; + connect par_ci_button "ci_par" ci_par; + connect par_pa_button "pa_par" pa_par; + connect par_os_button "os_par" os_par; + ); + set_tooltip dot_button "Use the dot engine to display the graph"; + set_tooltip fd_button "Use the fdp engine to display the graph"; + set_tooltip sf_button "Use the sfdp engine to display the graph"; + set_tooltip ne_button "Use the neato engine to display the graph"; + set_tooltip tw_button "Use the twopi engine to display the graph"; + set_tooltip ci_button "Use the circo engine to display the graph"; + set_tooltip pa_button "Use the patchwork engine to display the graph"; + set_tooltip os_button "Use the osage engine to display the graph"; + + connect dot_button "d" dot; + connect fd_button "fd" fd; + connect sf_button "sf" sf; + connect ne_button "ne" ne; + connect tw_button "tw" tw; + connect ci_button "ci" ci; + connect pa_button "pa" pa; + connect os_button "os" os; + + ignore (window#connect#destroy ~callback: ( + fun () -> + quit (); (* quit rdbg, this will stop the readloop below *) + Main.quit () (* terminate gtk *) + )); + + refresh () + +let gui = main +(* todo +- les oracles sont violés en silence +- couper les grosses fonctions en morceaux +- cacher les messages issus du #use +- lire les commandes dans text_in (comment ? c'est rdbgtop qui lance gtk maintenant...) +- reglage de la taille des boites +- utiliser les GEdit.spin_button ? + cf lablgtk/examples/spin.ml + https://lazka.github.io/pgi-docs/Gtk-3.0/classes/SpinButton.html#Gtk.SpinButton + *) +;; + +gui();; diff --git a/tools/rdbg4sasa/sasa-rdbg-cmds.ml b/tools/rdbg4sasa/sasa-rdbg-cmds.ml index edd7f2e7d08cc0df490b71fdb1a03a72b76e32fd..03bf1a8a168e7ded1abb75782e46fc45dcfbcbb0 100644 --- a/tools/rdbg4sasa/sasa-rdbg-cmds.ml +++ b/tools/rdbg4sasa/sasa-rdbg-cmds.ml @@ -16,10 +16,10 @@ let _ = Hashtbl.add roundtbl 1 (1,true);; (**********************************************************************) (* redefine (more meaningful) step and back-step for sasa *) -let sasa_step e = next_cond e (fun ne -> ne.kind = Ltop) -let sasa_bstep e = rev_cond e (fun ne -> ne.kind = Ltop);; -let s () = e:=sasa_step !e ; emacs_udate !e; store !e.nb;pe();; -let b () = e:=sasa_bstep !e ; emacs_udate !e; store !e.nb;pe();; +let sasa_step e = next_cond e (fun ne -> ne.kind = e.kind) +let sasa_bstep e = rev_cond e (fun ne -> ne.kind = e.kind) +let s () = e:=sasa_step !e ; emacs_udate !e; store !e.nb;pe() +let b () = e:=sasa_bstep !e ; emacs_udate !e; store !e.nb;pe() let p = try Topology.read dotfile @@ -137,7 +137,10 @@ let update_round_nb e = | Some (n,_) -> roundnb := n (* go to next and previous rounds *) -let next_round e = next_cond e round;; +let next_round e = + let ne = next_cond e round in + if ne.kind = e.kind then ne else next e + let nr () = e:=next_round !e; store !e.nb; !dot_view ();; let pr () = e:=goto_last_ckpt !e.nb; @@ -220,13 +223,18 @@ let goto_next_false_oracle e = List.mem ("ok", Bool) e.outputs && not (vb "ok" e)) -let viol () = +let viol_string () = if args.oracles <> [] then ( - e:=goto_next_false_oracle !e; !dot_view () + e:=goto_next_false_oracle !e; !dot_view (); "An oracle has been violated. Cf the .rif file" ) else ( - Printf.printf "No oracle is set.\n%!" + "No oracle is set." ) ;; +let viol () = Printf.printf "%s\n%!" (viol_string ()) + +let _ = add_doc_entry + "viol" "unit -> unit" "Move forward until the oracle is violated" + "sasa" "sasa-rdbg-cmds.ml" (**********************************************************************) (* Move forward until silence *) @@ -280,8 +288,16 @@ let _ = add_doc_entry "nd" "unit -> unit" "go to the next event and update the network" "sasa" "sasa-rdbg-cmds.ml"; add_doc_entry "bd" "unit -> unit" "go to the previous event and update the network" "sasa" "sasa-rdbg-cmds.ml"; add_doc_entry "nr" "unit -> unit" "go to the next round and update the network" "sasa" "sasa-rdbg-cmds.ml"; - add_doc_entry "pr" "unit -> unit" "go to the previous round and update the network" "sasa" "sasa-rdbg-cmds.ml" - ;; + add_doc_entry "pr" "unit -> unit" "go to the previous round and update the network" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "d_par" "unit -> unit" "cf d (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "ne_par" "unit -> unit" "cf ne (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "tw_par" "unit -> unit" "cf tw (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "ci_par" "unit -> unit" "cf ci (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "fd_par" "unit -> unit" "cf fd (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "sf_par" "unit -> unit" "cf sf (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "pa_par" "unit -> unit" "cf pa (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + add_doc_entry "os_par" "unit -> unit" "cf os (for topology with a parent field)" "sasa" "sasa-rdbg-cmds.ml"; + () let l () = l(); diff --git a/tools/rdbgui4sasa/dune b/tools/rdbgui4sasa/dune index 900d46574039db69ac8aa7f97a46d9f5ffac34da..add52da3dc1be6336974843921306f13ea894cd5 100644 --- a/tools/rdbgui4sasa/dune +++ b/tools/rdbgui4sasa/dune @@ -13,7 +13,7 @@ ) ) (install - (files chut_small.svg graph_small.png) + (files chut_small.svg graph_small.png rdbgui.ml) (section lib) (package rdbgui4sasa) ) diff --git a/tools/rdbgui4sasa/rdbgui.ml b/tools/rdbgui4sasa/rdbgui.ml index 848b262679ba23d3c9fef2742ce41abe9b20b2b7..e068a4eb6bfdf346926f6d3cdf88a35b0df2a39c 100644 --- a/tools/rdbgui4sasa/rdbgui.ml +++ b/tools/rdbgui4sasa/rdbgui.ml @@ -1,328 +1,17 @@ - let quote str = if String.contains str ' ' then ("\""^str^"\"") else str -let rdbg_cmd = - String.concat " " ("rdbg"::(List.tl (List.map quote (Array.to_list Sys.argv)))) - -(* let oc_stdin = Unix.open_process_out rdbg_cmd *) -let ic_stdout, oc_stdin = Unix.open_process rdbg_cmd -(* let ic_stdout, oc_stdin, ic_stderr = - Unix.open_process_full rdbg_cmd (Unix.environment()) *) - -let _ = - Unix.set_nonblock (Unix.descr_of_in_channel ic_stdout); -(* Unix.set_nonblock (Unix.descr_of_in_channel ic_stderr) *) - () -(* let p str = Printf.printf "%s\n%!" str *) - - -let read_stdout ic = - let buff = Bytes.create 256 in - let res = ref "" in - let cond = ref true in - Unix.sleepf 0.5; - while !cond do - try - let n = Stdlib.input ic buff 0 256 in - res := !res ^ (Bytes.sub_string buff 0 n); - if n < 256 then cond := false; - with Sys_blocked_io -> cond := false - done; - if !res <> "" then Printf.printf "%s%!" !res; - !res - - -let prefix = - try - let opam_dir = Unix.getenv "OPAM_SWITCH_PREFIX" in - opam_dir - with Not_found -> "$HOME/sasa/" - -let lib_prefix = prefix ^ "/lib/sasa" -let libui_prefix = prefix ^ "/lib/rdbgui4sasa" +let gui = + Printf.sprintf " --ocaml-cmd \"#use \\\"gtkgui.ml\\\";;\"" -let gui str = - Printf.fprintf oc_stdin "%s\n" str; (* sent the session choice *) - (* Printf.fprintf oc_stdin "#require \"sasa\";;\n%!" ; *) - (* Printf.fprintf oc_stdin "#use \"sasa-rdbg-cmds.ml\";;\n%!"; *) - Printf.fprintf oc_stdin - "del_hook \"print_event\"; add_hook \"print_event\" (print_sasa_event false);;\n%!"; - (* Printf.fprintf oc_stdin "print_sasa_event false !e;;\n"; (* print the first event *) *) - - let _locale = GMain.init () in - let _thread = GtkThread.start() in - let w = GWindow.window ~show:true ~title: "A rdbg GUI for sasa" () in - let box = GPack.vbox ~packing: w#add () in - let gbox = GPack.hbox ~packing: box#add () in - let gbox2 = GPack.hbox ~packing: box#add () in - let sw1 = GBin.scrolled_window ~border_width:10 ~shadow_type:`IN ~height:30 ~width:50 - ~packing:box#add () - in - let sw2 = GBin.scrolled_window ~border_width:10 ~shadow_type:`OUT ~height:250 - ~packing:box#add () - in - sw1#misc#set_tooltip_text "This window displays the commands sent to the rdbg cli"; - sw2#misc#set_tooltip_text "This window displays commands outputs"; - let text1 = GText.view ~wrap_mode:`CHAR ~height:50 ~editable:false ~width:50 - ~packing: sw1#add () ~cursor_visible:true - in - let text2 = GText.view ~wrap_mode:`CHAR ~height:250 ~editable:false - ~packing: sw2#add () ~cursor_visible:true - in - (* text2#place_cursor_onscreen (); *) - (* let text3 = GText.view ~editable:false ~packing: box#add () in *) - - (* let input_buff = Buffer.create 100 in *) - let p str = - (* Buffer.add_string input_buff (str^"\n"); *) - (* text1#set_buffer (GText.buffer ~text:(Buffer.contents input_buff) ()); *) - text1#set_buffer (GText.buffer ~text:str ()); - Printf.fprintf oc_stdin "%s\n%!" str; - Printf.printf "%s\n%!" str; - in - (* p str; *) - Printf.fprintf oc_stdin "#require \"sasa\";;\n%!" ; - (* Printf.fprintf oc_stdin "#use \"sasa-rdbg-cmds.ml\";;\n%!"; *) - Printf.fprintf oc_stdin "print_sasa_event false !e;;\n%!"; (* print the first event *) - - let readloop () = - while true do - let str = read_line () in - p str; - if str="q" then exit 0; - done; - in - (* let str = input_line ic_stdout in *) - (* let n_buff = GText.buffer ~text:str () in *) - (* text#set_buffer n_buff; *) - - +let rdbg_cmd = + String.concat " " ("rdbg"::(List.tl (List.map quote (Array.to_list Sys.argv)))) ^ gui - let bbox = GPack.hbox ~packing: box#add () in - - let change_label button str = - let icon = button#image in - button#set_label str; - button#set_image icon - in - - let back_step_button = - GButton.button ~use_mnemonic:true ~stock:`GO_BACK ~packing:bbox#add - ~label:"back step" () - in - back_step_button#misc#set_tooltip_text "Move BACKWARD to the previous STEP"; - change_label back_step_button "Ste_p"; - ignore (back_step_button#connect#clicked ~callback:(fun () -> p "bd")); - - let step_button = - GButton.button ~use_mnemonic:true ~packing:bbox#add ~stock:`GO_FORWARD - ~label:"step" () - in - step_button#misc#set_tooltip_text "Move FORWARD to the next STEP"; - change_label step_button "_Step"; - ignore (step_button#connect#clicked ~callback:(fun () -> p "sd")); - - let back_round_button = - GButton.button ~packing:bbox#add ~stock:`MEDIA_PREVIOUS ~use_mnemonic:true - ~label:"back round" () - in - back_round_button#misc#set_tooltip_text "Move BACKWARD to the previous ROUND"; - change_label back_round_button "Roun_d"; - ignore (back_round_button#connect#clicked ~callback:(fun () -> p "pr" )); - - let round_button = - GButton.button ~use_mnemonic:true ~stock:`MEDIA_FORWARD - ~packing:bbox#add ~label:"round" () - in - round_button#misc#set_tooltip_text "Move FORWARD to the next ROUND"; - change_label round_button "_Round"; - ignore (round_button#connect#clicked ~callback:(fun () -> p "nr")); - - let legitimate () = - let legitimate_button = GButton.button ~use_mnemonic:true - ~packing:bbox#add () in - legitimate_button#misc#set_tooltip_text - "Move FORWARD until a legitimate configuration is reached (silence by default)"; - let image = GMisc.image ~file:(libui_prefix^"/chut_small.svg") () in - legitimate_button#set_image image#coerce; - (* change_label legitimate_button "Silen_t"; *) - ignore (legitimate_button#connect#clicked ~callback:(fun () -> p "legitimate")) - - in - legitimate (); - - let graph () = - let graph_button = GButton.button ~use_mnemonic:true ~packing:bbox#add () in - graph_button#misc#set_tooltip_text - "Visualize the Topology states: Green=Enabled ; Gold=Active"; - let image = GMisc.image ~file:(libui_prefix^"/graph_small.png") () in - graph_button#set_image image#coerce; - ignore (graph_button#connect#clicked ~callback: (fun () -> p "graph_view" )); - - in - graph (); - - let viol_oracle () = - let viol_button = GButton.button ~use_mnemonic:true ~stock:`OK - ~packing:bbox#add () in - viol_button#misc#set_tooltip_text - "Move FORWARD until an oracle states something wrong happened"; - (* let image = GMisc.image ~file:"../rdbg-utils/oracle_small.jpg" () in *) - (* viol_button#set_image image#coerce; *) - change_label viol_button "_Oracle"; - ignore (viol_button#connect#clicked ~callback:(fun () -> p "viol")) - in - (* if args.oracles <> [] then *) - viol_oracle (); - - let undo_button = GButton.button ~use_mnemonic:true ~stock:`UNDO - ~packing:bbox#add ~label:"undo" () - in - undo_button#misc#set_tooltip_text "Undo the last move"; - ignore (undo_button#connect#clicked ~callback:(fun () -> p "u\nd")); - - let restart_button = GButton.button ~use_mnemonic:true ~stock:`REFRESH - ~packing:bbox#add ~label:"restart" () - in - restart_button#misc#set_tooltip_text "Restart from the beginning"; - change_label restart_button "Restar_t"; - ignore (restart_button#connect#clicked ~callback:(fun ()-> p "r\nd")); - - let info_button = - GButton.button ~use_mnemonic:true ~stock:`INFO ~packing:bbox#add ~label:"_Info" () - in - change_label info_button "_Info"; - info_button#misc#set_tooltip_text "Get information about the current session"; - ignore (info_button#connect#clicked ~callback: (fun() -> p "i")); - - let quit_button = - GButton.button ~use_mnemonic:true ~stock:`QUIT ~packing:bbox#add ~label:"bye" () - in - quit_button#misc#set_tooltip_text "Quit RDBGUI"; - ignore (quit_button#connect#clicked ~callback: (fun() -> p "q"; exit 0)); - - - let dot_button = GButton.radio_button ~packing:gbox#add ~label:"dot" () in - let fd_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"fdp" () - in - let sf_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"sfdp" () - in - let ne_button = GButton.radio_button ~packing:gbox#add - ~active:true ~group:dot_button#group ~label:"neato" () - in - let tw_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"twopi" () - in - let ci_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"circo" () - in - let pa_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"patchwork" () - in - let os_button = GButton.radio_button ~packing:gbox#add - ~group:dot_button#group ~label:"osage" () - in - - let par_dot () = - let par_dot_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"dot*" () in - let par_fd_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"fdp*" () in - let par_sf_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"sfdp*" () in - let par_ne_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"neato*" () in - let par_tw_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"twopi*" () in - let par_ci_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"circo*" () in - let par_pa_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"patchwork*" () in - let par_os_button = GButton.radio_button ~packing:gbox2#add - ~group:dot_button#group ~label:"osage*" () - in - par_dot_button#misc#set_tooltip_text "Use dot, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_fd_button#misc#set_tooltip_text "Use fdp, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_sf_button#misc#set_tooltip_text "Use sfdp, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_ne_button#misc#set_tooltip_text "Use neato, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_tw_button#misc#set_tooltip_text "Use twopi, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_ci_button#misc#set_tooltip_text "Use circo, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_pa_button#misc#set_tooltip_text "Use patchwork, but show only links to the parent (works if State.t contains a 'par:int' field)"; - par_os_button#misc#set_tooltip_text "Use osage, but show only links to the parent (works if State.t contains a 'par:int' field)"; - ignore (par_dot_button#connect#clicked - ~callback:(fun () -> p "dot_view := d_par; !dot_view();;")); - ignore (par_fd_button#connect#clicked - ~callback:(fun () -> p "dot_view := fd_par; !dot_view();;")); - ignore (par_sf_button#connect#clicked - ~callback:(fun () -> p "dot_view := sf_par; !dot_view();;")); - ignore (par_ne_button#connect#clicked - ~callback:(fun () -> p "dot_view := ne_par; !dot_view();;")); - ignore (par_tw_button#connect#clicked - ~callback:(fun () -> p "dot_view := tw_par; !dot_view();;")); - ignore (par_ci_button#connect#clicked - ~callback:(fun () -> p "dot_view := ci_par; !dot_view();;")); - ignore (par_pa_button#connect#clicked - ~callback:(fun () -> p "dot_view := pa_par; !dot_view();;")); - ignore (par_os_button#connect#clicked - ~callback:(fun () -> p "dot_view := os_par; !dot_view();;")) - in - - let have_parent () = (* is there a parent field in the state ? *) - (* List.exists (fun (v,_) -> Str.string_match (Str.regexp ".*_par.*") v 0) !e.data *) - true - in - if have_parent () then par_dot (); - dot_button#misc#set_tooltip_text "Use the dot engine to display the graph"; - fd_button#misc#set_tooltip_text "Use the fdp engine to display the graph"; - sf_button#misc#set_tooltip_text "Use the sfdp engine to display the graph"; - ne_button#misc#set_tooltip_text "Use the neato engine to display the graph"; - tw_button#misc#set_tooltip_text "Use the twopi engine to display the graph"; - ci_button#misc#set_tooltip_text "Use the circo engine to display the graph"; - pa_button#misc#set_tooltip_text "Use the patchwork engine to display the graph"; - os_button#misc#set_tooltip_text "Use the osage engine to display the graph"; - - ignore (dot_button#connect#clicked - ~callback:(fun () -> p "dot_view:=dot; !dot_view();;")); - ignore (fd_button#connect#clicked - ~callback:(fun () -> p "dot_view:=fd; !dot_view();;")); - ignore (sf_button#connect#clicked - ~callback:(fun () -> p "dot_view:=sf; !dot_view();;")); - ignore (ne_button#connect#clicked - ~callback:(fun () -> p "dot_view:=ne; !dot_view();;")); - ignore (tw_button#connect#clicked - ~callback:(fun () -> p "dot_view:=tw; !dot_view();;")); - ignore (ci_button#connect#clicked - ~callback:(fun () -> p "dot_view:=ci; !dot_view();;")); - ignore (pa_button#connect#clicked - ~callback:(fun () -> p "dot_view:=pa; !dot_view();;")); - ignore (os_button#connect#clicked - ~callback:(fun () -> p "dot_view:=os; !dot_view();;")); - - ignore (read_stdout ic_stdout); - let rec read_stdout_loop () = - let res = read_stdout ic_stdout in - let res = Str.global_replace (Str.regexp_string "(rdbg) ") "" res in - if res <> "" then text2#set_buffer (GText.buffer ~text:res ()); - (* let res = read_stdout ic_stderr in *) - (* if res <> "" then text3#set_buffer (GText.buffer ~text:res ()); *) - read_stdout_loop () - in - - let _ = Thread.create read_stdout_loop () in - - (* ignore (Sys.command "rdbg"); *) - readloop () - (* GMain.main () *) -;; - let welcome () = Printf.printf "rdbgui4sasa is a GUI wrapper around rdbg when used with sasa\n"; - Printf.printf "you can thus replace rdbg by rdbgui4sasa, but sasa *must be* involved in the session\n"; - Printf.printf "Example: \n"; + Printf.printf "you can thus replace rdbg by rdbgui4sasa, but sasa *must be* "; + Printf.printf "involved in the session\nExample: \n"; Printf.printf " rdbgui4sasa -sut \"sasa g.dot\"\n"; Printf.printf " rdbgui4sasa -h\n\n"; flush stdout @@ -331,5 +20,4 @@ let _ = let n = Array.length Sys.argv in welcome (); if n = 1 && Mypervasives.ls "rdbg-session" "ml" = [] then exit 0; - ignore (read_stdout ic_stdout); - gui (read_line ()) (* read the session choice *) + Sys.command rdbg_cmd