forked from vitessio/vitess
-
Notifications
You must be signed in to change notification settings - Fork 1
/
event.go
168 lines (132 loc) · 4.75 KB
/
event.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
Copyright 2017 Google Inc.
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 interface{} 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][]interface{})
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 interface{}) {
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 interface{}) {
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 interface{})
}
// 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 interface{}) {
ev.Update(update)
Dispatch(ev)
}