github.com/goki/ki@v1.1.11/ki/signal.go (about) 1 // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ki 6 7 import ( 8 "fmt" 9 "sync" 10 11 "github.com/goki/ki/kit" 12 ) 13 14 // note: Started this code based on: github.com/tucnak/meta/ 15 16 // NodeSignals are signals that a Ki node sends about updates to the tree 17 // structure using the NodeSignal (convert sig int64 to NodeSignals to get the 18 // stringer name). 19 type NodeSignals int64 20 21 // Standard signal types sent by ki.Node on its NodeSig for tree state changes 22 const ( 23 // NodeSignalNil is a nil signal value 24 NodeSignalNil NodeSignals = iota 25 26 // NodeSignalUpdated indicates that the node was updated -- the node Flags 27 // accumulate the specific changes made since the last update signal -- 28 // these flags are sent in the signal data -- strongly recommend using 29 // that instead of the flags, which can be subsequently updated by the 30 // time a signal is processed 31 NodeSignalUpdated 32 33 // NodeSignalDeleting indicates that the node is being deleted from its 34 // parent children list -- this is not blocked by Updating status and is 35 // delivered immediately. No further notifications are sent -- assume 36 // it will be destroyed unless you hear from it again. 37 NodeSignalDeleting 38 39 NodeSignalsN 40 ) 41 42 //go:generate stringer -type=NodeSignals 43 44 // SignalTrace can be set to true to automatically print out a trace of the 45 // signals as they are sent 46 var SignalTrace bool = false 47 48 // SignalTraceString can be set to a string that will then accumulate the 49 // trace of signals sent, for use in testing -- otherwise the trace just goes 50 // to stdout 51 var SignalTraceString *string 52 53 // RecvFunc is a receiver function type for signals -- gets the full 54 // connection information and signal, data as specified by the sender. It is 55 // good practice to avoid closures in these functions, which can be numerous 56 // and have a long lifetime, by converting the recv, send into their known 57 // types and referring to them directly 58 type RecvFunc func(recv, send Ki, sig int64, data interface{}) 59 60 // Signal implements general signal passing between Ki objects, like Qt's 61 // Signal / Slot system. 62 // 63 // This design pattern separates three different factors: 64 // * when to signal that something has happened 65 // * who should receive that signal 66 // * what should the receiver do in response to the signal 67 // 68 // Keeping these three things entirely separate greatly simplifies the overall 69 // logic. 70 // 71 // A receiver connects in advance to a given signal on a sender to receive its 72 // signals -- these connections are typically established in an initialization 73 // step. There can be multiple different signals on a given sender, and to 74 // make more efficient use of signal connections, the sender can also send an 75 // int64 signal value that further discriminates the nature of the event, as 76 // documented in the code associated with the sender (typically an enum is 77 // used). Furthermore, arbitrary data as an interface{} can be passed as 78 // well. 79 // 80 // The Signal uses a map indexed by the receiver pointer to hold the 81 // connections -- this means that there can only be one such connection per 82 // receiver, and the order of signal emission to different receiveres will be random. 83 // 84 // Typically an inline anonymous closure receiver function is used to keep all 85 // the relevant code in one place. Due to the typically long-standing nature 86 // of these connections, it is more efficient to avoid capturing external 87 // variables, and rely instead on appropriately interpreting the sent argument 88 // values. e.g.: 89 // 90 // send := sender.EmbeddedStruct(KiT_SendType).(*SendType) 91 // 92 // is guaranteed to result in a usable pointer to the sender of known type at 93 // least SendType, in a case where that sender might actually embed that 94 // SendType (otherwise if it is known to be of a given type, just directly 95 // converting as such is fine) 96 type Signal struct { 97 Cons map[Ki]RecvFunc `view:"-" json:"-" xml:"-" desc:"map of receivers and their functions"` 98 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"` 99 } 100 101 var KiT_Signal = kit.Types.AddType(&Signal{}, nil) 102 103 // ConnectOnly first deletes any existing connections and then attaches a new 104 // receiver to the signal 105 func (s *Signal) ConnectOnly(recv Ki, fun RecvFunc) { 106 s.DisconnectAll() 107 s.Connect(recv, fun) 108 } 109 110 // Connect attaches a new receiver and function to the signal -- only one such 111 // connection per receiver can be made, so any existing connection to that 112 // receiver will be overwritten 113 func (s *Signal) Connect(recv Ki, fun RecvFunc) { 114 s.Mu.Lock() 115 if s.Cons == nil { 116 s.Cons = make(map[Ki]RecvFunc) 117 } 118 s.Cons[recv] = fun 119 s.Mu.Unlock() 120 } 121 122 // Disconnect disconnects (deletes) the connection for a given receiver 123 func (s *Signal) Disconnect(recv Ki) { 124 s.Mu.Lock() 125 delete(s.Cons, recv) 126 s.Mu.Unlock() 127 } 128 129 // DisconnectDestroyed disconnects (deletes) the connection for a given receiver, 130 // if receiver is destroyed, assumed to be under an RLock (unlocks, relocks read lock). 131 // Returns true if was disconnected. 132 func (s *Signal) DisconnectDestroyed(recv Ki) bool { 133 if recv.IsDestroyed() { 134 s.Mu.RUnlock() 135 s.Disconnect(recv) 136 s.Mu.RLock() 137 return true 138 } 139 return false 140 } 141 142 // DisconnectAll removes all connections 143 func (s *Signal) DisconnectAll() { 144 s.Mu.Lock() 145 s.Cons = make(map[Ki]RecvFunc) 146 s.Mu.Unlock() 147 } 148 149 // EmitTrace records a trace of signal being emitted 150 func (s *Signal) EmitTrace(sender Ki, sig int64, data interface{}) { 151 if SignalTraceString != nil { 152 *SignalTraceString += fmt.Sprintf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Name(), NodeSignals(sig), data) 153 } else { 154 fmt.Printf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Path(), NodeSignals(sig), data) 155 } 156 } 157 158 // Emit sends the signal across all the connections to the receivers -- 159 // sequentially but in random order due to the randomization of map iteration 160 func (s *Signal) Emit(sender Ki, sig int64, data interface{}) { 161 if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 162 return 163 } 164 if SignalTrace { 165 s.EmitTrace(sender, sig, data) 166 } 167 s.Mu.RLock() 168 for recv, fun := range s.Cons { 169 if s.DisconnectDestroyed(recv) { 170 continue 171 } 172 s.Mu.RUnlock() 173 fun(recv, sender, sig, data) 174 s.Mu.RLock() 175 } 176 s.Mu.RUnlock() 177 } 178 179 // EmitGo is the concurrent version of Emit -- sends the signal across all the 180 // connections to the receivers as separate goroutines 181 func (s *Signal) EmitGo(sender Ki, sig int64, data interface{}) { 182 if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 183 return 184 } 185 if SignalTrace { 186 s.EmitTrace(sender, sig, data) 187 } 188 s.Mu.RLock() 189 for recv, fun := range s.Cons { 190 if s.DisconnectDestroyed(recv) { 191 continue 192 } 193 s.Mu.RUnlock() 194 go fun(recv, sender, sig, data) 195 s.Mu.RLock() 196 } 197 s.Mu.RUnlock() 198 } 199 200 // SignalFilterFunc is the function type for filtering signals before they are 201 // sent -- returns false to prevent sending, and true to allow sending 202 type SignalFilterFunc func(recv Ki) bool 203 204 // EmitFiltered calls function on each potential receiver, and only sends 205 // signal if function returns true 206 func (s *Signal) EmitFiltered(sender Ki, sig int64, data interface{}, filtFun SignalFilterFunc) { 207 s.Mu.RLock() 208 for recv, fun := range s.Cons { 209 if s.DisconnectDestroyed(recv) { 210 continue 211 } 212 s.Mu.RUnlock() 213 if filtFun(recv) { 214 fun(recv, sender, sig, data) 215 } 216 s.Mu.RLock() 217 } 218 s.Mu.RUnlock() 219 } 220 221 // EmitGoFiltered is the concurrent version of EmitFiltered -- calls function 222 // on each potential receiver, and only sends signal if function returns true 223 // (filtering is sequential iteration over receivers) 224 func (s *Signal) EmitGoFiltered(sender Ki, sig int64, data interface{}, filtFun SignalFilterFunc) { 225 s.Mu.RLock() 226 for recv, fun := range s.Cons { 227 if s.DisconnectDestroyed(recv) { 228 continue 229 } 230 s.Mu.RUnlock() 231 if filtFun(recv) { 232 go fun(recv, sender, sig, data) 233 } 234 s.Mu.RLock() 235 } 236 s.Mu.RUnlock() 237 } 238 239 // ConsFunc iterates over the connections with read lock and deletion of 240 // destroyed objects, calling given function on each connection -- if 241 // it returns false, then iteration is stopped, else continues. 242 // function is called with no lock in place. 243 func (s *Signal) ConsFunc(consFun func(recv Ki, fun RecvFunc) bool) { 244 s.Mu.RLock() 245 for recv, fun := range s.Cons { 246 if s.DisconnectDestroyed(recv) { 247 continue 248 } 249 s.Mu.RUnlock() 250 if !consFun(recv, fun) { 251 s.Mu.RLock() 252 break 253 } 254 s.Mu.RLock() 255 } 256 s.Mu.RUnlock() 257 } 258 259 // SendSig sends a signal to one given receiver -- receiver must already be 260 // connected so that its receiving function is available 261 func (s *Signal) SendSig(recv, sender Ki, sig int64, data interface{}) { 262 s.Mu.RLock() 263 fun := s.Cons[recv] 264 s.Mu.RUnlock() 265 if fun != nil { 266 fun(recv, sender, sig, data) 267 } 268 }