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.