-
-
Notifications
You must be signed in to change notification settings - Fork 1
add ListenWithID and StopListening #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
46085e6
e84d0e0
864618a
cc834b1
ccd7ede
1d4f816
0d33a22
fd1d9d3
7750aab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,12 +8,16 @@ import ( | |
| // ErrEventClosed is returned when an operation is attempted on a closed event. | ||
| var ErrEventClosed = errors.New("event is closed") | ||
|
|
||
| // ErrUnknownListener is returned when attempting to unregister an unknown id. | ||
| var ErrUnknownListener = errors.New("listener id is unknown") | ||
|
|
||
| // Event represents a generic, thread-safe event system that can handle multiple listeners. | ||
| // The type parameter T specifies the type of data that the event carries when triggered. | ||
| type Event[T any] struct { | ||
| listeners []func(T) | ||
| listeners map[int]func(T) | ||
| mu sync.RWMutex | ||
| closed bool | ||
| nextID int | ||
| } | ||
|
|
||
| // New creates and returns a new Event instance for the specified type T. | ||
|
|
@@ -33,8 +37,10 @@ func (e *Event[T]) Trigger(value T) error { | |
|
|
||
| // Copy the listeners to avoid holding the lock during execution. | ||
| // This ensures that triggering the event is thread-safe even if listeners are added or removed concurrently. | ||
| listeners := make([]func(T), len(e.listeners)) | ||
| copy(listeners, e.listeners) | ||
| listeners := make([]func(T), 0, len(e.listeners)) | ||
| for _, l := range e.listeners { | ||
| listeners = append(listeners, l) | ||
| } | ||
| e.mu.RUnlock() | ||
|
|
||
| var wg sync.WaitGroup | ||
|
|
@@ -54,20 +60,50 @@ func (e *Event[T]) Trigger(value T) error { | |
|
|
||
| // Listen registers a new listener callback function for the event. | ||
| // The listener will be invoked with the event's data whenever Trigger is called. | ||
| // Returns an ID which can be used with StopListening to deregister the listener. | ||
| // Returns ErrEventClosed if the event has been closed. | ||
| func (e *Event[T]) Listen(f func(T)) error { | ||
| func (e *Event[T]) Listen(f func(T)) (int, error) { | ||
| e.mu.Lock() | ||
| defer e.mu.Unlock() | ||
|
|
||
| // Lazy init to allow use of zero value | ||
| if e.listeners == nil { | ||
| e.listeners = make(map[int]func(T)) | ||
| } | ||
|
|
||
| if e.closed { | ||
| return ErrEventClosed | ||
| return -1, ErrEventClosed | ||
| } | ||
|
|
||
| e.listeners = append(e.listeners, f) | ||
| id := e.getID() | ||
|
|
||
| e.listeners[id] = f | ||
|
|
||
| return id, nil | ||
| } | ||
|
|
||
| // StopListening unregisters a listener, using the ID returned from Listen. | ||
| // The callback which was registered with that ID will no longer be called | ||
| // and any associated resources will be released. | ||
| // If the ID passed in is not registered, ErrUnknownListener will be returned. | ||
| func (e *Event[T]) StopListening(id int) error { | ||
| e.mu.Lock() | ||
| defer e.mu.Unlock() | ||
|
|
||
| _, ok := e.listeners[id] | ||
| if !ok { | ||
| return ErrUnknownListener | ||
| } | ||
| delete(e.listeners, id) | ||
| return nil | ||
| } | ||
|
Comment on lines
+89
to
99
|
||
|
|
||
| func (e *Event[T]) getID() int { | ||
| id := e.nextID | ||
| e.nextID++ | ||
| return id | ||
| } | ||
|
|
||
| // Close closes the event system, preventing any new listeners from being added or events from being triggered. | ||
| // After calling Close, any subsequent calls to Trigger or Listen will return ErrEventClosed. | ||
| // Existing listeners are removed, and resources are cleaned up. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,10 @@ | ||
| package event | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestZeroValue(t *testing.T) { | ||
| var ev Event[string] | ||
|
|
||
| // Can listen to zero value without panicing | ||
| ev.Listen(func(v string) { println(v) }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,3 +83,43 @@ func ExampleEvent_Close() { | |
| // 2 | ||
| // 3 | ||
| } | ||
|
|
||
| func ExampleEvent_StopListening() { | ||
| // Create a new event | ||
| exampleEvent := event.New[string]() | ||
|
|
||
| // Listen to the event | ||
| triggerCount := 0 | ||
| listenID, _ := exampleEvent.Listen(func(v string) { | ||
| triggerCount++ | ||
| fmt.Printf("%d - %s\n", triggerCount, v) | ||
|
Comment on lines
+92
to
+95
|
||
| }) | ||
|
|
||
| // Trigger the event | ||
| exampleEvent.Trigger("foo") | ||
| delay() // delay for deterministic output | ||
| exampleEvent.Trigger("bar") | ||
| delay() // delay for deterministic output | ||
| exampleEvent.Trigger("baz") | ||
|
|
||
| // Time for listeners to process the event | ||
| delay() | ||
|
|
||
| // Stop listening | ||
| exampleEvent.StopListening(listenID) | ||
|
|
||
| // Trigger the event again | ||
| exampleEvent.Trigger("foo") | ||
| delay() // delay for deterministic output | ||
| exampleEvent.Trigger("bar") | ||
| delay() // delay for deterministic output | ||
| exampleEvent.Trigger("baz") | ||
|
|
||
| // Keep the program alive | ||
| time.Sleep(time.Second) | ||
|
|
||
| // Output: | ||
| // 1 - foo | ||
| // 2 - bar | ||
| // 3 - baz | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next_iduses snake_case. This is not idiomatic Go and is likely to be reported bystylecheck(ST1003). Rename tonextID(and updategetIDaccordingly).