This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch edje-vector-intergration
in repository efl.

View the commit online.

commit 44179251cbdb990aa53579bf1f1449afbf5ff916
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 14:23:35 2026 -0600

    edje: cache materialized Efl_VG during vector transitions (Phase 5.5)
    
    Add Edje_Vg_Cache_Slot to Edje_Real_Part_Vector with desc_key, working_tree,
    and efl_vg fields. Two cache slots (cache_chosen, cache_param2) cover the
    two descriptions a transition can have active.
    
    Cache-or-build helper _edje_vg_cache_get_or_build validates on desc_key
    match and classes_dirty flag; cache miss clears and rebuilds. Per-frame cost
    during transitions drops from 3 materializations to 1 (only the lerp tree).
    Static rendering uses efl_duplicate for ref-counted canvas ownership.
    
    _edje_vg_cache_slot_clear handles teardown; lifetime invariant matches
    Phase 1.2 (cached_base_tree): the part is torn down before cache's desc_key
    pointee is freed. Phase 6 will wire classes_dirty in the color-class
    observer callback.
    
    Unit test edje_vg_cache_hit_on_same_desc validates hit/miss transitions,
    classes_dirty invalidation, and idempotent clear.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/lib/edje/edje_calc.c                 | 134 ++++++++++++++-----------------
 src/lib/edje/edje_load.c                 |   5 ++
 src/lib/edje/edje_private.h              |  28 +++++++
 src/lib/edje/edje_vg_tree.c              |  75 +++++++++++++++++
 src/lib/edje/edje_vg_tree.h              |  23 ++++++
 src/tests/edje/edje_test_vector_states.c |  80 ++++++++++++++++++
 6 files changed, 271 insertions(+), 74 deletions(-)

diff --git a/src/lib/edje/edje_calc.c b/src/lib/edje/edje_calc.c
index 3965d49703..07b03be821 100644
--- a/src/lib/edje/edje_calc.c
+++ b/src/lib/edje/edje_calc.c
@@ -3282,15 +3282,18 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
       when tree_id == -1 (its initialised-invalid value). */
    if (chosen_desc->vg.tree_id >= 0)
      {
+        /* Phase 5.5: cache-first materialization.  We maintain one cache slot
+           per description side (chosen and param2).  On a hit the slot returns
+           its already-built working_tree and efl_vg without any allocation.
+           Only the lerp output (which changes every frame because pos changes)
+           is rebuilt from scratch.  In the static case we efl_duplicate the
+           cached efl_vg, which is much cheaper than a full tree walk. */
         Edje_Real_Part_Vector *rpv = ep->typedata.vector;
         const Edje_Vg_Tree *base_a;
-        Edje_Vg_Tree *working_a = NULL;
-        Edje_Vg_Tree *working_b = NULL;
+        Efl_VG *efl_a = NULL, *efl_b = NULL, *root = NULL;
+        const Edje_Vg_Tree *working_a = NULL, *working_b = NULL;
         Edje_Vg_Tree *lerp_tree = NULL;
-        Efl_VG *efl_a = NULL, *efl_b = NULL;
         Eina_Rect viewbox;
-        Eina_List *l;
-        Edje_Vg_Override *ovr;
 
         if (!ed->file->vector_dir ||
             (unsigned int)chosen_desc->vg.tree_id >= ed->file->vector_dir->trees_count)
@@ -3303,34 +3306,23 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
 
         base_a = &ed->file->vector_dir->trees[chosen_desc->vg.tree_id];
 
-        /* Cache the tree pointer; useful for class-dirty invalidation in
-           Phase 6. */
+        /* Keep the Phase 4 borrowed pointer up-to-date (Phase 6 uses it). */
         rpv->cached_base_tree = base_a;
-        rpv->cached_tree_id = chosen_desc->vg.tree_id;
+        rpv->cached_tree_id   = chosen_desc->vg.tree_id;
 
-        /* Phase 4: when per-description overrides are present, dup the base
-           tree, apply each override in order.  When there are no overrides,
-           skip the dup and use the immutable base tree directly (fast path). */
-        if (chosen_desc->vg.overrides)
+        /* Build or fetch the chosen description's cache slot (A side). */
+        if (!_edje_vg_cache_get_or_build(&rpv->cache_chosen,
+                                         chosen_desc, base_a,
+                                         chosen_desc->vg.overrides,
+                                         rpv->classes_dirty,
+                                         &efl_a, &working_a))
           {
-             working_a = _edje_vg_tree_dup(base_a);
-             if (!working_a)
-               {
-                  ERR("Failed to dup base tree for override application (tree_id=%d)",
-                      chosen_desc->vg.tree_id);
-                  return;
-               }
-             EINA_LIST_FOREACH(chosen_desc->vg.overrides, l, ovr)
-               _edje_vg_tree_apply_override(working_a, ovr);
+             ERR("Failed to build cache for chosen description (tree_id=%d)",
+                 chosen_desc->vg.tree_id);
+             return;
           }
-        const Edje_Vg_Tree *src_a = working_a ? working_a : base_a;
 
-        /* Phase 5: when a transition is active (param2 non-NULL and pos > 0),
-           try to build a lerp tree across both descriptions.  If the next
-           description is not tree-based (tree_id < 0, e.g. Mode-C SVG), fall
-           through to the direct-materialise path instead; Mode-C bridging is
-           deferred. */
-        root = NULL;
+        /* Transition active? (param2 set and pos meaningfully non-zero) */
         if (ep->param2 && NEQ(pos, ZERO))
           {
              Edje_Part_Description_Vector *next_desc =
@@ -3342,67 +3334,61 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
                   const Edje_Vg_Tree *base_b =
                      &ed->file->vector_dir->trees[next_desc->vg.tree_id];
 
-                  /* Build B's working tree (dup + apply overrides if any). */
-                  if (next_desc->vg.overrides)
+                  /* Build or fetch the param2 description's cache slot (B side). */
+                  if (_edje_vg_cache_get_or_build(&rpv->cache_param2,
+                                                   next_desc, base_b,
+                                                   next_desc->vg.overrides,
+                                                   rpv->classes_dirty,
+                                                   &efl_b, &working_b))
                     {
-                       working_b = _edje_vg_tree_dup(base_b);
-                       if (working_b)
+                       /* Lerp tree: fresh every frame because pos changes.
+                          This is the only materialization that fires per-frame
+                          during a transition (efl_a and efl_b are cache hits). */
+                       lerp_tree = _edje_vg_tree_lerp(working_a, working_b,
+                                                       TO_DOUBLE(pos));
+                       if (lerp_tree)
                          {
-                            EINA_LIST_FOREACH(next_desc->vg.overrides, l, ovr)
-                              _edje_vg_tree_apply_override(working_b, ovr);
+                            root = _edje_vg_tree_to_efl_vg(lerp_tree);
+                            /* Path-only interpolation across A/B cached VGs.
+                               Returns EINA_FALSE on topology mismatch — root
+                               already holds B paths (step semantics), so
+                               failure is safe to ignore here. */
+                            if (root && efl_a && efl_b)
+                              efl_gfx_path_interpolate(root, efl_a, efl_b,
+                                                        TO_DOUBLE(pos));
                          }
                     }
-                  const Edje_Vg_Tree *src_b = working_b ? working_b : base_b;
-
-                  /* Build the lerp tree: structure mirrors B with non-path
-                     properties lerped from A. Paths stay at B's value here;
-                     efl_gfx_path_interpolate handles them below. */
-                  lerp_tree = _edje_vg_tree_lerp(src_a, src_b, TO_DOUBLE(pos));
-
-                  /* Materialise all three trees. efl_a and efl_b are
-                     short-lived references used only for path interpolation. */
-                  efl_a = _edje_vg_tree_to_efl_vg(src_a);
-                  efl_b = _edje_vg_tree_to_efl_vg(src_b);
-                  if (lerp_tree) root = _edje_vg_tree_to_efl_vg(lerp_tree);
-
-                  /* Path-only interpolation: walks the Efl_VG hierarchy and
-                     updates each shape's path in `root` via the EFL-native
-                     interpolator.  Returns EINA_FALSE on topology mismatch —
-                     root already holds B's paths (step semantics), so failure
-                     is safe to ignore. */
-                  if (root && efl_a && efl_b)
-                    efl_gfx_path_interpolate(root, efl_a, efl_b, TO_DOUBLE(pos));
-
-                  /* Release auxiliary A/B VG trees; root is independent. */
-                  if (efl_a) efl_unref(efl_a);
-                  if (efl_b) efl_unref(efl_b);
                }
           }
 
-        /* No transition active, transition fell through (Mode-C next desc),
-           or lerp_tree materialise failed: fall back to direct src_a render. */
+        /* Static fallback: no transition active, transition fell through
+           (Mode-C next desc), or lerp_tree materialization failed.
+           efl_duplicate is cheaper than re-walking the data model because
+           it copies the already-built Efl_VG object graph. */
         if (!root)
-          root = _edje_vg_tree_to_efl_vg(src_a);
+          {
+             if (efl_a)
+               root = efl_duplicate(efl_a);
+             /* efl_a is still cached in the slot; root is an independent copy. */
+          }
 
-        /* Viewbox: use lerped viewbox when transition is active (the lerp tree
-           already has the interpolated vbx/vby/vbw/vbh); else use base_a. */
+        /* Viewbox from lerp tree when transitioning, else from working_a. */
         if (lerp_tree)
           viewbox = EINA_RECT(lerp_tree->vbx, lerp_tree->vby,
                               lerp_tree->vbw, lerp_tree->vbh);
+        else if (working_a)
+          viewbox = EINA_RECT(working_a->vbx, working_a->vby,
+                              working_a->vbw, working_a->vbh);
         else
           viewbox = EINA_RECT(base_a->vbx, base_a->vby,
                               base_a->vbw, base_a->vbh);
 
-        /* TODO Phase 5+: cache materialised Efl_VG hierarchies to avoid the
-           dup-and-walk cost on every recalc.  During a transition we currently
-           build three Efl_VG instances per frame (A, B, and OUT).  At 60fps
-           this is 180 instantiations/sec for moderately-sized trees.  Cache
-           key: (description pointer, override-list-hash, classes_dirty). */
-
-        /* Free working trees and the lerp tree; root is the only survivor. */
+        /* lerp_tree is a per-frame allocation — always free it. */
         if (lerp_tree) _edje_vg_tree_free(lerp_tree);
-        if (working_a) _edje_vg_tree_free(working_a);
-        if (working_b) _edje_vg_tree_free(working_b);
+
+        /* Clear classes_dirty now that we've consumed it: any stale class
+           data has been flushed by the cache rebuild triggered above. */
+        rpv->classes_dirty = 0;
 
         if (!root)
           {
@@ -3413,8 +3399,8 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
         efl_canvas_vg_object_viewbox_set(ep->object, viewbox);
         efl_canvas_vg_object_root_node_set(ep->object, root);
         /* root_node_set takes its own ref via efl_replace; release our
-           caller-side ref from efl_add_ref so the tree is owned exclusively
-           by the VG object from this point forward. */
+           caller-side ref (from efl_add_ref / efl_duplicate) so the tree is
+           owned exclusively by the VG object from this point forward. */
         efl_unref(root);
         return;
      }
diff --git a/src/lib/edje/edje_load.c b/src/lib/edje/edje_load.c
index 972a5f2f38..83aa87daff 100644
--- a/src/lib/edje/edje_load.c
+++ b/src/lib/edje/edje_load.c
@@ -2019,6 +2019,11 @@ _edje_file_del(Edje *ed)
                   if (rp->typedata.vector->lottie_data)
                     free(rp->typedata.vector->lottie_data);
 
+                  /* Phase 5.5: release any cached working trees and Efl_VG
+                     objects before freeing the containing struct. */
+                  _edje_vg_cache_slot_clear(&rp->typedata.vector->cache_chosen);
+                  _edje_vg_cache_slot_clear(&rp->typedata.vector->cache_param2);
+
                   free(rp->typedata.vector);
                   rp->typedata.vector = NULL;
                }
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index 261eff8bb6..4b3c81c3c7 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -2024,6 +2024,27 @@ struct _Edje_Real_Part_Swallow
    } swallow_params; // 28 // FIXME: only if type SWALLOW
 };
 
+/* Phase 5.5 cache — per-description materialized output.  Each slot
+   carries the dup'd-and-overrides-applied Edje_Vg_Tree plus its
+   materialized Efl_VG.  The slot is keyed by description pointer
+   (Edje_Part_Description_Vector*).  Invalidation triggers:
+   - desc_key changes (different chosen/param2 description landed).
+   - classes_dirty is set (Phase 6 wires this via the color-class
+     observer callback).
+   On invalidation: efl_unref(efl_vg), _edje_vg_tree_free(working_tree).
+   Both slots' lifetimes are bounded by Edje_Real_Part_Vector — freed
+   at part teardown in edje_load.c.
+
+   working_tree is owned (calloc'd via _edje_vg_tree_dup).
+   efl_vg holds an owning ref (from _edje_vg_tree_to_efl_vg's
+   efl_add_ref).  desc_key is BORROWED (non-owning pointer). */
+typedef struct _Edje_Vg_Cache_Slot
+{
+   const void   *desc_key;       /* Edje_Part_Description_Vector* */
+   Edje_Vg_Tree *working_tree;
+   Efl_VG       *efl_vg;
+} Edje_Vg_Cache_Slot;
+
 struct _Edje_Real_Part_Vector
 {
    Eo        *anim;
@@ -2047,6 +2068,13 @@ struct _Edje_Real_Part_Vector
    const Edje_Vg_Tree *cached_base_tree;
    int                 cached_tree_id;
    Eina_Bool           classes_dirty : 1;
+   /* Phase 5.5: per-description materialized-Efl_VG cache slots.
+      cache_chosen is keyed on the chosen description (param1 side).
+      cache_param2 is keyed on the transition target (param2 side).
+      Both are zero-init'd by the calloc in edje_load.c so desc_key==NULL
+      means empty on construction. */
+   Edje_Vg_Cache_Slot  cache_chosen;
+   Edje_Vg_Cache_Slot  cache_param2;
 };
 
 struct _Edje_Real_Part
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index a4781a3350..cf83cff73b 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -1705,3 +1705,78 @@ _edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t)
 
    return root;
 }
+
+/* =========================================================================
+ * Phase 5.5: materialized-Efl_VG cache helpers
+ * ========================================================================= */
+
+EAPI void
+_edje_vg_cache_slot_clear(Edje_Vg_Cache_Slot *slot)
+{
+   if (!slot) return;
+   if (slot->efl_vg)
+     {
+        efl_unref(slot->efl_vg);
+        slot->efl_vg = NULL;
+     }
+   if (slot->working_tree)
+     {
+        _edje_vg_tree_free(slot->working_tree);
+        slot->working_tree = NULL;
+     }
+   slot->desc_key = NULL;
+}
+
+EAPI Eina_Bool
+_edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
+                             const void *desc_key,
+                             const Edje_Vg_Tree *base,
+                             const Eina_List *overrides,
+                             Eina_Bool classes_dirty_in,
+                             Efl_VG **out_efl_vg,
+                             const Edje_Vg_Tree **out_working_tree)
+{
+   if (!slot || !desc_key || !base) return EINA_FALSE;
+
+   /* Cache hit: same description and classes not dirty. */
+   if (slot->desc_key == desc_key &&
+       !classes_dirty_in &&
+       slot->efl_vg && slot->working_tree)
+     {
+        if (out_efl_vg)       *out_efl_vg       = slot->efl_vg;
+        if (out_working_tree) *out_working_tree  = slot->working_tree;
+        return EINA_TRUE;
+     }
+
+   /* Cache miss or invalidation: rebuild. */
+   _edje_vg_cache_slot_clear(slot);
+
+   /* Always dup the base tree so the slot owns an independent working tree.
+      This simplifies lifetime management — the slot's working_tree is never
+      an alias into the file's immutable base array. */
+   Edje_Vg_Tree *working = _edje_vg_tree_dup(base);
+   if (!working) return EINA_FALSE;
+
+   if (overrides)
+     {
+        const Eina_List *l;
+        const Edje_Vg_Override *ovr;
+        EINA_LIST_FOREACH(overrides, l, ovr)
+          _edje_vg_tree_apply_override(working, ovr);
+     }
+
+   Efl_VG *efl_vg = _edje_vg_tree_to_efl_vg(working);
+   if (!efl_vg)
+     {
+        _edje_vg_tree_free(working);
+        return EINA_FALSE;
+     }
+
+   slot->desc_key     = desc_key;
+   slot->working_tree = working;
+   slot->efl_vg       = efl_vg;
+
+   if (out_efl_vg)       *out_efl_vg       = efl_vg;
+   if (out_working_tree) *out_working_tree  = working;
+   return EINA_TRUE;
+}
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index 5db65173ba..eb57f2d1b8 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -152,6 +152,29 @@ EAPI Edje_Vg_Tree *_edje_vg_tree_lerp(const Edje_Vg_Tree *a,
                                        const Edje_Vg_Tree *b,
                                        double pos);
 
+/* --- Phase 5.5: materialized-Efl_VG cache helpers ----------------------- */
+
+/* Cache-or-build: if the slot's desc_key matches desc_key and classes_dirty_in
+   is clear, return the cached efl_vg and working_tree (cache hit).  Otherwise
+   invalidate the slot, dup base, apply overrides, materialise a fresh Efl_VG,
+   store in slot, and return (cache miss / rebuild).
+   Returned efl_vg and working_tree are BORROWED from the slot — do not unref
+   or free them; the slot owns both.
+   Returns EINA_TRUE on success (hit or rebuild), EINA_FALSE on OOM or bad args.
+   out_efl_vg and out_working_tree may each be NULL if the caller does not need
+   them. */
+EAPI Eina_Bool _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
+                                            const void *desc_key,
+                                            const Edje_Vg_Tree *base,
+                                            const Eina_List *overrides,
+                                            Eina_Bool classes_dirty_in,
+                                            Efl_VG **out_efl_vg,
+                                            const Edje_Vg_Tree **out_working_tree);
+
+/* Release the slot's owned contents (efl_unref + tree free) and reset
+   desc_key to NULL.  Safe to call on an already-cleared or zero-init'd slot. */
+EAPI void _edje_vg_cache_slot_clear(Edje_Vg_Cache_Slot *slot);
+
 /* --- Eet descriptor accessor -------------------------------------------- */
 
 /* Returns the static Eet_Data_Descriptor for Edje_Vg_Tree.
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 2b9593c2de..1ec678299c 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -118,6 +118,26 @@ Edje_Vg_Tree *_edje_vg_tree_lerp(const Edje_Vg_Tree *a,
                                   const Edje_Vg_Tree *b,
                                   double pos);
 
+/* Phase 5.5 cache slot — must match layout of Edje_Vg_Cache_Slot in
+ * edje_private.h exactly (three pointer-width fields). */
+typedef struct
+{
+   const void   *desc_key;
+   Edje_Vg_Tree *working_tree;
+   Efl_VG       *efl_vg;
+} Test_Vg_Cache_Slot;
+
+/* The cache helpers take Edje_Vg_Cache_Slot* from the library's perspective,
+ * but since both structs have identical layout, we cast freely. */
+Eina_Bool _edje_vg_cache_get_or_build(void *slot,
+                                       const void *desc_key,
+                                       const Edje_Vg_Tree *base,
+                                       const void *overrides,
+                                       Eina_Bool classes_dirty_in,
+                                       Efl_VG **out_efl_vg,
+                                       const Edje_Vg_Tree **out_working_tree);
+void      _edje_vg_cache_slot_clear(void *slot);
+
 /* Partial view of Edje_Vg_Override: all fields, inline payload shape sub-struct.
  * We replicate only the fields needed by the three Task 4.1 tests.
  * The payload is a full Edje_Vg_Node embedded (not a pointer), so we mirror
@@ -1420,6 +1440,64 @@ EFL_START_TEST(edje_vg_render_box_a_transition)
 }
 EFL_END_TEST
 
+/* Phase 5.5 — direct unit test for _edje_vg_cache_get_or_build /
+ * _edje_vg_cache_slot_clear.  We build a small tree, then:
+ *   a) verify cache hit returns the same efl_vg pointer on repeated calls;
+ *   b) verify cache miss on a different desc_key yields a new efl_vg pointer;
+ *   c) verify classes_dirty forces a rebuild even with the same desc_key;
+ *   d) verify _edje_vg_cache_slot_clear resets everything without crashing on
+ *      an already-clear slot.
+ */
+EFL_START_TEST(edje_vg_cache_hit_on_same_desc)
+{
+   Test_Vg_Cache_Slot slot = { NULL, NULL, NULL };
+   Edje_Vg_Tree *base = _edje_vg_tree_new_for_test();
+   fail_if(!base);
+
+   /* Sentinel pointer values as desc_key stand-ins — no actual struct needed. */
+   void *key_a = (void *)0x1234;
+   void *key_b = (void *)0x5678;
+
+   Efl_VG *vg1 = NULL;
+   const Edje_Vg_Tree *wt1 = NULL;
+
+   /* First call: cold miss — must build and return valid objects. */
+   fail_if(!_edje_vg_cache_get_or_build(&slot, key_a, base, NULL,
+                                         EINA_FALSE, &vg1, &wt1));
+   fail_if(!vg1);
+   fail_if(!wt1);
+
+   Efl_VG *vg2 = NULL;
+   const Edje_Vg_Tree *wt2 = NULL;
+
+   /* Second call with same key: cache hit — same pointer returned. */
+   fail_if(!_edje_vg_cache_get_or_build(&slot, key_a, base, NULL,
+                                         EINA_FALSE, &vg2, &wt2));
+   ck_assert_ptr_eq(vg1, vg2);
+   ck_assert_ptr_eq(wt1, wt2);
+
+   Efl_VG *vg3 = NULL;
+
+   /* Third call with different key: cache miss — fresh object. */
+   fail_if(!_edje_vg_cache_get_or_build(&slot, key_b, base, NULL,
+                                         EINA_FALSE, &vg3, NULL));
+   fail_if(vg3 == vg1);
+
+   Efl_VG *vg4 = NULL;
+
+   /* Fourth call: same key_b but classes_dirty forces rebuild. */
+   fail_if(!_edje_vg_cache_get_or_build(&slot, key_b, base, NULL,
+                                         EINA_TRUE, &vg4, NULL));
+   fail_if(vg4 == vg3);
+
+   /* Explicit clear: must not crash even when called twice (idempotent). */
+   _edje_vg_cache_slot_clear(&slot);
+   _edje_vg_cache_slot_clear(&slot);
+
+   _edje_vg_tree_free(base);
+}
+EFL_END_TEST
+
 void
 edje_test_vector_states(TCase *tc)
 {
@@ -1447,4 +1525,6 @@ edje_test_vector_states(TCase *tc)
    tcase_add_test(tc, edje_vg_tree_lerp_basic);
    /* Phase 5.4 end-to-end transition */
    tcase_add_test(tc, edje_vg_render_box_a_transition);
+   /* Phase 5.5 materialized-Efl_VG cache */
+   tcase_add_test(tc, edje_vg_cache_hit_on_same_desc);
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to