1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-10-02 23:58:52 +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
import (
"container/list"
"context"
"fmt"
"reflect"
@ -25,21 +26,19 @@ import (
"github.com/pkg/errors"
)
// Handler is a container for command handlers. A zero-value instance is a valid
// instance.
type Handler struct {
// Synchronous controls whether to spawn each event handler in its own
// goroutine. Default false (meaning goroutines are spawned).
Synchronous bool
handlers map[uint64]handler
horders []uint64
hserial uint64
hmutex sync.RWMutex
mutex sync.RWMutex
list list.List
}
func New() *Handler {
return &Handler{
handlers: map[uint64]handler{},
}
return &Handler{}
}
// 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 evT = evV.Type()
h.hmutex.RLock()
defer h.hmutex.RUnlock()
h.mutex.RLock()
defer h.mutex.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
}
for elem := h.list.Front(); elem != nil; elem = elem.Next() {
handler := elem.Value.(handler)
if handler.not(evT) {
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,
// as WaitFor may skip some events if it's not ran fast enough after the event
// arrived.
@ -213,47 +180,18 @@ func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
return nil, errors.Wrap(err, "handler reflect failed")
}
h.hmutex.Lock()
defer h.hmutex.Unlock()
// 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)
h.mutex.RLock()
elem := h.list.PushBack(*r)
h.mutex.RUnlock()
return func() {
h.hmutex.Lock()
defer h.hmutex.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
}
}
h.mutex.Lock()
v := h.list.Remove(elem)
h.mutex.Unlock()
// Clean up the handler.
hd.cleanup()
handler := v.(handler)
handler.cleanup()
}, nil
}