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  }