|
|
package lucifer3
import ( "log" "sync" )
type ServiceKey struct{}
type Event interface { EventName() string }
type EventBus struct { mu sync.Mutex listeners []*serviceListener signal chan struct{} }
// JoinCallback joins the event bus for a moment.
func (b *EventBus) JoinCallback(cb func(event Event, sender ServiceID) bool) { b.Join(newCallbackService(cb)) }
func (b *EventBus) Join(service Service) { // Take the signal here so that it will receive the events sent by the calling function
// so that they're processed without having to wait for a new event.
signal := b.signalCh()
b.mu.Lock() listener := &serviceListener{ bus: b, queue: make([]queuedEvent, 0, 16), service: service, }
go listener.run(signal)
b.listeners = append(b.listeners, listener) b.mu.Unlock() }
func (b *EventBus) Send(event Event, sender ServiceID, recipient *ServiceID) { b.mu.Lock() defer b.mu.Unlock()
deleteList := make([]int, 0, 0) for i, listener := range b.listeners { if !listener.service.Active() { deleteList = append(deleteList, i-len(deleteList)) continue } if recipient != nil && *recipient != listener.service.ServiceID() { continue }
listener.mu.Lock() listener.queue = append(listener.queue, queuedEvent{ event: event, sender: sender, }) listener.mu.Unlock() }
for i := range deleteList { b.listeners = append(b.listeners[:i], b.listeners[i+1:]...) }
if recipient != nil { log.Printf("%s (-> %s): %s", &sender, recipient, event.EventName()) } else { log.Printf("%s: %s", &sender, event.EventName()) }
if b.signal != nil { close(b.signal) b.signal = nil } }
func (b *EventBus) signalCh() <-chan struct{} { b.mu.Lock() defer b.mu.Unlock()
if b.signal == nil { b.signal = make(chan struct{}) }
return b.signal }
type serviceListener struct { mu sync.Mutex bus *EventBus queue []queuedEvent service Service }
func (l *serviceListener) run(signal <-chan struct{}) { queue := make([]queuedEvent, 0, 16)
for { // Listen for the signal, but stop here if the service has marked
// itself as inactive.
<-signal if !l.service.Active() { return }
// Take a new signal before copying the queue to avoid delay
// in case a message is about to be enqueued right now!
signal = l.bus.signalCh()
// Take a copy of the queue.
l.mu.Lock() for _, message := range l.queue { queue = append(queue, message) } l.queue = l.queue[:0] l.mu.Unlock()
// Handle the messages in the queue, but stop mid-queue if the previous message
// or external circumstances marked the service inactive.
for _, message := range queue { if !l.service.Active() { return }
l.service.Handle(l.bus, message.event, message.sender) } queue = queue[:0] } }
type queuedEvent struct { event Event sender ServiceID }
|