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 e7e14f642c8d1965765167ed7930a2b2a5e17f55
Author: [email protected] <[email protected]>
AuthorDate: Thu Mar 26 21:34:25 2026 -0600
feat(eet): add descriptor builder mapping Go struct types to EET descriptors
This commit introduces the core descriptor bridge between Go reflection and
EET's C API. The descriptor builder:
- Uses sync.Map for thread-safe caching of computed type descriptors
- Computes C memory layout matching EET's expectations
- Creates Eet_Data_Descriptor via eet_data_descriptor_stream_new
- Registers each struct field via eet_data_descriptor_element_add with
appropriate EET_T_* type and EET_G_* group constants
The resolveEETType function provides exhaustive type mapping for Go scalar
types, strings (with inline variant support), slices (arrays/lists), maps
(hashes), nested structs, and pointers to structs. Tests validate both
descriptor creation for basic types and caching behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
eet/descriptor.go | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
eet/eet_test.go | 31 ++++++++
2 files changed, 238 insertions(+)
diff --git a/eet/descriptor.go b/eet/descriptor.go
new file mode 100644
index 0000000..82d467a
--- /dev/null
+++ b/eet/descriptor.go
@@ -0,0 +1,207 @@
+package eet
+
+/*
+#include <Eet.h>
+#include <stdlib.h>
+
+// _ego_eet_descriptor_new creates a new Eet_Data_Descriptor using the stream
+// class with Eina list/hash callbacks pre-wired.
+static Eet_Data_Descriptor *_ego_eet_descriptor_new(const char *name, int size) {
+ Eet_Data_Descriptor_Class eddc;
+ EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, void);
+ eddc.name = name;
+ eddc.size = size;
+ return eet_data_descriptor_stream_new(&eddc);
+}
+
+// _ego_eet_descriptor_element_add wraps eet_data_descriptor_element_add.
+static void _ego_eet_descriptor_element_add(
+ Eet_Data_Descriptor *edd, const char *name,
+ int type, int group_type, int offset,
+ int count, const char *counter_name,
+ Eet_Data_Descriptor *subtype)
+{
+ eet_data_descriptor_element_add(edd, name, type, group_type,
+ offset, count, counter_name, subtype);
+}
+*/
+import "C"
+import (
+ "fmt"
+ "reflect"
+ "sync"
+ "unsafe"
+)
+
+// typeInfo holds the cached EET descriptor and field layout for a Go struct type.
+type typeInfo struct {
+ desc *C.Eet_Data_Descriptor
+ shadowSize int
+ fields []fieldDescriptor
+ err error
+}
+
+// fieldDescriptor extends CField with EET-specific metadata.
+type fieldDescriptor struct {
+ CField
+ eetType C.int
+ groupType C.int
+ subInfo *typeInfo
+}
+
+// Desc returns the underlying EET descriptor pointer.
+func (ti *typeInfo) Desc() unsafe.Pointer {
+ if ti == nil || ti.desc == nil {
+ return nil
+ }
+ return unsafe.Pointer(ti.desc)
+}
+
+// Fields returns the field descriptors.
+func (ti *typeInfo) Fields() []fieldDescriptor {
+ return ti.fields
+}
+
+var typeCache sync.Map // reflect.Type -> *typeInfo
+
+// GetTypeInfo returns the cached typeInfo for a struct type, building it on first access.
+func GetTypeInfo(t reflect.Type) (*typeInfo, error) {
+ if t.Kind() == reflect.Pointer {
+ t = t.Elem()
+ }
+ if t.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("%w: %s is not a struct", ErrUnsupported, t)
+ }
+
+ if v, ok := typeCache.Load(t); ok {
+ ti := v.(*typeInfo)
+ return ti, ti.err
+ }
+
+ ti := buildTypeInfo(t)
+ actual, _ := typeCache.LoadOrStore(t, ti)
+ ti = actual.(*typeInfo)
+ return ti, ti.err
+}
+
+func buildTypeInfo(t reflect.Type) *typeInfo {
+ layout := ComputeLayout(t)
+ totalSize := LayoutTotalSize(layout)
+
+ cName := C.CString(t.Name())
+ defer C.free(unsafe.Pointer(cName))
+
+ desc := C._ego_eet_descriptor_new(cName, C.int(totalSize))
+ if desc == nil {
+ return &typeInfo{err: fmt.Errorf("%w: failed to create descriptor for %s", ErrEncode, t.Name())}
+ }
+
+ ti := &typeInfo{
+ desc: desc,
+ shadowSize: totalSize,
+ }
+
+ for _, cf := range layout {
+ fd := fieldDescriptor{CField: cf}
+ var err error
+ fd.eetType, fd.groupType, fd.subInfo, err = resolveEETType(cf.GoType, cf.Tag)
+ if err != nil {
+ ti.err = err
+ return ti
+ }
+
+ cFieldName := C.CString(cf.Name)
+
+ var subDesc *C.Eet_Data_Descriptor
+ if fd.subInfo != nil {
+ subDesc = fd.subInfo.desc
+ }
+
+ var counterName *C.char
+ count := C.int(0)
+ if fd.groupType == C.EET_G_VAR_ARRAY {
+ counterName = C.CString(cf.Name + ".count")
+ }
+
+ C._ego_eet_descriptor_element_add(
+ desc, cFieldName,
+ fd.eetType, fd.groupType,
+ C.int(cf.Offset),
+ count, counterName,
+ subDesc,
+ )
+
+ C.free(unsafe.Pointer(cFieldName))
+ if counterName != nil {
+ C.free(unsafe.Pointer(counterName))
+ }
+
+ ti.fields = append(ti.fields, fd)
+ }
+
+ return ti
+}
+
+// resolveEETType maps a Go reflect.Type to EET_T_* and EET_G_* constants.
+func resolveEETType(t reflect.Type, tag FieldTag) (eetType, groupType C.int, sub *typeInfo, err error) {
+ switch t.Kind() {
+ case reflect.Bool:
+ return C.EET_T_UCHAR, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Int8:
+ return C.EET_T_CHAR, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Int16:
+ return C.EET_T_SHORT, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Int, reflect.Int32:
+ return C.EET_T_INT, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Int64:
+ return C.EET_T_LONG_LONG, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Uint8:
+ return C.EET_T_UCHAR, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Uint16:
+ return C.EET_T_USHORT, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Uint, reflect.Uint32:
+ return C.EET_T_UINT, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Uint64:
+ return C.EET_T_ULONG_LONG, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Float32:
+ return C.EET_T_FLOAT, C.EET_G_UNKNOWN, nil, nil
+ case reflect.Float64:
+ return C.EET_T_DOUBLE, C.EET_G_UNKNOWN, nil, nil
+ case reflect.String:
+ if tag.Inline {
+ return C.EET_T_INLINED_STRING, C.EET_G_UNKNOWN, nil, nil
+ }
+ return C.EET_T_STRING, C.EET_G_UNKNOWN, nil, nil
+
+ case reflect.Slice:
+ // For slices of basic types (string, int, etc.), subInfo may be nil
+ // and that's fine — EET handles basic-typed arrays/lists directly.
+ elemSub, _ := GetTypeInfo(t.Elem())
+ if tag.List {
+ return C.EET_T_UNKNOW, C.EET_G_LIST, elemSub, nil
+ }
+ return C.EET_T_UNKNOW, C.EET_G_VAR_ARRAY, elemSub, nil
+
+ case reflect.Map:
+ valSub, _ := GetTypeInfo(t.Elem())
+ return C.EET_T_UNKNOW, C.EET_G_HASH, valSub, nil
+
+ case reflect.Struct:
+ sub, serr := GetTypeInfo(t)
+ if serr != nil {
+ return 0, 0, nil, serr
+ }
+ return C.EET_T_UNKNOW, C.EET_G_UNKNOWN_NESTED, sub, nil
+
+ case reflect.Pointer:
+ if t.Elem().Kind() == reflect.Struct {
+ sub, serr := GetTypeInfo(t.Elem())
+ if serr != nil {
+ return 0, 0, nil, serr
+ }
+ return C.EET_T_UNKNOW, C.EET_G_UNKNOWN, sub, nil
+ }
+ }
+
+ return 0, 0, nil, fmt.Errorf("%w: %s", ErrUnsupported, t)
+}
diff --git a/eet/eet_test.go b/eet/eet_test.go
index 518ca3a..d14c7b4 100644
--- a/eet/eet_test.go
+++ b/eet/eet_test.go
@@ -88,6 +88,37 @@ func TestLayoutTotalSize(t *testing.T) {
}
}
+func TestDescriptorBasicTypes(t *testing.T) {
+ type Basic struct {
+ I int `eet:"i"`
+ F float64 `eet:"f"`
+ S string `eet:"s"`
+ B bool `eet:"b"`
+ }
+ info, err := eet.GetTypeInfo(reflect.TypeFor[Basic]())
+ if err != nil {
+ t.Fatalf("GetTypeInfo: %v", err)
+ }
+ if info.Desc() == nil {
+ t.Fatal("descriptor is nil")
+ }
+ if len(info.Fields()) != 4 {
+ t.Fatalf("got %d fields, want 4", len(info.Fields()))
+ }
+}
+
+func TestDescriptorCached(t *testing.T) {
+ type Cached struct {
+ X int `eet:"x"`
+ }
+ typ := reflect.TypeFor[Cached]()
+ a, _ := eet.GetTypeInfo(typ)
+ b, _ := eet.GetTypeInfo(typ)
+ if a != b {
+ t.Fatal("expected same typeInfo pointer for repeated calls")
+ }
+}
+
func TestParseTag(t *testing.T) {
tests := []struct {
tag string
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.