SVGHMI: Fixes UI lifecycle problems
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Sat, 25 Nov 2023 00:18:05 +0100
changeset 3880 89549813a6c1
parent 3879 17d0d1641090
child 3881 0b3ac94f494c
SVGHMI: Fixes UI lifecycle problems

Was sometime showing HMI tree of previously closed project : no more module globals to store hmi tree.
C++ dead object exceptions happened when re-opening SVGHMI UI or building after close of UI.
svghmi/svghmi.py
svghmi/ui.py
--- a/svghmi/svghmi.py	Fri Nov 24 23:56:38 2023 +0100
+++ b/svghmi/svghmi.py	Sat Nov 25 00:18:05 2023 +0100
@@ -44,20 +44,19 @@
 # note: this only works because library's Generate_C is
 #       systematicaly invoked before CTN's CTNGenerate_C
 
-hmi_tree_root = None
-
-on_hmitree_update = None
-
-maxConnectionsTotal = 0
 
 class SVGHMILibrary(POULibrary):
+
+    hmi_tree_root = None
+
+    maxConnectionsTotal = 0
+
     def GetLibraryPath(self):
          return paths.AbsNeighbourFile(__file__, "pous.xml")
 
     def Generate_C(self, buildpath, varlist, IECCFLAGS):
-        global hmi_tree_root, on_hmitree_update, maxConnectionsTotal
-
-        maxConnectionsTotal = 0
+
+        self.maxConnectionsTotal = 0
 
         already_found_watchdog = False
         found_SVGHMI_instance = False
@@ -65,7 +64,7 @@
             if isinstance(CTNChild, SVGHMI):
                 found_SVGHMI_instance = True
                 # collect maximum connection total for all svghmi nodes
-                maxConnectionsTotal += CTNChild.GetParamsAttributes("SVGHMI.MaxConnections")["value"]
+                self.maxConnectionsTotal += CTNChild.GetParamsAttributes("SVGHMI.MaxConnections")["value"]
 
                 # spot watchdog abuse
                 if CTNChild.GetParamsAttributes("SVGHMI.EnableWatchdog")["value"]:
@@ -114,14 +113,14 @@
         # Filter known HMI types
         hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES]
 
-        hmi_tree_root = None
+        self.hmi_tree_root = None
 
         # take first HMI_NODE (placed as special node), make it root
         for i,v in enumerate(hmi_types_instances):
             path = v["IEC_path"].split(".")
             derived = v["derived"]
             if derived == "HMI_NODE":
-                hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"])
+                self.hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"])
                 hmi_types_instances.pop(i)
                 break
 
@@ -144,7 +143,7 @@
             else:
                 name = path[-1]
             new_node = HMITreeNode(path, name, derived, v["type"], vartype, v["C_path"], **kwargs)
-            placement_result = hmi_tree_root.place_node(new_node)
+            placement_result = self.hmi_tree_root.place_node(new_node)
             if placement_result is not None:
                 cause, problematic_node = placement_result
                 if cause == "Non_Unique":
@@ -171,8 +170,7 @@
 
                 self.FatalError("SVGHMI : " + message)
 
-        if on_hmitree_update is not None:
-            on_hmitree_update(hmi_tree_root)
+        self.on_hmitree_update()
 
         variable_decl_array = []
         extern_variables_declarations = []
@@ -182,7 +180,7 @@
 
         hearbeat_IEC_path = ['CONFIG', 'HEARTBEAT']
 
-        for node in hmi_tree_root.traverse():
+        for node in self.hmi_tree_root.traverse():
             if not found_heartbeat and node.path == hearbeat_IEC_path:
                 hmi_tree_hearbeat_index = item_count
                 found_heartbeat = True
@@ -232,8 +230,8 @@
             "item_count": item_count,
             "var_access_code": targets.GetCode("var_access.c"),
             "PLC_ticktime": self.GetCTR().GetTicktime(),
-            "hmi_hash_ints": ",".join(map(str,hmi_tree_root.hash())),
-            "max_connections": maxConnectionsTotal
+            "hmi_hash_ints": ",".join(map(str,self.hmi_tree_root.hash())),
+            "max_connections": self.maxConnectionsTotal
             }
 
         gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c")
@@ -254,7 +252,7 @@
         # Backup HMI Tree in XML form so that it can be loaded without building
         hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
         hmitree_backup_file = open(hmitree_backup_path, 'wb')
-        hmitree_backup_file.write(etree.tostring(hmi_tree_root.etree()))
+        hmitree_backup_file.write(etree.tostring(self.hmi_tree_root.etree()))
         hmitree_backup_file.close()
 
         return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "",
@@ -268,15 +266,18 @@
         return [(name, iec_type, "") for name, iec_type in SPECIAL_NODES]
 
 
-
-def Register_SVGHMI_UI_for_HMI_tree_updates(ref):
-    global on_hmitree_update
-    def HMITreeUpdate(_hmi_tree_root):
-        obj = ref()
-        if obj is not None:
-            obj.HMITreeUpdate(_hmi_tree_root)
-
-    on_hmitree_update = HMITreeUpdate
+    registered_uis = []
+    def on_hmitree_update(self):
+        for uiref in self.registered_uis[:]:
+            obj = uiref()
+            if obj is None:
+                self.registered_uis.remove(uiref)
+            else:
+                obj.HMITreeUpdate(self.hmi_tree_root)
+
+
+    def Register_SVGHMI_UI_for_HMI_tree_updates(self, uiref):
+        self.registered_uis.append(uiref)
 
 
 class SVGHMIEditor(ConfTreeNodeEditor):
@@ -288,18 +289,19 @@
         self.Controler = controler
 
     def CreateSVGHMI_UI(self, parent):
-        global hmi_tree_root
-
-        if hmi_tree_root is None:
-            buildpath = self.Controler.GetCTRoot()._getBuildPath()
+        ctroot = self.Controler.GetCTRoot()
+        svghmilib = ctroot.Libraries["SVGHMI"]
+
+        if svghmilib.hmi_tree_root is None:
+            buildpath = ctroot._getBuildPath()
             hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
             if os.path.exists(hmitree_backup_path):
                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
-                hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
-
-        ret = SVGHMI_UI(parent, self.Controler, Register_SVGHMI_UI_for_HMI_tree_updates)
-
-        on_hmitree_update(hmi_tree_root)
+                svghmilib.hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
+
+        ret = SVGHMI_UI(parent, self.Controler, svghmilib.Register_SVGHMI_UI_for_HMI_tree_updates)
+
+        svghmilib.on_hmitree_update()
 
         return ret
 
@@ -450,9 +452,10 @@
         return res
 
     def GetHMITree(self):
-        global hmi_tree_root
+        ctroot = self.GetCTRoot()
+        svghmilib = ctroot.Libraries["SVGHMI"]
         self.ProgressStart("hmitree", "getting HMI tree")
-        res = [hmi_tree_root.etree(add_hash=True)]
+        res = [svghmilib.hmi_tree_root.etree(add_hash=True)]
         self.ProgressEnd("hmitree")
         return res
 
@@ -527,7 +530,10 @@
             url=url)
 
     def CTNGenerate_C(self, buildpath, locations):
-        global hmi_tree_root
+        ctroot = self.GetCTRoot()
+        svghmilib = ctroot.Libraries["SVGHMI"]
+        hmi_tree_root = svghmilib.hmi_tree_root
+        
 
         if hmi_tree_root is None:
             self.FatalError("SVGHMI : Library is not selected. Please select it in project config.")
@@ -545,7 +551,7 @@
         target_path = os.path.join(build_path, target_fname)
         hash_path = os.path.join(build_path, "svghmi_"+location_str+".md5")
 
-        self.GetCTRoot().logger.write("SVGHMI:\n")
+        ctroot.logger.write("SVGHMI:\n")
 
         if os.path.exists(svgfile):
 
@@ -729,7 +735,7 @@
                    watchdog_initial = self.GetParamsAttributes("SVGHMI.WatchdogInitial")["value"],
                    watchdog_interval = self.GetParamsAttributes("SVGHMI.WatchdogInterval")["value"],
                    maxConnections = self.GetParamsAttributes("SVGHMI.MaxConnections")["value"],
-                   maxConnections_total = maxConnectionsTotal,
+                   maxConnections_total = svghmilib.maxConnectionsTotal,
                    **svghmi_options
         ))
 
--- a/svghmi/ui.py	Fri Nov 24 23:56:38 2023 +0100
+++ b/svghmi/ui.py	Sat Nov 25 00:18:05 2023 +0100
@@ -717,7 +717,8 @@
         register_for_HMI_tree_updates(weakref.ref(self))
 
     def HMITreeUpdate(self, hmi_tree_root):
-        self.SelectionTree.MakeTree(hmi_tree_root)
+        if self:
+            self.SelectionTree.MakeTree(hmi_tree_root)
 
     def OnHMITreeNodeSelection(self, hmitree_nodes):
         self.Staging.OnHMITreeNodeSelection(hmitree_nodes)