├── go.mod ├── .gitignore ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ └── bug_report.md ├── changelog.yml └── workflows │ ├── lint.yml │ ├── release.yml │ └── go.yml ├── go.sum ├── simpleevent ├── README.md ├── default.go ├── data.go ├── all_test.go └── manager.go ├── benchmark_test.go ├── LICENSE ├── util.go ├── event_test.go ├── types.go ├── std.go ├── listener_remove_test.go ├── event.go ├── issues_test.go ├── std_test.go ├── manager.go ├── README.zh-CN.md ├── manager_fire.go ├── README.md └── manager_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gookit/event 2 | 3 | go 1.19 4 | 5 | require github.com/gookit/goutil v0.7.2 6 | 7 | require ( 8 | golang.org/x/sys v0.30.0 // indirect 9 | golang.org/x/term v0.29.0 // indirect 10 | golang.org/x/text v0.22.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swp 3 | .idea 4 | *.patch 5 | ### Go template 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | .DS_Store 19 | #go.sum -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | # Check for updates to GitHub Actions every weekday 13 | interval: "daily" 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gookit/goutil v0.7.2 h1:NSiqWWY+BT0MwIlKDeSVPfQmr9xTkkAqwDjhplobdgo= 2 | github.com/gookit/goutil v0.7.2/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU= 3 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 4 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 5 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 6 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 7 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 8 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 9 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 10 | -------------------------------------------------------------------------------- /simpleevent/README.md: -------------------------------------------------------------------------------- 1 | # simple event 2 | 3 | Very simple event manager implements. 4 | 5 | ## Usage 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "github.com/gookit/event/simpleevent" 13 | ) 14 | 15 | func main() { 16 | // register event handler 17 | simpleevent.On("event1", func(e *simpleevent.EventData) error { 18 | fmt.Printf("handle the event: %s\n", e.Name()) 19 | return nil 20 | }) 21 | 22 | // register more handler to the event. 23 | simpleevent.On("event1", func(e *simpleevent.EventData) error { 24 | fmt.Printf("oo, handle the event: %s\n", e.Name()) 25 | return nil 26 | }) 27 | 28 | // .... 29 | 30 | // fire event 31 | _ = simpleevent.Fire("event1", "arg0", "arg1") 32 | } 33 | ``` 34 | 35 | ## LICENSE 36 | 37 | **[MIT](../LICENSE)** -------------------------------------------------------------------------------- /simpleevent/default.go: -------------------------------------------------------------------------------- 1 | package simpleevent 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | ) 7 | 8 | // DefaultEM default event manager 9 | var DefaultEM = NewEventManager() 10 | 11 | // On register a event and handler 12 | func On(name string, handler HandlerFunc) { 13 | DefaultEM.On(name, handler) 14 | } 15 | 16 | // Has event check. 17 | func Has(name string) bool { 18 | return DefaultEM.HasEvent(name) 19 | } 20 | 21 | // Fire handlers by name. 22 | func Fire(name string, args ...any) error { 23 | return DefaultEM.Fire(name, args) 24 | } 25 | 26 | // MustFire fire event by name. will panic on error 27 | func MustFire(name string, args ...any) { 28 | DefaultEM.MustFire(name, args) 29 | } 30 | 31 | func funcName(f any) string { 32 | return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: inhere 7 | 8 | --- 9 | 10 | **System (please complete the following information):** 11 | 12 | - OS: `linux` [e.g. linux, macOS] 13 | - GO Version: `1.13` [e.g. `1.13`] 14 | - Pkg Version: `1.1.1` [e.g. `1.1.1`] 15 | 16 | **Describe the bug** 17 | 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | 22 | ```go 23 | // go code 24 | ``` 25 | 26 | **Expected behavior** 27 | 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Screenshots** 31 | 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Additional context** 35 | 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /simpleevent/data.go: -------------------------------------------------------------------------------- 1 | package simpleevent 2 | 3 | /************************************************************* 4 | * Event Data 5 | *************************************************************/ 6 | 7 | // EventData struct 8 | type EventData struct { 9 | aborted bool 10 | // event name 11 | name string 12 | // user data. 13 | Data []any 14 | } 15 | 16 | // Name get 17 | func (e *EventData) Name() string { 18 | return e.name 19 | } 20 | 21 | // Abort abort event exec 22 | func (e *EventData) Abort() { 23 | e.aborted = true 24 | } 25 | 26 | // IsAborted check. 27 | func (e *EventData) IsAborted() bool { 28 | return e.aborted 29 | } 30 | 31 | func (e *EventData) init(name string, data []any) { 32 | e.name = name 33 | e.Data = data 34 | } 35 | 36 | func (e *EventData) reset() { 37 | e.name = "" 38 | e.Data = make([]any, 0) 39 | e.aborted = false 40 | } 41 | -------------------------------------------------------------------------------- /.github/changelog.yml: -------------------------------------------------------------------------------- 1 | title: '## Change Log' 2 | # style allow: simple, markdown(mkdown), ghr(gh-release) 3 | style: gh-release 4 | # group names 5 | names: [Refactor, Fixed, Feature, Update, Other] 6 | # if empty will auto fetch by git remote 7 | #repo_url: https://github.com/gookit/goutil 8 | 9 | filters: 10 | # message length should >= 12 11 | - name: msg_len 12 | min_len: 12 13 | # message words should >= 3 14 | - name: words_len 15 | min_len: 3 16 | - name: keyword 17 | keyword: format code 18 | exclude: true 19 | - name: keywords 20 | keywords: format code, action test 21 | exclude: true 22 | 23 | # group match rules 24 | # not matched will use 'Other' group. 25 | rules: 26 | - name: Refactor 27 | start_withs: [refactor, break] 28 | contains: ['refactor:'] 29 | - name: Fixed 30 | start_withs: [fix] 31 | contains: ['fix:'] 32 | - name: Feature 33 | start_withs: [feat, new] 34 | contains: [feature, 'feat:'] 35 | - name: Update 36 | start_withs: [up] 37 | contains: ['update:', 'up:'] 38 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | package event_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gookit/event" 7 | ) 8 | 9 | func BenchmarkManager_Fire_no_listener(b *testing.B) { 10 | em := event.NewManager("test") 11 | em.On("app.up", event.ListenerFunc(func(e event.Event) error { 12 | return nil 13 | })) 14 | 15 | b.ResetTimer() 16 | b.ReportAllocs() 17 | 18 | for i := 0; i < b.N; i++ { 19 | _, _ = em.Fire("app.up", nil) 20 | } 21 | } 22 | 23 | func BenchmarkManager_Fire_normal(b *testing.B) { 24 | em := event.NewManager("test") 25 | em.On("app.up", event.ListenerFunc(func(e event.Event) error { 26 | return nil 27 | })) 28 | 29 | b.ResetTimer() 30 | b.ReportAllocs() 31 | 32 | for i := 0; i < b.N; i++ { 33 | _, _ = em.Fire("app.up", nil) 34 | } 35 | } 36 | 37 | func BenchmarkManager_Fire_wildcard(b *testing.B) { 38 | em := event.NewManager("test") 39 | em.On("app.*", event.ListenerFunc(func(e event.Event) error { 40 | return nil 41 | })) 42 | 43 | b.ResetTimer() 44 | b.ReportAllocs() 45 | 46 | for i := 0; i < b.N; i++ { 47 | _, _ = em.Fire("app.up", nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 inhere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: CodeLinter 2 | on: 3 | pull_request: 4 | paths: 5 | - 'go.mod' 6 | - '**.go' 7 | - '**.yml' 8 | push: 9 | paths: 10 | - '**.go' 11 | - 'go.mod' 12 | - '**.yml' 13 | 14 | jobs: 15 | 16 | test: 17 | name: Code linter 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Check out code 22 | uses: actions/checkout@v6 23 | 24 | - name: Setup Go Faster 25 | uses: WillAbides/setup-go-faster@v1.14.0 26 | timeout-minutes: 3 27 | with: 28 | go-version: "*" 29 | 30 | - name: Revive lint check 31 | uses: docker://morphy/revive-action:v2 32 | with: 33 | # Exclude patterns, separated by semicolons (optional) 34 | exclude: "./_examples/...;./testdata/..." 35 | 36 | - name: Run static check 37 | uses: reviewdog/action-staticcheck@v1 38 | if: ${{ github.event_name == 'pull_request'}} 39 | with: 40 | github_token: ${{ secrets.github_token }} 41 | # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review]. 42 | reporter: github-pr-check 43 | # Report all results. [added,diff_context,file,nofilter]. 44 | filter_mode: added 45 | # Exit with 1 when it find at least one finding. 46 | fail_on_error: true 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Tag-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | release: 10 | name: Release new version 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | strategy: 14 | fail-fast: true 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Setup ENV 23 | # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 24 | run: | 25 | echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV 26 | echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV 27 | 28 | - name: Generate changelog 29 | run: | 30 | curl https://github.com/gookit/gitw/releases/latest/download/chlog-linux-amd64 -L -o /usr/local/bin/chlog 31 | chmod a+x /usr/local/bin/chlog 32 | chlog -c .github/changelog.yml -o changelog.md prev last 33 | 34 | # https://github.com/softprops/action-gh-release 35 | - name: Create release and upload assets 36 | uses: softprops/action-gh-release@v2 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | name: ${{ env.RELEASE_TAG }} 41 | tag_name: ${{ env.RELEASE_TAG }} 42 | body_path: changelog.md 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | # files: macos-chlog.exe 45 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Unit-Tests 2 | on: 3 | pull_request: 4 | paths: 5 | - 'go.mod' 6 | - '**.go' 7 | - '**.yml' 8 | push: 9 | paths: 10 | - 'go.mod' 11 | - '**.go' 12 | - '**.yml' 13 | 14 | jobs: 15 | 16 | test: 17 | name: Test on go ${{ matrix.go_version }} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | go_version: [1.19, 1.21, 1.22, 1.23, 1.24, 1.25] 22 | 23 | steps: 24 | - name: Check out code 25 | uses: actions/checkout@v6 26 | # https://github.com/actions/setup-go 27 | - name: Use Go ${{ matrix.go_version }} 28 | timeout-minutes: 3 29 | uses: actions/setup-go@v6 30 | with: 31 | go-version: ${{ matrix.go_version }} 32 | 33 | - name: Run unit tests 34 | # run: go test -v -cover ./... 35 | # must add " for profile.cov on Windows OS 36 | run: go test -coverprofile="profile.cov" ./... 37 | 38 | - name: Send coverage 39 | uses: shogo82148/actions-goveralls@v1 40 | with: 41 | path-to-profile: profile.cov 42 | flag-name: Go-${{ matrix.go_version }} 43 | parallel: true 44 | shallow: true # 忽略请求报错 45 | 46 | # notifies that all test jobs are finished. 47 | # https://github.com/shogo82148/actions-goveralls 48 | finish: 49 | needs: test 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: shogo82148/actions-goveralls@v1 53 | with: 54 | shallow: true 55 | parallel-finished: true 56 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // MatchNodePath check for a string. 12 | // 13 | // From the gookit/goutil/strutil.MatchNodePath() 14 | // 15 | // Use on pattern: 16 | // - `*` match any to sep 17 | // - `**` match any to end. only allow at start or end on pattern. 18 | func matchNodePath(pattern, s string, sep string) bool { 19 | if pattern == Wildcard { 20 | return true 21 | } 22 | 23 | if i := strings.Index(pattern, AllNode); i >= 0 { 24 | if i == 0 { // at start 25 | return strings.HasSuffix(s, pattern[2:]) 26 | } 27 | return strings.HasPrefix(s, pattern[:len(pattern)-2]) 28 | } 29 | 30 | // eg: "eve.some.*.*" -> match "eve.some.thing.run" "eve.some.thing.do" 31 | pattern = strings.Replace(pattern, sep, "/", -1) 32 | s = strings.Replace(s, sep, "/", -1) 33 | ok, err := path.Match(pattern, s) 34 | if err != nil { 35 | ok = false 36 | } 37 | return ok 38 | } 39 | 40 | // regex for check good event name. 41 | var goodNameReg = regexp.MustCompile(`^[a-zA-Z][\w-.*]*$`) 42 | 43 | // goodName check event name is valid. 44 | func goodName(name string, isReg bool) string { 45 | name, err := goodNameOrErr(name, isReg) 46 | if err != nil { 47 | panic(err) 48 | } 49 | return name 50 | } 51 | 52 | // goodNameOrErr check event name is valid. 53 | func goodNameOrErr(name string, isReg bool) (string, error) { 54 | name = strings.TrimSpace(name) 55 | if name == "" { 56 | return "", errors.New("event: the event name cannot be empty") 57 | } 58 | 59 | // on add listener 60 | if isReg { 61 | if name == AllNode || name == Wildcard { 62 | return Wildcard, nil 63 | } 64 | if strings.HasPrefix(name, AllNode) { 65 | return name, nil 66 | } 67 | } 68 | 69 | if !goodNameReg.MatchString(name) { 70 | return name, errors.New(`event: name is invalid, must match regex:` + goodNameReg.String()) 71 | } 72 | return name, nil 73 | } 74 | 75 | func panicf(format string, args ...any) { 76 | panic(fmt.Sprintf(format, args...)) 77 | } 78 | -------------------------------------------------------------------------------- /simpleevent/all_test.go: -------------------------------------------------------------------------------- 1 | package simpleevent 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/gookit/goutil/testutil/assert" 9 | ) 10 | 11 | func TestEvent(t *testing.T) { 12 | buf := new(bytes.Buffer) 13 | 14 | assert.Panics(t, func() { 15 | On(" ", func(e *EventData) error { 16 | return nil 17 | }) 18 | }) 19 | 20 | On("n1", func(e *EventData) error { 21 | _, _ = fmt.Fprintf(buf, "event: %s", e.Name()) 22 | return nil 23 | }) 24 | 25 | On("n2", func(e *EventData) error { 26 | return fmt.Errorf("an error") 27 | }) 28 | 29 | assert.True(t, Has("n1")) 30 | assert.False(t, Has("not-exist")) 31 | 32 | assert.NotEmpty(t, DefaultEM.GetEventHandlers("n1")) 33 | assert.Contains(t, DefaultEM.EventHandlers(), "n1") 34 | 35 | assert.NoError(t, Fire("n1")) 36 | assert.Equal(t, "event: n1", buf.String()) 37 | buf.Reset() 38 | 39 | // add a wildcard handler 40 | On(Wildcard, func(e *EventData) error { 41 | _, _ = fmt.Fprintf(buf, ", W-event: %s", e.Name()) 42 | return nil 43 | }) 44 | assert.NoError(t, Fire("n1")) 45 | assert.NoError(t, Fire("not-exist")) 46 | assert.Equal(t, "event: n1, W-event: n1", buf.String()) 47 | buf.Reset() 48 | 49 | assert.Panics(t, func() { 50 | MustFire("n2") 51 | }) 52 | 53 | assert.Contains(t, DefaultEM.String(), "n1 handlers:") 54 | assert.Equal(t, 1, DefaultEM.EventNames()["n1"]) 55 | assert.NotContains(t, "not-exist", DefaultEM.EventNames()) 56 | 57 | assert.True(t, Has("n2")) 58 | DefaultEM.ClearHandlers("n2") 59 | assert.False(t, Has("n2")) 60 | 61 | DefaultEM.Clear() 62 | assert.False(t, Has("n1")) 63 | } 64 | 65 | func TestEventData_Abort(t *testing.T) { 66 | DefaultEM.Clear() 67 | buf := new(bytes.Buffer) 68 | 69 | // stop by Abort 70 | On("n1", func(e *EventData) error { 71 | buf.WriteString("handler0;") 72 | e.Abort() // abort 73 | return nil 74 | }) 75 | On("n1", func(e *EventData) error { 76 | buf.WriteString("handler1;") 77 | return nil 78 | }) 79 | 80 | assert.NoError(t, Fire("n1")) 81 | assert.Equal(t, "handler0;", buf.String()) 82 | 83 | // clear buffer 84 | buf.Reset() 85 | 86 | // stop by return error 87 | On("n2", func(e *EventData) error { 88 | buf.WriteString("handler0;") 89 | return fmt.Errorf("an error") 90 | }) 91 | On("n2", func(e *EventData) error { 92 | buf.WriteString("handler1;") 93 | return nil 94 | }) 95 | 96 | assert.Error(t, Fire("n2")) 97 | assert.Equal(t, "handler0;", buf.String()) 98 | } 99 | -------------------------------------------------------------------------------- /event_test.go: -------------------------------------------------------------------------------- 1 | package event_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/gookit/event" 10 | "github.com/gookit/goutil/testutil/assert" 11 | ) 12 | 13 | // Thread-safe buffer for testing 14 | type safeBuffer struct { 15 | bytes.Buffer 16 | mu sync.Mutex 17 | } 18 | 19 | func (sb *safeBuffer) Write(p []byte) (n int, err error) { 20 | sb.mu.Lock() 21 | defer sb.mu.Unlock() 22 | return sb.Buffer.Write(p) 23 | } 24 | 25 | type testListener struct { 26 | userData string 27 | } 28 | 29 | func (l *testListener) Handle(e event.Event) error { 30 | if ret := e.Get("result"); ret != nil { 31 | str := ret.(string) + fmt.Sprintf(" -> %s(%s)", e.Name(), l.userData) 32 | e.Set("result", str) 33 | } else { 34 | e.Set("result", fmt.Sprintf("handled: %s(%s)", e.Name(), l.userData)) 35 | } 36 | return nil 37 | } 38 | 39 | type testSubscriber struct { 40 | // ooo 41 | } 42 | 43 | func (s *testSubscriber) SubscribedEvents() map[string]any { 44 | return map[string]any{ 45 | "e1": event.ListenerFunc(s.e1Handler), 46 | "e2": event.ListenerItem{ 47 | Priority: event.AboveNormal, 48 | Listener: event.ListenerFunc(func(e event.Event) error { 49 | return fmt.Errorf("an error") 50 | }), 51 | }, 52 | "e3": &testListener{}, 53 | } 54 | } 55 | 56 | func (s *testSubscriber) e1Handler(e event.Event) error { 57 | e.Set("e1-key", "val1") 58 | return nil 59 | } 60 | 61 | type testSubscriber2 struct{} 62 | 63 | func (s testSubscriber2) SubscribedEvents() map[string]any { 64 | return map[string]any{ 65 | "e1": "invalid", 66 | } 67 | } 68 | 69 | type testEvent struct { 70 | event.ContextTrait 71 | name string 72 | data map[string]any 73 | abort bool 74 | } 75 | 76 | func (t *testEvent) Name() string { 77 | return t.name 78 | } 79 | 80 | func (t *testEvent) Get(key string) any { 81 | return t.data[key] 82 | } 83 | 84 | func (t *testEvent) Set(key string, val any) { 85 | t.data[key] = val 86 | } 87 | 88 | func (t *testEvent) Add(key string, val any) { 89 | t.data[key] = val 90 | } 91 | 92 | func (t *testEvent) Data() map[string]any { 93 | return t.data 94 | } 95 | 96 | func (t *testEvent) SetData(m event.M) event.Event { 97 | t.data = m 98 | return t 99 | } 100 | 101 | func (t *testEvent) Abort(b bool) { 102 | t.abort = b 103 | } 104 | 105 | func (t *testEvent) IsAborted() bool { 106 | return t.abort 107 | } 108 | 109 | func TestEvent(t *testing.T) { 110 | e := &event.BasicEvent{} 111 | e.SetName("n1") 112 | e.SetData(event.M{ 113 | "arg0": "val0", 114 | }) 115 | e.SetTarget("tgt") 116 | 117 | e.Add("arg1", "val1") 118 | 119 | assert.False(t, e.IsAborted()) 120 | e.Abort(true) 121 | assert.True(t, e.IsAborted()) 122 | 123 | assert.Equal(t, "n1", e.Name()) 124 | assert.Equal(t, "tgt", e.Target()) 125 | assert.Contains(t, e.Data(), "arg1") 126 | assert.Equal(t, "val0", e.Get("arg0")) 127 | assert.Equal(t, nil, e.Get("not-exist")) 128 | 129 | e.Set("arg1", "new val") 130 | assert.Equal(t, "new val", e.Get("arg1")) 131 | 132 | e1 := &event.BasicEvent{} 133 | e1.Set("k", "v") 134 | assert.Equal(t, "v", e1.Get("k")) 135 | // assert.NotEmpty(t, e1.Clone()) 136 | // assert.NotNil(t, e1.Context()) 137 | } 138 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | // There are some default priority constants 9 | const ( 10 | Min = -300 11 | Low = -200 12 | BelowNormal = -100 13 | Normal = 0 14 | AboveNormal = 100 15 | High = 200 16 | Max = 300 17 | ) 18 | 19 | /************************************************************* 20 | * region Listener 21 | *************************************************************/ 22 | 23 | // Listener interface 24 | type Listener interface { 25 | Handle(e Event) error 26 | } 27 | 28 | // ListenerFunc func definition. 29 | type ListenerFunc func(e Event) error 30 | 31 | // Handle event. implements the Listener interface 32 | func (fn ListenerFunc) Handle(e Event) error { 33 | return fn(e) 34 | } 35 | 36 | // Subscriber event subscriber interface. 37 | // 38 | // you can register multi event listeners in a struct func. 39 | type Subscriber interface { 40 | // SubscribedEvents register event listeners 41 | // 42 | // - key: is event name. eg "user.created" "user.*" "user.**" 43 | // - value: can be Listener or ListenerItem interface 44 | SubscribedEvents() map[string]any 45 | } 46 | 47 | // ListenerItem storage a event listener and it's priority value. 48 | type ListenerItem struct { 49 | Priority int 50 | Listener Listener 51 | } 52 | 53 | /************************************************************* 54 | * Listener Queue 55 | *************************************************************/ 56 | 57 | // ListenerQueue storage sorted Listener instance. 58 | type ListenerQueue struct { 59 | items []*ListenerItem 60 | } 61 | 62 | // Len get items length 63 | func (lq *ListenerQueue) Len() int { 64 | return len(lq.items) 65 | } 66 | 67 | // IsEmpty get items length == 0 68 | func (lq *ListenerQueue) IsEmpty() bool { 69 | return len(lq.items) == 0 70 | } 71 | 72 | // Push get items length 73 | func (lq *ListenerQueue) Push(li *ListenerItem) *ListenerQueue { 74 | lq.items = append(lq.items, li) 75 | return lq 76 | } 77 | 78 | // Sort the queue items by ListenerItem's priority. 79 | // 80 | // Priority: 81 | // 82 | // High > Low 83 | func (lq *ListenerQueue) Sort() *ListenerQueue { 84 | // if lq.IsEmpty() { 85 | // return lq 86 | // } 87 | ls := ByPriorityItems(lq.items) 88 | 89 | // check items is sorted 90 | if !sort.IsSorted(ls) { 91 | sort.Sort(ls) 92 | } 93 | 94 | return lq 95 | } 96 | 97 | // Items get all ListenerItem 98 | func (lq *ListenerQueue) Items() []*ListenerItem { 99 | return lq.items 100 | } 101 | 102 | // Remove a listener from the queue 103 | func (lq *ListenerQueue) Remove(listener Listener) { 104 | if listener == nil { 105 | return 106 | } 107 | 108 | // unsafe.Pointer(listener) 109 | ptrVal := getListenCompareKey(listener) 110 | 111 | var newItems []*ListenerItem 112 | for _, li := range lq.items { 113 | liPtrVal := getListenCompareKey(li.Listener) 114 | if liPtrVal == ptrVal { 115 | continue 116 | } 117 | 118 | newItems = append(newItems, li) 119 | } 120 | 121 | lq.items = newItems 122 | } 123 | 124 | // Clear all listeners 125 | func (lq *ListenerQueue) Clear() { 126 | lq.items = lq.items[:0] 127 | } 128 | 129 | // getListenCompareKey get listener compare key 130 | func getListenCompareKey(src Listener) reflect.Value { 131 | return reflect.ValueOf(src) 132 | } 133 | 134 | /************************************************************* 135 | * Sorted PriorityItems 136 | *************************************************************/ 137 | 138 | // ByPriorityItems type. implements the sort.Interface 139 | type ByPriorityItems []*ListenerItem 140 | 141 | // Len get items length 142 | func (ls ByPriorityItems) Len() int { 143 | return len(ls) 144 | } 145 | 146 | // Less implements the sort.Interface.Less. 147 | func (ls ByPriorityItems) Less(i, j int) bool { 148 | return ls[i].Priority > ls[j].Priority 149 | } 150 | 151 | // Swap implements the sort.Interface.Swap. 152 | func (ls ByPriorityItems) Swap(i, j int) { 153 | ls[i], ls[j] = ls[j], ls[i] 154 | } 155 | -------------------------------------------------------------------------------- /std.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // std default event manager 8 | var std = NewManager("default") 9 | 10 | // Std get default event manager 11 | func Std() *Manager { return std } 12 | 13 | // Config set default event manager options 14 | func Config(fn ...OptionFn) { std.WithOptions(fn...) } 15 | 16 | /************************************************************* 17 | * region Listener 18 | *************************************************************/ 19 | 20 | // On register a listener to the event. alias of Listen() 21 | func On(name string, listener Listener, priority ...int) { 22 | std.On(name, listener, priority...) 23 | } 24 | 25 | // Once register a listener to the event. trigger once 26 | func Once(name string, listener Listener, priority ...int) { 27 | std.Once(name, listener, priority...) 28 | } 29 | 30 | // Listen register a listener to the event 31 | func Listen(name string, listener Listener, priority ...int) { 32 | std.Listen(name, listener, priority...) 33 | } 34 | 35 | // Subscribe register a listener to the event 36 | func Subscribe(sbr Subscriber) { std.Subscribe(sbr) } 37 | 38 | // AddSubscriber register a listener to the event 39 | func AddSubscriber(sbr Subscriber) { std.AddSubscriber(sbr) } 40 | 41 | // HasListeners has listeners for the event name. 42 | func HasListeners(name string) bool { return std.HasListeners(name) } 43 | 44 | // Reset the default event manager 45 | func Reset() { std.Clear() } 46 | 47 | // CloseWait close chan and wait for all async events done. 48 | func CloseWait() error { return std.CloseWait() } 49 | 50 | /************************************************************* 51 | * region Trigger 52 | *************************************************************/ 53 | 54 | // AsyncFire simple async fire event by 'go' keywords 55 | func AsyncFire(e Event) { std.AsyncFire(e) } 56 | 57 | // Async fire event by channel 58 | func Async(name string, params M) { std.Async(name, params) } 59 | 60 | // FireAsync fire event by channel 61 | func FireAsync(e Event) { std.FireAsync(e) } 62 | 63 | // FireAsyncCtx async fire event by go channel, and with context TODO need? 64 | // func FireAsyncCtx(ctx context.Context, e Event) 65 | 66 | // Trigger alias of Fire 67 | func Trigger(name string, params M) (error, Event) { return std.Fire(name, params) } 68 | 69 | // Fire listeners by name. 70 | func Fire(name string, params M) (error, Event) { return std.Fire(name, params) } 71 | 72 | // FireCtx listeners by name with context. 73 | func FireCtx(ctx context.Context, name string, params M) (error, Event) { 74 | return std.FireCtx(ctx, name, params) 75 | } 76 | 77 | // FireEvent fire listeners by Event instance. 78 | func FireEvent(e Event) error { return std.FireEvent(e) } 79 | 80 | // FireEventCtx fire listeners by Event instance with context. 81 | func FireEventCtx(ctx context.Context, e Event) error { return std.FireEventCtx(ctx, e) } 82 | 83 | // TriggerEvent alias of FireEvent 84 | func TriggerEvent(e Event) error { return std.FireEvent(e) } 85 | 86 | // MustFire fire event by name. will panic on error 87 | func MustFire(name string, params M) Event { return std.MustFire(name, params) } 88 | 89 | // MustTrigger alias of MustFire 90 | func MustTrigger(name string, params M) Event { return std.MustFire(name, params) } 91 | 92 | // FireBatch fire multi event at once. 93 | func FireBatch(es ...any) []error { return std.FireBatch(es...) } 94 | 95 | /************************************************************* 96 | * region Event 97 | *************************************************************/ 98 | 99 | // AddEvent add a pre-defined event. 100 | func AddEvent(e Event) error { return std.AddEvent(e) } 101 | 102 | // AddEventFc add a pre-defined event factory func to manager. 103 | func AddEventFc(name string, fc FactoryFunc) error { return std.AddEventFc(name, fc) } 104 | 105 | // GetEvent get event by name. 106 | func GetEvent(name string) (Event, bool) { return std.GetEvent(name) } 107 | 108 | // HasEvent has event check. 109 | func HasEvent(name string) bool { return std.HasEvent(name) } 110 | -------------------------------------------------------------------------------- /simpleevent/manager.go: -------------------------------------------------------------------------------- 1 | package simpleevent 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | // Wildcard event name 10 | const Wildcard = "*" 11 | 12 | // HandlerFunc event handler func define 13 | type HandlerFunc func(e *EventData) error 14 | 15 | /************************************************************* 16 | * Event Manager 17 | *************************************************************/ 18 | 19 | // EventManager struct 20 | type EventManager struct { 21 | pool sync.Pool 22 | names map[string]int 23 | // storage event handlers 24 | handlers map[string][]HandlerFunc 25 | } 26 | 27 | // NewEventManager create EventManager instance 28 | func NewEventManager() *EventManager { 29 | em := &EventManager{ 30 | names: make(map[string]int), 31 | handlers: make(map[string][]HandlerFunc), 32 | } 33 | 34 | // set pool creator 35 | em.pool.New = func() any { 36 | return &EventData{} 37 | } 38 | 39 | return em 40 | } 41 | 42 | // On register a event handler 43 | func (em *EventManager) On(name string, handler HandlerFunc) { 44 | name = strings.TrimSpace(name) 45 | if name == "" { 46 | panic("event name cannot be empty") 47 | } 48 | 49 | if ls, ok := em.handlers[name]; ok { 50 | em.names[name]++ 51 | em.handlers[name] = append(ls, handler) 52 | } else { // first add. 53 | em.names[name] = 1 54 | em.handlers[name] = []HandlerFunc{handler} 55 | } 56 | } 57 | 58 | // MustFire fire handlers by name. will panic on error 59 | func (em *EventManager) MustFire(name string, args ...any) { 60 | err := em.Fire(name, args...) 61 | if err != nil { 62 | panic(err) 63 | } 64 | } 65 | 66 | // Fire handlers by name 67 | func (em *EventManager) Fire(name string, args ...any) (err error) { 68 | handlers, ok := em.handlers[name] 69 | if !ok { 70 | return 71 | } 72 | 73 | e := em.pool.Get().(*EventData) 74 | e.init(name, args) 75 | 76 | // call event handlers 77 | err = em.doFire(e, handlers) 78 | 79 | e.reset() 80 | em.pool.Put(e) 81 | return 82 | } 83 | 84 | func (em *EventManager) doFire(e *EventData, handlers []HandlerFunc) (err error) { 85 | err = em.callHandlers(e, handlers) 86 | if err != nil || e.IsAborted() { 87 | return 88 | } 89 | 90 | // group listen "app.*" 91 | // groupName := 92 | 93 | // Wildcard event handler 94 | if em.HasEvent(Wildcard) { 95 | err = em.callHandlers(e, em.handlers[Wildcard]) 96 | } 97 | 98 | return 99 | } 100 | 101 | func (em *EventManager) callHandlers(e *EventData, handlers []HandlerFunc) (err error) { 102 | for _, handler := range handlers { 103 | err = handler(e) 104 | if err != nil || e.IsAborted() { 105 | return 106 | } 107 | } 108 | return 109 | } 110 | 111 | // HasEvent has event check 112 | func (em *EventManager) HasEvent(name string) bool { 113 | _, ok := em.names[name] 114 | return ok 115 | } 116 | 117 | // GetEventHandlers get handlers and handlers by name 118 | func (em *EventManager) GetEventHandlers(name string) (es []HandlerFunc) { 119 | es, _ = em.handlers[name] 120 | return 121 | } 122 | 123 | // EventHandlers get all event handlers 124 | func (em *EventManager) EventHandlers() map[string][]HandlerFunc { 125 | return em.handlers 126 | } 127 | 128 | // EventNames get all event names 129 | func (em *EventManager) EventNames() map[string]int { 130 | return em.names 131 | } 132 | 133 | // String convert to string. 134 | func (em *EventManager) String() string { 135 | buf := new(bytes.Buffer) 136 | for name, hs := range em.handlers { 137 | buf.WriteString(name + " handlers:\n ") 138 | for _, h := range hs { 139 | buf.WriteString(funcName(h)) 140 | } 141 | buf.WriteString("\n") 142 | } 143 | return buf.String() 144 | } 145 | 146 | // ClearHandlers clear handlers by name 147 | func (em *EventManager) ClearHandlers(name string) bool { 148 | _, ok := em.names[name] 149 | if ok { 150 | delete(em.names, name) 151 | delete(em.handlers, name) 152 | } 153 | return ok 154 | } 155 | 156 | // Clear all handlers info. 157 | func (em *EventManager) Clear() { 158 | em.names = map[string]int{} 159 | em.handlers = map[string][]HandlerFunc{} 160 | } 161 | -------------------------------------------------------------------------------- /listener_remove_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gookit/goutil/testutil/assert" 7 | ) 8 | 9 | type globalTestVal struct { 10 | n int 11 | sum int 12 | } 13 | type testListenerCalc struct { 14 | bind int 15 | owner *globalTestVal 16 | } 17 | 18 | func (l testListenerCalc) Handle(_ Event) error { 19 | l.owner.n++ 20 | l.owner.sum += l.bind 21 | return nil 22 | } 23 | 24 | func Test_RemoveListener(t *testing.T) { 25 | t.Run("make func", func(t *testing.T) { 26 | global := &globalTestVal{} 27 | makeFn := func(a int) ListenerFunc { 28 | return func(e Event) error { 29 | global.n++ 30 | global.sum += a 31 | return nil 32 | } 33 | } 34 | 35 | evBus := NewManager("") 36 | const evName = "ev1" 37 | 38 | f1 := makeFn(11) 39 | f2 := makeFn(22) 40 | f3 := makeFn(33) 41 | p4 := &testListenerCalc{bind: 44, owner: global} 42 | p5 := &testListenerCalc{bind: 55, owner: global} 43 | p6 := &testListenerCalc{bind: 66, owner: global} 44 | 45 | evBus.On(evName, f1) 46 | evBus.On(evName, f2) 47 | evBus.On(evName, f3) 48 | evBus.On(evName, p4) 49 | evBus.On(evName, p5) 50 | evBus.On(evName, p6) 51 | 52 | evBus.MustTrigger(evName, nil) 53 | assert.Equal(t, global.n, 6) 54 | assert.Equal(t, global.sum, 231) // 11+22+33+44+55+66=231 55 | 56 | evBus.RemoveListener(evName, f2) 57 | evBus.RemoveListener(evName, p5) 58 | evBus.MustFire(evName, nil) 59 | assert.Equal(t, global.n, 6+4) 60 | assert.Equal(t, global.sum, 385) // 231+11+33+44+66=385 61 | 62 | evBus.RemoveListener(evName, f1) 63 | evBus.RemoveListener(evName, f1) // not exist function. 64 | evBus.MustFire(evName, nil) 65 | assert.Equal(t, global.n, 6+4+3) 66 | assert.Equal(t, global.sum, 528) // 385+33+44+66=528 67 | 68 | evBus.RemoveListener(evName, p6) 69 | evBus.RemoveListener(evName, p6) // not exist function. 70 | evBus.MustFire(evName, nil) 71 | assert.Equal(t, global.n, 6+4+3+2) 72 | assert.Equal(t, global.sum, 605) // 528+33+44=605 73 | }) 74 | 75 | t.Run("same value struct", func(t *testing.T) { 76 | global := &globalTestVal{} 77 | f1 := testListenerCalc{bind: 11, owner: global} 78 | f2 := testListenerCalc{bind: 22, owner: global} 79 | f2same := testListenerCalc{bind: 22, owner: global} 80 | f2copy := f2 // testListenerCalc{bind: 22, owner: global} 81 | 82 | evBus := NewManager("") 83 | const evName = "ev1" 84 | assert.Panics(t, func() { 85 | evBus.On(evName, f1) 86 | evBus.On(evName, f2) 87 | evBus.On(evName, f2same) 88 | evBus.On(evName, f2copy) 89 | }) 90 | 91 | evBus.MustFire(evName, nil) 92 | assert.Equal(t, global.n, 0) 93 | assert.Equal(t, global.sum, 0) // 11+22+22+22=77 94 | 95 | evBus.RemoveListener(evName, f1) 96 | evBus.MustFire(evName, nil) 97 | assert.Equal(t, global.n, 0) 98 | assert.Equal(t, global.sum, 0) // 77+22+22+22=143 99 | 100 | evBus.RemoveListener(evName, f2) 101 | evBus.MustFire(evName, nil) 102 | assert.Equal(t, global.n, 0) 103 | assert.Equal(t, global.sum, 0) 104 | }) 105 | 106 | t.Run("same func", func(t *testing.T) { 107 | global := &globalStatic 108 | 109 | f1 := ListenerFunc(testFuncCalc1) 110 | f2 := ListenerFunc(testFuncCalc2) 111 | f2same := ListenerFunc(testFuncCalc2) 112 | f2copy := f2 113 | 114 | evBus := NewManager("") 115 | const evName = "ev1" 116 | evBus.On(evName, f1) 117 | evBus.On(evName, f2) 118 | evBus.On(evName, f2same) 119 | evBus.On(evName, f2copy) 120 | 121 | evBus.MustFire(evName, nil) 122 | assert.Equal(t, global.n, 4) 123 | assert.Equal(t, global.sum, 77) // 11+22+22+22=77 124 | 125 | evBus.RemoveListener(evName, f1) 126 | evBus.MustFire(evName, nil) 127 | assert.Equal(t, global.n, 7) 128 | assert.Equal(t, global.sum, 143) // 77+22+22+22=143 129 | 130 | evBus.RemoveListener(evName, f2) 131 | evBus.MustFire(evName, nil) 132 | assert.Equal(t, global.n, 7) 133 | assert.Equal(t, global.sum, 143) // 134 | }) 135 | } 136 | 137 | var globalStatic = globalTestVal{} 138 | 139 | func testFuncCalc1(_ Event) error { 140 | globalStatic.n++ 141 | globalStatic.sum += 11 142 | return nil 143 | } 144 | func testFuncCalc2(_ Event) error { 145 | globalStatic.n++ 146 | globalStatic.sum += 22 147 | return nil 148 | } 149 | 150 | // / 151 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | // Package event is lightweight event manager and dispatcher implements by Go. 2 | package event 3 | 4 | import ( 5 | "context" 6 | ) 7 | 8 | // wildcard event name 9 | const ( 10 | Wildcard = "*" 11 | AnyNode = "*" 12 | AllNode = "**" 13 | ) 14 | 15 | const ( 16 | // ModeSimple old mode, simple match group listener. 17 | // 18 | // - "*" only allow one and must at end 19 | // 20 | // Support: "user.*" -> match "user.created" "user.updated" 21 | ModeSimple uint8 = iota 22 | 23 | // ModePath path mode. 24 | // 25 | // - "*" matches any sequence of non . characters (like at path.Match()) 26 | // - "**" match all characters to end, only allow at start or end on pattern. 27 | // 28 | // Support like this: 29 | // "eve.some.*.*" -> match "eve.some.thing.run" "eve.some.thing.do" 30 | // "eve.some.*.run" -> match "eve.some.thing.run", but not match "eve.some.thing.do" 31 | // "eve.some.**" -> match any start with "eve.some.". eg: "eve.some.thing.run" "eve.some.thing.do" 32 | // "**.thing.run" -> match any ends with ".thing.run". eg: "eve.some.thing.run" 33 | ModePath 34 | ) 35 | 36 | // M is short name for map[string]... 37 | type M = map[string]any 38 | 39 | // ManagerFace event manager interface 40 | type ManagerFace interface { 41 | // AddEvent events: add event 42 | AddEvent(Event) error 43 | // On listeners: add listeners 44 | On(name string, listener Listener, priority ...int) 45 | // Fire event 46 | Fire(name string, params M) (error, Event) 47 | } 48 | 49 | // Options event manager config options 50 | type Options struct { 51 | // EnableLock enable lock on fire event. default is False. 52 | EnableLock bool 53 | // ChannelSize for fire events by goroutine. default: 100 54 | ChannelSize int 55 | // ConsumerNum for fire events by goroutine. default: 3 56 | ConsumerNum int 57 | // MatchMode event name match mode. default is ModeSimple 58 | MatchMode uint8 59 | } 60 | 61 | // OptionFn event manager config option func 62 | type OptionFn func(o *Options) 63 | 64 | // UsePathMode set event name match mode to ModePath 65 | func UsePathMode(o *Options) { o.MatchMode = ModePath } 66 | 67 | // WithChannelSize set channel size for async fire event. 68 | func WithChannelSize(size int) OptionFn { 69 | return func(o *Options) { 70 | o.ChannelSize = size 71 | } 72 | } 73 | 74 | // WithConsumerNum set consumer num for async fire 75 | func WithConsumerNum(num int) OptionFn { 76 | return func(o *Options) { 77 | o.ConsumerNum = num 78 | } 79 | } 80 | 81 | // EnableLock enable lock on fire event. 82 | func EnableLock(enable bool) OptionFn { 83 | return func(o *Options) { 84 | o.EnableLock = enable 85 | } 86 | } 87 | 88 | // Event interface 89 | type Event interface { 90 | Name() string 91 | Get(key string) any 92 | Set(key string, val any) 93 | Add(key string, val any) 94 | Data() map[string]any 95 | SetData(M) Event 96 | Abort(bool) 97 | IsAborted() bool 98 | } 99 | 100 | // Cloneable interface. event can be cloned. 101 | // 102 | // Check and convert: 103 | // 104 | // if ec, ok := e.(Cloneable); ok {} 105 | type Cloneable interface { 106 | Event 107 | Clone() Event 108 | } 109 | 110 | // ContextAble context-able event interface 111 | // 112 | // Check and convert in listener: 113 | // 114 | // if ec, ok := e.(ContextAble); ok {} 115 | type ContextAble interface { 116 | Event 117 | Context() context.Context 118 | WithContext(ctx context.Context) 119 | } 120 | 121 | // FactoryFunc for create event instance. 122 | type FactoryFunc func() Event 123 | 124 | // BasicEvent a built-in implements Event interface 125 | type BasicEvent struct { 126 | // event name 127 | name string 128 | // user data. 129 | data map[string]any 130 | // target 131 | target any 132 | // mark is aborted 133 | aborted bool 134 | } 135 | 136 | // New create an event instance 137 | func New(name string, data M) *BasicEvent { return NewBasic(name, data) } 138 | 139 | // NewEvent create an event instance 140 | func NewEvent(name string, data M) *BasicEvent { return NewBasic(name, data) } 141 | 142 | // NewBasic new a basic event instance 143 | func NewBasic(name string, data M) *BasicEvent { 144 | if data == nil { 145 | data = make(map[string]any) 146 | } 147 | 148 | return &BasicEvent{ 149 | name: name, 150 | data: data, 151 | } 152 | } 153 | 154 | // Abort event loop exec 155 | func (e *BasicEvent) Abort(abort bool) { e.aborted = abort } 156 | 157 | // Fill event data 158 | func (e *BasicEvent) Fill(target any, data M) *BasicEvent { 159 | if data != nil { 160 | e.data = data 161 | } 162 | 163 | e.target = target 164 | return e 165 | } 166 | 167 | // AttachTo add current event to the event manager. 168 | func (e *BasicEvent) AttachTo(em ManagerFace) error { return em.AddEvent(e) } 169 | 170 | // Get data by index 171 | func (e *BasicEvent) Get(key string) any { 172 | if v, ok := e.data[key]; ok { 173 | return v 174 | } 175 | return nil 176 | } 177 | 178 | // Add value by key 179 | func (e *BasicEvent) Add(key string, val any) { 180 | if _, ok := e.data[key]; !ok { 181 | e.Set(key, val) 182 | } 183 | } 184 | 185 | // Set value by key 186 | func (e *BasicEvent) Set(key string, val any) { 187 | if e.data == nil { 188 | e.data = make(map[string]any) 189 | } 190 | e.data[key] = val 191 | } 192 | 193 | // Name get event name 194 | func (e *BasicEvent) Name() string { return e.name } 195 | 196 | // Data get all data 197 | func (e *BasicEvent) Data() map[string]any { return e.data } 198 | 199 | // IsAborted check. 200 | func (e *BasicEvent) IsAborted() bool { return e.aborted } 201 | 202 | // Target get target 203 | func (e *BasicEvent) Target() any { return e.target } 204 | 205 | // SetName set event name 206 | func (e *BasicEvent) SetName(name string) *BasicEvent { 207 | e.name = name 208 | return e 209 | } 210 | 211 | // SetData set data to the event 212 | func (e *BasicEvent) SetData(data M) Event { 213 | if data != nil { 214 | e.data = data 215 | } 216 | return e 217 | } 218 | 219 | // SetTarget set event target 220 | func (e *BasicEvent) SetTarget(target any) *BasicEvent { 221 | e.target = target 222 | return e 223 | } 224 | 225 | // ContextTrait event context trait 226 | type ContextTrait struct { 227 | // context 228 | ctx context.Context 229 | } 230 | 231 | // Context get context 232 | func (t *ContextTrait) Context() context.Context { 233 | if t.ctx == nil { 234 | return context.Background() 235 | } 236 | return t.ctx 237 | } 238 | 239 | // WithContext set context 240 | func (t *ContextTrait) WithContext(ctx context.Context) { 241 | t.ctx = ctx 242 | } 243 | 244 | // ContextEvent event with context 245 | type contextEvent struct { 246 | Event 247 | ContextTrait 248 | } 249 | 250 | func newContextEvent(ctx context.Context, e Event) ContextAble { 251 | return &contextEvent{Event: e, ContextTrait: ContextTrait{ctx: ctx}} 252 | } 253 | -------------------------------------------------------------------------------- /issues_test.go: -------------------------------------------------------------------------------- 1 | package event_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "sync/atomic" 8 | "testing" 9 | "time" 10 | 11 | "github.com/gookit/event" 12 | "github.com/gookit/goutil/testutil/assert" 13 | ) 14 | 15 | type testNotify struct{} 16 | 17 | func (notify *testNotify) Handle(_ event.Event) error { 18 | isRun = true 19 | return nil 20 | } 21 | 22 | var isRun = false 23 | 24 | // https://github.com/gookit/event/issues/8 25 | func TestIssue_8(t *testing.T) { 26 | notify := testNotify{} 27 | 28 | event.On("*", ¬ify) 29 | err, _ := event.Fire("test_notify", event.M{}) 30 | assert.Nil(t, err) 31 | assert.True(t, isRun) 32 | 33 | event.On("test_notify", ¬ify) 34 | err, _ = event.Fire("test_notify", event.M{}) 35 | assert.Nil(t, err) 36 | assert.True(t, isRun) 37 | } 38 | 39 | // https://github.com/gookit/event/issues/9 40 | func TestIssues_9(t *testing.T) { 41 | evBus := event.NewManager("") 42 | eName := "evt1" 43 | 44 | f1 := makeFn(11) 45 | evBus.On(eName, f1) 46 | 47 | f2 := makeFn(22) 48 | evBus.On(eName, f2) 49 | assert.Equal(t, 2, evBus.ListenersCount(eName)) 50 | 51 | f3 := event.ListenerFunc(func(e event.Event) error { 52 | // dump.Println(e.Name()) 53 | return nil 54 | }) 55 | evBus.On(eName, f3) 56 | assert.Equal(t, 3, evBus.ListenersCount(eName)) 57 | 58 | evBus.RemoveListener(eName, f1) // DON'T REMOVE ALL !!! 59 | assert.Equal(t, 2, evBus.ListenersCount(eName)) 60 | 61 | evBus.MustFire(eName, event.M{"arg0": "val0", "arg1": "val1"}) 62 | } 63 | 64 | func makeFn(a int) event.ListenerFunc { 65 | return func(e event.Event) error { 66 | e.Set("val", a) 67 | // dump.Println(a, e.Name()) 68 | return nil 69 | } 70 | } 71 | 72 | // https://github.com/gookit/event/issues/20 73 | func TestIssues_20(t *testing.T) { 74 | buf := new(bytes.Buffer) 75 | mgr := event.NewManager("test") 76 | 77 | handler := event.ListenerFunc(func(e event.Event) error { 78 | _, _ = fmt.Fprintf(buf, "%s-%s|", e.Name(), e.Get("user")) 79 | return nil 80 | }) 81 | 82 | mgr.On("app.user.*", handler) 83 | // ERROR: if not register "app.user.add", will not trigger "app.user.*" 84 | // mgr.On("app.user.add", handler) 85 | 86 | err, _ := mgr.Fire("app.user.add", event.M{"user": "INHERE"}) 87 | assert.NoError(t, err) 88 | assert.Equal(t, "app.user.add-INHERE|", buf.String()) 89 | 90 | // dump.P(buf.String()) 91 | } 92 | 93 | // https://github.com/gookit/event/issues/53 94 | // 我现在 在事件1里面, 触发启动事件2 是 启动不了的,貌似他会进入一个死循环进行卡死状态 95 | func TestIssues_53(t *testing.T) { 96 | buf := new(bytes.Buffer) 97 | mgr := event.NewManager("test") 98 | 99 | handler1 := event.ListenerFunc(func(e event.Event) error { 100 | _, _ = fmt.Fprintf(buf, "%s-%s|", e.Name(), e.Get("user")) 101 | // trigger event2 102 | err, _ := mgr.Fire("app.event2", event.M{"user": "INHERE"}) 103 | return err 104 | }) 105 | 106 | handler2 := event.ListenerFunc(func(e event.Event) error { 107 | _, _ = fmt.Fprintf(buf, "%s-%s|", e.Name(), e.Get("user")) 108 | return nil 109 | }) 110 | 111 | mgr.On("app.event1", handler1) 112 | mgr.On("app.event2", handler2) 113 | 114 | err, _ := mgr.Fire("app.event1", event.M{"user": "INHERE"}) 115 | assert.NoError(t, err) 116 | assert.StrContains(t, "app.event1-INHERE|app.event2-INHERE|", buf.String()) 117 | } 118 | 119 | // https://github.com/gookit/event/issues/61 120 | // prepare: 此时设置 ConsumerNum = 10, 每个任务耗时1s, 触发100个任务 121 | // expected: 10s左右执行完所有任务 122 | // actual: 执行了 100s左右 123 | func TestIssues_61(t *testing.T) { 124 | var em = event.NewManager("default", func(o *event.Options) { 125 | o.ConsumerNum = 10 126 | o.EnableLock = false 127 | }) 128 | defer em.MustCloseWait() 129 | 130 | var listener event.ListenerFunc = func(e event.Event) error { 131 | time.Sleep(1 * time.Second) 132 | fmt.Println("event received!", e.Name(), "index", e.Get("arg0")) 133 | return nil 134 | } 135 | 136 | em.On("app.evt1", listener, event.Normal) 137 | 138 | for i := 0; i < 20; i++ { 139 | em.FireAsync(event.New("app.evt1", event.M{"arg0": i})) 140 | } 141 | 142 | fmt.Println("publish event finished!") 143 | } 144 | 145 | // https://github.com/gookit/event/issues/67 我这有 投递了一个异步事件, 经常发现 这个事件执行了几次 后面都没 执行了 146 | func TestIssues_67(t *testing.T) { 147 | em := event.NewManager("test", event.WithConsumerNum(10), event.WithChannelSize(100)) 148 | 149 | var counter atomic.Int32 150 | em.On("new.member", event.ListenerFunc(func(e event.Event) error { 151 | counter.Add(1) 152 | fmt.Print(e.Get("memberId"), " ") 153 | return nil 154 | })) 155 | 156 | total := 200 157 | for i := 0; i < total; i++ { 158 | em.Async("new.member", event.M{"memberId": i + 1, "superiorId": "superior23"}) 159 | } 160 | em.MustCloseWait() // MUST wait all async event done 161 | 162 | fmt.Println("Total:", counter.Load()) 163 | assert.Eq(t, int32(total), counter.Load()) 164 | } 165 | 166 | type MyEventI68 struct { 167 | event.BasicEvent 168 | customData string 169 | } 170 | 171 | func (e *MyEventI68) CustomData() string { 172 | return e.customData 173 | } 174 | 175 | // https://github.com/gookit/event/issues/68 Custom events failed to execute 176 | func TestIssues_68(t *testing.T) { 177 | e := &MyEventI68{customData: "hello"} 178 | e.SetName("e1") 179 | event.Reset() 180 | assert.NoErr(t, event.AddEvent(e)) 181 | 182 | // add listener 183 | event.On("e1", event.ListenerFunc(func(e event.Event) error { 184 | fmt.Printf("custom Data: %s\n", e.(*MyEventI68).CustomData()) 185 | return nil 186 | })) 187 | 188 | // trigger 189 | err, e2 := event.Fire("e1", nil) 190 | assert.NoErr(t, err) 191 | assert.Eq(t, "e1", e2.Name()) 192 | } 193 | 194 | // https://github.com/gookit/event/issues/78 195 | // It is expected to support event passing context, timeout control, and log trace passing such as trace ID information. 196 | // 希望事件支持通过上下文,超时控制和日志跟踪传递,例如跟踪ID信息。 197 | func TestIssues_78(t *testing.T) { 198 | // Test with context 199 | ctx := context.Background() 200 | 201 | // Create a context with value (simulating trace ID) 202 | ctx = context.WithValue(ctx, "trace_id", "trace-12345") 203 | 204 | // Create a context with timeout 205 | ctx, cancel := context.WithTimeout(ctx, 1*time.Second) 206 | defer cancel() 207 | 208 | manager := event.NewManager("test") 209 | 210 | var traceID string 211 | var ctxErr error 212 | 213 | // Register event listener 214 | manager.On("app.test", event.ListenerFunc(func(e event.Event) error { 215 | ec, ok := e.(event.ContextAble) 216 | if !ok { 217 | return nil 218 | } 219 | 220 | // Get trace ID from context 221 | traceID, _ = ec.Context().Value("trace_id").(string) 222 | 223 | // Check if context is canceled 224 | select { 225 | case <-ec.Context().Done(): 226 | ctxErr = ec.Context().Err() 227 | return ctxErr 228 | default: 229 | } 230 | 231 | return nil 232 | })) 233 | 234 | // Test firing event with context 235 | err, _ := manager.FireCtx(ctx, "app.test", event.M{"key": "value"}) 236 | assert.NoError(t, err) 237 | assert.Equal(t, "trace-12345", traceID) 238 | assert.Nil(t, ctxErr) 239 | 240 | // Test with std 241 | stdTraceID := "" 242 | event.On("std.test", event.ListenerFunc(func(e event.Event) error { 243 | ec, ok := e.(event.ContextAble) 244 | if !ok { 245 | return nil 246 | } 247 | stdTraceID, _ = ec.Context().Value("trace_id").(string) 248 | return nil 249 | })) 250 | 251 | err, _ = event.FireCtx(ctx, "std.test", event.M{"key": "value"}) 252 | assert.NoError(t, err) 253 | assert.Equal(t, "trace-12345", stdTraceID) 254 | } 255 | -------------------------------------------------------------------------------- /std_test.go: -------------------------------------------------------------------------------- 1 | package event_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "testing" 8 | "time" 9 | 10 | "github.com/gookit/event" 11 | "github.com/gookit/goutil/testutil/assert" 12 | ) 13 | 14 | var emptyListener = func(e event.Event) error { 15 | return nil 16 | } 17 | 18 | func TestAddEvent(t *testing.T) { 19 | event.Reset() 20 | event.Std().RemoveEvents() 21 | 22 | // no name 23 | assert.Err(t, event.AddEvent(&event.BasicEvent{})) 24 | 25 | _, ok := event.GetEvent("evt1") 26 | assert.False(t, ok) 27 | 28 | // event.AddEvent 29 | e := event.NewBasic("evt1", event.M{"k1": "inhere"}) 30 | assert.NoErr(t, event.AddEvent(e)) 31 | // add by AttachTo 32 | err := event.NewBasic("evt2", nil).AttachTo(event.Std()) 33 | assert.NoError(t, err) 34 | 35 | assert.False(t, e.IsAborted()) 36 | assert.True(t, event.HasEvent("evt1")) 37 | assert.True(t, event.HasEvent("evt2")) 38 | assert.False(t, event.HasEvent("not-exist")) 39 | 40 | // event.GetEvent 41 | r1, ok := event.GetEvent("evt1") 42 | assert.True(t, ok) 43 | assert.Equal(t, e, r1) 44 | 45 | // RemoveEvent 46 | event.Std().RemoveEvent("evt2") 47 | assert.False(t, event.HasEvent("evt2")) 48 | 49 | // RemoveEvents 50 | event.Std().RemoveEvents() 51 | assert.False(t, event.HasEvent("evt1")) 52 | } 53 | 54 | func TestAddEventFc(t *testing.T) { 55 | // clear all 56 | event.Reset() 57 | err := event.AddEvent(&testEvent{ 58 | name: "evt1", 59 | }) 60 | assert.NoError(t, err) 61 | assert.True(t, event.HasEvent("evt1")) 62 | 63 | err = event.AddEventFc("test", func() event.Event { 64 | return event.NewBasic("test", nil) 65 | }) 66 | 67 | assert.NoError(t, err) 68 | assert.True(t, event.HasEvent("test")) 69 | } 70 | 71 | func TestFire(t *testing.T) { 72 | buf := new(bytes.Buffer) 73 | fn := func(e event.Event) error { 74 | _, _ = fmt.Fprintf(buf, "event: %s", e.Name()) 75 | return nil 76 | } 77 | 78 | event.On("evt1", event.ListenerFunc(fn), 0) 79 | event.On("evt1", event.ListenerFunc(emptyListener), event.High) 80 | assert.True(t, event.HasListeners("evt1")) 81 | 82 | err, e := event.Fire("evt1", nil) 83 | assert.NoError(t, err) 84 | assert.Equal(t, "evt1", e.Name()) 85 | assert.Equal(t, "event: evt1", buf.String()) 86 | 87 | err = event.NewBasic("evt2", nil).AttachTo(event.Std()) 88 | assert.NoError(t, err) 89 | 90 | event.On("evt2", event.ListenerFunc(func(e event.Event) error { 91 | assert.Equal(t, "evt2", e.Name()) 92 | assert.Equal(t, "v", e.Get("k")) 93 | return nil 94 | }), event.AboveNormal) 95 | 96 | assert.True(t, event.HasListeners("evt2")) 97 | err, e = event.Trigger("evt2", event.M{"k": "v"}) 98 | assert.NoError(t, err) 99 | assert.Equal(t, "evt2", e.Name()) 100 | assert.Equal(t, map[string]any{"k": "v"}, e.Data()) 101 | 102 | // clear all 103 | event.Reset() 104 | assert.False(t, event.HasListeners("evt1")) 105 | assert.False(t, event.HasListeners("evt2")) 106 | 107 | err, e = event.Fire("not-exist", nil) 108 | assert.NoError(t, err) 109 | assert.NotEmpty(t, e) 110 | } 111 | 112 | func TestAddSubscriber(t *testing.T) { 113 | event.Reset() 114 | event.AddSubscriber(&testSubscriber{}) 115 | 116 | assert.True(t, event.HasListeners("e1")) 117 | assert.True(t, event.HasListeners("e2")) 118 | assert.True(t, event.HasListeners("e3")) 119 | 120 | ers := event.FireBatch("e1", event.NewBasic("e2", nil)) 121 | assert.Len(t, ers, 1) 122 | 123 | assert.Panics(t, func() { 124 | event.Subscribe(testSubscriber2{}) 125 | }) 126 | } 127 | 128 | func TestFireEvent(t *testing.T) { 129 | event.Reset() 130 | 131 | buf := new(bytes.Buffer) 132 | evt1 := event.NewBasic("evt1", nil).Fill(nil, event.M{"n": "inhere"}) 133 | assert.NoErr(t, event.AddEvent(evt1)) 134 | 135 | assert.True(t, event.HasEvent("evt1")) 136 | assert.False(t, event.HasEvent("not-exist")) 137 | 138 | event.Listen("evt1", event.ListenerFunc(func(e event.Event) error { 139 | _, _ = fmt.Fprintf(buf, "event: %s, params: n=%s", e.Name(), e.Get("n")) 140 | return nil 141 | }), event.Normal) 142 | 143 | assert.True(t, event.HasListeners("evt1")) 144 | assert.False(t, event.HasListeners("not-exist")) 145 | 146 | // FireEvent 147 | err := event.FireEvent(evt1) 148 | assert.NoError(t, err) 149 | assert.Equal(t, "event: evt1, params: n=inhere", buf.String()) 150 | buf.Reset() 151 | 152 | // TriggerEvent 153 | err = event.TriggerEvent(evt1) 154 | assert.NoError(t, err) 155 | assert.Equal(t, "event: evt1, params: n=inhere", buf.String()) 156 | buf.Reset() 157 | 158 | // FireEventCtx 159 | var ctxVal any 160 | ctx := context.WithValue(context.Background(), "ctx1", "ctx-value1") 161 | event.Listen("evt2", event.ListenerFunc(func(e event.Event) error { 162 | ec, ok := e.(event.ContextAble) 163 | if ok { 164 | ctxVal = ec.Context().Value("ctx1") 165 | } 166 | return nil 167 | })) 168 | 169 | evt2 := event.NewEvent("evt2", event.M{"name": "inhere"}) 170 | err = event.FireEventCtx(ctx, evt2) 171 | assert.NoError(t, err) 172 | assert.Equal(t, "ctx-value1", ctxVal) 173 | // ce, ok := evt2.(event.ContextAble) 174 | // assert.Equal(t, "ctx-value1", evt2.Context().Value("ctx1")) 175 | buf.Reset() 176 | 177 | // AsyncFire 178 | event.AsyncFire(evt1) 179 | time.Sleep(time.Second) 180 | assert.Equal(t, "event: evt1, params: n=inhere", buf.String()) 181 | } 182 | 183 | func TestAsync(t *testing.T) { 184 | event.Reset() 185 | event.Config(event.UsePathMode) 186 | 187 | buf := new(safeBuffer) 188 | event.On("test", event.ListenerFunc(func(e event.Event) error { 189 | buf.WriteString("test:") 190 | buf.WriteString(e.Get("key").(string)) 191 | buf.WriteString("|") 192 | return nil 193 | })) 194 | 195 | event.Async("test", event.M{"key": "val1"}) 196 | te := &testEvent{name: "test", data: event.M{"key": "val2"}} 197 | event.FireAsync(te) 198 | assert.NoError(t, event.CloseWait()) 199 | 200 | s := buf.String() 201 | assert.Contains(t, s, "test:val1|") 202 | assert.Contains(t, s, "test:val2|") 203 | } 204 | 205 | func TestFire_point_at_end(t *testing.T) { 206 | // clear all 207 | event.Reset() 208 | event.Listen("db.*", event.ListenerFunc(func(e event.Event) error { 209 | e.Set("key", "val") 210 | return nil 211 | })) 212 | 213 | err, e := event.Fire("db.add", nil) 214 | assert.NoError(t, err) 215 | assert.Equal(t, "val", e.Get("key")) 216 | 217 | err, e = event.Fire("db", nil) 218 | assert.NotEmpty(t, e) 219 | assert.NoError(t, err) 220 | 221 | err, e = event.Fire("db.", nil) 222 | assert.NotEmpty(t, e) 223 | assert.NoError(t, err) 224 | } 225 | 226 | func TestFire_notExist(t *testing.T) { 227 | // clear all 228 | event.Reset() 229 | 230 | err, e := event.Fire("not-exist", nil) 231 | assert.NoError(t, err) 232 | assert.NotEmpty(t, e) 233 | } 234 | 235 | func TestMustFire(t *testing.T) { 236 | event.Reset() 237 | 238 | event.On("n1", event.ListenerFunc(func(e event.Event) error { 239 | return fmt.Errorf("an error") 240 | }), event.Max) 241 | event.On("n2", event.ListenerFunc(emptyListener), event.Min) 242 | 243 | assert.Panics(t, func() { 244 | _ = event.MustFire("n1", nil) 245 | }) 246 | 247 | assert.NotPanics(t, func() { 248 | _ = event.MustTrigger("n2", nil) 249 | }) 250 | } 251 | 252 | func TestOn(t *testing.T) { 253 | event.Reset() 254 | 255 | assert.Panics(t, func() { 256 | event.On("", event.ListenerFunc(emptyListener), 0) 257 | }) 258 | assert.Panics(t, func() { 259 | event.On("name", nil, 0) 260 | }) 261 | assert.Panics(t, func() { 262 | event.On("++df", event.ListenerFunc(emptyListener), 0) 263 | }) 264 | 265 | std := event.Std() 266 | event.On("n1", event.ListenerFunc(emptyListener), event.Min) 267 | assert.Equal(t, 1, std.ListenersCount("n1")) 268 | assert.Equal(t, 0, std.ListenersCount("not-exist")) 269 | assert.True(t, event.HasListeners("n1")) 270 | assert.False(t, event.HasListeners("name")) 271 | 272 | assert.NotEmpty(t, std.Listeners()) 273 | assert.NotEmpty(t, std.ListenersByName("n1")) 274 | 275 | std.RemoveListeners("n1") 276 | assert.False(t, event.HasListeners("n1")) 277 | } 278 | func TestOnce(t *testing.T) { 279 | event.Reset() 280 | 281 | event.Once("evt1", event.ListenerFunc(emptyListener)) 282 | assert.True(t, event.Std().HasListeners("evt1")) 283 | err, _ := event.Trigger("evt1", nil) 284 | assert.Nil(t, err) 285 | assert.False(t, event.Std().HasListeners("evt1")) 286 | } 287 | -------------------------------------------------------------------------------- /manager.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | defaultChannelSize = 100 10 | defaultConsumerNum = 3 11 | ) 12 | 13 | // Manager event manager definition. for manage events and listeners 14 | type Manager struct { 15 | Options 16 | sync.Mutex 17 | 18 | wg sync.WaitGroup 19 | ch chan Event 20 | oc sync.Once 21 | err error // latest error 22 | 23 | // name of the manager 24 | name string 25 | // pool sync.Pool 26 | // is a sample for new BasicEvent 27 | sample *BasicEvent 28 | 29 | // storage user custom Event instance. you can pre-define some Event instances. 30 | events map[string]Event 31 | // storage user pre-defined event factory func. 32 | eventFc map[string]FactoryFunc 33 | 34 | // storage all event name and ListenerQueue map 35 | listeners map[string]*ListenerQueue 36 | // storage all event names by listened 37 | listenedNames map[string]int 38 | } 39 | 40 | // NewM create event manager. alias of the NewManager() 41 | func NewM(name string, fns ...OptionFn) *Manager { 42 | return NewManager(name, fns...) 43 | } 44 | 45 | // NewManager create event manager 46 | func NewManager(name string, fns ...OptionFn) *Manager { 47 | em := &Manager{ 48 | name: name, 49 | // ctx: context.Background(), 50 | // sample event 51 | sample: &BasicEvent{}, 52 | // events storage 53 | eventFc: make(map[string]FactoryFunc), 54 | // listeners 55 | listeners: make(map[string]*ListenerQueue), 56 | listenedNames: make(map[string]int), 57 | } 58 | 59 | // em.EnableLock = true 60 | // for async fire by goroutine 61 | em.ConsumerNum = defaultConsumerNum 62 | em.ChannelSize = defaultChannelSize 63 | 64 | // apply options 65 | return em.WithOptions(fns...) 66 | } 67 | 68 | // WithOptions create event manager with options 69 | func (em *Manager) WithOptions(fns ...OptionFn) *Manager { 70 | for _, fn := range fns { 71 | fn(&em.Options) 72 | } 73 | return em 74 | } 75 | 76 | /************************************************************* 77 | * region Register listeners 78 | *************************************************************/ 79 | 80 | // AddListener register an event handler/listener. alias of the method On() 81 | func (em *Manager) AddListener(name string, listener Listener, priority ...int) { 82 | em.On(name, listener, priority...) 83 | } 84 | 85 | // Listen register an event handler/listener. alias of the On() 86 | func (em *Manager) Listen(name string, listener Listener, priority ...int) { 87 | em.On(name, listener, priority...) 88 | } 89 | 90 | // Once register an event handler/listener. trigger once. 91 | func (em *Manager) Once(name string, listener Listener, priority ...int) { 92 | var listenerOnce Listener 93 | listenerOnce = ListenerFunc(func(e Event) error { 94 | em.RemoveListener(name, listenerOnce) 95 | return listener.Handle(e) 96 | }) 97 | em.On(name, listenerOnce, priority...) 98 | } 99 | 100 | // On register a event handler/listener. can setting priority. 101 | // 102 | // Usage: 103 | // 104 | // em.On("evt0", listener) 105 | // em.On("evt0", listener, High) 106 | func (em *Manager) On(name string, listener Listener, priority ...int) { 107 | pv := Normal 108 | if len(priority) > 0 { 109 | pv = priority[0] 110 | } 111 | 112 | em.addListenerItem(name, &ListenerItem{pv, listener}) 113 | } 114 | 115 | // Subscribe add events by subscriber interface. alias of the AddSubscriber() 116 | func (em *Manager) Subscribe(sbr Subscriber) { 117 | em.AddSubscriber(sbr) 118 | } 119 | 120 | // AddSubscriber add events by subscriber interface. 121 | // 122 | // you can register multi event listeners in a struct func. 123 | // more usage please see README or tests. 124 | func (em *Manager) AddSubscriber(sbr Subscriber) { 125 | for name, listener := range sbr.SubscribedEvents() { 126 | switch lt := listener.(type) { 127 | case Listener: 128 | em.On(name, lt) 129 | // case ListenerFunc: 130 | // em.On(name, lt) 131 | case ListenerItem: 132 | em.addListenerItem(name, <) 133 | default: 134 | panic("event: the value must be an Listener or ListenerItem instance") 135 | } 136 | } 137 | } 138 | 139 | func (em *Manager) addListenerItem(name string, li *ListenerItem) { 140 | name = goodName(name, true) 141 | if li.Listener == nil { 142 | panicf("event: the event %q listener cannot be empty", name) 143 | } 144 | if reflect.ValueOf(li.Listener).Kind() == reflect.Struct { 145 | panicf("event: %q - struct listener must be pointer", name) 146 | } 147 | 148 | // exists, append it. 149 | if lq, ok := em.listeners[name]; ok { 150 | lq.Push(li) 151 | } else { // first add. 152 | em.listenedNames[name] = 1 153 | em.listeners[name] = (&ListenerQueue{}).Push(li) 154 | } 155 | } 156 | 157 | /************************************************************* 158 | * region Event Manage 159 | *************************************************************/ 160 | 161 | // AddEvent add a pre-defined event instance to manager. 162 | func (em *Manager) AddEvent(e Event) error { 163 | name, err := goodNameOrErr(e.Name(), false) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | if ec, ok := e.(Cloneable); ok { 169 | em.addEventFc(name, func() Event { 170 | return ec.Clone() 171 | }) 172 | } else { 173 | em.addEventFc(name, func() Event { 174 | return e 175 | }) 176 | } 177 | return nil 178 | } 179 | 180 | // AddEventFc add a pre-defined event factory func to manager. 181 | func (em *Manager) AddEventFc(name string, fc FactoryFunc) (err error) { 182 | name, err = goodNameOrErr(name, false) 183 | if err == nil { 184 | em.addEventFc(name, fc) 185 | } 186 | return err 187 | } 188 | 189 | func (em *Manager) addEventFc(name string, fc FactoryFunc) { 190 | em.Lock() 191 | em.eventFc[name] = fc 192 | em.Unlock() 193 | } 194 | 195 | // GetEvent get a pre-defined event instance by name 196 | func (em *Manager) GetEvent(name string) (e Event, ok bool) { 197 | fc, ok := em.eventFc[name] 198 | if ok { 199 | return fc(), true 200 | } 201 | return 202 | } 203 | 204 | // HasEvent has pre-defined event check 205 | func (em *Manager) HasEvent(name string) bool { 206 | _, ok := em.eventFc[name] 207 | return ok 208 | } 209 | 210 | // RemoveEvent delete pre-define Event by name 211 | func (em *Manager) RemoveEvent(name string) { 212 | if _, ok := em.eventFc[name]; ok { 213 | delete(em.eventFc, name) 214 | } 215 | } 216 | 217 | // RemoveEvents remove all registered events 218 | func (em *Manager) RemoveEvents() { 219 | em.eventFc = map[string]FactoryFunc{} 220 | } 221 | 222 | /************************************************************* 223 | * region Helper Methods 224 | *************************************************************/ 225 | 226 | // newBasicEvent create new BasicEvent by clone em.sample 227 | func (em *Manager) newBasicEvent(name string, data M) *BasicEvent { 228 | var cp = *em.sample 229 | cp.SetName(name) 230 | cp.SetData(data) 231 | return &cp 232 | } 233 | 234 | // HasListeners check has direct listeners for the event name. 235 | func (em *Manager) HasListeners(name string) bool { 236 | _, ok := em.listenedNames[name] 237 | return ok 238 | } 239 | 240 | // Listeners get all listeners 241 | func (em *Manager) Listeners() map[string]*ListenerQueue { return em.listeners } 242 | 243 | // ListenersByName get listeners by given event name 244 | func (em *Manager) ListenersByName(name string) *ListenerQueue { 245 | return em.listeners[name] 246 | } 247 | 248 | // ListenersCount get listeners number for the event name. 249 | func (em *Manager) ListenersCount(name string) int { 250 | if lq, ok := em.listeners[name]; ok { 251 | return lq.Len() 252 | } 253 | return 0 254 | } 255 | 256 | // ListenedNames get listened event names 257 | func (em *Manager) ListenedNames() map[string]int { 258 | return em.listenedNames 259 | } 260 | 261 | // RemoveListener remove a given listener, you can limit event name. 262 | // 263 | // Usage: 264 | // 265 | // RemoveListener("", listener) 266 | // RemoveListener("name", listener) // limit event name. 267 | func (em *Manager) RemoveListener(name string, listener Listener) { 268 | if name != "" { 269 | if lq, ok := em.listeners[name]; ok { 270 | lq.Remove(listener) 271 | 272 | // delete from manager 273 | if lq.IsEmpty() { 274 | delete(em.listeners, name) 275 | delete(em.listenedNames, name) 276 | } 277 | } 278 | return 279 | } 280 | 281 | // name is empty. find all listener and remove matched. 282 | for name, lq := range em.listeners { 283 | lq.Remove(listener) 284 | 285 | // delete from manager 286 | if lq.IsEmpty() { 287 | delete(em.listeners, name) 288 | delete(em.listenedNames, name) 289 | } 290 | } 291 | } 292 | 293 | // RemoveListeners remove listeners by given name 294 | func (em *Manager) RemoveListeners(name string) { 295 | _, ok := em.listenedNames[name] 296 | if ok { 297 | em.listeners[name].Clear() 298 | 299 | // delete from manager 300 | delete(em.listeners, name) 301 | delete(em.listenedNames, name) 302 | } 303 | } 304 | 305 | // Clear alias of the Reset() 306 | func (em *Manager) Clear() { em.Reset() } 307 | 308 | // Close event channel, deny to fire new event. 309 | func (em *Manager) Close() error { 310 | if em.ch != nil { 311 | close(em.ch) 312 | } 313 | return nil 314 | } 315 | 316 | // Reset the manager, clear all data. 317 | func (em *Manager) Reset() { 318 | // clear all listeners 319 | for _, lq := range em.listeners { 320 | lq.Clear() 321 | } 322 | 323 | // reset all 324 | em.ch = nil 325 | em.oc = sync.Once{} 326 | em.wg = sync.WaitGroup{} 327 | 328 | em.eventFc = make(map[string]FactoryFunc) 329 | em.listeners = make(map[string]*ListenerQueue) 330 | em.listenedNames = make(map[string]int) 331 | } 332 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Event 2 | 3 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/event?style=flat-square) 4 | [![GoDoc](https://pkg.go.dev/badge/github.com/gookit/event.svg)](https://pkg.go.dev/github.com/gookit/event) 5 | [![Actions Status](https://github.com/gookit/event/workflows/Unit-Tests/badge.svg)](https://github.com/gookit/event/actions) 6 | [![Coverage Status](https://coveralls.io/repos/github/gookit/event/badge.svg?branch=master)](https://coveralls.io/github/gookit/event?branch=master) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/gookit/event)](https://goreportcard.com/report/github.com/gookit/event) 8 | 9 | `event` Go 实现的轻量级的事件管理、调度工具库 10 | 11 | - 支持自定义创建预定义的事件对象 12 | - 支持对一个事件添加多个监听器 13 | - 支持设置事件监听器的优先级,优先级越高越先触发 14 | - 支持通过通配符 `*` 来进行一组事件的匹配监听. 15 | - `ModeSimple` - 注册 `app.*` 事件的监听,触发 `app.run` `app.end` 时,都将同时会触发 `app.*` 监听器 16 | - `ModePath` - **NEW** `*` 只匹配一段非 `.` 的字符,可以进行更精细的监听; `**` 匹配任意多个字符,只能用于开头或结尾 17 | - 支持直接使用通配符 `*` 来监听全部事件的触发 18 | - 支持触发事件时投递到 `chan`, 异步进行消费处理. 触发: `Async(), FireAsync()` 19 | - 完善的单元测试,单元覆盖率 `> 95%` 20 | 21 | ## [English](README.md) 22 | 23 | English introduction, please see **[EN README](README.md)** 24 | 25 | ## GoDoc 26 | 27 | - [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event) 28 | 29 | ## 安装 30 | 31 | ```shell 32 | go get github.com/gookit/event 33 | ``` 34 | 35 | ## 主要方法 36 | 37 | - `On/Listen(name string, listener Listener, priority ...int)` 注册事件监听 38 | - `Subscribe/AddSubscriber(sbr Subscriber)` 订阅,支持注册多个事件监听 39 | - `Trigger/Fire(name string, params M) (error, Event)` 触发事件 40 | - `MustTrigger/MustFire(name string, params M) Event` 触发事件,有错误则会panic 41 | - `FireEvent(e Event) (err error)` 根据给定的事件实例,触发事件 42 | - `FireBatch(es ...any) (ers []error)` 一次触发多个事件 43 | - `Async/FireC(name string, params M)` 投递事件到 `chan`,异步消费处理 44 | - `FireAsync(e Event)` 投递事件到 `chan`,异步消费处理 45 | - `AsyncFire(e Event)` 简单的通过 `go` 异步触发事件 46 | 47 | ## 快速使用 48 | 49 | ```go 50 | package main 51 | 52 | import ( 53 | "fmt" 54 | 55 | "github.com/gookit/event" 56 | ) 57 | 58 | func main() { 59 | // 注册事件监听器 60 | event.On("evt1", event.ListenerFunc(func(e event.Event) error { 61 | fmt.Printf("handle event: %s\n", e.Name()) 62 | return nil 63 | }), event.Normal) 64 | 65 | // 注册多个监听器 66 | event.On("evt1", event.ListenerFunc(func(e event.Event) error { 67 | fmt.Printf("handle event: %s\n", e.Name()) 68 | return nil 69 | }), event.High) 70 | 71 | // ... ... 72 | 73 | // 触发事件 74 | // 注意:第二个监听器的优先级更高,所以它会先被执行 75 | event.MustFire("evt1", event.M{"arg0": "val0", "arg1": "val1"}) 76 | } 77 | ``` 78 | 79 | > Note: 注意:第二个监听器的优先级更高,所以它会先被执行 80 | 81 | ## 使用通配符 82 | 83 | ### 匹配模式 ModeSimple 84 | 85 | `ModeSimple` 是默认模式, 注册事件监听器和名称以通配符 `*` 结尾: 86 | 87 | ```go 88 | func main() { 89 | dbListener1 := event.ListenerFunc(func(e event.Event) error { 90 | fmt.Printf("handle event: %s\n", e.Name()) 91 | return nil 92 | }) 93 | 94 | event.On("app.db.*", dbListener1, event.Normal) 95 | } 96 | ``` 97 | 98 | **在其他逻辑上触发事件**: 99 | 100 | ```go 101 | func doCreate() { 102 | // do something ... 103 | // Trigger event 104 | event.MustFire("app.db.create", event.M{"arg0": "val0", "arg1": "val1"}) 105 | } 106 | 107 | func doUpdate() { 108 | // do something ... 109 | // Trigger event 110 | event.MustFire("app.db.update", event.M{"arg0": "val0"}) 111 | } 112 | ``` 113 | 114 | 像上面这样,触发 `app.db.create` `app.db.update` 事件,都会触发执行 `dbListener1` 监听器. 115 | 116 | ### 匹配模式 ModePath 117 | 118 | `ModePath` 是 `v1.1.0` 新增的模式,通配符 `*` 匹配逻辑有调整: 119 | 120 | - `*` 只匹配一段非 `.` 的字符,可以进行更精细的监听匹配 121 | - `**` 则匹配任意多个字符,并且只能用于开头或结尾 122 | 123 | ```go 124 | em := event.NewManager("test", event.UsePathMode) 125 | 126 | // 注册事件监听器 127 | em.On("app.**", appListener) 128 | em.On("app.db.*", dbListener) 129 | em.On("app.*.create", createListener) 130 | em.On("app.*.update", updateListener) 131 | 132 | // ... ... 133 | 134 | // 触发事件 135 | // TIP: 将会触发 appListener, dbListener, createListener 136 | em.Fire("app.db.create", event.M{"arg0": "val0", "arg1": "val1"}) 137 | ``` 138 | 139 | ## 异步消费事件 140 | 141 | ### 使用 `chan` 消费事件 142 | 143 | 可以使用 `Async/FireC/FireAsync` 方法触发事件,事件将会写入 `chan` 异步消费。可以使用 `CloseWait()` 关闭chan并等待事件全部消费完成。 144 | 145 | > **Note**: `event.NewBasic()/event.New()` 可以创建通用的Event实例; `Async/FireC` 无需构建 Event,内部根据参数构建的。 146 | 147 | **新增配置选项**: 148 | 149 | - `ChannelSize` 设置 `chan` 的缓冲大小 150 | - `ConsumerNum` 设置启动多少个协程来消费事件 151 | 152 | ```go 153 | func main() { 154 | // 注意:在程序退出时关闭事件chan 155 | // defer event.Close() 156 | defer event.CloseWait() 157 | 158 | // 注册事件监听器 159 | event.On("app.evt1", event.ListenerFunc(func(e event.Event) error { 160 | fmt.Printf("handle event: %s\n", e.Name()) 161 | return nil 162 | }), event.Normal) 163 | 164 | // 注册多个监听器 165 | event.On("app.evt1", event.ListenerFunc(func(e event.Event) error { 166 | fmt.Printf("handle event: %s\n", e.Name()) 167 | return nil 168 | }), event.High) 169 | 170 | // ... ... 171 | 172 | // 异步消费事件 173 | event.FireC("app.evt1", event.M{"arg0": "val0", "arg1": "val1"}) 174 | event.FireAsync(event.New("app.evt1", event.M{"arg0": "val2"}) 175 | } 176 | ``` 177 | 178 | > Note: 应当在程序退出时关闭事件chan. 可以使用下面的方法: 179 | 180 | - `event.Close()` 立即关闭 `chan` 不再接受新的事件 181 | - `event.CloseWait()` 关闭 `chan` 并等待所有事件处理完成 182 | 183 | ## 编写事件监听器 184 | 185 | ### 使用匿名函数 186 | 187 | ```go 188 | package mypgk 189 | 190 | import ( 191 | "fmt" 192 | 193 | "github.com/gookit/event" 194 | ) 195 | 196 | var fnHandler = func(e event.Event) error { 197 | fmt.Printf("handle event: %s\n", e.Name()) 198 | return nil 199 | } 200 | 201 | func Run() { 202 | // register 203 | event.On("evt1", event.ListenerFunc(fnHandler), event.High) 204 | } 205 | ``` 206 | 207 | ### 使用结构体方法 208 | 209 | **interface:** 210 | 211 | ```go 212 | // Listener interface 213 | type Listener interface { 214 | Handle(e Event) error 215 | } 216 | ``` 217 | 218 | **示例:** 219 | 220 | > 实现接口 `event.Listener` 221 | 222 | ```go 223 | package mypgk 224 | 225 | import ( 226 | "fmt" 227 | 228 | "github.com/gookit/event" 229 | ) 230 | 231 | type MyListener struct { 232 | // userData string 233 | } 234 | 235 | func (l *MyListener) Handle(e event.Event) error { 236 | e.Set("result", "OK") 237 | return nil 238 | } 239 | ``` 240 | 241 | ### 同时注册多个事件监听 242 | 243 | **interface:** 244 | 245 | ```go 246 | // Subscriber event subscriber interface. 247 | // you can register multi event listeners in a struct func. 248 | type Subscriber interface { 249 | // SubscribedEvents register event listeners 250 | // key: is event name 251 | // value: can be Listener or ListenerItem interface 252 | SubscribedEvents() map[string]any 253 | } 254 | ``` 255 | 256 | **示例** 257 | 258 | > 实现接口 `event.Subscriber` 259 | 260 | ```go 261 | package mypgk 262 | 263 | import ( 264 | "fmt" 265 | 266 | "github.com/gookit/event" 267 | ) 268 | 269 | type MySubscriber struct { 270 | // ooo 271 | } 272 | 273 | func (s *MySubscriber) SubscribedEvents() map[string]any { 274 | return map[string]any{ 275 | "e1": event.ListenerFunc(s.e1Handler), 276 | "e2": event.ListenerItem{ 277 | Priority: event.AboveNormal, 278 | Listener: event.ListenerFunc(func(e Event) error { 279 | return fmt.Errorf("an error") 280 | }), 281 | }, 282 | "e3": &MyListener{}, 283 | } 284 | } 285 | 286 | func (s *MySubscriber) e1Handler(e event.Event) error { 287 | e.Set("e1-key", "val1") 288 | return nil 289 | } 290 | ``` 291 | 292 | ## 编写自定义事件 293 | 294 | 如果你希望自定义事件对象或者提前定义好一些固定事件信息,可以实现 `event.Event` 接口. 295 | 296 | **interface:** 297 | 298 | ```go 299 | // Event interface 300 | type Event interface { 301 | Name() string 302 | Get(key string) any 303 | Add(key string, val any) 304 | Set(key string, val any) 305 | Data() map[string]any 306 | SetData(M) Event 307 | Abort(bool) 308 | IsAborted() bool 309 | } 310 | ``` 311 | 312 | **示例** 313 | 314 | ```go 315 | package mypgk 316 | 317 | import ( 318 | "fmt" 319 | 320 | "github.com/gookit/event" 321 | ) 322 | 323 | type MyEvent struct{ 324 | event.BasicEvent 325 | customData string 326 | } 327 | 328 | func (e *MyEvent) CustomData() string { 329 | return e.customData 330 | } 331 | ``` 332 | 333 | **使用:** 334 | 335 | ```go 336 | e := &MyEvent{customData: "hello"} 337 | e.SetName("e1") 338 | event.AddEvent(e) 339 | 340 | // add listener 341 | event.On("e1", event.ListenerFunc(func(e event.Event) error { 342 | fmt.Printf("custom Data: %s\n", e.(*MyEvent).CustomData()) 343 | return nil 344 | })) 345 | 346 | // trigger 347 | event.Fire("e1", nil) 348 | // OR 349 | // event.FireEvent(e) 350 | ``` 351 | 352 | > **Note**: `AddEvent()` 是用于添加预先定义的公共事件信息,都是在初始化阶段添加,所以没加锁. 在业务中动态创建的Event可以直接使用 `FireEvent()` 触发 353 | 354 | ## Gookit 工具包 355 | 356 | - [gookit/ini](https://github.com/gookit/ini) INI配置读取管理,支持多文件加载,数据覆盖合并, 解析ENV变量, 解析变量引用 357 | - [gookit/rux](https://github.com/gookit/rux) 简单且快速的 Go web 框架,支持中间件,兼容 http.Handler 接口 358 | - [gookit/gcli](https://github.com/gookit/gcli) Go的命令行应用,工具库,运行CLI命令,支持命令行色彩,用户交互,进度显示,数据格式化显示 359 | - [gookit/slog](https://github.com/gookit/slog) 用Go编写的轻量级,可扩展,可配置的日志记录库 360 | - [gookit/event](https://github.com/gookit/event) Go实现的轻量级的事件管理、调度程序库, 支持设置监听器的优先级, 支持对一组事件进行监听 361 | - [gookit/cache](https://github.com/gookit/cache) 通用的缓存使用包装库,通过包装各种常用的驱动,来提供统一的使用API 362 | - [gookit/config](https://github.com/gookit/config) Go应用配置管理,支持多种格式(JSON, YAML, TOML, INI, HCL, ENV, Flags),多文件加载,远程文件加载,数据合并 363 | - [gookit/color](https://github.com/gookit/color) CLI 控制台颜色渲染工具库, 拥有简洁的使用API,支持16色,256色,RGB色彩渲染输出 364 | - [gookit/filter](https://github.com/gookit/filter) 提供对Golang数据的过滤,净化,转换 365 | - [gookit/validate](https://github.com/gookit/validate) Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器 366 | - [gookit/goutil](https://github.com/gookit/goutil) Go 的一些工具函数,格式化,特殊处理,常用信息获取等 367 | - 更多请查看 https://github.com/gookit 368 | 369 | ## LICENSE 370 | 371 | **[MIT](LICENSE)** 372 | -------------------------------------------------------------------------------- /manager_fire.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/gookit/goutil/x/basefn" 9 | ) 10 | 11 | /************************************************************* 12 | * region Trigger event 13 | *************************************************************/ 14 | 15 | // MustTrigger alias of the method MustFire() 16 | func (em *Manager) MustTrigger(name string, params M) Event { return em.MustFire(name, params) } 17 | 18 | // MustFire fire event by name. will panic on error 19 | func (em *Manager) MustFire(name string, params M) Event { 20 | err, e := em.Fire(name, params) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return e 25 | } 26 | 27 | // Trigger alias of the method Fire() 28 | func (em *Manager) Trigger(name string, params M) (error, Event) { return em.Fire(name, params) } 29 | 30 | // Fire trigger event by name. if not found listener, will return (nil, nil) 31 | func (em *Manager) Fire(name string, params M) (err error, e Event) { 32 | // call listeners handle event 33 | e, err = em.fireByName(name, params, false) 34 | return 35 | } 36 | 37 | // FireCtx fire event by name with context 38 | func (em *Manager) FireCtx(ctx context.Context, name string, params M) (err error, e Event) { 39 | // call listeners handle event 40 | e, err = em.fireByNameCtx(ctx, name, params, false) 41 | return 42 | } 43 | 44 | // Async fire event by go channel. 45 | // 46 | // Note: if you want to use this method, you should 47 | // call the method Close() after all events are fired. 48 | func (em *Manager) Async(name string, params M) { 49 | _, _ = em.fireByName(name, params, true) 50 | } 51 | 52 | // FireC async fire event by go channel. alias of the method Async() 53 | // 54 | // Note: if you want to use this method, you should 55 | // call the method Close() after all events are fired. 56 | func (em *Manager) FireC(name string, params M) { 57 | _, _ = em.fireByName(name, params, true) 58 | } 59 | 60 | // fire event by name. 61 | // 62 | // if useCh is true, will async fire by channel. always return (nil, nil) 63 | // 64 | // On useCh=false: 65 | // - will call listeners handle event. 66 | // - if not found listener, will return (nil, nil) 67 | func (em *Manager) fireByName(name string, params M, useCh bool) (Event, error) { 68 | return em.fireByNameCtx(nil, name, params, useCh) 69 | } 70 | 71 | // fireByNameCtx fire event by name with context 72 | func (em *Manager) fireByNameCtx(ctx context.Context, name string, params M, useCh bool) (e Event, err error) { 73 | name, err = goodNameOrErr(name, false) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | // use pre-defined Event 79 | if fc, ok := em.eventFc[name]; ok { 80 | e = fc() // make new instance 81 | if params != nil { 82 | e.SetData(params) 83 | } 84 | } else { 85 | // create new basic event instance 86 | e = em.newBasicEvent(name, params) 87 | } 88 | 89 | // warp context 90 | if ctx != nil { 91 | if ec, ok := e.(ContextAble); ok { 92 | ec.WithContext(ctx) 93 | } else { 94 | e = newContextEvent(ctx, e) 95 | } 96 | } 97 | 98 | // fire by channel 99 | if useCh { 100 | em.FireAsync(e) 101 | return nil, nil 102 | } 103 | 104 | // call listeners handle event 105 | err = em.fireEvent(e) 106 | return 107 | } 108 | 109 | // FireEvent fire event by given Event instance. 110 | func (em *Manager) FireEvent(e Event) error { return em.fireEvent(e) } 111 | 112 | // FireEventCtx fire event by given Event instance with context 113 | func (em *Manager) FireEventCtx(ctx context.Context, e Event) (err error) { 114 | if ec, ok := e.(ContextAble); ok { 115 | ec.WithContext(ctx) 116 | return em.fireEvent(ec) 117 | } 118 | return em.fireEvent(newContextEvent(ctx, e)) 119 | } 120 | 121 | // FireEventCtx fire event by given Event instance with context 122 | func (em *Manager) fireEvent(e Event) (err error) { 123 | if em.EnableLock { 124 | em.Lock() 125 | defer em.Unlock() 126 | } 127 | 128 | // ensure aborted is false. 129 | e.Abort(false) 130 | name := e.Name() 131 | 132 | // get context 133 | var ctx context.Context 134 | if ec, ok := e.(ContextAble); ok { 135 | ctx = ec.Context() 136 | } 137 | 138 | // fire group listeners by wildcard. eg "db.user.*" 139 | if em.MatchMode == ModePath { 140 | err = em.firePathMode(ctx, name, e) 141 | return 142 | } 143 | 144 | // handle mode: ModeSimple 145 | err = em.fireSimpleMode(ctx, name, e) 146 | if err != nil || e.IsAborted() { 147 | return 148 | } 149 | 150 | // fire wildcard event listeners 151 | if lq, ok := em.listeners[Wildcard]; ok { 152 | for _, li := range lq.Sort().Items() { 153 | // Check context cancellation 154 | if ctx != nil { 155 | select { 156 | case <-ctx.Done(): 157 | err = ctx.Err() 158 | return 159 | default: 160 | } 161 | } 162 | 163 | err = li.Listener.Handle(e) 164 | if err != nil || e.IsAborted() { 165 | break 166 | } 167 | } 168 | } 169 | return 170 | } 171 | 172 | // ModeSimple has group listeners by wildcard. eg "db.user.*" 173 | // 174 | // Example: 175 | // - event "db.user.add" will trigger listeners on the "db.user.*" 176 | func (em *Manager) fireSimpleMode(ctx context.Context, name string, e Event) (err error) { 177 | // fire direct matched listeners. eg: db.user.add 178 | if lq, ok := em.listeners[name]; ok { 179 | // sort by priority before call. 180 | for _, li := range lq.Sort().Items() { 181 | // Check context cancellation 182 | if ctx != nil { 183 | select { 184 | case <-ctx.Done(): 185 | err = ctx.Err() 186 | return 187 | default: 188 | } 189 | } 190 | 191 | err = li.Listener.Handle(e) 192 | if err != nil || e.IsAborted() { 193 | return 194 | } 195 | } 196 | } 197 | 198 | pos := strings.LastIndexByte(name, '.') 199 | 200 | // exists group 201 | if pos > 0 && pos < len(name) { 202 | groupName := name[:pos+1] + Wildcard // "app.*" 203 | 204 | if lq, ok := em.listeners[groupName]; ok { 205 | for _, li := range lq.Sort().Items() { 206 | // Check context cancellation 207 | if ctx != nil { 208 | select { 209 | case <-ctx.Done(): 210 | err = ctx.Err() 211 | return 212 | default: 213 | } 214 | } 215 | 216 | err = li.Listener.Handle(e) 217 | if err != nil || e.IsAborted() { 218 | return 219 | } 220 | } 221 | } 222 | } 223 | 224 | return nil 225 | } 226 | 227 | // firePathMode fire group listeners by ModePath. 228 | // 229 | // Example: 230 | // - event "db.user.add" will trigger listeners on the "db.**" 231 | // - event "db.user.add" will trigger listeners on the "db.user.*" 232 | func (em *Manager) firePathMode(ctx context.Context, name string, e Event) (err error) { 233 | for pattern, lq := range em.listeners { 234 | if pattern == name || matchNodePath(pattern, name, ".") { 235 | for _, li := range lq.Sort().Items() { 236 | // Check context cancellation 237 | if ctx != nil { 238 | select { 239 | case <-ctx.Done(): 240 | err = ctx.Err() 241 | return 242 | default: 243 | } 244 | } 245 | 246 | err = li.Listener.Handle(e) 247 | if err != nil || e.IsAborted() { 248 | return 249 | } 250 | } 251 | } 252 | } 253 | 254 | return nil 255 | } 256 | 257 | /************************************************************* 258 | * region Fire by channel 259 | *************************************************************/ 260 | 261 | // FireAsyncCtx async fire event by go channel, and with context TODO need? 262 | // func (em *Manager) FireAsyncCtx(ctx context.Context, e Event) 263 | 264 | // FireAsync async fire event by go channel. 265 | // 266 | // Note: if you want to use this method, you should 267 | // call the method Close() after all events are fired. 268 | // 269 | // Example: 270 | // 271 | // em := NewManager("test") 272 | // em.FireAsync("db.user.add", M{"id": 1001}) 273 | func (em *Manager) FireAsync(e Event) { 274 | // once make consumers 275 | em.oc.Do(func() { 276 | em.makeConsumers() 277 | }) 278 | 279 | // dispatch event 280 | em.ch <- e 281 | } 282 | 283 | // async fire event by 'go' keywords 284 | func (em *Manager) makeConsumers() { 285 | if em.ConsumerNum <= 0 { 286 | em.ConsumerNum = defaultConsumerNum 287 | } 288 | if em.ChannelSize <= 0 { 289 | em.ChannelSize = defaultChannelSize 290 | } 291 | 292 | em.ch = make(chan Event, em.ChannelSize) 293 | 294 | // make event consumers 295 | for i := 0; i < em.ConsumerNum; i++ { 296 | em.wg.Add(1) 297 | 298 | go func() { 299 | defer func() { 300 | if err := recover(); err != nil { 301 | em.err = fmt.Errorf("async consum event error: %v", err) 302 | } 303 | em.wg.Done() 304 | }() 305 | 306 | // keep running until channel closed 307 | for e := range em.ch { 308 | _ = em.FireEvent(e) // ignore async fire error 309 | } 310 | }() 311 | } 312 | } 313 | 314 | // FireBatch fire multi event at once. 315 | // 316 | // Usage: 317 | // 318 | // FireBatch("name1", "name2", &MyEvent{}) 319 | func (em *Manager) FireBatch(es ...any) (ers []error) { 320 | var err error 321 | for _, e := range es { 322 | if name, ok := e.(string); ok { 323 | err, _ = em.Fire(name, nil) 324 | } else if evt, ok1 := e.(Event); ok1 { 325 | err = em.FireEvent(evt) 326 | } // ignore invalid param. 327 | 328 | if err != nil { 329 | ers = append(ers, err) 330 | } 331 | } 332 | return 333 | } 334 | 335 | // AsyncFire simple async fire event by 'go' keywords 336 | func (em *Manager) AsyncFire(e Event) { 337 | go func(e Event) { 338 | _ = em.FireEvent(e) 339 | }(e) 340 | } 341 | 342 | // AwaitFire async fire event by 'go' keywords, but will wait return result 343 | func (em *Manager) AwaitFire(e Event) (err error) { 344 | ch := make(chan error) 345 | 346 | go func(e Event) { 347 | err1 := em.FireEvent(e) 348 | ch <- err1 349 | }(e) 350 | 351 | err = <-ch 352 | close(ch) 353 | return 354 | } 355 | 356 | /************************************************************* 357 | * region Helper methods 358 | *************************************************************/ 359 | 360 | // MustCloseWait close channel and wait all async event done. panic if error 361 | func (em *Manager) MustCloseWait() { basefn.PanicErr(em.CloseWait()) } 362 | 363 | // CloseWait close channel and wait all async event done. 364 | func (em *Manager) CloseWait() error { 365 | if err := em.Close(); err != nil { 366 | return err 367 | } 368 | return em.Wait() 369 | } 370 | 371 | // Wait wait all async event done. 372 | func (em *Manager) Wait() error { 373 | em.wg.Wait() 374 | return em.err 375 | } 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Event 2 | 3 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/event?style=flat-square) 4 | [![GoDoc](https://pkg.go.dev/badge/github.com/gookit/event.svg)](https://pkg.go.dev/github.com/gookit/event) 5 | [![Actions Status](https://github.com/gookit/event/workflows/Unit-Tests/badge.svg)](https://github.com/gookit/event/actions) 6 | [![Coverage Status](https://coveralls.io/repos/github/gookit/event/badge.svg?branch=master)](https://coveralls.io/github/gookit/event?branch=master) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/gookit/event)](https://goreportcard.com/report/github.com/gookit/event) 8 | 9 | Lightweight event management, dispatch tool library implemented by Go 10 | 11 | - Support for custom definition event objects 12 | - Support for adding multiple listeners to an event 13 | - Support setting the priority of the event listener, the higher the priority, the first to trigger 14 | - Support for a set of event listeners based on the event name prefix `PREFIX.*`. 15 | - `ModeSimple`(default) - `app.*` event listen, trigger `app.run` `app.end`, Both will fire the `app.*` listener 16 | - New match mode: `ModePath` 17 | - `*` Only match a segment of characters that are not `.`, allowing for finer monitoring and matching 18 | - `**` matches any number of characters and can only be used at the beginning or end 19 | - Support for using the wildcard `*` to listen for triggers for all events 20 | - Support async trigger event by `go` channel consumers. use `Async(), FireAsync()` 21 | - Complete unit testing, unit coverage `> 95%` 22 | 23 | ## [中文说明](README.zh-CN.md) 24 | 25 | 中文说明请看 **[README.zh-CN](README.zh-CN.md)** 26 | 27 | ## GoDoc 28 | 29 | - [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event) 30 | 31 | ## Install 32 | 33 | ```shell 34 | go get github.com/gookit/event 35 | ``` 36 | 37 | ## Main method 38 | 39 | - `On/Listen(name string, listener Listener, priority ...int)` Register event listener 40 | - `Subscribe/AddSubscriber(sbr Subscriber)` Subscribe to support registration of multiple event listeners 41 | - `Trigger/Fire(name string, params M) (error, Event)` Trigger event by name and params 42 | - `FireCtx(ctx context.Context, name string, params M) (error, Event)` Trigger event with context 43 | - `MustTrigger/MustFire(name string, params M) Event` Trigger event, there will be panic if there is an error 44 | - `FireEvent(e Event) (err error)` Trigger an event based on a given event instance 45 | - `FireBatch(es ...any) (ers []error)` Trigger multiple events at once 46 | - `Async/FireC(name string, params M)` Push event to `chan`, asynchronous consumption processing 47 | - `FireAsync(e Event)` Push event to `chan`, asynchronous consumption processing 48 | - `AsyncFire(e Event)` Async fire event by 'go' keywords 49 | 50 | ## Quick start 51 | 52 | ```go 53 | package main 54 | 55 | import ( 56 | "fmt" 57 | 58 | "github.com/gookit/event" 59 | ) 60 | 61 | func main() { 62 | // Register event listener 63 | event.On("evt1", event.ListenerFunc(func(e event.Event) error { 64 | fmt.Printf("handle event: %s\n", e.Name()) 65 | return nil 66 | }), event.Normal) 67 | 68 | // Register multiple listeners 69 | event.On("evt1", event.ListenerFunc(func(e event.Event) error { 70 | fmt.Printf("handle event: %s\n", e.Name()) 71 | return nil 72 | }), event.High) 73 | 74 | // ... ... 75 | 76 | // Trigger event 77 | // Note: The second listener has a higher priority, so it will be executed first. 78 | event.MustFire("evt1", event.M{"arg0": "val0", "arg1": "val1"}) 79 | } 80 | ``` 81 | 82 | > Note: The second listener has a higher priority, so it will be executed first. 83 | 84 | ## Using the wildcard 85 | 86 | ### Match mode `ModePath` 87 | 88 | Register event listener and name end with wildcard `*`: 89 | 90 | ```go 91 | func main() { 92 | dbListener1 := event.ListenerFunc(func(e event.Event) error { 93 | fmt.Printf("handle event: %s\n", e.Name()) 94 | return nil 95 | }) 96 | 97 | event.On("app.db.*", dbListener1, event.Normal) 98 | } 99 | ``` 100 | 101 | Trigger events on other logic: 102 | 103 | ```go 104 | func doCreate() { 105 | // do something ... 106 | // Trigger event 107 | event.MustFire("app.db.create", event.M{"arg0": "val0", "arg1": "val1"}) 108 | } 109 | 110 | func doUpdate() { 111 | // do something ... 112 | // Trigger event 113 | event.MustFire("app.db.update", event.M{"arg0": "val0"}) 114 | } 115 | ``` 116 | 117 | Like the above, triggering the `app.db.create` `app.db.update` event 118 | will trigger the execution of the `dbListener1` listener. 119 | 120 | ### Match mode `ModePath` 121 | 122 | `ModePath` It is a new pattern of `v1.1.0`, and the wildcard `*` matching logic has been adjusted: 123 | 124 | - `*` Only match a segment of characters that are not `.`, allowing for finer monitoring and matching 125 | - `**` matches any number of characters and can only be used at the beginning or end 126 | 127 | ```go 128 | em := event.NewManager("test", event.UsePathMode) 129 | 130 | // register listener 131 | em.On("app.**", appListener) 132 | em.On("app.db.*", dbListener) 133 | em.On("app.*.create", createListener) 134 | em.On("app.*.update", updateListener) 135 | 136 | // ... ... 137 | 138 | // fire event 139 | // TIP: will trigger appListener, dbListener, createListener 140 | em.Fire("app.db.create", event.M{"arg0": "val0", "arg1": "val1"}) 141 | ``` 142 | 143 | ## Async fire events 144 | 145 | ### Use `chan` fire events 146 | 147 | You can use the `Async/FireC/FireAsync` method to trigger events, and the events will be written to chan for asynchronous consumption. 148 | You can use `CloseWait()` to close the chan and wait for all events to be consumed. 149 | 150 | Added option configuration: 151 | 152 | - `ChannelSize` Set buffer size for `chan` 153 | - `ConsumerNum` Set how many coroutines to start to consume events 154 | 155 | ```go 156 | func main() { 157 | // Note: close event chan on program exit 158 | defer event.CloseWait() 159 | // defer event.Close() 160 | 161 | // register event listener 162 | event.On("app.evt1", event.ListenerFunc(func(e event.Event) error { 163 | fmt.Printf("handle event: %s\n", e.Name()) 164 | return nil 165 | }), event.Normal) 166 | 167 | event.On("app.evt1", event.ListenerFunc(func(e event.Event) error { 168 | fmt.Printf("handle event: %s\n", e.Name()) 169 | return nil 170 | }), event.High) 171 | 172 | // ... ... 173 | 174 | // Asynchronous consumption of events 175 | event.FireC("app.evt1", event.M{"arg0": "val0", "arg1": "val1"}) 176 | } 177 | ``` 178 | 179 | > Note: The event chan should be closed when the program exits. 180 | > You can use the following method: 181 | 182 | - `event.Close()` Close `chan` and no longer accept new events 183 | - `event.CloseWait()` Close `chan` and wait for all event processing to complete 184 | 185 | ## Write event listeners 186 | 187 | ### Using anonymous functions 188 | 189 | You can use anonymous function for quick write an event lister. 190 | 191 | ```go 192 | package mypgk 193 | 194 | import ( 195 | "fmt" 196 | 197 | "github.com/gookit/event" 198 | ) 199 | 200 | var fnHandler = func(e event.Event) error { 201 | fmt.Printf("handle event: %s\n", e.Name()) 202 | return nil 203 | } 204 | 205 | func Run() { 206 | // register 207 | event.On("evt1", event.ListenerFunc(fnHandler), event.High) 208 | } 209 | ``` 210 | 211 | ### Using the structure method 212 | 213 | You can use struct write an event lister, and it should implementation interface `event.Listener`. 214 | 215 | **interface:** 216 | 217 | ```go 218 | // Listener interface 219 | type Listener interface { 220 | Handle(e Event) error 221 | } 222 | ``` 223 | 224 | **example:** 225 | 226 | > Implementation interface `event.Listener` 227 | 228 | ```go 229 | package mypgk 230 | 231 | import "github.com/gookit/event" 232 | 233 | type MyListener struct { 234 | // userData string 235 | } 236 | 237 | func (l *MyListener) Handle(e event.Event) error { 238 | e.Set("result", "OK") 239 | return nil 240 | } 241 | ``` 242 | 243 | ## Register multiple event listeners 244 | 245 | Can implementation interface `event.Subscriber` for register 246 | multiple event listeners at once. 247 | 248 | **interface:** 249 | 250 | ```go 251 | // Subscriber event subscriber interface. 252 | // you can register multi event listeners in a struct func. 253 | type Subscriber interface { 254 | // SubscribedEvents register event listeners 255 | // key: is event name 256 | // value: can be Listener or ListenerItem interface 257 | SubscribedEvents() map[string]any 258 | } 259 | ``` 260 | 261 | **Example** 262 | 263 | > Implementation interface `event.Subscriber` 264 | 265 | ```go 266 | package mypgk 267 | 268 | import ( 269 | "fmt" 270 | 271 | "github.com/gookit/event" 272 | ) 273 | 274 | type MySubscriber struct { 275 | // ooo 276 | } 277 | 278 | func (s *MySubscriber) SubscribedEvents() map[string]any { 279 | return map[string]any{ 280 | "e1": event.ListenerFunc(s.e1Handler), 281 | "e2": event.ListenerItem{ 282 | Priority: event.AboveNormal, 283 | Listener: event.ListenerFunc(func(e Event) error { 284 | return fmt.Errorf("an error") 285 | }), 286 | }, 287 | "e3": &MyListener{}, 288 | } 289 | } 290 | 291 | func (s *MySubscriber) e1Handler(e event.Event) error { 292 | e.Set("e1-key", "val1") 293 | return nil 294 | } 295 | ``` 296 | 297 | ## Write custom events 298 | 299 | If you want to customize the event object or define some fixed event information in advance, 300 | you can implement the `event.Event` interface. 301 | 302 | **interface:** 303 | 304 | ```go 305 | // Event interface 306 | type Event interface { 307 | Name() string 308 | Get(key string) any 309 | Add(key string, val any) 310 | Set(key string, val any) 311 | Data() map[string]any 312 | SetData(M) Event 313 | Abort(bool) 314 | IsAborted() bool 315 | } 316 | ``` 317 | 318 | **examples:** 319 | 320 | ```go 321 | package mypgk 322 | 323 | import "github.com/gookit/event" 324 | 325 | type MyEvent struct { 326 | event.BasicEvent 327 | customData string 328 | } 329 | 330 | func (e *MyEvent) CustomData() string { 331 | return e.customData 332 | } 333 | ``` 334 | 335 | Usage: 336 | 337 | ```go 338 | e := &MyEvent{customData: "hello"} 339 | e.SetName("e1") 340 | event.AddEvent(e) 341 | 342 | // add listener 343 | event.On("e1", event.ListenerFunc(func(e event.Event) error { 344 | fmt.Printf("custom Data: %s\n", e.(*MyEvent).CustomData()) 345 | return nil 346 | })) 347 | 348 | // trigger 349 | event.Fire("e1", nil) 350 | // OR 351 | // event.FireEvent(e) 352 | ``` 353 | 354 | > **Note**: is used to add pre-defined public event information, which is added in the initialization phase, so it is not locked. 355 | > `Event` dynamically created in business can be directly triggered by `FireEvent()` 356 | 357 | ## Gookit packages 358 | 359 | - [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files 360 | - [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP 361 | - [gookit/gcli](https://github.com/gookit/gcli) build CLI application, tool library, running CLI commands 362 | - [gookit/slog](https://github.com/gookit/slog) Lightweight, extensible, configurable logging library written in Go 363 | - [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go 364 | - [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached. 365 | - [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags 366 | - [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support 367 | - [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data 368 | - [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data 369 | - [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more 370 | - More, please see https://github.com/gookit 371 | 372 | ## LICENSE 373 | 374 | **[MIT](LICENSE)** 375 | -------------------------------------------------------------------------------- /manager_test.go: -------------------------------------------------------------------------------- 1 | package event_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/gookit/event" 12 | "github.com/gookit/goutil/testutil/assert" 13 | ) 14 | 15 | func TestManager_FireEvent(t *testing.T) { 16 | em := event.NewManager("test") 17 | em.EnableLock = true 18 | 19 | e1 := event.NewBasic("e1", nil) 20 | assert.NoErr(t, em.AddEvent(e1)) 21 | 22 | em.On("e1", &testListener{"HI"}, event.Min) 23 | em.On("e1", &testListener{"WEL"}, event.High) 24 | em.AddListener("e1", &testListener{"COM"}, event.BelowNormal) 25 | 26 | err := em.FireEvent(e1) 27 | assert.NoError(t, err) 28 | assert.Equal(t, "handled: e1(WEL) -> e1(COM) -> e1(HI)", e1.Get("result")) 29 | 30 | // not exist 31 | err = em.FireEvent(e1.SetName("e2")) 32 | assert.NoError(t, err) 33 | 34 | em.Clear() 35 | } 36 | 37 | func TestManager_FireEvent2(t *testing.T) { 38 | buf := new(bytes.Buffer) 39 | mgr := event.NewM("test") 40 | 41 | evt1 := event.New("evt1", nil).Fill(nil, event.M{"n": "inhere"}) 42 | assert.NoErr(t, mgr.AddEvent(evt1)) 43 | 44 | assert.True(t, mgr.HasEvent("evt1")) 45 | assert.False(t, mgr.HasEvent("not-exist")) 46 | 47 | mgr.On("evt1", event.ListenerFunc(func(e event.Event) error { 48 | _, _ = fmt.Fprintf(buf, "event: %s, params: n=%s", e.Name(), e.Get("n")) 49 | return nil 50 | }), event.Normal) 51 | 52 | assert.True(t, mgr.HasListeners("evt1")) 53 | assert.False(t, mgr.HasListeners("not-exist")) 54 | 55 | err := mgr.FireEvent(evt1) 56 | assert.NoError(t, err) 57 | assert.Equal(t, "event: evt1, params: n=inhere", buf.String()) 58 | buf.Reset() 59 | 60 | mgr.On(event.Wildcard, event.ListenerFunc(func(e event.Event) error { 61 | buf.WriteString("|Wildcard handler") 62 | return nil 63 | })) 64 | 65 | err = mgr.FireEvent(evt1) 66 | assert.NoError(t, err) 67 | assert.Equal(t, "event: evt1, params: n=inhere|Wildcard handler", buf.String()) 68 | } 69 | 70 | func TestManager_AsyncFire(t *testing.T) { 71 | em := event.NewManager("test") 72 | em.On("e1", event.ListenerFunc(func(e event.Event) error { 73 | assert.Equal(t, map[string]any{"k": "v"}, e.Data()) 74 | e.Set("nk", "nv") 75 | return nil 76 | })) 77 | 78 | e1 := event.NewBasic("e1", event.M{"k": "v"}) 79 | em.AsyncFire(e1) 80 | time.Sleep(time.Second / 10) 81 | assert.Equal(t, "nv", e1.Get("nk")) 82 | 83 | var wg sync.WaitGroup 84 | em.On("e2", event.ListenerFunc(func(e event.Event) error { 85 | defer wg.Done() 86 | assert.Equal(t, "v", e.Get("k")) 87 | return nil 88 | })) 89 | 90 | wg.Add(1) 91 | em.AsyncFire(e1.SetName("e2")) 92 | wg.Wait() 93 | em.Clear() 94 | } 95 | 96 | func TestManager_AwaitFire(t *testing.T) { 97 | em := event.NewManager("test") 98 | em.On("e1", event.ListenerFunc(func(e event.Event) error { 99 | assert.Equal(t, map[string]any{"k": "v"}, e.Data()) 100 | e.Set("nk", "nv") 101 | return nil 102 | })) 103 | 104 | e1 := event.NewBasic("e1", event.M{"k": "v"}) 105 | err := em.AwaitFire(e1) 106 | 107 | assert.NoError(t, err) 108 | assert.Contains(t, e1.Data(), "nk") 109 | assert.Equal(t, "nv", e1.Get("nk")) 110 | } 111 | 112 | func TestManager_AddSubscriber(t *testing.T) { 113 | em := event.NewManager("test") 114 | em.AddSubscriber(&testSubscriber{}) 115 | 116 | assert.True(t, em.HasListeners("e1")) 117 | assert.True(t, em.HasListeners("e2")) 118 | assert.True(t, em.HasListeners("e3")) 119 | 120 | ers := em.FireBatch("e1", event.NewBasic("e2", nil)) 121 | assert.Len(t, ers, 1) 122 | 123 | assert.Panics(t, func() { 124 | em.AddSubscriber(testSubscriber2{}) 125 | }) 126 | 127 | em.Clear() 128 | } 129 | 130 | func TestManager_ListenGroupEvent(t *testing.T) { 131 | em := event.NewManager("test") 132 | 133 | e1 := event.NewBasic("app.evt1", event.M{"buf": new(bytes.Buffer)}) 134 | err := e1.AttachTo(em) 135 | assert.NoError(t, err) 136 | 137 | l2 := event.ListenerFunc(func(e event.Event) error { 138 | e.Get("buf").(*bytes.Buffer).WriteString(" > 2 " + e.Name()) 139 | return nil 140 | }) 141 | l3 := event.ListenerFunc(func(e event.Event) error { 142 | e.Get("buf").(*bytes.Buffer).WriteString(" > 3 " + e.Name()) 143 | return nil 144 | }) 145 | 146 | em.On("app.evt1", event.ListenerFunc(func(e event.Event) error { 147 | e.Get("buf").(*bytes.Buffer).WriteString("Hi > 1 " + e.Name()) 148 | return nil 149 | })) 150 | em.On("app.*", l2) 151 | em.On("*", l3) 152 | 153 | buf := e1.Get("buf").(*bytes.Buffer) 154 | err, e := em.Fire("app.evt1", nil) 155 | assert.NoError(t, err) 156 | assert.Equal(t, "app.evt1", e.Name()) 157 | // assert.Equal(t, e1, e) 158 | assert.Equal(t, "Hi > 1 app.evt1 > 2 app.evt1 > 3 app.evt1", buf.String()) 159 | 160 | em.RemoveListener("app.*", l2) 161 | assert.Len(t, em.ListenedNames(), 2) 162 | em.On("app.*", event.ListenerFunc(func(e event.Event) error { 163 | return fmt.Errorf("an error") 164 | })) 165 | 166 | buf.Reset() 167 | err, e = em.Fire("app.evt1", nil) 168 | assert.Error(t, err) 169 | assert.Equal(t, "Hi > 1 app.evt1", buf.String()) 170 | 171 | em.RemoveListeners("app.*") 172 | em.RemoveListener("", l3) 173 | em.On("app.*", l2) // re-add 174 | em.On("*", event.ListenerFunc(func(e event.Event) error { 175 | return fmt.Errorf("an error") 176 | })) 177 | assert.Len(t, em.ListenedNames(), 3) 178 | 179 | buf.Reset() 180 | err, e = em.Trigger("app.evt1", nil) 181 | assert.Error(t, err) 182 | // assert.Equal(t, e1, e) 183 | assert.Equal(t, "app.evt1", e.Name()) 184 | assert.Equal(t, "Hi > 1 app.evt1 > 2 app.evt1", buf.String()) 185 | 186 | em.RemoveListener("", nil) 187 | 188 | // clear 189 | em.Clear() 190 | buf.Reset() 191 | } 192 | 193 | func TestManager_Fire_WithWildcard(t *testing.T) { 194 | buf := new(bytes.Buffer) 195 | mgr := event.NewManager("test") 196 | 197 | const Event2FurcasTicketCreate = "kapal.furcas.ticket.create" 198 | 199 | handler := event.ListenerFunc(func(e event.Event) error { 200 | _, _ = fmt.Fprintf(buf, "%s-%s|", e.Name(), e.Get("user")) 201 | return nil 202 | }) 203 | 204 | mgr.On("kapal.furcas.ticket.*", handler) 205 | mgr.On(Event2FurcasTicketCreate, handler) 206 | 207 | err, _ := mgr.Fire(Event2FurcasTicketCreate, event.M{"user": "inhere"}) 208 | assert.NoError(t, err) 209 | assert.Equal( 210 | t, 211 | "kapal.furcas.ticket.create-inhere|kapal.furcas.ticket.create-inhere|", 212 | buf.String(), 213 | ) 214 | buf.Reset() 215 | 216 | // add Wildcard listen 217 | mgr.On("*", handler) 218 | 219 | err, _ = mgr.Trigger(Event2FurcasTicketCreate, event.M{"user": "inhere"}) 220 | assert.NoError(t, err) 221 | assert.Equal( 222 | t, 223 | "kapal.furcas.ticket.create-inhere|kapal.furcas.ticket.create-inhere|kapal.furcas.ticket.create-inhere|", 224 | buf.String(), 225 | ) 226 | } 227 | 228 | func TestManager_Fire_usePathMode(t *testing.T) { 229 | buf := new(bytes.Buffer) 230 | em := event.NewManager("test", event.UsePathMode, event.EnableLock(true)) 231 | 232 | em.Listen("db.user.*", event.ListenerFunc(func(e event.Event) error { 233 | _, _ = buf.WriteString("db.user.*|") 234 | return nil 235 | })) 236 | em.Listen("db.**", event.ListenerFunc(func(e event.Event) error { 237 | _, _ = buf.WriteString("db.**|") 238 | return nil 239 | }), 1) 240 | em.Listen("db.user.add", event.ListenerFunc(func(e event.Event) error { 241 | _, _ = buf.WriteString("db.user.add|") 242 | return nil 243 | }), 2) 244 | assert.True(t, em.HasListeners("db.user.*")) 245 | 246 | t.Run("fire case1", func(t *testing.T) { 247 | err, e := em.Fire("db.user.add", event.M{"user": "inhere"}) 248 | assert.NoError(t, err) 249 | assert.Equal(t, "db.user.add", e.Name()) 250 | assert.Equal(t, "inhere", e.Get("user")) 251 | str := buf.String() 252 | fmt.Println(str) 253 | assert.Contains(t, str, "db.**|") 254 | assert.Contains(t, str, "db.user.*|") 255 | assert.Contains(t, str, "db.user.add|") 256 | assert.True(t, strings.Count(str, "|") == 3) 257 | }) 258 | buf.Reset() 259 | 260 | t.Run("fire case2", func(t *testing.T) { 261 | err, e := em.Fire("db.user.del", event.M{"user": "inhere"}) 262 | assert.NoError(t, err) 263 | assert.Equal(t, "inhere", e.Get("user")) 264 | str := buf.String() 265 | fmt.Println(str) 266 | assert.Contains(t, str, "db.**|") 267 | assert.Contains(t, str, "db.user.*|") 268 | assert.True(t, strings.Count(str, "|") == 2) 269 | }) 270 | buf.Reset() 271 | 272 | em.RemoveListeners("db.user.*") 273 | assert.False(t, em.HasListeners("db.user.*")) 274 | 275 | em.Listen("*", event.ListenerFunc(func(e event.Event) error { 276 | _, _ = buf.WriteString("*|") 277 | return nil 278 | }), 3) 279 | em.Listen("db.*.update", event.ListenerFunc(func(e event.Event) error { 280 | _, _ = buf.WriteString("db.*.update|") 281 | return nil 282 | }), 4) 283 | 284 | t.Run("fire case3", func(t *testing.T) { 285 | err, e := em.Fire("db.user.update", event.M{"user": "inhere"}) 286 | assert.NoError(t, err) 287 | assert.Equal(t, "inhere", e.Get("user")) 288 | str := buf.String() 289 | fmt.Println(str) 290 | assert.Contains(t, str, "*|") 291 | assert.Contains(t, str, "db.**|") 292 | assert.Contains(t, str, "db.*.update|") 293 | assert.True(t, strings.Count(str, "|") == 3) 294 | }) 295 | buf.Reset() 296 | 297 | t.Run("not-exist", func(t *testing.T) { 298 | err, e := em.Fire("not-exist", event.M{"user": "inhere"}) 299 | assert.NoError(t, err) 300 | assert.Equal(t, "inhere", e.Get("user")) 301 | str := buf.String() 302 | fmt.Println(str) 303 | assert.Equal(t, "*|", str) 304 | }) 305 | } 306 | 307 | func TestManager_Fire_AllNode(t *testing.T) { 308 | em := event.NewManager("test", event.UsePathMode, event.EnableLock(false)) 309 | 310 | buf := new(bytes.Buffer) 311 | em.Listen("**.add", event.ListenerFunc(func(e event.Event) error { 312 | _, _ = buf.WriteString("**.add|") 313 | return nil 314 | })) 315 | 316 | err, e := em.Trigger("db.user.add", event.M{"user": "inhere"}) 317 | assert.NoError(t, err) 318 | assert.Equal(t, "inhere", e.Get("user")) 319 | str := buf.String() 320 | assert.Equal(t, "**.add|", str) 321 | } 322 | 323 | func TestManager_FireC(t *testing.T) { 324 | em := event.NewManager("test", event.UsePathMode, event.EnableLock(true)) 325 | defer func(em *event.Manager) { 326 | _ = em.Close() 327 | }(em) 328 | 329 | buf := new(bytes.Buffer) 330 | em.Listen("db.user.*", event.ListenerFunc(func(e event.Event) error { 331 | _, _ = buf.WriteString("db.user.*|") 332 | return nil 333 | })) 334 | em.Listen("db.**", event.ListenerFunc(func(e event.Event) error { 335 | _, _ = buf.WriteString("db.**|") 336 | return nil 337 | }), 1) 338 | 339 | em.Listen("db.user.add", event.ListenerFunc(func(e event.Event) error { 340 | _, _ = buf.WriteString("db.user.add|") 341 | return nil 342 | }), 2) 343 | 344 | assert.True(t, em.HasListeners("db.user.*")) 345 | 346 | em.FireC("db.user.add", event.M{"user": "inhere"}) 347 | time.Sleep(time.Millisecond * 100) // wait for async 348 | 349 | str := buf.String() 350 | fmt.Println(str) 351 | assert.Contains(t, str, "db.**|") 352 | assert.Contains(t, str, "db.user.*|") 353 | assert.Contains(t, str, "db.user.add|") 354 | assert.True(t, strings.Count(str, "|") == 3) 355 | buf.Reset() 356 | 357 | em.WithOptions(func(o *event.Options) { 358 | o.ChannelSize = 0 359 | o.ConsumerNum = 0 360 | }) 361 | em.Async("not-exist", event.M{"user": "inhere"}) 362 | time.Sleep(time.Millisecond * 100) // wait for async 363 | assert.Empty(t, buf.String()) 364 | } 365 | 366 | func TestManager_Wait(t *testing.T) { 367 | em := event.NewManager("test", event.UsePathMode) 368 | 369 | buf := new(bytes.Buffer) 370 | em.Listen("db.user.*", event.ListenerFunc(func(e event.Event) error { 371 | time.Sleep(time.Millisecond * 200) 372 | _, _ = buf.WriteString("db.user.*|") 373 | return nil 374 | })) 375 | assert.True(t, em.HasListeners("db.user.*")) 376 | 377 | em.Async("db.user.add", event.M{"user": "inhere"}) 378 | assert.NoError(t, em.CloseWait()) 379 | 380 | str := buf.String() 381 | fmt.Println(str) 382 | assert.Equal(t, "db.user.*|", str) 383 | buf.Reset() 384 | } 385 | 386 | func TestManager_Once(t *testing.T) { 387 | em := event.NewManager("test") 388 | 389 | em.Once("evt1", event.ListenerFunc(emptyListener)) 390 | assert.True(t, em.HasListeners("evt1")) 391 | em.Trigger("evt1", nil) 392 | assert.False(t, em.HasListeners("evt1")) 393 | } 394 | --------------------------------------------------------------------------------