Python Runtime : stil untested optimization of Python Safe Globals. svghmi
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Fri, 10 Jul 2020 11:15:41 +0200
branchsvghmi
changeset 2992 d48ad9c3ec91
parent 2991 ddf6144a56cf
child 2993 b76f303ffce6
Python Runtime : stil untested optimization of Python Safe Globals.

Now only one python_poll block per python extension is used to trigger
execution of OnChange calls.
py_ext/PythonFileCTNMixin.py
--- a/py_ext/PythonFileCTNMixin.py	Fri Jul 03 14:02:04 2020 +0200
+++ b/py_ext/PythonFileCTNMixin.py	Fri Jul 10 11:15:41 2020 +0200
@@ -101,9 +101,8 @@
                 variable.gettype(),
                 variable.getinitial())
                for variable in variables]
-        ret.extend([("On"+variable.getname()+"Change", "python_poll", "")
-                    for variable in variables
-                    if variable.getonchange()])
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        ret.append(("On_"+location_str+"_Change", "python_poll", ""))
         return ret
 
     def CTNGenerate_C(self, buildpath, locations):
@@ -112,9 +111,7 @@
         configname = self.GetCTRoot().GetProjectConfigNames()[0]
 
         def _onchangecode(var):
-            return '"' + var.getonchange() + \
-                "('" + var.getname() + "')\"" \
-                if var.getonchange() else '""'
+            return var.getonchange() + "('" + var.getname() + "')"
 
         def _onchange(var):
             return repr(var.getonchange()) \
@@ -135,6 +132,9 @@
                 "pyextname": pyextname
             },
             self.CodeFile.variables.variable)
+
+        onchange_var_count = len([None for varinfo in varinfos if varinfo["onchange"]])
+
         # python side PLC global variables access stub
         globalstubs = "\n".join([
             """\
@@ -155,6 +155,14 @@
     %(opts)s))
 """ % varinfo for varinfo in varinfos])
 
+        on_change_func_body = "\n".join(["""
+    if changes.next():
+        # %(name)s
+        try:
+            %(onchangecode)s
+        except Exception as e:
+            errors.append("%(name)s: "+str(e))
+""" % varinfo for varinfo in varinfos if varinfo["onchange"]])
         # Runtime calls (start, stop, init, and cleanup)
         rtcalls = ""
         for section in self.SECTIONS_NAMES:
@@ -174,6 +182,9 @@
             "globalstubs": globalstubs,
             "globalsection": globalsection,
             "rtcalls": rtcalls,
+            "location_str": location_str,
+            "on_change_func_body":on_change_func_body,
+            "onchange_var_count": onchange_var_count
         }
 
         PyFileContent = """\
@@ -185,6 +196,11 @@
 ## Code for PLC global variable access
 from runtime.typemapping import TypeTranslator
 import ctypes
+
+_PySafeGetChanges_%(pyextname)s = PLCBinary.PySafeGetChanges_%(location_str)s
+_PySafeGetChanges_%(pyextname)s.restype = ctypes.POINTER(ctypes.c_int * %(onchange_var_count)d)
+_PySafeGetChanges_%(pyextname)s.argtypes = None
+
 _%(pyextname)sGlobalsDesc = []
 __ext_name__ = "%(pyextname)s"
 PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
@@ -196,6 +212,16 @@
 ## Beremiz python runtime calls
 %(rtcalls)s
 
+def On_%(pyextname)s_Change():
+    changesP = _PySafeGetChanges_%(pyextname)s()
+    if not changesP:
+        raise Exception("PySafeGetChanges returned NULL!")
+    changes = iter(changesP.contents)
+    errors = []
+%(on_change_func_body)s
+    if len(errors)>0 :
+        raise Exception("Exception in %(pyextname)s OnChange call:\\\\n" + "\\\\n".join(errors))
+
 del __ext_name__
 
 """ % loc_dict
@@ -231,7 +257,7 @@
 """
 
         vardeconchangefmt = """\
-PYTHON_POLL* __%(name)s_notifier;
+int __%(name)s_rbuffer_written = 0;
 """
 
         varretfmt = """\
@@ -255,15 +281,21 @@
         IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
         if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){
             __%(name)s_rbuffer = tmp;
-            PYTHON_POLL_body__(__%(name)s_notifier);
+            /* mark variable as changed */
+            __%(name)s_rbuffer_written = 1;
+            some_change = 1;
         }
         AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
     }
 """
-        varinitonchangefmt = """\
-    __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE();
-    __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
-    __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s));
+
+        varcollectchangefmt = """\
+    while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1));
+    pysafe_changes[change_index++] = __%(name)s_rbuffer_written;
+    /* mark variable as unchanged */
+    __%(name)s_rbuffer_written = 0;
+    AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+
 """
         vardec = "\n".join([(vardecfmt + vardeconchangefmt
                              if varinfo["onchange"] else vardecfmt) % varinfo
@@ -272,16 +304,20 @@
         varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else
                              varpubfmt) % varinfo
                             for varinfo in varinfos])
-        varinit = "\n".join([varinitonchangefmt %
-                             dict(onchangelen=len(varinfo["onchangecode"]), **varinfo)
+        varcollectchange = "\n".join([varcollectchangefmt % varinfo
                              for varinfo in varinfos if varinfo["onchange"]])
 
+        pysafe_pypoll_code = "On_"+pyextname+"_Change()"
+
         loc_dict = {
             "vardec": vardec,
-            "varinit": varinit,
             "varret": varret,
             "varpub": varpub,
             "location_str": location_str,
+            "pysafe_pypoll_code": '"'+pysafe_pypoll_code+'"',
+            "pysafe_pypoll_code_len": len(pysafe_pypoll_code),
+            "varcollectchange": varcollectchange,
+            "onchange_var_count": onchange_var_count
         }
 
         # TODO : use config name obtained from model instead of default
@@ -297,12 +333,17 @@
 #include "config.h"
 #include "beremiz.h"
 
+PYTHON_POLL* __%(location_str)s_notifier;
+
 /* User variables reference */
 %(vardec)s
 
 /* Beremiz confnode functions */
 int __init_%(location_str)s(int argc,char **argv){
-%(varinit)s
+    __%(location_str)s_notifier = __GET_GLOBAL_ON_%(location_str)s_CHANGE();
+    __SET_VAR(__%(location_str)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
+    __SET_VAR(__%(location_str)s_notifier->,CODE,,__STRING_LITERAL(%(pysafe_pypoll_code_len)d,%(pysafe_pypoll_code)s));
+
     return 0;
 }
 
@@ -314,8 +355,21 @@
 }
 
 void __publish_%(location_str)s(void){
+    int some_change = 0;
 %(varpub)s
-}
+    // call python part if there was at least a change
+    if(some_change){
+        PYTHON_POLL_body__(__%(location_str)s_notifier);
+    }
+}
+
+static int pysafe_changes[%(onchange_var_count)d];
+void* PySafeGetChanges_%(location_str)s(void){
+    int change_index=0;
+%(varcollectchange)s
+    return (void*)&pysafe_changes[0];
+}
+
 """ % loc_dict
 
         Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str)