vitess-gh/go/event/event.go

169 строки
4.7 KiB
Go

/*
Copyright 2019 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package event provides a reflect-based framework for low-frequency global
dispatching of events, which are values of any arbitrary type, to a set of
listener functions, which are usually registered by plugin packages during init().
Listeners should do work in a separate goroutine if it might block. Dispatch
should be called synchronously to make sure work enters the listener's work
queue before moving on. After Dispatch returns, the listener is responsible for
arranging to flush its work queue before program termination if desired.
For example, any package can define an event type:
package mypackage
type MyEvent struct {
field1, field2 string
}
Then, any other package (e.g. a plugin) can listen for those events:
package myplugin
import (
"event"
"mypackage"
)
func onMyEvent(ev mypackage.MyEvent) {
// do something with ev
}
func init() {
event.AddListener(onMyEvent)
}
Any registered listeners that accept a single argument of type MyEvent will
be called when a value of type MyEvent is dispatched:
package myotherpackage
import (
"event"
"mypackage"
)
func InMediasRes() {
ev := mypackage.MyEvent{
field1: "foo",
field2: "bar",
}
event.Dispatch(ev)
}
In addition, listener functions that accept an interface type will be called
for any dispatched value that implements the specified interface. A listener
that accepts `any` will be called for every event type. Listeners can also
accept pointer types, but they will only be called if the dispatch site calls
Dispatch() on a pointer.
*/
package event
import (
"fmt"
"reflect"
"sync"
)
var (
listenersMutex sync.RWMutex // protects listeners and interfaces
listeners = make(map[reflect.Type][]any)
interfaces = make([]reflect.Type, 0)
)
// BadListenerError is raised via panic() when AddListener is called with an
// invalid listener function.
type BadListenerError string
func (why BadListenerError) Error() string {
return fmt.Sprintf("bad listener func: %s", string(why))
}
// AddListener registers a listener function that will be called when a matching
// event is dispatched. The type of the function's first (and only) argument
// declares the event type (or interface) to listen for.
func AddListener(fn any) {
listenersMutex.Lock()
defer listenersMutex.Unlock()
fnType := reflect.TypeOf(fn)
// check that the function type is what we think: # of inputs/outputs, etc.
// panic if conditions not met (because it's a programming error to have that happen)
switch {
case fnType.Kind() != reflect.Func:
panic(BadListenerError("listener must be a function"))
case fnType.NumIn() != 1:
panic(BadListenerError("listener must take exactly one input argument"))
}
// the first input parameter is the event
evType := fnType.In(0)
// keep a list of listeners for each event type
listeners[evType] = append(listeners[evType], fn)
// if eventType is an interface, store it in a separate list
// so we can check non-interface objects against all interfaces
if evType.Kind() == reflect.Interface {
interfaces = append(interfaces, evType)
}
}
// Dispatch sends an event to all registered listeners that were declared
// to accept values of the event's type, or interfaces that the value implements.
func Dispatch(ev any) {
listenersMutex.RLock()
defer listenersMutex.RUnlock()
evType := reflect.TypeOf(ev)
vals := []reflect.Value{reflect.ValueOf(ev)}
// call listeners for the actual static type
callListeners(evType, vals)
// also check if the type implements any of the registered interfaces
for _, in := range interfaces {
if evType.Implements(in) {
callListeners(in, vals)
}
}
}
func callListeners(t reflect.Type, vals []reflect.Value) {
for _, fn := range listeners[t] {
reflect.ValueOf(fn).Call(vals)
}
}
// Updater is an interface that events can implement to combine updating and
// dispatching into one call.
type Updater interface {
// Update is called by DispatchUpdate() before the event is dispatched.
Update(update any)
}
// DispatchUpdate calls Update() on the event and then dispatches it. This is a
// shortcut for combining updates and dispatches into a single call.
func DispatchUpdate(ev Updater, update any) {
ev.Update(update)
Dispatch(ev)
}