1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-09-12 05:06:28 +00:00
arikawa/utils/handler/handler.go

270 lines
6.6 KiB
Go

// 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 167 ns/op for roughly each handler. Scaling
// that up to 100 handlers is roughly the same as multiplying 167 ns by 100,
// which gives 16700 ns or 0.0167 ms.
//
// BenchmarkReflect-8 7260909 167 ns/op
//
// Usage
//
// Handler's usage is mostly similar to Discordgo, in that AddHandler expects a
// function with only one argument or an event channel. For more information,
// refer to AddHandler.
package handler
import (
"context"
"fmt"
"reflect"
"sync"
"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
mutex sync.RWMutex
slab slab
}
func New() *Handler {
return &Handler{}
}
// Call calls all handlers with the given event. This is an internal method; use
// with care.
func (h *Handler) Call(ev interface{}) {
var evV = reflect.ValueOf(ev)
var evT = evV.Type()
h.mutex.RLock()
defer h.mutex.RUnlock()
for _, entry := range h.slab.Entries {
if entry.isInvalid() || entry.not(evT) {
continue
}
if h.Synchronous {
entry.call(evV)
} else {
go entry.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.
//
// This method is more intended to be used as a filter. For a persistent event
// channel, consider adding it directly as a handler with AddHandler.
func (h *Handler) ChanFor(fn func(interface{}) bool) (out <-chan interface{}, cancel func()) {
result := make(chan interface{})
closer := make(chan struct{})
removeHandler := h.AddHandler(func(v interface{}) {
if fn(v) {
select {
case result <- v:
case <-closer:
}
}
})
// Only allow cancel to be called once.
var once sync.Once
cancel = func() {
once.Do(func() {
removeHandler()
close(closer)
})
}
out = result
return
}
// AddHandler adds the handler, returning a function that would remove this
// handler when called. A handler type is either a single-argument no-return
// function or a channel.
//
// Function
//
// A handler can be a function with a single argument that is the expected event
// type. It must not have any returns or any other number of arguments.
//
// // An example of a valid function handler.
// h.AddHandler(func(*gateway.MessageCreateEvent) {})
//
// Channel
//
// A handler can also be a channel. The underlying type that the channel wraps
// around will be the event type. As such, the type rules are the same as
// function handlers.
//
// Keep in mind that the user must NOT close the channel. In fact, the channel
// should not be closed at all. The caller function WILL PANIC if the channel is
// closed!
//
// When the rm callback that is returned is called, it will also guarantee that
// all blocking sends will be cancelled. This helps prevent dangling goroutines.
//
// // An example of a valid channel handler.
// ch := make(chan *gateway.MessageCreateEvent)
// h.AddHandler(ch)
//
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. Refer to AddHandler for more information.
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)
}
func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
// Reflect the handler
r, err := newHandler(fn)
if err != nil {
return nil, errors.Wrap(err, "handler reflect failed")
}
h.mutex.Lock()
id := h.slab.Put(r)
h.mutex.Unlock()
return func() {
h.mutex.Lock()
popped := h.slab.Pop(id)
h.mutex.Unlock()
popped.cleanup()
}, nil
}
type handler struct {
event reflect.Type // underlying type; arg0 or chan underlying type
callback reflect.Value
isIface bool
chanclose reflect.Value // IsValid() if chan
}
// newHandler reflects either a channel or a function into a handler. A function
// must only have a single argument being the event and no return, and a channel
// must have the event type as the underlying type.
func newHandler(unknown interface{}) (handler, error) {
fnV := reflect.ValueOf(unknown)
fnT := fnV.Type()
// underlying event type
var handler = handler{
callback: fnV,
}
switch fnT.Kind() {
case reflect.Func:
if fnT.NumIn() != 1 {
return handler, errors.New("function can only accept 1 event as argument")
}
if fnT.NumOut() > 0 {
return handler, errors.New("function can't accept returns")
}
handler.event = fnT.In(0)
case reflect.Chan:
handler.event = fnT.Elem()
handler.chanclose = reflect.ValueOf(make(chan struct{}))
default:
return handler, errors.New("given interface is not a function or channel")
}
var kind = handler.event.Kind()
// Accept either pointer type or interface{} type
if kind != reflect.Ptr && kind != reflect.Interface {
return handler, errors.New("first argument is not pointer")
}
handler.isIface = kind == reflect.Interface
return handler, 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) {
if h.chanclose.IsValid() {
reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectSend, Chan: h.callback, Send: event},
{Dir: reflect.SelectRecv, Chan: h.chanclose},
})
} else {
h.callback.Call([]reflect.Value{event})
}
}
func (h handler) cleanup() {
if h.chanclose.IsValid() {
// Closing this channel will force all ongoing selects to return
// immediately.
h.chanclose.Close()
}
}