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 7657b9211e58dcef7bb3cff17fd6d4f036dc0ae9
Author: [email protected] <[email protected]>
AuthorDate: Tue Apr 28 22:39:49 2026 -0600
edje: add vector tree dup and override-apply helpers (Task 4.1)
Add two Phase 4.1 substrate functions to support per-state vector
overrides in subsequent commits:
_edje_vg_tree_dup(t) — Recursively deep-clone the in-memory tree.
Strings are stringshare-ref'd from the source (cheap); arrays
(stroke_dash, gradient stops) and container children are duplicated
into fresh allocations. The result is fully independent.
_edje_vg_tree_apply_override(working, ovr) — Apply a single
override onto a working tree in place. Look up the target via
_edje_vg_tree_node_by_name, validate expected_type, then copy
fields from ovr->payload to the matched node based on field_mask
bits. Defensive no-op on NULL input, missing target, or type
mismatch (VEC002 catches unresolvable targets at parse time; the
runtime stays robust if a parse-time check is bypassed).
Runtime per-recalc override application (Task 4.5) will dup the
immutable base tree and apply each override in sequence. This commit
lands the substrate without consumers.
Three unit tests verify dup independence, VISIBLE bit override, and
unresolved target handling. All 52 tests pass; valgrind reports 0
bytes definitely lost on the new code paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_vg_tree.c | 278 +++++++++++++++++++++++++++++++
src/lib/edje/edje_vg_tree.h | 18 ++
src/tests/edje/edje_test_vector_states.c | 171 +++++++++++++++++++
3 files changed, 467 insertions(+)
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index c28338d9fe..296ee60f95 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -460,6 +460,284 @@ _edje_vg_tree_equal(const Edje_Vg_Tree *a, const Edje_Vg_Tree *b)
return _node_equal(a->root, b->root);
}
+/* =========================================================================
+ * Phase 4.1 — Deep-dup and override-apply helpers
+ * ========================================================================= */
+
+/* Duplicate a color binding — stringshare_ref the class string. */
+static void
+_color_binding_dup(Edje_Vg_Color_Binding *dst,
+ const Edje_Vg_Color_Binding *src)
+{
+ dst->r = src->r;
+ dst->g = src->g;
+ dst->b = src->b;
+ dst->a = src->a;
+ dst->color_class = src->color_class
+ ? eina_stringshare_ref(src->color_class) : NULL;
+}
+
+static Edje_Vg_Node *_node_dup(const Edje_Vg_Node *src);
+
+static Edje_Vg_Node *
+_node_dup(const Edje_Vg_Node *src)
+{
+ if (!src) return NULL;
+
+ Edje_Vg_Node *dst = calloc(1, sizeof(Edje_Vg_Node));
+ if (!dst) return NULL;
+
+ /* Common fields */
+ dst->type = src->type;
+ dst->visible = src->visible;
+ dst->name = src->name ? eina_stringshare_ref(src->name) : NULL;
+ _color_binding_dup(&dst->color, &src->color);
+ dst->xform = src->xform; /* Edje_Vg_Transform has no heap pointers */
+
+ switch (src->type)
+ {
+ case EDJE_VG_NODE_SHAPE:
+ dst->shape.path =
+ src->shape.path ? eina_stringshare_ref(src->shape.path) : NULL;
+ _color_binding_dup(&dst->shape.fill, &src->shape.fill);
+ _color_binding_dup(&dst->shape.stroke_color, &src->shape.stroke_color);
+ dst->shape.stroke_width = src->shape.stroke_width;
+ dst->shape.stroke_cap = src->shape.stroke_cap;
+ dst->shape.stroke_join = src->shape.stroke_join;
+ dst->shape.fill_rule = src->shape.fill_rule;
+ dst->shape.stroke_dash_count = src->shape.stroke_dash_count;
+ dst->shape.gradient_ref =
+ src->shape.gradient_ref
+ ? eina_stringshare_ref(src->shape.gradient_ref) : NULL;
+ if (src->shape.stroke_dash_count > 0 && src->shape.stroke_dash)
+ {
+ size_t sz =
+ src->shape.stroke_dash_count * sizeof(double);
+ dst->shape.stroke_dash = malloc(sz);
+ if (dst->shape.stroke_dash)
+ memcpy(dst->shape.stroke_dash,
+ src->shape.stroke_dash, sz);
+ else
+ dst->shape.stroke_dash_count = 0;
+ }
+ break;
+
+ case EDJE_VG_NODE_CONTAINER:
+ {
+ Eina_List *l;
+ Edje_Vg_Node *child;
+ EINA_LIST_FOREACH(src->container.children, l, child)
+ {
+ Edje_Vg_Node *dc = _node_dup(child);
+ if (dc)
+ dst->container.children =
+ eina_list_append(dst->container.children, dc);
+ }
+ }
+ break;
+
+ case EDJE_VG_NODE_GRADIENT_LINEAR:
+ case EDJE_VG_NODE_GRADIENT_RADIAL:
+ dst->gradient.spread = src->gradient.spread;
+ dst->gradient.x0 = src->gradient.x0;
+ dst->gradient.y0 = src->gradient.y0;
+ dst->gradient.x1 = src->gradient.x1;
+ dst->gradient.y1 = src->gradient.y1;
+ dst->gradient.radius = src->gradient.radius;
+ dst->gradient.is_radial = src->gradient.is_radial;
+ dst->gradient.stops_count = src->gradient.stops_count;
+ if (src->gradient.stops_count > 0 && src->gradient.stops)
+ {
+ size_t sz =
+ src->gradient.stops_count * sizeof(Edje_Vg_Stop);
+ dst->gradient.stops = malloc(sz);
+ if (dst->gradient.stops)
+ {
+ memcpy(dst->gradient.stops, src->gradient.stops, sz);
+ /* stringshare_ref each stop's color_class */
+ unsigned int i;
+ for (i = 0; i < dst->gradient.stops_count; i++)
+ {
+ const char *cc =
+ dst->gradient.stops[i].color.color_class;
+ if (cc)
+ dst->gradient.stops[i].color.color_class =
+ eina_stringshare_ref(cc);
+ }
+ }
+ else
+ dst->gradient.stops_count = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return dst;
+}
+
+EAPI Edje_Vg_Tree *
+_edje_vg_tree_dup(const Edje_Vg_Tree *t)
+{
+ if (!t) return NULL;
+
+ Edje_Vg_Tree *dst = calloc(1, sizeof(Edje_Vg_Tree));
+ if (!dst) return NULL;
+
+ dst->vbx = t->vbx;
+ dst->vby = t->vby;
+ dst->vbw = t->vbw;
+ dst->vbh = t->vbh;
+ dst->root = _node_dup(t->root);
+
+ return dst;
+}
+
+EAPI void
+_edje_vg_tree_apply_override(Edje_Vg_Tree *working,
+ const Edje_Vg_Override *ovr)
+{
+ if (!working || !ovr || !ovr->target_name) return;
+
+ Edje_Vg_Node *node =
+ _edje_vg_tree_node_by_name(working, ovr->target_name);
+ if (!node || node->type != ovr->expected_type) return;
+
+ uint64_t m = ovr->field_mask;
+
+ if (m & EDJE_VG_OVR_VISIBLE)
+ node->visible = ovr->payload.visible;
+
+ if (m & EDJE_VG_OVR_COLOR_RAW)
+ {
+ node->color.r = ovr->payload.color.r;
+ node->color.g = ovr->payload.color.g;
+ node->color.b = ovr->payload.color.b;
+ node->color.a = ovr->payload.color.a;
+ }
+
+ if (m & EDJE_VG_OVR_COLOR_CLASS)
+ eina_stringshare_replace(&node->color.color_class,
+ ovr->payload.color.color_class);
+
+ if (m & EDJE_VG_OVR_TRANSFORM)
+ node->xform = ovr->payload.xform;
+
+ if (m & EDJE_VG_OVR_PATH)
+ eina_stringshare_replace(&node->shape.path,
+ ovr->payload.shape.path);
+
+ if (m & EDJE_VG_OVR_FILL_RAW)
+ {
+ node->shape.fill.r = ovr->payload.shape.fill.r;
+ node->shape.fill.g = ovr->payload.shape.fill.g;
+ node->shape.fill.b = ovr->payload.shape.fill.b;
+ node->shape.fill.a = ovr->payload.shape.fill.a;
+ }
+
+ if (m & EDJE_VG_OVR_FILL_CLASS)
+ eina_stringshare_replace(&node->shape.fill.color_class,
+ ovr->payload.shape.fill.color_class);
+
+ if (m & EDJE_VG_OVR_STROKE_RAW)
+ {
+ node->shape.stroke_color.r = ovr->payload.shape.stroke_color.r;
+ node->shape.stroke_color.g = ovr->payload.shape.stroke_color.g;
+ node->shape.stroke_color.b = ovr->payload.shape.stroke_color.b;
+ node->shape.stroke_color.a = ovr->payload.shape.stroke_color.a;
+ }
+
+ if (m & EDJE_VG_OVR_STROKE_CLASS)
+ eina_stringshare_replace(&node->shape.stroke_color.color_class,
+ ovr->payload.shape.stroke_color.color_class);
+
+ if (m & EDJE_VG_OVR_STROKE_WIDTH)
+ node->shape.stroke_width = ovr->payload.shape.stroke_width;
+
+ if (m & EDJE_VG_OVR_STROKE_DASH)
+ {
+ free(node->shape.stroke_dash);
+ node->shape.stroke_dash = NULL;
+ node->shape.stroke_dash_count = 0;
+ if (ovr->payload.shape.stroke_dash_count > 0
+ && ovr->payload.shape.stroke_dash)
+ {
+ size_t sz =
+ ovr->payload.shape.stroke_dash_count * sizeof(double);
+ node->shape.stroke_dash = malloc(sz);
+ if (node->shape.stroke_dash)
+ {
+ memcpy(node->shape.stroke_dash,
+ ovr->payload.shape.stroke_dash, sz);
+ node->shape.stroke_dash_count =
+ ovr->payload.shape.stroke_dash_count;
+ }
+ }
+ }
+
+ if (m & EDJE_VG_OVR_STROKE_CAP)
+ node->shape.stroke_cap = ovr->payload.shape.stroke_cap;
+
+ if (m & EDJE_VG_OVR_STROKE_JOIN)
+ node->shape.stroke_join = ovr->payload.shape.stroke_join;
+
+ if (m & EDJE_VG_OVR_FILL_RULE)
+ node->shape.fill_rule = ovr->payload.shape.fill_rule;
+
+ if (m & EDJE_VG_OVR_GRAD_GEOM)
+ {
+ node->gradient.x0 = ovr->payload.gradient.x0;
+ node->gradient.y0 = ovr->payload.gradient.y0;
+ node->gradient.x1 = ovr->payload.gradient.x1;
+ node->gradient.y1 = ovr->payload.gradient.y1;
+ node->gradient.radius = ovr->payload.gradient.radius;
+ }
+
+ if (m & EDJE_VG_OVR_GRAD_SPREAD)
+ node->gradient.spread = ovr->payload.gradient.spread;
+
+ if (m & EDJE_VG_OVR_GRAD_STOPS)
+ {
+ /* Free old stops (with stringshare refs). */
+ if (node->gradient.stops)
+ {
+ unsigned int i;
+ for (i = 0; i < node->gradient.stops_count; i++)
+ _color_binding_free_fields(
+ &node->gradient.stops[i].color);
+ free(node->gradient.stops);
+ node->gradient.stops = NULL;
+ node->gradient.stops_count = 0;
+ }
+ /* Dup new stops from override payload. */
+ if (ovr->payload.gradient.stops_count > 0
+ && ovr->payload.gradient.stops)
+ {
+ size_t sz =
+ ovr->payload.gradient.stops_count * sizeof(Edje_Vg_Stop);
+ node->gradient.stops = malloc(sz);
+ if (node->gradient.stops)
+ {
+ memcpy(node->gradient.stops,
+ ovr->payload.gradient.stops, sz);
+ node->gradient.stops_count =
+ ovr->payload.gradient.stops_count;
+ /* Own our own stringshare refs for color_class. */
+ unsigned int i;
+ for (i = 0; i < node->gradient.stops_count; i++)
+ {
+ const char *cc =
+ node->gradient.stops[i].color.color_class;
+ if (cc)
+ node->gradient.stops[i].color.color_class =
+ eina_stringshare_ref(cc);
+ }
+ }
+ }
+ }
+}
+
/* =========================================================================
* Phase 3.1 — Materializer: Edje_Vg_Tree → Efl_VG object tree
*
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index af50623c8f..4f56404042 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -66,6 +66,24 @@ EAPI Eina_Bool _edje_vg_tree_validate_unique_names(const Edje_Vg_Tree *t,
* as raw RGBA in this phase; Phase 6 adds class resolution. */
EAPI Efl_VG *_edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t);
+/* --- Phase 4.1 helpers --------------------------------------------------- */
+
+/* Deep-clone the in-memory tree. Strings are stringshare-shared with the
+ * source via eina_stringshare_ref where the source is already stringshared.
+ * Container children are recursively duplicated; gradient stops and
+ * stroke_dash arrays are memcpy'd into fresh allocations. The result is
+ * fully independent of the source — mutating one does not affect the other. */
+EAPI Edje_Vg_Tree *_edje_vg_tree_dup(const Edje_Vg_Tree *t);
+
+/* Apply a single override onto a working tree in place. The override's
+ * target_name is looked up via _edje_vg_tree_node_by_name. If not found
+ * or expected_type mismatches, the call is a defensive no-op (VEC002 is
+ * supposed to catch unresolvable targets at parse time). For each
+ * EDJE_VG_OVR_* bit set in field_mask, the corresponding fields are
+ * copied from override->payload onto the matched node. */
+EAPI void _edje_vg_tree_apply_override(Edje_Vg_Tree *working,
+ const Edje_Vg_Override *ovr);
+
/* --- 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 b7e8ef654f..6b0a7d3e24 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -55,6 +55,53 @@ Eina_Bool _edje_vg_tree_validate_unique_names(const Edje_Vg_Tree *t,
/* Phase 3.1 materializer */
Efl_VG *_edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t);
+/* Phase 4.1 helpers */
+Edje_Vg_Tree *_edje_vg_tree_dup(const Edje_Vg_Tree *t);
+void _edje_vg_tree_apply_override(Edje_Vg_Tree *working,
+ const void *ovr);
+
+/* 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
+ * the minimum needed: visible (unsigned char) at the third field of Edje_Vg_Node.
+ *
+ * Layout of struct _Edje_Vg_Override (edje_private.h):
+ * const char *target_name; // +0
+ * Edje_Vg_Node_Type expected_type; // +1 (int-sized enum)
+ * uint64_t field_mask; // +2
+ * Edje_Vg_Node payload; // +3 — embedded, layout below
+ *
+ * We cannot include edje_private.h (pulls lua.h), so we declare a test-local
+ * struct that mirrors the layout. To keep it simple, we only fill and read
+ * fields that the three tests exercise: visible at payload offset.
+ *
+ * Edje_Vg_Node layout (from edje_private.h):
+ * const char *name; // ptr
+ * Edje_Vg_Node_Type type; // int enum
+ * unsigned char visible; // third field
+ * ...
+ *
+ * We use the same opaque Edje_Vg_Node struct view defined above. The
+ * Edje_Vg_Override struct below uses it as the payload type so sizeof()
+ * matches exactly when ONLY name+type+visible are needed. The tail of the
+ * payload is opaque and initialized to zero via memset.
+ */
+typedef struct {
+ const char *target_name;
+ Edje_Vg_Node_Type expected_type;
+ uint64_t field_mask;
+ struct {
+ const char *name;
+ Edje_Vg_Node_Type type;
+ unsigned char visible;
+ /* opaque tail — zeroed by memset in tests */
+ } payload;
+} _Test_Edje_Vg_Override;
+
+/* Replicate the EDJE_VG_OVR_* bitmask constants so the test file does not
+ * need to include edje_private.h. Values must stay in sync with the header. */
+#define _TEST_VG_OVR_VISIBLE (1ULL << 0)
+
/*
* Partial-view forward declarations for the Task 3.0 round-trip test.
*
@@ -598,6 +645,127 @@ EFL_START_TEST(edje_vg_render_box_c)
}
EFL_END_TEST
+/*
+ * edje_vg_tree_dup_independent (Task 4.1)
+ *
+ * Verifies _edje_vg_tree_dup produces a structurally equal but independent
+ * clone: the pointers differ, structural equality holds, and mutating the
+ * clone does not affect the original.
+ */
+EFL_START_TEST(edje_vg_tree_dup_independent)
+{
+ Edje_Vg_Tree *src = ""
+ Edje_Vg_Tree *dup = _edje_vg_tree_dup(src);
+ fail_if(!dup);
+ fail_if(dup == src); /* fresh allocation */
+ fail_if(dup->root == src->root); /* deep clone — root pointer differs */
+ fail_if(!_edje_vg_tree_equal(src, dup)); /* structurally equal */
+
+ /* Mutating dup must not affect src. */
+ /* Access body via the partial _Edje_Vg_Node view. body->visible is the
+ * third field: name (ptr), type (enum), visible (uchar). We use the
+ * partial view defined at the top of this file. */
+ Edje_Vg_Node *body = _edje_vg_tree_node_by_name(dup, "body");
+ fail_if(!body);
+ /* The partial view only exposes name and type. Mutate visible via a
+ * direct byte write at the known offset (offsetof name+type in the
+ * partial struct). To keep the test portable, cast to the implementation
+ * struct — but we only know the opaque handle. Instead, call
+ * apply_override to flip visible on dup and confirm src is unaffected. */
+
+ /* We verify independence by applying an override to the dup tree and
+ * confirming the src tree's node is unchanged. */
+ _Test_Edje_Vg_Override ovr;
+ memset(&ovr, 0, sizeof(ovr));
+ ovr.target_name = "body";
+ ovr.expected_type = EDJE_VG_NODE_SHAPE;
+ ovr.field_mask = _TEST_VG_OVR_VISIBLE;
+ ovr.payload.visible = 0;
+
+ _edje_vg_tree_apply_override(dup, &ovr);
+
+ Edje_Vg_Node *dup_body = _edje_vg_tree_node_by_name(dup, "body");
+ Edje_Vg_Node *src_body = _edje_vg_tree_node_by_name(src, "body");
+ fail_if(!dup_body);
+ fail_if(!src_body);
+
+ /* dup_body->visible is 0 after override; src_body->visible stays 1.
+ * Access visible via the opaque partial view by casting to a struct that
+ * matches the real _Edje_Vg_Node layout for its first three fields. */
+ typedef struct { const char *name; int type; unsigned char visible; }
+ _NodeHead;
+ _NodeHead *db = (_NodeHead *)(void *)dup_body;
+ _NodeHead *sb = (_NodeHead *)(void *)src_body;
+ ck_assert_int_eq(db->visible, 0); /* override applied on dup */
+ ck_assert_int_eq(sb->visible, 1); /* src unchanged */
+
+ _edje_vg_tree_free(dup);
+ _edje_vg_tree_free(src);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_tree_apply_override_visible (Task 4.1)
+ *
+ * Verifies that applying an override with EDJE_VG_OVR_VISIBLE correctly
+ * sets the matched node's visible field to 0.
+ */
+EFL_START_TEST(edje_vg_tree_apply_override_visible)
+{
+ Edje_Vg_Tree *t = _edje_vg_tree_new_for_test();
+
+ _Test_Edje_Vg_Override ovr;
+ memset(&ovr, 0, sizeof(ovr));
+ ovr.target_name = "body";
+ ovr.expected_type = EDJE_VG_NODE_SHAPE;
+ ovr.field_mask = _TEST_VG_OVR_VISIBLE;
+ ovr.payload.visible = 0;
+
+ _edje_vg_tree_apply_override(t, &ovr);
+
+ Edje_Vg_Node *body = _edje_vg_tree_node_by_name(t, "body");
+ fail_if(!body);
+
+ typedef struct { const char *name; int type; unsigned char visible; }
+ _NodeHead;
+ _NodeHead *bh = (_NodeHead *)(void *)body;
+ ck_assert_int_eq(bh->visible, 0);
+
+ _edje_vg_tree_free(t);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_tree_apply_override_unresolved_noop (Task 4.1)
+ *
+ * Verifies that an override targeting a nonexistent node name is a no-op:
+ * no crash, and the existing nodes are not modified.
+ */
+EFL_START_TEST(edje_vg_tree_apply_override_unresolved_noop)
+{
+ Edje_Vg_Tree *t = _edje_vg_tree_new_for_test();
+
+ _Test_Edje_Vg_Override ovr;
+ memset(&ovr, 0, sizeof(ovr));
+ ovr.target_name = "nonexistent";
+ ovr.expected_type = EDJE_VG_NODE_SHAPE;
+ ovr.field_mask = _TEST_VG_OVR_VISIBLE;
+ ovr.payload.visible = 0;
+
+ _edje_vg_tree_apply_override(t, &ovr); /* must not crash */
+
+ Edje_Vg_Node *body = _edje_vg_tree_node_by_name(t, "body");
+ fail_if(!body);
+
+ typedef struct { const char *name; int type; unsigned char visible; }
+ _NodeHead;
+ _NodeHead *bh = (_NodeHead *)(void *)body;
+ ck_assert_int_eq(bh->visible, 1); /* still the default — override was no-op */
+
+ _edje_vg_tree_free(t);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -608,4 +776,7 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_render_box_a);
tcase_add_test(tc, edje_vg_render_box_b);
tcase_add_test(tc, edje_vg_render_box_c);
+ tcase_add_test(tc, edje_vg_tree_dup_independent);
+ tcase_add_test(tc, edje_vg_tree_apply_override_visible);
+ tcase_add_test(tc, edje_vg_tree_apply_override_unresolved_noop);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.