github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/eventstream/eventstream.go (about) 1 package eventstream 2 3 import ( 4 "sync" 5 "sync/atomic" 6 ) 7 8 // Handler defines a callback function that must be pass when subscribing. 9 type Handler func(interface{}) 10 11 // Predicate is a function used to filter messages before being forwarded to a subscriber 12 type Predicate func(evt interface{}) bool 13 14 type EventStream struct { 15 sync.RWMutex 16 17 // slice containing our subscriptions 18 subscriptions []*Subscription 19 20 // Atomically maintained elements counter 21 counter int32 22 } 23 24 // Create a new EventStream value and returns it back. 25 func NewEventStream() *EventStream { 26 es := &EventStream{ 27 subscriptions: []*Subscription{}, 28 } 29 30 return es 31 } 32 33 // Subscribe the given handler to the EventStream 34 func (es *EventStream) Subscribe(handler Handler) *Subscription { 35 sub := &Subscription{ 36 handler: handler, 37 active: 1, 38 } 39 40 es.Lock() 41 defer es.Unlock() 42 43 sub.id = es.counter 44 es.counter++ 45 es.subscriptions = append(es.subscriptions, sub) 46 47 return sub 48 } 49 50 // SubscribeWithPredicate creates a new Subscription value and sets a predicate to filter messages passed to 51 // the subscriber, it returns a pointer to the Subscription value 52 func (es *EventStream) SubscribeWithPredicate(handler Handler, p Predicate) *Subscription { 53 sub := es.Subscribe(handler) 54 sub.p = p 55 56 return sub 57 } 58 59 // Unsubscribes the given subscription from the EventStream 60 func (es *EventStream) Unsubscribe(sub *Subscription) { 61 if sub == nil { 62 return 63 } 64 65 if sub.IsActive() { 66 es.Lock() 67 defer es.Unlock() 68 69 if sub.Deactivate() { 70 if es.counter == 0 { 71 es.subscriptions = nil 72 73 return 74 } 75 76 l := es.counter - 1 77 es.subscriptions[sub.id] = es.subscriptions[l] 78 es.subscriptions[sub.id].id = sub.id 79 es.subscriptions[l] = nil 80 es.subscriptions = es.subscriptions[:l] 81 es.counter-- 82 83 if es.counter == 0 { 84 es.subscriptions = nil 85 } 86 } 87 } 88 } 89 90 // Publishes the given event to all the subscribers in the stream 91 func (es *EventStream) Publish(evt interface{}) { 92 subs := make([]*Subscription, 0, es.Length()) 93 es.RLock() 94 for _, sub := range es.subscriptions { 95 if sub.IsActive() { 96 subs = append(subs, sub) 97 } 98 } 99 es.RUnlock() 100 101 for _, sub := range subs { 102 // there is a subscription predicate and it didn't pass, return 103 if sub.p != nil && !sub.p(evt) { 104 continue 105 } 106 107 // finally here, lets execute our handler 108 sub.handler(evt) 109 } 110 } 111 112 // Returns an integer that represents the current number of subscribers to the stream 113 func (es *EventStream) Length() int32 { 114 es.RLock() 115 defer es.RUnlock() 116 return es.counter 117 } 118 119 // Subscription is returned from the Subscribe function. 120 // 121 // This value and can be passed to Unsubscribe when the observer is no longer interested in receiving messages 122 type Subscription struct { 123 id int32 124 handler Handler 125 p Predicate 126 active uint32 127 } 128 129 // Activates the Subscription setting its active flag as 1, if the subscription 130 // was already active it returns false, true otherwise 131 func (s *Subscription) Activate() bool { 132 return atomic.CompareAndSwapUint32(&s.active, 0, 1) 133 } 134 135 // Deactivates the Subscription setting its active flag as 0, if the subscription 136 // was already inactive it returns false, true otherwise 137 func (s *Subscription) Deactivate() bool { 138 return atomic.CompareAndSwapUint32(&s.active, 1, 0) 139 } 140 141 // Returns true if the active flag of the Subscription is set as 1 142 // otherwise it returns false 143 func (s *Subscription) IsActive() bool { 144 return atomic.LoadUint32(&s.active) == 1 145 }