|
1 | 1 | package event
|
2 | 2 |
|
3 |
| -import "sync" |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "sync" |
| 6 | +) |
4 | 7 |
|
5 |
| -// Event represents an event system that can handle multiple listeners. |
| 8 | +// ErrEventClosed is returned when an operation is attempted on a closed event. |
| 9 | +var ErrEventClosed = errors.New("event is closed") |
| 10 | + |
| 11 | +// Event represents a generic, thread-safe event system that can handle multiple listeners. |
| 12 | +// The type parameter T specifies the type of data that the event carries when triggered. |
6 | 13 | type Event[T any] struct {
|
7 |
| - listeners []chan T |
8 |
| - mu sync.Mutex |
| 14 | + listeners []func(T) |
| 15 | + mu sync.RWMutex |
9 | 16 | closed bool
|
10 | 17 | }
|
11 | 18 |
|
12 |
| -// New creates a new event. |
| 19 | +// New creates and returns a new Event instance for the specified type T. |
13 | 20 | func New[T any]() *Event[T] {
|
14 |
| - // Create a new event |
15 |
| - return &Event[T]{ |
16 |
| - listeners: []chan T{}, |
17 |
| - } |
| 21 | + return &Event[T]{} |
18 | 22 | }
|
19 | 23 |
|
20 |
| -// Trigger triggers the event and notifies all listeners. |
21 |
| -func (e *Event[T]) Trigger(value T) { |
22 |
| - e.mu.Lock() |
23 |
| - defer e.mu.Unlock() |
| 24 | +// Trigger notifies all registered listeners by invoking their callback functions with the provided value. |
| 25 | +// It runs each listener in a separate goroutine and waits for all listeners to complete. |
| 26 | +// Returns ErrEventClosed if the event has been closed. |
| 27 | +func (e *Event[T]) Trigger(value T) error { |
| 28 | + e.mu.RLock() |
| 29 | + if e.closed { |
| 30 | + e.mu.RUnlock() |
| 31 | + return ErrEventClosed |
| 32 | + } |
| 33 | + |
| 34 | + // Copy the listeners to avoid holding the lock during execution. |
| 35 | + // This ensures that triggering the event is thread-safe even if listeners are added or removed concurrently. |
| 36 | + listeners := make([]func(T), len(e.listeners)) |
| 37 | + copy(listeners, e.listeners) |
| 38 | + e.mu.RUnlock() |
| 39 | + |
| 40 | + var wg sync.WaitGroup |
| 41 | + for _, listener := range listeners { |
| 42 | + wg.Add(1) |
24 | 43 |
|
25 |
| - for _, listener := range e.listeners { |
26 |
| - go func(l chan T) { |
27 |
| - if !e.closed { |
28 |
| - l <- value |
29 |
| - } |
| 44 | + go func(f func(T)) { |
| 45 | + defer wg.Done() |
| 46 | + f(value) |
30 | 47 | }(listener)
|
31 | 48 | }
|
| 49 | + |
| 50 | + wg.Wait() |
| 51 | + |
| 52 | + return nil |
32 | 53 | }
|
33 | 54 |
|
34 |
| -// Listen gets called when the event is triggered. |
35 |
| -func (e *Event[T]) Listen(f func(T)) { |
36 |
| - // Check if the event is closed |
37 |
| - if e.closed { |
38 |
| - return |
39 |
| - } |
| 55 | +// Listen registers a new listener callback function for the event. |
| 56 | +// The listener will be invoked with the event's data whenever Trigger is called. |
| 57 | +// Returns ErrEventClosed if the event has been closed. |
| 58 | +func (e *Event[T]) Listen(f func(T)) error { |
| 59 | + e.mu.Lock() |
| 60 | + defer e.mu.Unlock() |
40 | 61 |
|
41 |
| - // Create listener slice if it doesn't exist |
42 |
| - if e.listeners == nil { |
43 |
| - e.listeners = []chan T{} |
| 62 | + if e.closed { |
| 63 | + return ErrEventClosed |
44 | 64 | }
|
45 | 65 |
|
46 |
| - // Create a new channel |
47 |
| - ch := make(chan T) |
| 66 | + e.listeners = append(e.listeners, f) |
48 | 67 |
|
49 |
| - e.mu.Lock() |
50 |
| - e.listeners = append(e.listeners, ch) |
51 |
| - e.mu.Unlock() |
52 |
| - |
53 |
| - go func() { |
54 |
| - for v := range ch { |
55 |
| - if !e.closed { |
56 |
| - f(v) |
57 |
| - } |
58 |
| - } |
59 |
| - }() |
| 68 | + return nil |
60 | 69 | }
|
61 | 70 |
|
62 |
| -// Close closes the event and all its listeners. |
63 |
| -// After calling this method, the event can't be used anymore and new listeners can't be added. |
| 71 | +// Close closes the event system, preventing any new listeners from being added or events from being triggered. |
| 72 | +// After calling Close, any subsequent calls to Trigger or Listen will return ErrEventClosed. |
| 73 | +// Existing listeners are removed, and resources are cleaned up. |
64 | 74 | func (e *Event[T]) Close() {
|
65 | 75 | e.mu.Lock()
|
66 | 76 | defer e.mu.Unlock()
|
67 | 77 |
|
68 |
| - for _, listener := range e.listeners { |
69 |
| - close(listener) |
| 78 | + if e.closed { |
| 79 | + return |
70 | 80 | }
|
71 | 81 |
|
72 |
| - e.listeners = nil |
73 | 82 | e.closed = true
|
| 83 | + e.listeners = nil // Release references to listener functions |
74 | 84 | }
|
0 commit comments