detached cd00a46

Core: Allow callbacks to be re-used and unreferenced

Maintain a local callback cache and purego wrappers to support wrapped
callbacks.

Move callback handling to glib to avoid import cycles since gobject
already depends on glib.
commit
cd00a466270f9e1c4fe59210b560f3906c817f9b
author
Peter Fern · · 2024-01-03 02:12 UTC
committer
Jeroen Wijenbergh · · 2024-03-05 11:56 UTC
parent
888497b
signature
unsigned
4 files · +78 -19
M internal/gir/types/template.go +5 -1
@@ -36,9 +36,13 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string) {
3636 if strings.ToLower(ns) == "gobject" {
3737 gobjectNs = ""
3838 }
39+ glibNs := "glib."
40+ if strings.ToLower(ns) == "glib" {
41+ glibNs = ""
42+ }
3943 switch k {
4044 case CallbackType:
41- c = fmt.Sprintf("purego.NewCallback(%s)", n)
45+ c = fmt.Sprintf("%sNewCallback(%s)", glibNs, n)
4246 case ClassesType:
4347 if stars == 0 {
4448 c = n
@@ -36,9 +36,13 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string) {
36 if strings.ToLower(ns) == "gobject" {36 if strings.ToLower(ns) == "gobject" {
37 gobjectNs = ""37 gobjectNs = ""
38 }38 }
39+ glibNs := "glib."
40+ if strings.ToLower(ns) == "glib" {
41+ glibNs = ""
42+ }
39 switch k {43 switch k {
40 case CallbackType:44 case CallbackType:
41- c = fmt.Sprintf("purego.NewCallback(%s)", n)45+ c = fmt.Sprintf("%sNewCallback(%s)", glibNs, n)
42 case ClassesType:46 case ClassesType:
43 if stars == 0 {47 if stars == 0 {
44 c = n48 c = n
M templates/glib +53 -5
@@ -1,14 +1,62 @@
11 package glib
22
33 import (
4- "fmt"
5- "github.com/jwijenbergh/puregotk/internal/core"
4+ "fmt"
5+ "reflect"
6+ "sync"
7+
8+ "github.com/jwijenbergh/purego"
9+ "github.com/jwijenbergh/puregotk/internal/core"
610 )
711
12+var callbacks = struct {
13+ sync.RWMutex
14+ refs map[uintptr]uintptr
15+}{
16+ refs: make(map[uintptr]uintptr),
17+}
18+
19+// GetCallback retrives a callback reference by value.
20+// Users should not need to call this.
21+func GetCallback(cbPtr uintptr) (uintptr, bool) {
22+ callbacks.RLock()
23+ defer callbacks.RUnlock()
24+ refPtr, ok := callbacks.refs[cbPtr]
25+ return refPtr, ok
26+}
27+
28+// SaveCallback saves a reference to the callback value.
29+// Users should not need to call this.
30+func SaveCallback(cbPtr uintptr, refPtr uintptr) {
31+ callbacks.Lock()
32+ callbacks.refs[cbPtr] = refPtr
33+ callbacks.Unlock()
34+}
35+
36+// UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot
37+func UnrefCallback(fn interface{}) error {
38+ cbPtr := reflect.ValueOf(fn).Pointer()
39+ refPtr, ok := GetCallback(cbPtr)
40+ if !ok {
41+ return purego.UnrefCallback(fn)
42+ }
43+ defer func() {
44+ callbacks.Lock()
45+ delete(callbacks.refs, cbPtr)
46+ callbacks.Unlock()
47+ }()
48+ return purego.UnrefCallbackPtr(refPtr)
49+}
50+
51+// NewCallback is an alias to purego.NewCallback
52+func NewCallback(fn interface{}) uintptr {
53+ return purego.NewCallback(fn)
54+}
55+
856 func (e *Error) Error() string {
9- return fmt.Sprintf("Gtk reported an error with message: '%s', domain: '%v' and code: '%v'", e.MessageGo(), e.Domain, e.Code)
57+ return fmt.Sprintf("Gtk reported an error with message: '%s', domain: '%v' and code: '%v'", e.MessageGo(), e.Domain, e.Code)
1058 }
1159
1260 func (e *Error) MessageGo() string {
13- return core.GoString(e.Message)
14-}⏎̸
61+ return core.GoString(e.Message)
62+}
@@ -1,14 +1,62 @@
1 package glib1 package glib
2 2
3 import (3 import (
4- "fmt"4+ "fmt"
5- "github.com/jwijenbergh/puregotk/internal/core"5+ "reflect"
6+ "sync"
7+
8+ "github.com/jwijenbergh/purego"
9+ "github.com/jwijenbergh/puregotk/internal/core"
6 )10 )
7 11
12+var callbacks = struct {
13+ sync.RWMutex
14+ refs map[uintptr]uintptr
15+}{
16+ refs: make(map[uintptr]uintptr),
17+}
18+
19+// GetCallback retrives a callback reference by value.
20+// Users should not need to call this.
21+func GetCallback(cbPtr uintptr) (uintptr, bool) {
22+ callbacks.RLock()
23+ defer callbacks.RUnlock()
24+ refPtr, ok := callbacks.refs[cbPtr]
25+ return refPtr, ok
26+}
27+
28+// SaveCallback saves a reference to the callback value.
29+// Users should not need to call this.
30+func SaveCallback(cbPtr uintptr, refPtr uintptr) {
31+ callbacks.Lock()
32+ callbacks.refs[cbPtr] = refPtr
33+ callbacks.Unlock()
34+}
35+
36+// UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot
37+func UnrefCallback(fn interface{}) error {
38+ cbPtr := reflect.ValueOf(fn).Pointer()
39+ refPtr, ok := GetCallback(cbPtr)
40+ if !ok {
41+ return purego.UnrefCallback(fn)
42+ }
43+ defer func() {
44+ callbacks.Lock()
45+ delete(callbacks.refs, cbPtr)
46+ callbacks.Unlock()
47+ }()
48+ return purego.UnrefCallbackPtr(refPtr)
49+}
50+
51+// NewCallback is an alias to purego.NewCallback
52+func NewCallback(fn interface{}) uintptr {
53+ return purego.NewCallback(fn)
54+}
55+
8 func (e *Error) Error() string {56 func (e *Error) Error() string {
9- return fmt.Sprintf("Gtk reported an error with message: '%s', domain: '%v' and code: '%v'", e.MessageGo(), e.Domain, e.Code)57+ return fmt.Sprintf("Gtk reported an error with message: '%s', domain: '%v' and code: '%v'", e.MessageGo(), e.Domain, e.Code)
10 }58 }
11 59
12 func (e *Error) MessageGo() string {60 func (e *Error) MessageGo() string {
13- return core.GoString(e.Message)61+ return core.GoString(e.Message)
14-}⏎̸62+}
M templates/go +18 -12
@@ -185,19 +185,25 @@ func (c *{{.Name}}) SetGoPointer(ptr uintptr) {
185185
186186 {{.Doc}}
187187 func (x *{{$outer.Name}}) Connect{{.Name}}(cb func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 {
188- fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} {
189- fa := {{$outer.Name}}{}
190- fa.Ptr = clsPtr
191- {{if .Ret.Class}}
192- {{.Name}}Cls := cb(fa {{convc .Args.Pure.Call}})
193- return {{.Name}}Cls.Ptr
194- {{else if .Ret.Value}}
195- return cb(fa {{convc .Args.Pure.Call}})
196- {{else}}
197- cb(fa {{convc .Args.Pure.Call}})
198- {{end}}
188+ cbPtr := reflect.ValueOf(cb).Pointer()
189+ cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr)
190+ if !ok {
191+ fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} {
192+ fa := {{$outer.Name}}{}
193+ fa.Ptr = clsPtr
194+ {{if .Ret.Class}}
195+ {{.Name}}Cls := cb(fa {{convc .Args.Pure.Call}})
196+ return {{.Name}}Cls.Ptr
197+ {{else if .Ret.Value}}
198+ return cb(fa {{convc .Args.Pure.Call}})
199+ {{else}}
200+ cb(fa {{convc .Args.Pure.Call}})
201+ {{end}}
202+ }
203+ cbRefPtr = purego.NewCallback(fcb)
204+ {{if $NotGLib}}glib.{{end}}SaveCallback(cbPtr, cbRefPtr)
199205 }
200- return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", purego.NewCallback(fcb))
206+ return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr)
201207 }
202208 {{end}}
203209
@@ -185,19 +185,25 @@ func (c *{{.Name}}) SetGoPointer(ptr uintptr) {
185 185
186 {{.Doc}}186 {{.Doc}}
187 func (x *{{$outer.Name}}) Connect{{.Name}}(cb func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 {187 func (x *{{$outer.Name}}) Connect{{.Name}}(cb func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 {
188- fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} {188+ cbPtr := reflect.ValueOf(cb).Pointer()
189- fa := {{$outer.Name}}{}189+ cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr)
190- fa.Ptr = clsPtr190+ if !ok {
191- {{if .Ret.Class}}191+ fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} {
192- {{.Name}}Cls := cb(fa {{convc .Args.Pure.Call}})192+ fa := {{$outer.Name}}{}
193- return {{.Name}}Cls.Ptr193+ fa.Ptr = clsPtr
194- {{else if .Ret.Value}}194+ {{if .Ret.Class}}
195- return cb(fa {{convc .Args.Pure.Call}})195+ {{.Name}}Cls := cb(fa {{convc .Args.Pure.Call}})
196- {{else}}196+ return {{.Name}}Cls.Ptr
197- cb(fa {{convc .Args.Pure.Call}})197+ {{else if .Ret.Value}}
198- {{end}}198+ return cb(fa {{convc .Args.Pure.Call}})
199+ {{else}}
200+ cb(fa {{convc .Args.Pure.Call}})
201+ {{end}}
202+ }
203+ cbRefPtr = purego.NewCallback(fcb)
204+ {{if $NotGLib}}glib.{{end}}SaveCallback(cbPtr, cbRefPtr)
199 }205 }
200- return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", purego.NewCallback(fcb))206+ return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr)
201 }207 }
202 {{end}}208 {{end}}
203 209
M templates/gobject +2 -1
@@ -4,6 +4,7 @@ import (
44 "reflect"
55
66 "github.com/jwijenbergh/purego"
7+ "github.com/jwijenbergh/puregotk/v4/glib"
78 )
89
910 type Ptr interface {
@@ -46,7 +47,7 @@ func (o Object) DisconnectSignal(handler uint32) {
4647
4748 // NewCallback is an alias to purego.NewCallback
4849 func NewCallback(fn interface{}) uintptr {
49- return purego.NewCallback(fn)
50+ return glib.NewCallback(fn)
5051 }
5152
5253 // types
@@ -4,6 +4,7 @@ import (
4 "reflect"4 "reflect"
5 5
6 "github.com/jwijenbergh/purego"6 "github.com/jwijenbergh/purego"
7+ "github.com/jwijenbergh/puregotk/v4/glib"
7 )8 )
8 9
9 type Ptr interface {10 type Ptr interface {
@@ -46,7 +47,7 @@ func (o Object) DisconnectSignal(handler uint32) {
46 47
47 // NewCallback is an alias to purego.NewCallback48 // NewCallback is an alias to purego.NewCallback
48 func NewCallback(fn interface{}) uintptr {49 func NewCallback(fn interface{}) uintptr {
49- return purego.NewCallback(fn)50+ return glib.NewCallback(fn)
50 }51 }
51 52
52 // types53 // types

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close