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) { | |||
| 36 | 36 | if strings.ToLower(ns) == "gobject" { | |
| 37 | 37 | gobjectNs = "" | |
| 38 | 38 | } | |
| 39 | + | glibNs := "glib." | |
| 40 | + | if strings.ToLower(ns) == "glib" { | |
| 41 | + | glibNs = "" | |
| 42 | + | } | |
| 39 | 43 | switch k { | |
| 40 | 44 | case CallbackType: | |
| 41 | - | c = fmt.Sprintf("purego.NewCallback(%s)", n) | |
| 45 | + | c = fmt.Sprintf("%sNewCallback(%s)", glibNs, n) | |
| 42 | 46 | case ClassesType: | |
| 43 | 47 | if stars == 0 { | |
| 44 | 48 | 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 = n | 48 | c = n | ||
M templates/glib +53 -5
| @@ -1,14 +1,62 @@ | |||
| 1 | 1 | package glib | |
| 2 | 2 | ||
| 3 | 3 | 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" | |
| 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 | 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 | 60 | 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 glib | 1 | 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) { | |||
| 185 | 185 | ||
| 186 | 186 | {{.Doc}} | |
| 187 | 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}} { | |
| 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) | |
| 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 | 208 | {{end}} | |
| 203 | 209 | ||
| @@ -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 = clsPtr | 190 | + | 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.Ptr | 193 | + | 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 ( | |||
| 4 | 4 | "reflect" | |
| 5 | 5 | ||
| 6 | 6 | "github.com/jwijenbergh/purego" | |
| 7 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |
| 7 | 8 | ) | |
| 8 | 9 | ||
| 9 | 10 | type Ptr interface { | |
| @@ -46,7 +47,7 @@ func (o Object) DisconnectSignal(handler uint32) { | |||
| 46 | 47 | ||
| 47 | 48 | // NewCallback is an alias to purego.NewCallback | |
| 48 | 49 | func NewCallback(fn interface{}) uintptr { | |
| 49 | - | return purego.NewCallback(fn) | |
| 50 | + | return glib.NewCallback(fn) | |
| 50 | 51 | } | |
| 51 | 52 | ||
| 52 | 53 | // 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.NewCallback | 48 | // 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 | // types | 53 | // types | ||