Currently, go-wayland-scanner
generates a piece of code that uses reflect
to
get the pointer of a Go closure to compare its value with existing closures
added inside a registry, like so:
https://github.com/rajveermalviya/go-wayland/blob/5ab8266fceaf345964f7f5aafb37da467149d867/cmd/go-wayland-scanner/scanner.go#L624
This piece of code depends on an implementation detail that may not necessarily
be correct in all cases, which is why Go doesn't allow comparing closures in the
first place.
To fix this, I propose adding a new
github.com/rajveermalviya/go-wayland/wayland/utils/handler
package that
contains specific structures to hold closures and allow removing them using a
callback.
Package handler
should expose a public API that looks roughly like this
pseudocode:
package handler
// Registry implements a registry of function handlers. All its methods are
// concurrently safe.
type Registry struct {
mu sync.Mutex
// any implementation
}
// Add adds the given function value into the registry and returns a function
// that removes the given function from the registry.
func (r *Registry) Add(fn interface{}) (remove func())
The generated code could then look like this:
// AddConfigureHandler : adds handler for SurfaceConfigureEvent
func (i *Surface) AddConfigureHandler(f SurfaceConfigureHandlerFunc) (remove func()) {
if f == nil {
return
}
return i.configureHandlers.Add(f)
}
Package handler
can be implemented in multiple ways. One way would be to wrap
the closure inside a boxed struct and use its pointer as the key to a map:
type Registry struct {
mu sync.Mutex
h map[*box]struct{}
}
type box struct {
v interface{}
}
func (r *Registry) Add(fn interface{}) func() {
v := &box{fn}
r.mu.Lock()
r.h[v] = struct{}{}
r.mu.Unlock()
return func() {
r.mu.Lock()
delete(r.h, v)
r.mu.Unlock()
}
}
While this is the simplest way to implement handler
, it might be fairly costly
on the garbage collector, since an extra heap pointer will be made for each
closure added, and if the user removes closures a lot, the garbage collector
will likely spend a lot of time catching up to free the boxes.
Another way that is slightly more complicated would be to use a free list, like
what I have in diamondburned/arikawa/v3/utils/handler.