1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-10-03 08:08:47 +00:00

Handler: Changed to a linked list instead of a slice-backed map

This change should slightly improve the performance of the handler
container.

A rough benchmark was written and tested; the source code is at
https://gist.github.com/diamondburned/c369d13efda5c702a0e59874deee64bd.
This commit is contained in:
diamondburned 2020-10-28 22:37:38 -07:00
parent b7b8118d0b
commit ef48d686cd

View file

@ -17,6 +17,7 @@
package handler package handler
import ( import (
"container/list"
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
@ -25,21 +26,19 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Handler is a container for command handlers. A zero-value instance is a valid
// instance.
type Handler struct { type Handler struct {
// Synchronous controls whether to spawn each event handler in its own // Synchronous controls whether to spawn each event handler in its own
// goroutine. Default false (meaning goroutines are spawned). // goroutine. Default false (meaning goroutines are spawned).
Synchronous bool Synchronous bool
handlers map[uint64]handler mutex sync.RWMutex
horders []uint64 list list.List
hserial uint64
hmutex sync.RWMutex
} }
func New() *Handler { func New() *Handler {
return &Handler{ return &Handler{}
handlers: map[uint64]handler{},
}
} }
// Call calls all handlers with the given event. This is an internal method; use // Call calls all handlers with the given event. This is an internal method; use
@ -48,15 +47,11 @@ func (h *Handler) Call(ev interface{}) {
var evV = reflect.ValueOf(ev) var evV = reflect.ValueOf(ev)
var evT = evV.Type() var evT = evV.Type()
h.hmutex.RLock() h.mutex.RLock()
defer h.hmutex.RUnlock() defer h.mutex.RUnlock()
for _, order := range h.horders { for elem := h.list.Front(); elem != nil; elem = elem.Next() {
handler, ok := h.handlers[order] handler := elem.Value.(handler)
if !ok {
// This shouldn't ever happen, but we're adding this just in case.
continue
}
if handler.not(evT) { if handler.not(evT) {
continue continue
@ -70,34 +65,6 @@ func (h *Handler) Call(ev interface{}) {
} }
} }
// CallDirect is the same as Call, but only calls those event handlers that
// listen for this specific event, i.e. that aren't interface handlers.
func (h *Handler) CallDirect(ev interface{}) {
var evV = reflect.ValueOf(ev)
var evT = evV.Type()
h.hmutex.RLock()
defer h.hmutex.RUnlock()
for _, order := range h.horders {
handler, ok := h.handlers[order]
if !ok {
// This shouldn't ever happen, but we're adding this just in case.
continue
}
if evT != handler.event {
continue
}
if h.Synchronous {
handler.call(evV)
} else {
go handler.call(evV)
}
}
}
// WaitFor blocks until there's an event. It's advised to use ChanFor instead, // WaitFor blocks until there's an event. It's advised to use ChanFor instead,
// as WaitFor may skip some events if it's not ran fast enough after the event // as WaitFor may skip some events if it's not ran fast enough after the event
// arrived. // arrived.
@ -213,47 +180,18 @@ func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
return nil, errors.Wrap(err, "handler reflect failed") return nil, errors.Wrap(err, "handler reflect failed")
} }
h.hmutex.Lock() h.mutex.RLock()
defer h.hmutex.Unlock() elem := h.list.PushBack(*r)
h.mutex.RUnlock()
// Get the current counter value and increment the counter:
serial := h.hserial
h.hserial++
// Create a map if there's none:
if h.handlers == nil {
h.handlers = map[uint64]handler{}
}
// Use the serial for the map:
h.handlers[serial] = *r
// Append the serial into the list of keys:
h.horders = append(h.horders, serial)
return func() { return func() {
h.hmutex.Lock() h.mutex.Lock()
defer h.hmutex.Unlock() v := h.list.Remove(elem)
h.mutex.Unlock()
// Take and delete the handler from the map, but return if we can't find
// it.
hd, ok := h.handlers[serial]
if !ok {
return
}
delete(h.handlers, serial)
// Delete the key from the orders slice:
for i, order := range h.horders {
if order == serial {
h.horders = append(h.horders[:i], h.horders[i+1:]...)
break
}
}
// Clean up the handler. // Clean up the handler.
hd.cleanup() handler := v.(handler)
handler.cleanup()
}, nil }, nil
} }