Files
2026-05-11 15:53:43 +02:00

117 lines
2.0 KiB
Go

package eventbus
import "sync"
type Event struct {
Seq int64
WorkflowRunID string
AlbumID string
Quality string
EventType string
Step string
Message string
Data interface{}
}
type Subscription struct {
Ring *RingBuffer[*Event]
C chan struct{}
done chan struct{}
once sync.Once
}
type EventBus struct {
mu sync.RWMutex
topics map[string]map[*Subscription]struct{}
global map[*Subscription]struct{}
}
func New() *EventBus {
return &EventBus{
topics: make(map[string]map[*Subscription]struct{}),
global: make(map[*Subscription]struct{}),
}
}
func (b *EventBus) Publish(topic string, event *Event) {
b.mu.RLock()
defer b.mu.RUnlock()
if subs, ok := b.topics[topic]; ok {
for sub := range subs {
sub.Ring.Push(event)
select {
case sub.C <- struct{}{}:
default:
}
}
}
for sub := range b.global {
sub.Ring.Push(event)
select {
case sub.C <- struct{}{}:
default:
}
}
}
func (b *EventBus) Subscribe(topic string) (*Subscription, func()) {
sub := &Subscription{
Ring: NewRingBuffer[*Event](256),
C: make(chan struct{}, 1),
done: make(chan struct{}),
}
b.mu.Lock()
if b.topics[topic] == nil {
b.topics[topic] = make(map[*Subscription]struct{})
}
b.topics[topic][sub] = struct{}{}
b.mu.Unlock()
cleanup := func() {
sub.once.Do(func() {
b.mu.Lock()
delete(b.topics[topic], sub)
if len(b.topics[topic]) == 0 {
delete(b.topics, topic)
}
b.mu.Unlock()
close(sub.done)
})
}
return sub, cleanup
}
func (b *EventBus) SubscribeGlobal() (*Subscription, func()) {
sub := &Subscription{
Ring: NewRingBuffer[*Event](256),
C: make(chan struct{}, 1),
done: make(chan struct{}),
}
b.mu.Lock()
b.global[sub] = struct{}{}
b.mu.Unlock()
cleanup := func() {
sub.once.Do(func() {
b.mu.Lock()
delete(b.global, sub)
b.mu.Unlock()
close(sub.done)
})
}
return sub, cleanup
}
func (b *EventBus) HasTopic(topic string) bool {
b.mu.RLock()
defer b.mu.RUnlock()
_, ok := b.topics[topic]
return ok
}