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  }