github.com/secoba/wails/v2@v2.6.4/internal/frontend/runtime/events.go (about)

     1  package runtime
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/samber/lo"
     7  	"github.com/secoba/wails/v2/internal/frontend"
     8  )
     9  
    10  type Logger interface {
    11  	Trace(format string, v ...interface{})
    12  }
    13  
    14  // eventListener holds a callback function which is invoked when
    15  // the event listened for is emitted. It has a counter which indicates
    16  // how the total number of events it is interested in. A value of zero
    17  // means it does not expire (default).
    18  type eventListener struct {
    19  	callback func(...interface{}) // Function to call with emitted event data
    20  	counter  int                  // The number of times this callback may be called. -1 = infinite
    21  	delete   bool                 // Flag to indicate that this listener should be deleted
    22  }
    23  
    24  // Events handles eventing
    25  type Events struct {
    26  	log      Logger
    27  	frontend []frontend.Frontend
    28  
    29  	// Go event listeners
    30  	listeners  map[string][]*eventListener
    31  	notifyLock sync.RWMutex
    32  }
    33  
    34  func (e *Events) Notify(sender frontend.Frontend, name string, data ...interface{}) {
    35  	e.notifyBackend(name, data...)
    36  	for _, thisFrontend := range e.frontend {
    37  		if thisFrontend == sender {
    38  			continue
    39  		}
    40  		thisFrontend.Notify(name, data...)
    41  	}
    42  }
    43  
    44  func (e *Events) On(eventName string, callback func(...interface{})) func() {
    45  	return e.registerListener(eventName, callback, -1)
    46  }
    47  
    48  func (e *Events) OnMultiple(eventName string, callback func(...interface{}), counter int) func() {
    49  	return e.registerListener(eventName, callback, counter)
    50  }
    51  
    52  func (e *Events) Once(eventName string, callback func(...interface{})) func() {
    53  	return e.registerListener(eventName, callback, 1)
    54  }
    55  
    56  func (e *Events) Emit(eventName string, data ...interface{}) {
    57  	e.notifyBackend(eventName, data...)
    58  	for _, thisFrontend := range e.frontend {
    59  		thisFrontend.Notify(eventName, data...)
    60  	}
    61  }
    62  
    63  func (e *Events) Off(eventName string) {
    64  	e.unRegisterListener(eventName)
    65  }
    66  
    67  func (e *Events) OffAll() {
    68  	e.notifyLock.Lock()
    69  	for eventName := range e.listeners {
    70  		delete(e.listeners, eventName)
    71  	}
    72  	e.notifyLock.Unlock()
    73  }
    74  
    75  // NewEvents creates a new log subsystem
    76  func NewEvents(log Logger) *Events {
    77  	result := &Events{
    78  		log:       log,
    79  		listeners: make(map[string][]*eventListener),
    80  	}
    81  	return result
    82  }
    83  
    84  // registerListener provides a means of subscribing to events of type "eventName"
    85  func (e *Events) registerListener(eventName string, callback func(...interface{}), counter int) func() {
    86  	// Create new eventListener
    87  	thisListener := &eventListener{
    88  		callback: callback,
    89  		counter:  counter,
    90  		delete:   false,
    91  	}
    92  	e.notifyLock.Lock()
    93  	// Append the new listener to the listeners slice
    94  	e.listeners[eventName] = append(e.listeners[eventName], thisListener)
    95  	e.notifyLock.Unlock()
    96  	return func() {
    97  		e.notifyLock.Lock()
    98  		defer e.notifyLock.Unlock()
    99  
   100  		if _, ok := e.listeners[eventName]; !ok {
   101  			return
   102  		}
   103  		e.listeners[eventName] = lo.Filter(e.listeners[eventName], func(l *eventListener, i int) bool {
   104  			return l != thisListener
   105  		})
   106  	}
   107  }
   108  
   109  // unRegisterListener provides a means of unsubscribing to events of type "eventName"
   110  func (e *Events) unRegisterListener(eventName string) {
   111  	e.notifyLock.Lock()
   112  	// Clear the listeners
   113  	delete(e.listeners, eventName)
   114  	e.notifyLock.Unlock()
   115  }
   116  
   117  // Notify backend for the given event name
   118  func (e *Events) notifyBackend(eventName string, data ...interface{}) {
   119  	e.notifyLock.Lock()
   120  	defer e.notifyLock.Unlock()
   121  
   122  	// Get list of event listeners
   123  	listeners := e.listeners[eventName]
   124  	if listeners == nil {
   125  		e.log.Trace("No listeners for event '%s'", eventName)
   126  		return
   127  	}
   128  
   129  	// We have a dirty flag to indicate that there are items to delete
   130  	itemsToDelete := false
   131  
   132  	// Callback in goroutine
   133  	for _, listener := range listeners {
   134  		if listener.counter > 0 {
   135  			listener.counter--
   136  		}
   137  		go listener.callback(data...)
   138  
   139  		if listener.counter == 0 {
   140  			listener.delete = true
   141  			itemsToDelete = true
   142  		}
   143  	}
   144  
   145  	// Do we have items to delete?
   146  	if itemsToDelete {
   147  
   148  		// Create a new Listeners slice
   149  		var newListeners []*eventListener
   150  
   151  		// Iterate over current listeners
   152  		for _, listener := range listeners {
   153  			// If we aren't deleting the listener, add it to the new list
   154  			if !listener.delete {
   155  				newListeners = append(newListeners, listener)
   156  			}
   157  		}
   158  
   159  		// Save new listeners or remove entry
   160  		if len(newListeners) > 0 {
   161  			e.listeners[eventName] = newListeners
   162  		} else {
   163  			delete(e.listeners, eventName)
   164  		}
   165  	}
   166  }
   167  
   168  func (e *Events) AddFrontend(appFrontend frontend.Frontend) {
   169  	e.frontend = append(e.frontend, appFrontend)
   170  }