package event import ( "fmt" "sync" "github.com/wailsapp/wails/lib/interfaces" "github.com/wailsapp/wails/lib/logger" "github.com/wailsapp/wails/lib/messages" ) // Manager handles and processes events type Manager struct { incomingEvents chan *messages.EventData quitChannel chan struct{} listeners map[string][]*eventListener running bool log *logger.CustomLogger renderer interfaces.Renderer // Messages will be dispatched to the frontend wg sync.WaitGroup mu sync.Mutex } // NewManager creates a new event manager with a 100 event buffer func NewManager() interfaces.EventManager { return &Manager{ incomingEvents: make(chan *messages.EventData, 100), quitChannel: make(chan struct{}, 1), listeners: make(map[string][]*eventListener), running: false, log: logger.NewCustomLogger("Events"), } } // PushEvent places the given event on to the event queue func (e *Manager) PushEvent(eventData *messages.EventData) { e.incomingEvents <- eventData } // eventListener holds a callback function which is invoked when // the event listened for is emitted. It has a counter which indicates // how the total number of events it is interested in. A value of zero // means it does not expire (default). type eventListener struct { callback func(...interface{}) // Function to call with emitted event data counter uint // Expire after counter callbacks. 0 = infinite expired bool // Indicates if the listener has expired } // Creates a new event listener from the given callback function func (e *Manager) addEventListener(eventName string, callback func(...interface{}), counter uint) error { // Sanity check inputs if callback == nil { return fmt.Errorf("nil callback bassed to addEventListener") } // Create the callback listener := &eventListener{ callback: callback, counter: counter, } e.mu.Lock() // Check event has been registered before if e.listeners[eventName] == nil { e.listeners[eventName] = []*eventListener{} } // Register listener e.listeners[eventName] = append(e.listeners[eventName], listener) e.mu.Unlock() // All good mate return nil } // On adds a listener for the given event func (e *Manager) On(eventName string, callback func(...interface{})) { // Add a persistent eventListener (counter = 0) err := e.addEventListener(eventName, callback, 0) if err != nil { e.log.Error(err.Error()) } } // Once adds a listener for the given event that will auto remove // after one callback func (e *Manager) Once(eventName string, callback func(...interface{})) { // Add a persistent eventListener (counter = 0) err := e.addEventListener(eventName, callback, 1) if err != nil { e.log.Error(err.Error()) } } // OnMultiple adds a listener for the given event that will trigger // at most times. func (e *Manager) OnMultiple(eventName string, callback func(...interface{}), counter uint) { // Add a persistent eventListener (counter = 0) err := e.addEventListener(eventName, callback, counter) if err != nil { e.log.Error(err.Error()) } } // Emit broadcasts the given event to the subscribed listeners func (e *Manager) Emit(eventName string, optionalData ...interface{}) { e.incomingEvents <- &messages.EventData{Name: eventName, Data: optionalData} } // Start the event manager's queue processing func (e *Manager) Start(renderer interfaces.Renderer) { e.log.Info("Starting") // Store renderer e.renderer = renderer // Set up waitgroup so we can wait for goroutine to quit e.running = true e.wg.Add(1) // Run main loop in separate goroutine go func() { e.log.Info("Listening") for e.running { // TODO: Listen for application exit select { case event := <-e.incomingEvents: e.log.DebugFields("Got Event", logger.Fields{ "data": event.Data, "name": event.Name, }) // Notify renderer err := e.renderer.NotifyEvent(event) if err != nil { e.log.Error(err.Error()) } e.mu.Lock() // Iterate listeners for _, listener := range e.listeners[event.Name] { if !listener.expired { // Call listener, perhaps with data if event.Data == nil { go listener.callback() } else { unpacked := event.Data.([]interface{}) go listener.callback(unpacked...) } } // Update listen counter if listener.counter > 0 { listener.counter = listener.counter - 1 if listener.counter == 0 { listener.expired = true } } } e.mu.Unlock() case <-e.quitChannel: e.running = false } } e.wg.Done() }() } // Shutdown is called when exiting the Application func (e *Manager) Shutdown() { e.log.Debug("Shutting Down") e.quitChannel <- struct{}{} e.log.Debug("Waiting for main loop to exit") e.wg.Wait() }