1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-15 19:33:17 +00:00
arikawa/handler/handler.go

246 lines
5.3 KiB
Go
Raw Normal View History

// Package handler handles incoming Gateway events. It reflects the function's
// first argument and caches that for use in each event.
//
// Performance
//
// Each call to the event would take 156 ns/op for roughly each handler. Scaling
// that up to 100 handlers is multiplying 156 ns by 100, which gives 15600 ns,
// or 0.0156 ms.
//
// BenchmarkReflect-8 7260909 156 ns/op
//
// Usage
//
2020-01-18 07:45:44 +00:00
// Handler's usage is similar to discordgo, in that AddHandler expects a
// function with only one argument. The only argument must be a pointer to one
// of the events, or an interface{} which would accept all events.
//
// AddHandler would panic if the handler is invalid.
//
// s.AddHandler(func(m *gateway.MessageCreateEvent) {
// log.Println(m.Author.Username, "said", m.Content)
// })
//
2020-01-17 05:17:46 +00:00
package handler
import (
"context"
2020-01-17 05:17:46 +00:00
"fmt"
"reflect"
"sync"
"github.com/pkg/errors"
)
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
2020-01-25 05:24:33 +00:00
horders []uint64
2020-01-17 05:17:46 +00:00
hserial uint64
2020-02-05 04:30:17 +00:00
hmutex sync.RWMutex
2020-01-17 05:17:46 +00:00
}
func New() *Handler {
return &Handler{
handlers: map[uint64]handler{},
}
}
func (h *Handler) Call(ev interface{}) {
var evV = reflect.ValueOf(ev)
var evT = evV.Type()
2020-02-05 04:30:17 +00:00
h.hmutex.RLock()
defer h.hmutex.RUnlock()
2020-01-17 05:17:46 +00:00
2020-01-25 05:24:33 +00:00
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
}
2020-01-17 05:17:46 +00:00
if handler.not(evT) {
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.
func (h *Handler) WaitFor(ctx context.Context, fn func(interface{}) bool) interface{} {
var result = make(chan interface{})
cancel := h.AddHandler(func(v interface{}) {
if fn(v) {
result <- v
}
})
defer cancel()
select {
case r := <-result:
return r
case <-ctx.Done():
return nil
}
}
// ChanFor returns a channel that would receive all incoming events that match
// the callback given. The cancel() function removes the handler and drops all
// hanging goroutines.
func (h *Handler) ChanFor(fn func(interface{}) bool) (out <-chan interface{}, cancel func()) {
result := make(chan interface{})
closer := make(chan struct{})
2020-02-05 04:30:17 +00:00
removeHandler := h.AddHandler(func(v interface{}) {
2020-02-05 04:30:17 +00:00
if fn(v) {
select {
case result <- v:
case <-closer:
}
2020-02-05 04:30:17 +00:00
}
})
// Only allow cancel to be called once.
var once sync.Once
cancel = func() {
once.Do(func() {
removeHandler()
close(closer)
})
}
out = result
2020-02-05 04:30:17 +00:00
return
2020-02-05 04:30:17 +00:00
}
// AddHandler adds the handler, returning a function that would remove this
// handler when called.
2020-01-17 05:17:46 +00:00
func (h *Handler) AddHandler(handler interface{}) (rm func()) {
rm, err := h.addHandler(handler)
if err != nil {
panic(err)
}
return rm
}
// AddHandlerCheck adds the handler, but safe-guards reflect panics with a
// recoverer, returning the error.
func (h *Handler) AddHandlerCheck(handler interface{}) (rm func(), err error) {
// Reflect would actually panic if anything goes wrong, so this is just in
// case.
defer func() {
if rec := recover(); rec != nil {
if recErr, ok := rec.(error); ok {
err = recErr
} else {
err = fmt.Errorf("%v", rec)
}
}
}()
return h.addHandler(handler)
}
2020-02-05 04:30:17 +00:00
func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
2020-01-17 05:17:46 +00:00
// Reflect the handler
2020-02-05 04:30:17 +00:00
r, err := reflectFn(fn)
2020-01-17 05:17:46 +00:00
if err != nil {
return nil, errors.Wrap(err, "Handler reflect failed")
}
h.hmutex.Lock()
defer h.hmutex.Unlock()
2020-01-25 05:24:33 +00:00
// Get the current counter value and increment the counter:
2020-01-17 05:17:46 +00:00
serial := h.hserial
h.hserial++
2020-02-05 04:30:17 +00:00
// Create a map if there's none:
if h.handlers == nil {
h.handlers = map[uint64]handler{}
}
2020-01-25 05:24:33 +00:00
// Use the serial for the map:
2020-01-17 05:17:46 +00:00
h.handlers[serial] = *r
2020-01-25 05:24:33 +00:00
// Append the serial into the list of keys:
h.horders = append(h.horders, serial)
2020-01-17 05:17:46 +00:00
return func() {
h.hmutex.Lock()
defer h.hmutex.Unlock()
2020-01-25 05:24:33 +00:00
// Delete the handler from the map:
2020-01-17 05:17:46 +00:00
delete(h.handlers, serial)
2020-01-25 05:24:33 +00:00
// 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
}
}
2020-01-17 05:17:46 +00:00
}, nil
}
type handler struct {
event reflect.Type
callback reflect.Value
isIface bool
}
func reflectFn(function interface{}) (*handler, error) {
fnV := reflect.ValueOf(function)
fnT := fnV.Type()
if fnT.Kind() != reflect.Func {
return nil, errors.New("given interface is not a function")
}
if fnT.NumIn() != 1 {
return nil, errors.New("function can only accept 1 event as argument")
}
2020-02-05 04:30:17 +00:00
if fnT.NumOut() > 0 {
return nil, errors.New("function can't accept returns")
}
2020-01-17 05:17:46 +00:00
argT := fnT.In(0)
kind := argT.Kind()
// Accept either pointer type or interface{} type
if kind != reflect.Ptr && kind != reflect.Interface {
return nil, errors.New("first argument is not pointer")
}
return &handler{
event: argT,
callback: fnV,
isIface: kind == reflect.Interface,
}, nil
}
func (h handler) not(event reflect.Type) bool {
if h.isIface {
return !event.Implements(h.event)
}
return h.event != event
}
func (h handler) call(event reflect.Value) {
h.callback.Call([]reflect.Value{event})
}