github.com/goki/ki@v1.1.17/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 any) 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 receivers 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 98 // [view: -] map of receivers and their functions 99 Cons map[Ki]RecvFunc `view:"-" json:"-" xml:"-" desc:"map of receivers and their functions"` 100 101 // [view: -] read-write mutex that protects Cons map access -- use RLock for all Cons reads, Lock for all writes 102 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"` 103 } 104 105 var KiT_Signal = kit.Types.AddType(&Signal{}, nil) 106 107 // ConnectOnly first deletes any existing connections and then attaches a new 108 // receiver to the signal 109 func (s *Signal) ConnectOnly(recv Ki, fun RecvFunc) { 110 s.DisconnectAll() 111 s.Connect(recv, fun) 112 } 113 114 // Connect attaches a new receiver and function to the signal -- only one such 115 // connection per receiver can be made, so any existing connection to that 116 // receiver will be overwritten 117 func (s *Signal) Connect(recv Ki, fun RecvFunc) { 118 s.Mu.Lock() 119 if s.Cons == nil { 120 s.Cons = make(map[Ki]RecvFunc) 121 } 122 s.Cons[recv] = fun 123 s.Mu.Unlock() 124 } 125 126 // Disconnect disconnects (deletes) the connection for a given receiver 127 func (s *Signal) Disconnect(recv Ki) { 128 s.Mu.Lock() 129 delete(s.Cons, recv) 130 s.Mu.Unlock() 131 } 132 133 // DisconnectDestroyed disconnects (deletes) the connection for a given receiver, 134 // if receiver is destroyed, assumed to be under an RLock (unlocks, relocks read lock). 135 // Returns true if was disconnected. 136 func (s *Signal) DisconnectDestroyed(recv Ki) bool { 137 if recv.IsDestroyed() { 138 s.Mu.RUnlock() 139 s.Disconnect(recv) 140 s.Mu.RLock() 141 return true 142 } 143 return false 144 } 145 146 // DisconnectAll removes all connections 147 func (s *Signal) DisconnectAll() { 148 s.Mu.Lock() 149 s.Cons = make(map[Ki]RecvFunc) 150 s.Mu.Unlock() 151 } 152 153 // EmitTrace records a trace of signal being emitted 154 func (s *Signal) EmitTrace(sender Ki, sig int64, data any) { 155 if SignalTraceString != nil { 156 *SignalTraceString += fmt.Sprintf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Name(), NodeSignals(sig), data) 157 } else { 158 fmt.Printf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Path(), NodeSignals(sig), data) 159 } 160 } 161 162 // Emit sends the signal across all the connections to the receivers -- 163 // sequentially but in random order due to the randomization of map iteration 164 func (s *Signal) Emit(sender Ki, sig int64, data any) { 165 if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 166 return 167 } 168 if SignalTrace { 169 s.EmitTrace(sender, sig, data) 170 } 171 s.Mu.RLock() 172 for recv, fun := range s.Cons { 173 if s.DisconnectDestroyed(recv) { 174 continue 175 } 176 s.Mu.RUnlock() 177 fun(recv, sender, sig, data) 178 s.Mu.RLock() 179 } 180 s.Mu.RUnlock() 181 } 182 183 // EmitGo is the concurrent version of Emit -- sends the signal across all the 184 // connections to the receivers as separate goroutines 185 func (s *Signal) EmitGo(sender Ki, sig int64, data any) { 186 if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 187 return 188 } 189 if SignalTrace { 190 s.EmitTrace(sender, sig, data) 191 } 192 s.Mu.RLock() 193 for recv, fun := range s.Cons { 194 if s.DisconnectDestroyed(recv) { 195 continue 196 } 197 s.Mu.RUnlock() 198 go fun(recv, sender, sig, data) 199 s.Mu.RLock() 200 } 201 s.Mu.RUnlock() 202 } 203 204 // SignalFilterFunc is the function type for filtering signals before they are 205 // sent -- returns false to prevent sending, and true to allow sending 206 type SignalFilterFunc func(recv Ki) bool 207 208 // EmitFiltered calls function on each potential receiver, and only sends 209 // signal if function returns true 210 func (s *Signal) EmitFiltered(sender Ki, sig int64, data any, filtFun SignalFilterFunc) { 211 s.Mu.RLock() 212 for recv, fun := range s.Cons { 213 if s.DisconnectDestroyed(recv) { 214 continue 215 } 216 s.Mu.RUnlock() 217 if filtFun(recv) { 218 fun(recv, sender, sig, data) 219 } 220 s.Mu.RLock() 221 } 222 s.Mu.RUnlock() 223 } 224 225 // EmitGoFiltered is the concurrent version of EmitFiltered -- calls function 226 // on each potential receiver, and only sends signal if function returns true 227 // (filtering is sequential iteration over receivers) 228 func (s *Signal) EmitGoFiltered(sender Ki, sig int64, data any, filtFun SignalFilterFunc) { 229 s.Mu.RLock() 230 for recv, fun := range s.Cons { 231 if s.DisconnectDestroyed(recv) { 232 continue 233 } 234 s.Mu.RUnlock() 235 if filtFun(recv) { 236 go fun(recv, sender, sig, data) 237 } 238 s.Mu.RLock() 239 } 240 s.Mu.RUnlock() 241 } 242 243 // ConsFunc iterates over the connections with read lock and deletion of 244 // destroyed objects, calling given function on each connection -- if 245 // it returns false, then iteration is stopped, else continues. 246 // function is called with no lock in place. 247 func (s *Signal) ConsFunc(consFun func(recv Ki, fun RecvFunc) bool) { 248 s.Mu.RLock() 249 for recv, fun := range s.Cons { 250 if s.DisconnectDestroyed(recv) { 251 continue 252 } 253 s.Mu.RUnlock() 254 if !consFun(recv, fun) { 255 s.Mu.RLock() 256 break 257 } 258 s.Mu.RLock() 259 } 260 s.Mu.RUnlock() 261 } 262 263 // SendSig sends a signal to one given receiver -- receiver must already be 264 // connected so that its receiving function is available 265 func (s *Signal) SendSig(recv, sender Ki, sig int64, data any) { 266 s.Mu.RLock() 267 fun := s.Cons[recv] 268 s.Mu.RUnlock() 269 if fun != nil { 270 fun(recv, sender, sig, data) 271 } 272 }