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 cfb56c656e7f9c6f49275fb1e001cf36ea1d0236
Author: [email protected] <[email protected]>
AuthorDate: Sun Mar 8 22:28:58 2026 -0600

    feat: add Object wrapper for EFL Eo objects
    
    Introduce a Go wrapper type for EFL Eo objects that provides nil-safe
    access to the underlying pointer. The wrapper includes:
    
    - WrapObject constructor with GC finalizer safety net to prevent leaks
    - Ptr() and IsNil() methods that handle nil receivers gracefully
    - Ref() and Unref() methods with nil guards and finalizer disarm
      protection to prevent double-unref during GC
    
    Comprehensive tests verify nil value handling, nil pointer receivers,
    and correct wrapping of live Eo objects.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 efl/export_test.go |   8 ++++
 efl/object.go      | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 efl/object_test.go |  69 +++++++++++++++++++++++++++++++
 3 files changed, 194 insertions(+)

diff --git a/efl/export_test.go b/efl/export_test.go
new file mode 100644
index 0000000..7acfd02
--- /dev/null
+++ b/efl/export_test.go
@@ -0,0 +1,8 @@
+package efl
+
+import "unsafe"
+
+// NewEoObject is a test helper that creates a loop-timer Eo object under the
+// main loop. Must be called on the EFL thread. Exposed only for use by package
+// tests.
+func NewEoObject() unsafe.Pointer { return newEoObject() }
diff --git a/efl/object.go b/efl/object.go
new file mode 100644
index 0000000..1498788
--- /dev/null
+++ b/efl/object.go
@@ -0,0 +1,117 @@
+package efl
+
+/*
+#cgo pkg-config: elementary
+
+#include <Elementary.h>
+
+// _ego_efl_ref increases the reference count of obj and returns obj.
+// efl_ref is a real EO_API function, not a macro.
+static Eo *_ego_efl_ref(Eo *obj) {
+    return efl_ref(obj);
+}
+
+// _ego_efl_unref decreases the reference count of obj.
+// efl_unref is a real EO_API function, not a macro.
+static void _ego_efl_unref(Eo *obj) {
+    efl_unref(obj);
+}
+
+// _ego_new_object creates a loop timer Eo object under the main loop.
+// EFL_OBJECT_CLASS is abstract and cannot be directly instantiated; a loop
+// timer is the lightest concrete Eo type available without a display context.
+// efl_add_ref is used (macro wrapping _efl_add_common) so the caller owns the
+// extra reference and must call efl_unref when done.
+static Eo *_ego_new_object(void) {
+    return efl_add_ref(EFL_LOOP_TIMER_CLASS, efl_main_loop_get(),
+                       efl_loop_timer_interval_set(efl_added, 999.0));
+}
+*/
+import "C"
+
+import (
+	"runtime"
+	"unsafe"
+)
+
+// Object is a Go wrapper around an EFL Eo object pointer. It holds a raw
+// unsafe.Pointer rather than a typed C pointer so that higher-level packages
+// can embed or alias it without importing cgo themselves.
+//
+// All methods that touch the underlying Eo* — Ref, Unref — must be called on
+// the EFL thread (i.e. inside a Post or Sync callback). WrapObject installs a
+// GC finalizer that posts an Unref automatically.
+type Object struct {
+	ptr unsafe.Pointer // Eo*; nil means no underlying object
+}
+
+// WrapObject creates an Object that wraps the given raw Eo pointer. It returns
+// nil when ptr is nil. For non-nil pointers it registers a finalizer that posts
+// an Unref to the EFL thread if the Object is garbage-collected without an
+// explicit Unref, acting as a safety net against leaks.
+func WrapObject(ptr unsafe.Pointer) *Object {
+	if ptr == nil {
+		return nil
+	}
+	o := &Object{ptr: ptr}
+	runtime.SetFinalizer(o, func(obj *Object) {
+		// Post the Unref so it runs on the EFL thread. The closed guard inside
+		// Post means this is a no-op after Shutdown — acceptable because the EFL
+		// runtime itself cleans up remaining objects during elm_shutdown.
+		p := obj.ptr
+		Post(func() {
+			C._ego_efl_unref((*C.Eo)(p))
+		})
+	})
+	return o
+}
+
+// Ptr returns the underlying raw Eo pointer. It is nil-safe: calling Ptr on a
+// nil *Object returns nil.
+func (o *Object) Ptr() unsafe.Pointer {
+	if o == nil {
+		return nil
+	}
+	return o.ptr
+}
+
+// IsNil reports whether the Object has no underlying Eo pointer. It is
+// nil-safe: calling IsNil on a nil *Object returns true.
+func (o *Object) IsNil() bool {
+	if o == nil {
+		return true
+	}
+	return o.ptr == nil
+}
+
+// Ref increments the reference count of the underlying Eo object.
+// Must be called on the EFL thread.
+func (o *Object) Ref() {
+	if o == nil || o.ptr == nil {
+		return
+	}
+	C._ego_efl_ref((*C.Eo)(o.ptr))
+}
+
+// Unref decrements the reference count of the underlying Eo object and
+// disarms the GC finalizer so the finalizer cannot issue a second Unref.
+// Must be called on the EFL thread.
+func (o *Object) Unref() {
+	if o == nil || o.ptr == nil {
+		return
+	}
+	// Disarm the finalizer before touching ptr so that even if the GC runs
+	// concurrently (it observes the finalizer being cleared) it cannot queue
+	// a second Unref for the same pointer.
+	runtime.SetFinalizer(o, nil)
+	p := o.ptr
+	o.ptr = nil
+	C._ego_efl_unref((*C.Eo)(p))
+}
+
+// newEoObject allocates a loop-timer Eo instance under the main loop.
+// Must be called on the EFL thread. The caller is responsible for calling
+// Unref when done with the object.
+func newEoObject() unsafe.Pointer {
+	return unsafe.Pointer(C._ego_new_object())
+}
diff --git a/efl/object_test.go b/efl/object_test.go
new file mode 100644
index 0000000..43d26ac
--- /dev/null
+++ b/efl/object_test.go
@@ -0,0 +1,69 @@
+package efl_test
+
+import (
+	"testing"
+
+	"git.enlightenment.org/cedric/ego/efl"
+)
+
+// TestObjectNil verifies that both a zero-value Object and a nil *Object
+// pointer report IsNil()==true and Ptr()==nil without panicking.
+func TestObjectNil(t *testing.T) {
+	t.Run("zero value", func(t *testing.T) {
+		var o efl.Object
+		if !o.IsNil() {
+			t.Fatal("zero Object: IsNil() returned false, want true")
+		}
+		if o.Ptr() != nil {
+			t.Fatal("zero Object: Ptr() returned non-nil, want nil")
+		}
+	})
+
+	t.Run("nil pointer receiver", func(t *testing.T) {
+		var op *efl.Object
+		if !op.IsNil() {
+			t.Fatal("nil *Object: IsNil() returned false, want true")
+		}
+		if op.Ptr() != nil {
+			t.Fatal("nil *Object: Ptr() returned non-nil, want nil")
+		}
+	})
+}
+
+// TestWrapObjectNil verifies that WrapObject(nil) returns a nil *Object.
+func TestWrapObjectNil(t *testing.T) {
+	got := efl.WrapObject(nil)
+	if got != nil {
+		t.Fatalf("WrapObject(nil) = %v, want nil", got)
+	}
+}
+
+// TestWrapObject verifies that wrapping a real Eo object produces a non-nil
+// *Object whose Ptr() is non-nil and IsNil() returns false. The Eo object is
+// allocated and freed on the EFL thread via Sync.
+func TestWrapObject(t *testing.T) {
+	var o *efl.Object
+
+	efl.Sync(func() {
+		raw := efl.NewEoObject()
+		if raw == nil {
+			return // handled below
+		}
+		o = efl.WrapObject(raw)
+	})
+
+	if o == nil {
+		t.Fatal("WrapObject returned nil for a live Eo object")
+	}
+	if o.IsNil() {
+		t.Fatal("wrapped Object: IsNil() returned true, want false")
+	}
+	if o.Ptr() == nil {
+		t.Fatal("wrapped Object: Ptr() returned nil, want non-nil")
+	}
+
+	// Release the object on the EFL thread.
+	efl.Sync(func() {
+		o.Unref()
+	})
+}

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

Reply via email to