SVGHMI : still trying to optimize. Added xslt code to identitfy minimum set of elements needed by a particular page. Plan is to remove unseen/unused elements from the DOM, and re-appending them later when used, on page switch. Disabled previous optimization. svghmi
authorEdouard Tisserant
Thu, 27 Feb 2020 13:14:24 +0100
branchsvghmi
changeset 2843 94696b3f69fb
parent 2842 2f73f001955a
child 2844 eee5dcd9fc92
SVGHMI : still trying to optimize. Added xslt code to identitfy minimum set of elements needed by a particular page. Plan is to remove unseen/unused elements from the DOM, and re-appending them later when used, on page switch. Disabled previous optimization.
svghmi/gen_index_xhtml.xslt
svghmi/gen_index_xhtml.ysl2
svghmi/svghmi.js
--- a/svghmi/gen_index_xhtml.xslt	Fri Feb 21 16:22:44 2020 +0100
+++ b/svghmi/gen_index_xhtml.xslt	Thu Feb 27 13:14:24 2020 +0100
@@ -126,40 +126,6 @@
       <xsl:text>All units must be set to "px" in Inkscape's document properties</xsl:text>
     </xsl:message>
   </xsl:template>
-  <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:use">
-    <g>
-      <xsl:attribute name="style">
-        <xsl:value-of select="@style"/>
-      </xsl:attribute>
-      <xsl:attribute name="transform">
-        <xsl:value-of select="@transform"/>
-      </xsl:attribute>
-      <xsl:attribute name="id">
-        <xsl:value-of select="@id"/>
-      </xsl:attribute>
-      <xsl:variable name="targetid" select="substring-after(@xlink:href,'#')"/>
-      <xsl:apply-templates mode="unlink_clone" select="//svg:*[@id = $targetid]"/>
-    </g>
-  </xsl:template>
-  <xsl:template xmlns="http://www.w3.org/2000/svg" mode="unlink_clone" match="@*">
-    <xsl:copy/>
-  </xsl:template>
-  <xsl:template xmlns="http://www.w3.org/2000/svg" mode="unlink_clone" match="svg:*">
-    <xsl:choose>
-      <xsl:when test="@id = $hmi_elements/@id">
-        <use>
-          <xsl:attribute name="xlink:href">
-            <xsl:value-of select="concat('#',@id)"/>
-          </xsl:attribute>
-        </use>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:copy>
-          <xsl:apply-templates mode="unlink_clone" select="@* | node()"/>
-        </xsl:copy>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
   <xsl:template match="/">
     <xsl:comment>
       <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
@@ -238,17 +204,35 @@
     <xsl:param name="elems"/>
     <xsl:variable name="descend" select="$elems/descendant-or-self::svg:*"/>
     <xsl:variable name="clones" select="$descend[self::svg:use]"/>
-    <xsl:variable name="reals" select="$descend[not(self::svg:use)]"/>
     <xsl:variable name="originals" select="//svg:*[concat('#',@id) = $clones/@xlink:href]"/>
     <xsl:choose>
       <xsl:when test="$originals">
-        <func:result select="$reals | func:refered_elements($originals)"/>
+        <func:result select="$descend | func:refered_elements($originals)"/>
       </xsl:when>
       <xsl:otherwise>
-        <func:result select="$reals"/>
+        <func:result select="$descend"/>
       </xsl:otherwise>
     </xsl:choose>
   </func:function>
+  <func:function name="func:included_geometry">
+    <xsl:param name="elt"/>
+    <xsl:variable name="g" select="$geometry[@Id = $elt/@id]"/>
+    <func:result select="$geometry[@Id != $elt/@id and&#10;                           @x &gt;= $g/@x and @y &gt;= $g/@y and &#10;                           @x+@w &lt;= $g/@x+$g/@w and @y+@h &lt;= $g/@y+$g/@h]"/>
+  </func:function>
+  <func:function name="func:sumarized_elements">
+    <xsl:param name="elements"/>
+    <xsl:variable name="short_list" select="$elements[not(ancestor::*/@id = $elements/@id)]"/>
+    <xsl:variable name="filled_groups" select="$short_list/parent::svg:*[not(descendant::*[not(self::svg:g)][not(@id = $short_list/descendant-or-self::*[not(self::svg:g)]/@id)])]"/>
+    <xsl:variable name="groups_to_add" select="$filled_groups[not(ancestor::*/@id = $filled_groups/@id)]"/>
+    <func:result select="$groups_to_add | $short_list[not(ancestor::svg:g/@id = $filled_groups/@id)]"/>
+  </func:function>
+  <func:function name="func:all_related_elements">
+    <xsl:param name="page"/>
+    <xsl:variable name="page_included_geometry" select="func:included_geometry($page)"/>
+    <xsl:variable name="page_sub_elements" select="func:refered_elements($page)"/>
+    <xsl:variable name="page_included_elements" select="//svg:*[@id = $page_included_geometry/@Id]"/>
+    <func:result select="$page_sub_elements | $page_included_elements"/>
+  </func:function>
   <xsl:template name="scripts">
     <xsl:text>//(function(){
 </xsl:text>
@@ -359,10 +343,10 @@
     <xsl:for-each select="$hmi_pages">
       <xsl:variable name="desc" select="func:parselabel(@inkscape:label)/widget"/>
       <xsl:variable name="page" select="."/>
-      <xsl:variable name="p" select="$hmi_geometry[@Id = $page/@id]"/>
-      <xsl:variable name="page_ids" select="$hmi_geometry[@Id != $page/@id and &#10;                                @x &gt;= $p/@x and @y &gt;= $p/@y and &#10;                                @x+@w &lt;= $p/@x+$p/@w and @y+@h &lt;= $p/@y+$p/@h]/@Id"/>
-      <xsl:variable name="page_sub_ids" select="func:refered_elements($page)[@id = $hmi_elements/@id]/@id"/>
-      <xsl:variable name="all_page_ids" select="$page_ids | $page_sub_ids[not(. = $page_ids)]"/>
+      <xsl:variable name="p" select="$geometry[@Id = $page/@id]"/>
+      <xsl:variable name="page_all_elements" select="func:all_related_elements($page)"/>
+      <xsl:variable name="all_page_ids" select="$page_all_elements[@id = $hmi_elements/@id and @id != $page/@id]/@id"/>
+      <xsl:variable name="shorter_list" select="func:sumarized_elements($page_all_elements)"/>
       <xsl:text>    "</xsl:text>
       <xsl:value-of select="$desc/arg[1]/@value"/>
       <xsl:text>": {
@@ -395,6 +379,16 @@
       </xsl:for-each>
       <xsl:text>        ]
 </xsl:text>
+      <xsl:text>        required_elements: [
+</xsl:text>
+      <xsl:for-each select="$shorter_list">
+        <xsl:text>            "</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text>",
+</xsl:text>
+      </xsl:for-each>
+      <xsl:text>        ]
+</xsl:text>
       <xsl:text>    }</xsl:text>
       <xsl:if test="position()!=last()">
         <xsl:text>,</xsl:text>
@@ -852,6 +846,32 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>function prepare_svg() {
+</xsl:text>
+    <xsl:text>    /* set everybody hidden initially for better performance */
+</xsl:text>
+    <xsl:text>    for(let widget in hmi_widgets){
+</xsl:text>
+    <xsl:text>        if(widget.element != undefined)
+</xsl:text>
+    <xsl:text>            widget.element.style.display = "none";
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>        /*for(let name in page_desc){
+</xsl:text>
+    <xsl:text>            if(name != new_desc){
+</xsl:text>
+    <xsl:text>                page_desc[name].widget.element.style.display = "none";
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }*/
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>function switch_page(page_name) {
 </xsl:text>
     <xsl:text>    let old_desc = page_desc[current_page];
@@ -862,18 +882,30 @@
 </xsl:text>
     <xsl:text>    if(new_desc == undefined){
 </xsl:text>
+    <xsl:text>        /* TODO LOG ERROR */
+</xsl:text>
     <xsl:text>        return;
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    /* remove subsribers of previous page if any */
-</xsl:text>
     <xsl:text>    if(old_desc){
 </xsl:text>
     <xsl:text>        for(let widget of old_desc.widgets){
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            /* hide widget */
+</xsl:text>
+    <xsl:text>            if(widget.element != undefined)
+</xsl:text>
+    <xsl:text>                widget.element.style.display = "none";
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            /* remove subsribers */
+</xsl:text>
     <xsl:text>            for(let index of widget.indexes){
 </xsl:text>
     <xsl:text>                subscribers[index].delete(widget);
@@ -884,17 +916,35 @@
 </xsl:text>
     <xsl:text>        old_desc.widget.element.style.display = "none";
 </xsl:text>
-    <xsl:text>    } else {
-</xsl:text>
-    <xsl:text>        /* initial page switch : set everybody hidden */
-</xsl:text>
-    <xsl:text>        for(let name in page_desc){
-</xsl:text>
-    <xsl:text>            if(name != new_desc){
-</xsl:text>
-    <xsl:text>                page_desc[name].widget.element.style.display = "none";
-</xsl:text>
-    <xsl:text>            }
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    for(let widget of new_desc.widgets){
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        /* unhide widget */
+</xsl:text>
+    <xsl:text>        if(widget.element != undefined)
+</xsl:text>
+    <xsl:text>            widget.element.style.display = "inline";
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        /* add widget's subsribers */
+</xsl:text>
+    <xsl:text>        for(let index of widget.indexes){
+</xsl:text>
+    <xsl:text>            subscribers[index].add(widget);
+</xsl:text>
+    <xsl:text>            /* dispatch current cache in newly opened page widgets */
+</xsl:text>
+    <xsl:text>            let cached_val = cache[index];
+</xsl:text>
+    <xsl:text>            if(cached_val != undefined)
+</xsl:text>
+    <xsl:text>                dispatch_value_to_widget(widget, index, cached_val, cached_val);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
@@ -902,28 +952,6 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    /* add new subsribers if any */
-</xsl:text>
-    <xsl:text>    for(let widget of new_desc.widgets){
-</xsl:text>
-    <xsl:text>        for(let index of widget.indexes){
-</xsl:text>
-    <xsl:text>            subscribers[index].add(widget);
-</xsl:text>
-    <xsl:text>            /* dispatch current cache in newly opened page widgets */
-</xsl:text>
-    <xsl:text>            let cached_val = cache[index];
-</xsl:text>
-    <xsl:text>            if(cached_val != undefined)
-</xsl:text>
-    <xsl:text>                dispatch_value_to_widget(widget, index, cached_val, cached_val);
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    new_desc.widget.element.style.display = "inline";
 </xsl:text>
     <xsl:text>
@@ -952,6 +980,8 @@
 </xsl:text>
     <xsl:text>    // show main page
 </xsl:text>
+    <xsl:text>    prepare_svg();
+</xsl:text>
     <xsl:text>    switch_page(default_page);
 </xsl:text>
     <xsl:text>};
--- a/svghmi/gen_index_xhtml.ysl2	Fri Feb 21 16:22:44 2020 +0100
+++ b/svghmi/gen_index_xhtml.ysl2	Thu Feb 27 13:14:24 2020 +0100
@@ -72,6 +72,10 @@
     const "_indexed_hmitree" apply "$hmitree", mode="index";
     const "indexed_hmitree", "exsl:node-set($_indexed_hmitree)";
 
+
+    // TODO globally discardable elements, not (used by | ancestor of) any page
+
+
     template "*", mode="index" {
         param "index", "0";
         param "parentpath", "''";
@@ -113,6 +117,7 @@
     template "@* | node()", mode="inline_svg" {
       /* use real xsl:copy instead copy-of alias from yslt.yml2 */
       xsl:copy apply "@* | node()", mode="inline_svg";
+      /* TODO filter out globally discardable elements */
     }
 
     /* replaces inkscape's height and width hints. forces fit */
@@ -133,32 +138,35 @@
         error > All units must be set to "px" in Inkscape's document properties
     }
 
-    /* clone unlinkink until widget for better perf with webkit */
-    svgtmpl "svg:use", mode="inline_svg" 
-    {
-        g{
-            attrib "style" > «@style»
-            attrib "transform" > «@transform»
-            /* keep same id and label in case it is a widget */
-            //attrib "inkscape:label","@inkscape:label";
-            attrib "id" > «@id»
-            const "targetid","substring-after(@xlink:href,'#')";
-            apply "//svg:*[@id = $targetid]", mode="unlink_clone";
-        }
-    }
-    svgtmpl "@*", mode="unlink_clone" xsl:copy;
-    svgtmpl "svg:*", mode="unlink_clone" {
-        choose {
-            when "@id = $hmi_elements/@id" {
-                use{
-                    attrib "xlink:href" > «concat('#',@id)»
-                }
-            }
-            otherwise {
-                xsl:copy apply "@* | node()", mode="unlink_clone";
-            }
-        }
-    }
+    
+    //// Commented out before implementing runtime DOM remove/append on page switch - would have side effect
+    ////
+    //// /* clone unlinkink until widget for better perf with webkit */
+    //// svgtmpl "svg:use", mode="inline_svg" 
+    //// {
+    ////     g{
+    ////         attrib "style" > «@style»
+    ////         attrib "transform" > «@transform»
+    ////         /* keep same id and label in case it is a widget */
+    ////         //attrib "inkscape:label","@inkscape:label";
+    ////         attrib "id" > «@id»
+    ////         const "targetid","substring-after(@xlink:href,'#')";
+    ////         apply "//svg:*[@id = $targetid]", mode="unlink_clone";
+    ////     }
+    //// }
+    //// svgtmpl "@*", mode="unlink_clone" xsl:copy;
+    //// svgtmpl "svg:*", mode="unlink_clone" {
+    ////     choose {
+    ////         when "@id = $hmi_elements/@id" {
+    ////             use{
+    ////                 attrib "xlink:href" > «concat('#',@id)»
+    ////             }
+    ////         }
+    ////         otherwise {
+    ////             xsl:copy apply "@* | node()", mode="unlink_clone";
+    ////         }
+    ////     }
+    //// }
 
     // template "svg:use/@style", mode="inline_svg"{
     //     attrib "style" > all:initial;
@@ -212,7 +220,7 @@
         }
     */
 
-    func:function name="func:parselabel" {
+    def "func:parselabel" {
         param "label";
         const "description", "substring-after($label,'HMI:')";
 
@@ -243,22 +251,49 @@
             }
         }
 
-        func:result select="exsl:node-set($ast)"
+        result "exsl:node-set($ast)";
     }
 
     // returns all directly or indirectly refered elements
-    func:function name="func:refered_elements" {
+    def "func:refered_elements" {
         param "elems";
         const "descend", "$elems/descendant-or-self::svg:*";
         const "clones", "$descend[self::svg:use]";
-        const "reals", "$descend[not(self::svg:use)]";
         const "originals", "//svg:*[concat('#',@id) = $clones/@xlink:href]";
         choose {
             when "$originals" 
-                func:result select="$reals | func:refered_elements($originals)";
+                result "$descend | func:refered_elements($originals)";
             otherwise
-                func:result select="$reals";
-        }
+                result "$descend";
+        }
+    }
+
+    // return included geometry a given element
+    def "func:included_geometry" {
+        param "elt";
+        const "g", "$geometry[@Id = $elt/@id]"; 
+        result """$geometry[@Id != $elt/@id and
+                           @x >= $g/@x and @y >= $g/@y and 
+                           @x+@w <= $g/@x+$g/@w and @y+@h <= $g/@y+$g/@h]""";
+
+    }
+
+    def "func:sumarized_elements" {
+        param "elements";
+        const "short_list", "$elements[not(ancestor::*/@id = $elements/@id)]";
+        /* TODO exclude globally discardable elements from group fulfillment check */
+        const "filled_groups", "$short_list/parent::svg:*[not(descendant::*[not(self::svg:g)][not(@id = $short_list/descendant-or-self::*[not(self::svg:g)]/@id)])]";
+        const "groups_to_add", "$filled_groups[not(ancestor::*/@id = $filled_groups/@id)]";
+        result "$groups_to_add | $short_list[not(ancestor::svg:g/@id = $filled_groups/@id)]";
+    }
+
+    def "func:all_related_elements" {
+        param "page";
+        const "page_included_geometry", "func:included_geometry($page)";
+        const "page_sub_elements", "func:refered_elements($page)";
+
+        const "page_included_elements", "//svg:*[@id = $page_included_geometry/@Id]";
+        result "$page_sub_elements | $page_included_elements";
     }
 
     function "scripts"
@@ -324,17 +359,20 @@
 
         | ]
         |
+
         | var page_desc = {
 
         foreach "$hmi_pages" {
             const "desc", "func:parselabel(@inkscape:label)/widget";
-            const "page", "."; 
-            const "p", "$hmi_geometry[@Id = $page/@id]"; 
-            const "page_ids","""$hmi_geometry[@Id != $page/@id and 
-                                @x >= $p/@x and @y >= $p/@y and 
-                                @x+@w <= $p/@x+$p/@w and @y+@h <= $p/@y+$p/@h]/@Id""";
-            const "page_sub_ids", "func:refered_elements($page)[@id = $hmi_elements/@id]/@id";
-            const "all_page_ids","$page_ids | $page_sub_ids[not(. = $page_ids)]"; 
+            const "page", ".";
+            const "p", "$geometry[@Id = $page/@id]"; 
+
+            const "page_all_elements", "func:all_related_elements($page)";
+
+            const "all_page_ids","$page_all_elements[@id = $hmi_elements/@id and @id != $page/@id]/@id";
+
+            const "shorter_list", "func:sumarized_elements($page_all_elements)";
+
             |     "«$desc/arg[1]/@value»": {
             |         widget: hmi_widgets["«@id»"],
             |         bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
@@ -343,6 +381,11 @@
             |             hmi_widgets["«.»"]`if "position()!=last()" > ,`
             }
             |         ]
+            |         required_elements: [
+            foreach "$shorter_list" {
+            |             "«@id»",
+            }
+            |         ]
             |     }`if "position()!=last()" > ,`
         }
         | }
--- a/svghmi/svghmi.js	Fri Feb 21 16:22:44 2020 +0100
+++ b/svghmi/svghmi.js	Thu Feb 27 13:14:24 2020 +0100
@@ -217,33 +217,50 @@
 
 var current_page;
 
+function prepare_svg() {
+    /* set everybody hidden initially for better performance */
+    for(let widget in hmi_widgets){
+        if(widget.element != undefined)
+            widget.element.style.display = "none";
+    }
+        /*for(let name in page_desc){
+            if(name != new_desc){
+                page_desc[name].widget.element.style.display = "none";
+            }
+        }*/
+};
+
 function switch_page(page_name) {
     let old_desc = page_desc[current_page];
     let new_desc = page_desc[page_name];
 
     if(new_desc == undefined){
+        /* TODO LOG ERROR */
         return;
     }
 
-    /* remove subsribers of previous page if any */
     if(old_desc){
         for(let widget of old_desc.widgets){
+
+            /* hide widget */
+            if(widget.element != undefined)
+                widget.element.style.display = "none";
+
+            /* remove subsribers */
             for(let index of widget.indexes){
                 subscribers[index].delete(widget);
             }
         }
         old_desc.widget.element.style.display = "none";
-    } else {
-        /* initial page switch : set everybody hidden */
-        for(let name in page_desc){
-            if(name != new_desc){
-                page_desc[name].widget.element.style.display = "none";
-            }
-        }
-    }
-
-    /* add new subsribers if any */
+    }
+
     for(let widget of new_desc.widgets){
+
+        /* unhide widget */
+        if(widget.element != undefined)
+            widget.element.style.display = "inline";
+
+        /* add widget's subsribers */
         for(let index of widget.indexes){
             subscribers[index].add(widget);
             /* dispatch current cache in newly opened page widgets */
@@ -267,6 +284,7 @@
     init_widgets();
     send_reset();
     // show main page
+    prepare_svg();
     switch_page(default_page);
 };