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  }