-
Notifications
You must be signed in to change notification settings - Fork 0
/
signal.go
248 lines (221 loc) · 8.38 KB
/
signal.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Copyright (c) 2018, The GoKi Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ki
import (
"fmt"
"sync"
"github.com/goki/ki/kit"
)
// note: Started this code based on: github.com/tucnak/meta/
// NodeSignals are signals that a Ki node sends about updates to the tree
// structure using the NodeSignal (convert sig int64 to NodeSignals to get the
// stringer name).
type NodeSignals int64
// Standard signal types sent by ki.Node on its NodeSig for tree state changes
const (
// NodeSignalNil is a nil signal value
NodeSignalNil NodeSignals = iota
// NodeSignalUpdated indicates that the node was updated -- the node Flags
// accumulate the specific changes made since the last update signal --
// these flags are sent in the signal data -- strongly recommend using
// that instead of the flags, which can be subsequently updated by the
// time a signal is processed
NodeSignalUpdated
// NodeSignalDeleting indicates that the node is being deleted from its
// parent children list -- this is not blocked by Updating status and is
// delivered immediately
NodeSignalDeleting
// NodeSignalDestroying indicates that the node is about to be destroyed
// -- this is a second pass after removal from parent -- all of its
// children and Ki fields will be destroyed too -- not blocked by updating
// status and delivered immediately
NodeSignalDestroying
NodeSignalsN
)
//go:generate stringer -type=NodeSignals
// SignalTrace can be set to true to automatically print out a trace of the
// signals as they are sent
var SignalTrace bool = false
// SignalTraceString can be set to a string that will then accumulate the
// trace of signals sent, for use in testing -- otherwise the trace just goes
// to stdout
var SignalTraceString *string
// RecvFunc is a receiver function type for signals -- gets the full
// connection information and signal, data as specified by the sender. It is
// good practice to avoid closures in these functions, which can be numerous
// and have a long lifetime, by converting the recv, send into their known
// types and referring to them directly
type RecvFunc func(recv, send Ki, sig int64, data interface{})
// Signal implements general signal passing between Ki objects, like Qt's
// Signal / Slot system.
//
// This design pattern separates three different factors:
// * when to signal that something has happened
// * who should receive that signal
// * what should the receiver do in response to the signal
//
// Keeping these three things entirely separate greatly simplifies the overall
// logic.
//
// A receiver connects in advance to a given signal on a sender to receive its
// signals -- these connections are typically established in an initialization
// step. There can be multiple different signals on a given sender, and to
// make more efficient use of signal connections, the sender can also send an
// int64 signal value that further discriminates the nature of the event, as
// documented in the code associated with the sender (typically an enum is
// used). Furthermore, arbitrary data as an interface{} can be passed as
// well.
//
// The Signal uses a map indexed by the receiver pointer to hold the
// connections -- this means that there can only be one such connection per
// receiver, and the order of signal emission to different receiveres will be random.
//
// Typically an inline anonymous closure receiver function is used to keep all
// the relevant code in one place. Due to the typically long-standing nature
// of these connections, it is more efficient to avoid capturing external
// variables, and rely instead on appropriately interpreting the sent argument
// values. e.g.:
//
// send := sender.EmbeddedStruct(KiT_SendType).(*SendType)
//
// is guaranteed to result in a usable pointer to the sender of known type at
// least SendType, in a case where that sender might actually embed that
// SendType (otherwise if it is known to be of a given type, just directly
// converting as such is fine)
type Signal struct {
Cons map[Ki]RecvFunc `view:"-" json:"-" xml:"-" desc:"map of receivers and their functions"`
Mu sync.RWMutex `view:"-" json:"-" xml:"-" desc:"read-write mutex that protects Cons map access -- use RLock for all Cons reads, Lock for all writes"`
}
var KiT_Signal = kit.Types.AddType(&Signal{}, nil)
// ConnectOnly first deletes any existing connections and then attaches a new
// receiver to the signal
func (s *Signal) ConnectOnly(recv Ki, fun RecvFunc) {
s.DisconnectAll()
s.Connect(recv, fun)
}
// Connect attaches a new receiver and function to the signal -- only one such
// connection per receiver can be made, so any existing connection to that
// receiver will be overwritten
func (s *Signal) Connect(recv Ki, fun RecvFunc) {
s.Mu.Lock()
if s.Cons == nil {
s.Cons = make(map[Ki]RecvFunc)
}
s.Cons[recv] = fun
s.Mu.Unlock()
}
// Disconnect disconnects (deletes) the connection for a given receiver
func (s *Signal) Disconnect(recv Ki) {
s.Mu.Lock()
delete(s.Cons, recv)
s.Mu.Unlock()
}
// DisconnectAll removes all connections
func (s *Signal) DisconnectAll() {
s.Mu.Lock()
s.Cons = make(map[Ki]RecvFunc)
s.Mu.Unlock()
}
// EmitTrace records a trace of signal being emitted
func (s *Signal) EmitTrace(sender Ki, sig int64, data interface{}) {
if SignalTraceString != nil {
*SignalTraceString += fmt.Sprintf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Name(), NodeSignals(sig), data)
} else {
fmt.Printf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.PathUnique(), NodeSignals(sig), data)
}
}
// Emit sends the signal across all the connections to the receivers --
// sequentially but in random order due to the randomization of map iteration
func (s *Signal) Emit(sender Ki, sig int64, data interface{}) {
if sender == nil || sender.IsDestroyed() { // dead nodes don't talk..
return
}
if SignalTrace {
s.EmitTrace(sender, sig, data)
}
s.Mu.RLock()
for recv, fun := range s.Cons {
if recv.IsDestroyed() {
// fmt.Printf("ki.Signal deleting destroyed receiver: %v type %T\n", recv.Name(), recv)
delete(s.Cons, recv)
continue
}
s.Mu.RUnlock()
fun(recv, sender, sig, data)
s.Mu.RLock()
}
s.Mu.RUnlock()
}
// EmitGo is the concurrent version of Emit -- sends the signal across all the
// connections to the receivers as separate goroutines
func (s *Signal) EmitGo(sender Ki, sig int64, data interface{}) {
if sender == nil || sender.IsDestroyed() { // dead nodes don't talk..
return
}
if SignalTrace {
s.EmitTrace(sender, sig, data)
}
s.Mu.RLock()
for recv, fun := range s.Cons {
if recv.IsDestroyed() {
// fmt.Printf("ki.Signal deleting destroyed receiver: %v type %T\n", recv.Name(), recv)
delete(s.Cons, recv)
continue
}
s.Mu.RUnlock()
go fun(recv, sender, sig, data)
s.Mu.RLock()
}
s.Mu.RUnlock()
}
// SignalFilterFunc is the function type for filtering signals before they are
// sent -- returns false to prevent sending, and true to allow sending
type SignalFilterFunc func(recv Ki) bool
// EmitFiltered calls function on each potential receiver, and only sends
// signal if function returns true
func (s *Signal) EmitFiltered(sender Ki, sig int64, data interface{}, filtFun SignalFilterFunc) {
s.Mu.RLock()
for recv, fun := range s.Cons {
if recv.IsDestroyed() {
// fmt.Printf("ki.Signal deleting destroyed receiver: %v type %T\n", recv.Name(), recv)
delete(s.Cons, recv)
continue
}
s.Mu.RUnlock()
if filtFun(recv) {
fun(recv, sender, sig, data)
}
s.Mu.RLock()
}
s.Mu.RUnlock()
}
// EmitGoFiltered is the concurrent version of EmitFiltered -- calls function
// on each potential receiver, and only sends signal if function returns true
// (filtering is sequential iteration over receivers)
func (s *Signal) EmitGoFiltered(sender Ki, sig int64, data interface{}, filtFun SignalFilterFunc) {
s.Mu.RLock()
for recv, fun := range s.Cons {
if recv.IsDestroyed() {
// fmt.Printf("ki.Signal deleting destroyed receiver: %v type %T\n", recv.Name(), recv)
delete(s.Cons, recv)
continue
}
s.Mu.RUnlock()
if filtFun(recv) {
go fun(recv, sender, sig, data)
}
s.Mu.RLock()
}
s.Mu.RUnlock()
}
// SendSig sends a signal to one given receiver -- receiver must already be
// connected so that its receiving function is available
func (s *Signal) SendSig(recv, sender Ki, sig int64, data interface{}) {
s.Mu.RLock()
fun := s.Cons[recv]
s.Mu.RUnlock()
if fun != nil {
fun(recv, sender, sig, data)
}
}