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

git pushed a commit to branch main
in repository ego.

View the commit online.

commit ced4b1cca9c3dda40e6bb9a71ef59038a3f7c6ab
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 13:41:17 2026 -0600

    feat(efl): add struct marshal/unmarshal with eina tags
    
    ValueMarshal now handles Go structs → EINA_VALUE_TYPE_STRUCT,
    building C struct descriptors dynamically from reflection.
    ValueUnmarshal reverses the conversion. Supports eina:"name"
    tags, unexported field skipping, eina:"-" skip, and nested structs.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 efl/value.go              |  16 +-
 efl/value_marshal.go      |  13 +-
 efl/value_marshal_test.go | 106 ++++++++
 efl/value_struct.go       | 627 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 754 insertions(+), 8 deletions(-)

diff --git a/efl/value.go b/efl/value.go
index 3018bca..35ef26e 100644
--- a/efl/value.go
+++ b/efl/value.go
@@ -91,9 +91,14 @@ type ScalarType interface {
 
 // Value is a Go wrapper around a C Eina_Value scalar.
 // When owned is true, Free releases the underlying C allocation.
+//
+// structDescs is non-nil only for struct-typed Values.  It collects all
+// heap-allocated Eina_Value_Struct_Desc pointers (own plus all transitively
+// embedded nested struct descriptors) that must outlive the Value.
 type Value struct {
-	ptr   *C.Eina_Value
-	owned bool
+	ptr         *C.Eina_Value
+	owned       bool
+	structDescs []unsafe.Pointer
 }
 
 // NewValue creates a new Eina_Value of the type inferred from T and sets it to
@@ -360,6 +365,13 @@ func (v *Value) Free() {
 	}
 	C._ego_value_free(v.ptr)
 	v.ptr = nil
+	// Struct-typed values carry heap-allocated Eina_Value_Struct_Descs (own
+	// plus all transitively nested struct descriptors) that must be released
+	// after the value is freed.
+	for _, desc := range v.structDescs {
+		freeStructDesc(desc)
+	}
+	v.structDescs = nil
 }
 
 // IsNil reports whether the underlying C pointer is nil.
diff --git a/efl/value_marshal.go b/efl/value_marshal.go
index 947df13..d334bd5 100644
--- a/efl/value_marshal.go
+++ b/efl/value_marshal.go
@@ -6,11 +6,12 @@ import (
 	"reflect"
 )
 
-// ValueMarshal converts any Go scalar value to a *Value.
+// ValueMarshal converts a Go value to a *Value.
 //
-// Supported input types: bool, string, all integer and float variants, and
-// *Value (pass-through). Slice, map, and struct inputs return a "not yet
-// implemented" error. nil returns an error.
+// Supported input types: bool, string, all integer and float variants,
+// *Value (pass-through), slices with scalar or nested elements, maps with
+// string keys, and structs (marshalled via Eina_Value_Struct). nil returns
+// an error.
 func ValueMarshal(v any) (*Value, error) {
 	if v == nil {
 		return nil, errors.New("efl: ValueMarshal: nil input")
@@ -60,7 +61,7 @@ func ValueMarshal(v any) (*Value, error) {
 		case reflect.Map:
 			return marshalMap(rv)
 		case reflect.Struct:
-			return nil, errors.New("efl: ValueMarshal: struct: not yet implemented")
+			return marshalStruct(rv)
 		default:
 			return nil, fmt.Errorf("efl: ValueMarshal: unsupported type %T", v)
 		}
@@ -194,7 +195,7 @@ func ValueUnmarshal(val *Value, dst any) error {
 		return unmarshalMap(val, elem)
 
 	case reflect.Struct:
-		return errors.New("efl: ValueUnmarshal: struct: not yet implemented")
+		return unmarshalStruct(val, elem)
 
 	default:
 		return fmt.Errorf("efl: ValueUnmarshal: unsupported destination type %T", dst)
diff --git a/efl/value_marshal_test.go b/efl/value_marshal_test.go
index 03493cc..31a549a 100644
--- a/efl/value_marshal_test.go
+++ b/efl/value_marshal_test.go
@@ -235,3 +235,109 @@ func TestMarshalMapStringInt(t *testing.T) {
 		t.Errorf("got %v", got)
 	}
 }
+
+func TestMarshalStruct(t *testing.T) {
+	type Config struct {
+		FontSize int     `eina:"font_size"`
+		Theme    string  `eina:"theme"`
+		Scale    float64 `eina:"scale"`
+	}
+	v, err := ValueMarshal(Config{FontSize: 14, Theme: "dark", Scale: 1.5})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got Config
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got.FontSize != 14 {
+		t.Errorf("FontSize=%d", got.FontSize)
+	}
+	if got.Theme != "dark" {
+		t.Errorf("Theme=%q", got.Theme)
+	}
+	if got.Scale != 1.5 {
+		t.Errorf("Scale=%f", got.Scale)
+	}
+}
+
+func TestMarshalStructNoTag(t *testing.T) {
+	type Simple struct {
+		Name  string
+		Count int
+	}
+	v, err := ValueMarshal(Simple{Name: "test", Count: 5})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got Simple
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got.Name != "test" || got.Count != 5 {
+		t.Errorf("got %+v", got)
+	}
+}
+
+func TestMarshalStructSkipUnexported(t *testing.T) {
+	type WithPrivate struct {
+		Public  int `eina:"public"`
+		private string //nolint:unused
+	}
+	v, err := ValueMarshal(WithPrivate{Public: 42})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got WithPrivate
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got.Public != 42 {
+		t.Errorf("Public=%d", got.Public)
+	}
+}
+
+func TestMarshalStructSkipTag(t *testing.T) {
+	type WithSkip struct {
+		Keep int    `eina:"keep"`
+		Skip string `eina:"-"`
+	}
+	v, err := ValueMarshal(WithSkip{Keep: 1, Skip: "ignored"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got WithSkip
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got.Keep != 1 {
+		t.Errorf("Keep=%d", got.Keep)
+	}
+}
+
+func TestMarshalStructNested(t *testing.T) {
+	type Inner struct {
+		X int `eina:"x"`
+		Y int `eina:"y"`
+	}
+	type Outer struct {
+		Name  string `eina:"name"`
+		Point Inner  `eina:"point"`
+	}
+	v, err := ValueMarshal(Outer{Name: "origin", Point: Inner{X: 10, Y: 20}})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer v.Free()
+	var got Outer
+	if err := ValueUnmarshal(v, &got); err != nil {
+		t.Fatal(err)
+	}
+	if got.Name != "origin" || got.Point.X != 10 || got.Point.Y != 20 {
+		t.Errorf("got %+v", got)
+	}
+}
diff --git a/efl/value_struct.go b/efl/value_struct.go
new file mode 100644
index 0000000..31c0049
--- /dev/null
+++ b/efl/value_struct.go
@@ -0,0 +1,627 @@
+package efl
+
+/*
+#cgo pkg-config: eina
+
+#include <Eina.h>
+#include <stdlib.h>
+#include <string.h>
+
+// _ego_struct_desc_new allocates a heap Eina_Value_Struct_Desc for count
+// members occupying size bytes total.  A NULL-terminated sentinel is appended
+// beyond the count slots so that Eina's linear-search find_member terminates
+// correctly.
+static Eina_Value_Struct_Desc *_ego_struct_desc_new(unsigned int count, unsigned int size) {
+    Eina_Value_Struct_Desc *desc = calloc(1, sizeof(Eina_Value_Struct_Desc));
+    if (!desc) return NULL;
+    desc->version      = EINA_VALUE_STRUCT_DESC_VERSION;
+    desc->ops          = NULL;
+    desc->member_count = count;
+    desc->size         = size;
+    // count+1 slots: the extra slot is the NULL sentinel.
+    Eina_Value_Struct_Member *members = calloc(count + 1, sizeof(Eina_Value_Struct_Member));
+    if (!members) { free(desc); return NULL; }
+    members[count].name   = NULL;
+    members[count].type   = NULL;
+    members[count].offset = 0;
+    desc->members = members;
+    return desc;
+}
+
+// _ego_struct_desc_set_member fills slot idx of the descriptor.  name is
+// copied so the caller may release its C string immediately.
+static void _ego_struct_desc_set_member(Eina_Value_Struct_Desc *desc,
+                                        unsigned int idx,
+                                        const char *name,
+                                        const Eina_Value_Type *type,
+                                        unsigned int offset) {
+    Eina_Value_Struct_Member *m = (Eina_Value_Struct_Member *)&desc->members[idx];
+    m->name   = strdup(name);
+    m->type   = type;
+    m->offset = offset;
+}
+
+// _ego_struct_desc_free releases a descriptor allocated by _ego_struct_desc_new.
+static void _ego_struct_desc_free(Eina_Value_Struct_Desc *desc) {
+    if (!desc) return;
+    for (unsigned int i = 0; i < desc->member_count; i++) {
+        free((void *)desc->members[i].name);
+    }
+    free((void *)desc->members);
+    free(desc);
+}
+
+// _ego_value_struct_new is a non-inline wrapper around eina_value_struct_new.
+static Eina_Value *_ego_value_struct_new(const Eina_Value_Struct_Desc *desc) {
+    return eina_value_struct_new(desc);
+}
+
+// _ego_value_struct_pset is a non-inline wrapper around eina_value_struct_pset.
+static Eina_Bool _ego_value_struct_pset(Eina_Value *v, const char *name, const void *ptr) {
+    return eina_value_struct_pset(v, name, ptr);
+}
+
+// _ego_value_struct_pget is a non-inline wrapper around eina_value_struct_pget.
+static Eina_Bool _ego_value_struct_pget(const Eina_Value *v, const char *name, void *ptr) {
+    return eina_value_struct_pget(v, name, ptr);
+}
+
+// _ego_value_struct_set_string stores a string member.  The double indirection
+// is required because EINA_VALUE_TYPE_STRING stores a char* and pset expects a
+// pointer to that pointer.
+static Eina_Bool _ego_value_struct_set_string(Eina_Value *v, const char *name, const char *str) {
+    return eina_value_struct_pset(v, name, &str);
+}
+
+// _ego_value_struct_get_string retrieves a string member.  Returns an internal
+// pointer owned by the Eina_Value; the caller must not free it.
+static const char *_ego_value_struct_get_string(const Eina_Value *v, const char *name) {
+    const char *s = NULL;
+    eina_value_struct_pget(v, name, &s);
+    return s;
+}
+
+// _ego_value_struct_desc wraps the inline eina_value_struct_desc_get.
+static const Eina_Value_Struct_Desc *_ego_value_struct_desc(const Eina_Value *v) {
+    return eina_value_struct_desc_get(v);
+}
+
+// _ego_value_struct_value_set is a non-inline wrapper for the inline
+// eina_value_struct_value_set.  Eina copies the src Eina_Value into the named
+// member slot; src may be freed by the caller afterwards.
+static Eina_Bool _ego_value_struct_value_set(Eina_Value *v, const char *name, const Eina_Value *src) {
+    return eina_value_struct_value_set(v, name, src);
+}
+
+// _ego_value_struct_value_get allocates and returns a fresh heap Eina_Value
+// that is a copy of the named nested member.  Returns NULL on failure.
+// The caller must free with eina_value_free.
+static Eina_Value *_ego_value_struct_value_get_new(const Eina_Value *v, const char *name) {
+    Eina_Value dst;
+    memset(&dst, 0, sizeof(dst));
+    if (!eina_value_struct_value_get(v, name, &dst)) return NULL;
+    // dst is stack-allocated; copy it to a heap Eina_Value for Go to own.
+    Eina_Value *out = eina_value_new(dst.type);
+    if (!out) { eina_value_flush(&dst); return NULL; }
+    if (!eina_value_copy(&dst, out)) {
+        eina_value_free(out);
+        eina_value_flush(&dst);
+        return NULL;
+    }
+    eina_value_flush(&dst);
+    return out;
+}
+
+// Forward declaration: _ego_value_free is defined in value.go's preamble and
+// releases a heap Eina_Value.  We use it here to free the inner struct value
+// after embedding it into the outer struct via eina_value_struct_value_set.
+static void _ego_value_free(Eina_Value *v);
+*/
+import "C"
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"unsafe"
+)
+
+// freeStructDesc releases the Eina_Value_Struct_Desc pointed to by p.
+// It is called from Value.Free() which lives in value.go.
+func freeStructDesc(p unsafe.Pointer) {
+	C._ego_struct_desc_free((*C.Eina_Value_Struct_Desc)(p))
+}
+
+// ---------------------------------------------------------------------------
+// Layout helpers
+// ---------------------------------------------------------------------------
+
+// einaFieldSize returns the byte size of the C representation for a given
+// Eina_Value_Type within an Eina_Value_Struct memory block.
+//
+// For compound types (STRUCT, ARRAY, HASH) Eina stores the runtime descriptor
+// struct (Eina_Value_Struct, Eina_Value_Array, Eina_Value_Hash) inline in the
+// outer struct's memory block.  Each of these is two pointers wide (a type/desc
+// pointer and a data pointer) on all supported platforms.
+func einaFieldSize(t *C.Eina_Value_Type) uint {
+	switch t {
+	case C.EINA_VALUE_TYPE_BOOL, C.EINA_VALUE_TYPE_CHAR:
+		return 1
+	case C.EINA_VALUE_TYPE_SHORT, C.EINA_VALUE_TYPE_USHORT:
+		return 2
+	case C.EINA_VALUE_TYPE_INT, C.EINA_VALUE_TYPE_UINT, C.EINA_VALUE_TYPE_FLOAT:
+		return 4
+	case C.EINA_VALUE_TYPE_INT64, C.EINA_VALUE_TYPE_UINT64, C.EINA_VALUE_TYPE_DOUBLE:
+		return 8
+	case C.EINA_VALUE_TYPE_STRING, C.EINA_VALUE_TYPE_STRINGSHARE:
+		// A char* pointer.
+		return uint(unsafe.Sizeof(uintptr(0)))
+	case C.EINA_VALUE_TYPE_STRUCT, C.EINA_VALUE_TYPE_ARRAY, C.EINA_VALUE_TYPE_HASH:
+		// Eina stores the inner runtime descriptor struct (two pointer-sized words).
+		return uint(unsafe.Sizeof(uintptr(0))) * 2
+	case C.EINA_VALUE_TYPE_VALUE:
+		// sizeof(Eina_Value) — type* + union (one pointer-sized word).
+		return uint(unsafe.Sizeof(uintptr(0))) * 2
+	default:
+		return uint(unsafe.Sizeof(uintptr(0)))
+	}
+}
+
+// einaFieldAlign returns the required alignment (in bytes) for a field of the
+// given type.  For most types it equals the size; bool and char are 1-byte
+// aligned.
+func einaFieldAlign(t *C.Eina_Value_Type) uint {
+	switch t {
+	case C.EINA_VALUE_TYPE_BOOL, C.EINA_VALUE_TYPE_CHAR:
+		return 1
+	case C.EINA_VALUE_TYPE_SHORT, C.EINA_VALUE_TYPE_USHORT:
+		return 2
+	case C.EINA_VALUE_TYPE_INT, C.EINA_VALUE_TYPE_UINT, C.EINA_VALUE_TYPE_FLOAT:
+		return 4
+	default:
+		// 8-byte types and pointer-sized types.
+		sz := einaFieldSize(t)
+		if sz > 8 {
+			return 8
+		}
+		return sz
+	}
+}
+
+// alignUp rounds offset up to the next multiple of align.
+func alignUp(offset, align uint) uint {
+	return (offset + align - 1) &^ (align - 1)
+}
+
+// ---------------------------------------------------------------------------
+// Field metadata helpers
+// ---------------------------------------------------------------------------
+
+// structFieldName returns the Eina member name for field f:
+//   - tag `eina:"-"` → skip (returns "", false)
+//   - tag `eina:"name"` → "name"
+//   - no tag → lowercased Go field name
+func structFieldName(f reflect.StructField) (name string, keep bool) {
+	if tag, ok := f.Tag.Lookup("eina"); ok {
+		if tag == "-" {
+			return "", false
+		}
+		// Support "name,options" syntax (forward-compat; only "name" used now).
+		if comma := strings.IndexByte(tag, ','); comma >= 0 {
+			tag = tag[:comma]
+		}
+		if tag != "" {
+			return tag, true
+		}
+	}
+	return strings.ToLower(f.Name), true
+}
+
+// goKindToEinaType maps a Go reflect.Kind (for scalar fields) to the
+// corresponding *C.Eina_Value_Type.  Returns nil for non-scalar kinds.
+func goKindToEinaType(k reflect.Kind) *C.Eina_Value_Type {
+	switch k {
+	case reflect.Int, reflect.Int32:
+		return C.EINA_VALUE_TYPE_INT
+	case reflect.Uint, reflect.Uint32:
+		return C.EINA_VALUE_TYPE_UINT
+	case reflect.Int64:
+		return C.EINA_VALUE_TYPE_INT64
+	case reflect.Uint64:
+		return C.EINA_VALUE_TYPE_UINT64
+	case reflect.Int16:
+		return C.EINA_VALUE_TYPE_SHORT
+	case reflect.Uint16:
+		return C.EINA_VALUE_TYPE_USHORT
+	case reflect.Float32:
+		return C.EINA_VALUE_TYPE_FLOAT
+	case reflect.Float64:
+		return C.EINA_VALUE_TYPE_DOUBLE
+	case reflect.String:
+		return C.EINA_VALUE_TYPE_STRING
+	case reflect.Bool:
+		return C.EINA_VALUE_TYPE_BOOL
+	case reflect.Uint8: // byte
+		return C.EINA_VALUE_TYPE_CHAR
+	case reflect.Int8:
+		// int8 is promoted to int for Eina (matches NewValue behavior).
+		return C.EINA_VALUE_TYPE_INT
+	default:
+		return nil
+	}
+}
+
+// nestedEinaType returns the Eina_Value_Type constant for a nested (non-scalar)
+// Go kind: Struct → EINA_VALUE_TYPE_STRUCT, Slice → EINA_VALUE_TYPE_ARRAY,
+// Map → EINA_VALUE_TYPE_HASH.  Returns nil for other kinds.
+func nestedEinaType(k reflect.Kind) *C.Eina_Value_Type {
+	switch k {
+	case reflect.Struct:
+		return C.EINA_VALUE_TYPE_STRUCT
+	case reflect.Slice:
+		return C.EINA_VALUE_TYPE_ARRAY
+	case reflect.Map:
+		return C.EINA_VALUE_TYPE_HASH
+	default:
+		return nil
+	}
+}
+
+// ---------------------------------------------------------------------------
+// marshalStruct
+// ---------------------------------------------------------------------------
+
+// marshalStruct converts a Go struct value (rv must be reflect.Struct kind)
+// into a *Value of Eina type EINA_VALUE_TYPE_STRUCT.
+func marshalStruct(rv reflect.Value) (*Value, error) {
+	rt := rv.Type()
+	nFields := rt.NumField()
+
+	// First pass: collect active fields and determine offsets/total size.
+	type fieldInfo struct {
+		name      string
+		idx       int
+		einaType  *C.Eina_Value_Type
+		offset    uint
+		isNested  bool // true for struct/slice/map fields
+	}
+
+	fields := make([]fieldInfo, 0, nFields)
+	var cursor uint
+
+	for i := range nFields {
+		f := rt.Field(i)
+		if !f.IsExported() {
+			continue
+		}
+		name, keep := structFieldName(f)
+		if !keep {
+			continue
+		}
+
+		k := f.Type.Kind()
+		et := goKindToEinaType(k)
+		nested := false
+		if et == nil {
+			// Struct, slice, or map: use the matching Eina container type so that
+			// eina_value_struct_value_set/get type-checks pass.
+			et = nestedEinaType(k)
+			if et == nil {
+				return nil, fmt.Errorf("efl: marshalStruct: field %q has unsupported kind %v", name, k)
+			}
+			nested = true
+		}
+
+		align := einaFieldAlign(et)
+		cursor = alignUp(cursor, align)
+		fields = append(fields, fieldInfo{
+			name:     name,
+			idx:      i,
+			einaType: et,
+			offset:   cursor,
+			isNested: nested,
+		})
+		cursor += einaFieldSize(et)
+	}
+
+	// Total size must be at least 1 byte (Eina rejects zero-size structs).
+	totalSize := cursor
+	if totalSize == 0 {
+		totalSize = 1
+	}
+
+	count := C.uint(len(fields))
+	desc := C._ego_struct_desc_new(count, C.uint(totalSize))
+	if desc == nil {
+		return nil, fmt.Errorf("efl: marshalStruct: _ego_struct_desc_new returned nil")
+	}
+
+	// Populate descriptor members.
+	for i, fi := range fields {
+		cname := C.CString(fi.name)
+		C._ego_struct_desc_set_member(desc, C.uint(i), cname, fi.einaType, C.uint(fi.offset))
+		C.free(unsafe.Pointer(cname))
+	}
+
+	raw := C._ego_value_struct_new(desc)
+	if raw == nil {
+		C._ego_struct_desc_free(desc)
+		return nil, fmt.Errorf("efl: marshalStruct: eina_value_struct_new returned nil")
+	}
+
+	// The own descriptor is the first entry; nested descriptors are appended below.
+	val := &Value{
+		ptr:         raw,
+		owned:       true,
+		structDescs: []unsafe.Pointer{unsafe.Pointer(desc)},
+	}
+
+	// Second pass: set field values.
+	for _, fi := range fields {
+		fv := rv.Field(fi.idx)
+		cname := C.CString(fi.name)
+
+		if fi.isNested {
+			sub, err := ValueMarshal(fv.Interface())
+			if err != nil {
+				C.free(unsafe.Pointer(cname))
+				val.Free()
+				return nil, fmt.Errorf("efl: marshalStruct: field %q: %w", fi.name, err)
+			}
+			ok := C._ego_value_struct_value_set(raw, cname, sub.ptr)
+			C.free(unsafe.Pointer(cname))
+			if ok == 0 {
+				// Set failed; free the sub value including its descriptors.
+				if sub.owned {
+					sub.Free()
+				}
+				val.Free()
+				return nil, fmt.Errorf("efl: marshalStruct: struct_value_set failed for field %q", fi.name)
+			}
+			// The outer struct's memory block now contains a copy of the inner
+			// struct's data (allocated by _eina_value_type_struct_copy), but still
+			// references the inner struct's descriptor via a pointer.  Transfer
+			// descriptor ownership to val so the descriptors outlive the outer struct.
+			if sub.owned {
+				val.structDescs = append(val.structDescs, sub.structDescs...)
+				sub.structDescs = nil // prevent double-free in the bypass below
+				// Free the inner Eina_Value + its managed memory.  The outer struct
+				// holds its own independent copy so this is safe.
+				// We bypass sub.Free() to avoid freeing descriptors we just transferred.
+				C._ego_value_free(sub.ptr)
+				sub.ptr = nil
+				sub.owned = false
+			}
+			continue
+		}
+
+		// Scalar field.
+		var setErr error
+		switch fi.einaType {
+		case C.EINA_VALUE_TYPE_STRING:
+			cs := C.CString(fv.String())
+			if C._ego_value_struct_set_string(raw, cname, cs) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: set_string failed for field %q", fi.name)
+			}
+			C.free(unsafe.Pointer(cs))
+
+		case C.EINA_VALUE_TYPE_INT:
+			cv := C.int(fv.Int())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(INT) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_UINT:
+			cv := C.uint(fv.Uint())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(UINT) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_INT64:
+			cv := C.int64_t(fv.Int())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(INT64) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_UINT64:
+			cv := C.uint64_t(fv.Uint())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(UINT64) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_SHORT:
+			cv := C.short(fv.Int())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(SHORT) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_USHORT:
+			cv := C.ushort(fv.Uint())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(USHORT) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_FLOAT:
+			cv := C.float(fv.Float())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(FLOAT) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_DOUBLE:
+			cv := C.double(fv.Float())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(DOUBLE) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_BOOL:
+			var cv C.uchar
+			if fv.Bool() {
+				cv = 1
+			}
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(BOOL) failed for field %q", fi.name)
+			}
+
+		case C.EINA_VALUE_TYPE_CHAR:
+			cv := C.char(fv.Uint())
+			if C._ego_value_struct_pset(raw, cname, unsafe.Pointer(&cv)) == 0 {
+				setErr = fmt.Errorf("efl: marshalStruct: pset(CHAR) failed for field %q", fi.name)
+			}
+		}
+
+		C.free(unsafe.Pointer(cname))
+		if setErr != nil {
+			val.Free()
+			return nil, setErr
+		}
+	}
+
+	return val, nil
+}
+
+// ---------------------------------------------------------------------------
+// unmarshalStruct
+// ---------------------------------------------------------------------------
+
+// unmarshalStruct reads fields from the struct-typed Value val and writes them
+// into the Go struct pointed to by elem (a reflect.Value of Kind Struct).
+func unmarshalStruct(val *Value, elem reflect.Value) error {
+	rt := elem.Type()
+	nFields := rt.NumField()
+
+	for i := range nFields {
+		f := rt.Field(i)
+		if !f.IsExported() {
+			continue
+		}
+		name, keep := structFieldName(f)
+		if !keep {
+			continue
+		}
+
+		fv := elem.Field(i)
+		k := f.Type.Kind()
+		et := goKindToEinaType(k)
+
+		cname := C.CString(name)
+
+		if et == nil {
+			// Nested struct, slice, or map: retrieve via struct_value_get_new.
+			subPtr := C._ego_value_struct_value_get_new(val.ptr, cname)
+			C.free(unsafe.Pointer(cname))
+			if subPtr == nil {
+				return fmt.Errorf("efl: unmarshalStruct: struct_value_get_new failed for field %q", name)
+			}
+			sub := &Value{ptr: subPtr, owned: true}
+			dst := reflect.New(f.Type)
+			if err := ValueUnmarshal(sub, dst.Interface()); err != nil {
+				sub.Free()
+				return fmt.Errorf("efl: unmarshalStruct: field %q: %w", name, err)
+			}
+			sub.Free()
+			fv.Set(dst.Elem())
+			continue
+		}
+
+		var getErr error
+		switch et {
+		case C.EINA_VALUE_TYPE_STRING:
+			cs := C._ego_value_struct_get_string(val.ptr, cname)
+			C.free(unsafe.Pointer(cname))
+			if cs == nil {
+				// Empty string fields may return nil; treat as empty.
+				fv.SetString("")
+				continue
+			}
+			fv.SetString(C.GoString(cs))
+			continue
+
+		case C.EINA_VALUE_TYPE_INT:
+			var cv C.int
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(INT) failed for field %q", name)
+			} else {
+				fv.SetInt(int64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_UINT:
+			var cv C.uint
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(UINT) failed for field %q", name)
+			} else {
+				fv.SetUint(uint64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_INT64:
+			var cv C.int64_t
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(INT64) failed for field %q", name)
+			} else {
+				fv.SetInt(int64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_UINT64:
+			var cv C.uint64_t
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(UINT64) failed for field %q", name)
+			} else {
+				fv.SetUint(uint64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_SHORT:
+			var cv C.short
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(SHORT) failed for field %q", name)
+			} else {
+				fv.SetInt(int64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_USHORT:
+			var cv C.ushort
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(USHORT) failed for field %q", name)
+			} else {
+				fv.SetUint(uint64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_FLOAT:
+			var cv C.float
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(FLOAT) failed for field %q", name)
+			} else {
+				fv.SetFloat(float64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_DOUBLE:
+			var cv C.double
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(DOUBLE) failed for field %q", name)
+			} else {
+				fv.SetFloat(float64(cv))
+			}
+
+		case C.EINA_VALUE_TYPE_BOOL:
+			var cv C.uchar
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(BOOL) failed for field %q", name)
+			} else {
+				fv.SetBool(cv != 0)
+			}
+
+		case C.EINA_VALUE_TYPE_CHAR:
+			var cv C.char
+			if C._ego_value_struct_pget(val.ptr, cname, unsafe.Pointer(&cv)) == 0 {
+				getErr = fmt.Errorf("efl: unmarshalStruct: pget(CHAR) failed for field %q", name)
+			} else {
+				fv.SetUint(uint64(byte(cv)))
+			}
+		}
+
+		C.free(unsafe.Pointer(cname))
+		if getErr != nil {
+			return getErr
+		}
+	}
+
+	return nil
+}

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

Reply via email to