From 5ef922278ac9e759ac47d5ef8d4446d9a4e0f1ba Mon Sep 17 00:00:00 2001 From: 1 <1@72wo.cn> Date: Tue, 20 Jan 2026 22:08:36 +0000 Subject: [PATCH] =?UTF-8?q?```=20feat(pet):=20=E9=87=8D=E6=9E=84=E5=AE=A0?= =?UTF-8?q?=E7=89=A9=E7=B9=81=E6=AE=96=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=9B=8B=E5=AD=B5=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/concurrent-swiss-map/.golangci.yml | 46 ++ .../concurrent-swiss-map/.goreleaser.yml | 27 + common/utils/concurrent-swiss-map/LICENSE | 21 + common/utils/concurrent-swiss-map/README.md | 103 ++++ .../utils/concurrent-swiss-map/benchmark.png | Bin 0 -> 161285 bytes .../concurrent_swiss_map.go | 286 ++++++++++ .../concurrent_swiss_map_benchmark_test.go | 533 ++++++++++++++++++ .../concurrent_swiss_map_test.go | 332 +++++++++++ .../concurrent-swiss-map/example/base/base.go | 57 ++ common/utils/concurrent-swiss-map/go.mod | 3 + common/utils/concurrent-swiss-map/go.sum | 0 common/utils/concurrent-swiss-map/img.png | Bin 0 -> 120647 bytes .../maphash}/LICENSE | 2 +- .../concurrent-swiss-map/maphash/README.md | 4 + .../concurrent-swiss-map/maphash/hasher.go | 48 ++ .../concurrent-swiss-map/maphash/runtime.go | 117 ++++ .../utils/concurrent-swiss-map/swiss/LICENSE | 201 +++++++ .../concurrent-swiss-map/swiss/README.md | 2 + .../utils/concurrent-swiss-map/swiss/bits.go | 59 ++ .../concurrent-swiss-map/swiss/bits_amd64.go | 52 ++ .../utils/concurrent-swiss-map/swiss/map.go | 357 ++++++++++++ .../concurrent-swiss-map/swiss/simd/match.s | 19 + .../swiss/simd/match_amd64.go | 9 + common/utils/limit/NOTICE | 13 - common/utils/limit/README.md | 42 -- common/utils/limit/example_test.go | 68 --- common/utils/limit/go.mod | 5 - common/utils/limit/go.sum | 2 - common/utils/limit/limit.go | 278 --------- common/utils/limit/limit_test.go | 95 ---- common/utils/timer/LICENSE | 21 + common/utils/timer/README.md | 130 +++++ common/utils/timer/_long-time-test/build.sh | 1 + .../timer/_long-time-test/long-time-test.go | 157 ++++++ common/utils/timer/go.mod | 5 + common/utils/timer/go.sum | 2 + common/utils/timer/min_heap.go | 221 ++++++++ common/utils/timer/min_heap_node.go | 62 ++ common/utils/timer/min_heap_node_test.go | 64 +++ common/utils/timer/min_heap_test.go | 329 +++++++++++ common/utils/timer/option.go | 39 ++ common/utils/timer/t_test.go | 17 + common/utils/timer/time_wheel.go | 294 ++++++++++ common/utils/timer/time_wheel_node.go | 114 ++++ common/utils/timer/time_wheel_test.go | 189 +++++++ common/utils/timer/time_wheel_utils.go | 10 + common/utils/timer/timer.go | 53 ++ common/utils/timer/timer_test.go | 219 +++++++ common/utils/timer/timer_wheel_utils_test.go | 14 + go.work | 5 +- logic/controller/login_getserver.go | 2 +- logic/controller/pet_egg.go | 48 +- logic/controller/user_info.go | 6 +- logic/main.go | 15 + logic/service/pet/egg.go | 45 +- logic/service/player/player.go | 4 +- modules/config/model/boss_effect.go | 2 +- modules/config/model/cdk.go | 4 +- modules/config/model/egg.go | 52 ++ modules/config/model/task.go | 2 +- modules/player/model/egg.go | 76 +++ modules/player/model/{FRIEND.go => friend.go} | 0 modules/player/model/{player.go => info.go} | 0 modules/player/model/sign.go | 2 +- modules/player/service/egg.go | 48 ++ modules/player/service/info.go | 12 +- modules/player/service/pet.go | 4 +- modules/player/service/user.go | 2 + 68 files changed, 4467 insertions(+), 584 deletions(-) create mode 100644 common/utils/concurrent-swiss-map/.golangci.yml create mode 100644 common/utils/concurrent-swiss-map/.goreleaser.yml create mode 100644 common/utils/concurrent-swiss-map/LICENSE create mode 100644 common/utils/concurrent-swiss-map/README.md create mode 100644 common/utils/concurrent-swiss-map/benchmark.png create mode 100644 common/utils/concurrent-swiss-map/concurrent_swiss_map.go create mode 100644 common/utils/concurrent-swiss-map/concurrent_swiss_map_benchmark_test.go create mode 100644 common/utils/concurrent-swiss-map/concurrent_swiss_map_test.go create mode 100644 common/utils/concurrent-swiss-map/example/base/base.go create mode 100644 common/utils/concurrent-swiss-map/go.mod create mode 100644 common/utils/concurrent-swiss-map/go.sum create mode 100644 common/utils/concurrent-swiss-map/img.png rename common/utils/{limit => concurrent-swiss-map/maphash}/LICENSE (99%) create mode 100644 common/utils/concurrent-swiss-map/maphash/README.md create mode 100644 common/utils/concurrent-swiss-map/maphash/hasher.go create mode 100644 common/utils/concurrent-swiss-map/maphash/runtime.go create mode 100644 common/utils/concurrent-swiss-map/swiss/LICENSE create mode 100644 common/utils/concurrent-swiss-map/swiss/README.md create mode 100644 common/utils/concurrent-swiss-map/swiss/bits.go create mode 100644 common/utils/concurrent-swiss-map/swiss/bits_amd64.go create mode 100644 common/utils/concurrent-swiss-map/swiss/map.go create mode 100644 common/utils/concurrent-swiss-map/swiss/simd/match.s create mode 100644 common/utils/concurrent-swiss-map/swiss/simd/match_amd64.go delete mode 100644 common/utils/limit/NOTICE delete mode 100644 common/utils/limit/README.md delete mode 100644 common/utils/limit/example_test.go delete mode 100644 common/utils/limit/go.mod delete mode 100644 common/utils/limit/go.sum delete mode 100644 common/utils/limit/limit.go delete mode 100644 common/utils/limit/limit_test.go create mode 100644 common/utils/timer/LICENSE create mode 100644 common/utils/timer/README.md create mode 100755 common/utils/timer/_long-time-test/build.sh create mode 100644 common/utils/timer/_long-time-test/long-time-test.go create mode 100644 common/utils/timer/go.mod create mode 100644 common/utils/timer/go.sum create mode 100644 common/utils/timer/min_heap.go create mode 100644 common/utils/timer/min_heap_node.go create mode 100644 common/utils/timer/min_heap_node_test.go create mode 100644 common/utils/timer/min_heap_test.go create mode 100644 common/utils/timer/option.go create mode 100644 common/utils/timer/t_test.go create mode 100644 common/utils/timer/time_wheel.go create mode 100644 common/utils/timer/time_wheel_node.go create mode 100644 common/utils/timer/time_wheel_test.go create mode 100644 common/utils/timer/time_wheel_utils.go create mode 100644 common/utils/timer/timer.go create mode 100644 common/utils/timer/timer_test.go create mode 100644 common/utils/timer/timer_wheel_utils_test.go create mode 100644 modules/config/model/egg.go create mode 100644 modules/player/model/egg.go rename modules/player/model/{FRIEND.go => friend.go} (100%) rename modules/player/model/{player.go => info.go} (100%) create mode 100644 modules/player/service/egg.go diff --git a/common/utils/concurrent-swiss-map/.golangci.yml b/common/utils/concurrent-swiss-map/.golangci.yml new file mode 100644 index 000000000..f9cb8f2be --- /dev/null +++ b/common/utils/concurrent-swiss-map/.golangci.yml @@ -0,0 +1,46 @@ +run: + skip-dirs: + - swiss + - swiss/simd + - maphash + skip-files: + - "concurrent_swiss_map_benchmark_test.go" + skip-dirs-use-default: false + +linters-settings: + lll: + line-length: 140 + funlen: + lines: 70 + +linters: + disable-all: true + enable: + - bodyclose + - depguard + - errcheck + - exhaustive + - funlen + - goconst + - gocritic + - gocyclo + - revive + - gosimple + - govet + - gosec + - ineffassign + - lll + - misspell + - nakedret + - gofumpt + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - whitespace + +service: + golangci-lint-version: 1.50.x # use the fixed version to not introduce new linters unexpectedly + prepare: + - echo "here I can run custom commands, but no preparation needed for this repo" \ No newline at end of file diff --git a/common/utils/concurrent-swiss-map/.goreleaser.yml b/common/utils/concurrent-swiss-map/.goreleaser.yml new file mode 100644 index 000000000..04dc6bb66 --- /dev/null +++ b/common/utils/concurrent-swiss-map/.goreleaser.yml @@ -0,0 +1,27 @@ +project_name: concurrent-swiss-map + +release: + github: + name: concurrent-swiss-map + owner: mhmtszr + +before: + hooks: + - go mod tidy + +builds: + - skip: true + +changelog: + sort: asc + use: github + filters: + exclude: + - '^test:' + - '^docs:' + - '^chore:' + - 'merge conflict' + - Merge pull request + - Merge remote-tracking branch + - Merge branch + - go mod tidy \ No newline at end of file diff --git a/common/utils/concurrent-swiss-map/LICENSE b/common/utils/concurrent-swiss-map/LICENSE new file mode 100644 index 000000000..57eae8b7d --- /dev/null +++ b/common/utils/concurrent-swiss-map/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mehmet Sezer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/utils/concurrent-swiss-map/README.md b/common/utils/concurrent-swiss-map/README.md new file mode 100644 index 000000000..7f6696fa1 --- /dev/null +++ b/common/utils/concurrent-swiss-map/README.md @@ -0,0 +1,103 @@ +# Concurrent Swiss Map [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][go-report-img]][go-report] + +**Concurrent Swiss Map** is an open-source Go library that provides a high-performance, thread-safe generic concurrent hash map implementation designed to handle concurrent access efficiently. It's built with a focus on simplicity, speed, and reliability, making it a solid choice for scenarios where concurrent access to a hash map is crucial. + +Uses [dolthub/swiss](https://github.com/dolthub/swiss) map implementation under the hood. + +## Installation + +Supports 1.18+ Go versions because of Go Generics + +``` +go get github.com/mhmtszr/concurrent-swiss-map +``` + +## Usage + +New functions will be added soon... + +```go +package main + +import ( + "hash/fnv" + + csmap "github.com/mhmtszr/concurrent-swiss-map" +) + +func main() { + myMap := csmap.New[string, int]( + // set the number of map shards. the default value is 32. + csmap.WithShardCount[string, int](32), + + // if don't set custom hasher, use the built-in maphash. + csmap.WithCustomHasher[string, int](func(key string) uint64 { + hash := fnv.New64a() + hash.Write([]byte(key)) + return hash.Sum64() + }), + + // set the total capacity, every shard map has total capacity/shard count capacity. the default value is 0. + csmap.WithSize[string, int](1000), + ) + + key := "swiss-map" + myMap.Store(key, 10) + + val, ok := myMap.Load(key) + println("load val:", val, "exists:", ok) + + deleted := myMap.Delete(key) + println("deleted:", deleted) + + ok = myMap.Has(key) + println("has:", ok) + + empty := myMap.IsEmpty() + println("empty:", empty) + + myMap.SetIfAbsent(key, 11) + + myMap.Range(func(key string, value int) (stop bool) { + println("range:", key, value) + return true + }) + + count := myMap.Count() + println("count:", count) + + // Output: + // load val: 10 exists: true + // deleted: true + // has: false + // empty: true + // range: swiss-map 11 + // count: 1 +} +``` + +## Basic Architecture +![img.png](img.png) + +## Benchmark Test +Benchmark was made on: +- Apple M1 Max +- 32 GB memory + +Benchmark test results can be obtained by running [this file](concurrent_swiss_map_benchmark_test.go) on local computers. + +![benchmark.png](benchmark.png) + +### Benchmark Results + +- Memory usage of the concurrent swiss map is better than other map implementations in all checked test scenarios. +- In high concurrent systems, the concurrent swiss map is faster, but in systems containing few concurrent operations, it works similarly to RWMutexMap. + +[doc-img]: https://godoc.org/github.com/mhmtszr/concurrent-swiss-map?status.svg +[doc]: https://godoc.org/github.com/mhmtszr/concurrent-swiss-map +[ci-img]: https://github.com/mhmtszr/concurrent-swiss-map/actions/workflows/build-test.yml/badge.svg +[ci]: https://github.com/mhmtszr/concurrent-swiss-map/actions/workflows/build-test.yml +[cov-img]: https://codecov.io/gh/mhmtszr/concurrent-swiss-map/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/mhmtszr/concurrent-swiss-map +[go-report-img]: https://goreportcard.com/badge/github.com/mhmtszr/concurrent-swiss-map +[go-report]: https://goreportcard.com/report/github.com/mhmtszr/concurrent-swiss-map \ No newline at end of file diff --git a/common/utils/concurrent-swiss-map/benchmark.png b/common/utils/concurrent-swiss-map/benchmark.png new file mode 100644 index 0000000000000000000000000000000000000000..3899f0c1b4dce3af345b04ffda197f9025bdaf7b GIT binary patch literal 161285 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
    !XCL5Vxm^bjUrBsmh9*jnEl%=F!dw9 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 20 { + myMap.Delete(key) + } + return false + }) + if myMap.Count() != 3 { + t.Fatal("total should be 3, because currently range deletes values that bigger than 20.") + } +} + +func TestMarshal(t *testing.T) { + myMap := csmap.New[string, int]( + csmap.WithSize[string, int](1024), + ) + + myMap.Store("aaa", 10) + myMap.Store("aab", 11) + + b, _ := myMap.MarshalJSON() + + newMap := csmap.New[string, int]( + csmap.WithSize[string, int](1024), + ) + + _ = newMap.UnmarshalJSON(b) + + if myMap.Count() != 2 || !myMap.Has("aaa") || !myMap.Has("aab") { + t.Fatal("count should be 2 after unmarshal") + } +} + +func TestBasicConcurrentWriteDeleteCount(t *testing.T) { + myMap := csmap.New[int, string]( + csmap.WithShardCount[int, string](32), + csmap.WithSize[int, string](1000), + ) + + var wg sync.WaitGroup + wg.Add(1000000) + for i := 0; i < 1000000; i++ { + i := i + go func() { + defer wg.Done() + myMap.Store(i, strconv.Itoa(i)) + }() + } + wg.Wait() + wg.Add(1000000) + for i := 0; i < 1000000; i++ { + i := i + go func() { + defer wg.Done() + if !myMap.Has(i) { + t.Error(strconv.Itoa(i) + " should exist") + return + } + }() + } + + wg.Wait() + wg.Add(1000000) + + for i := 0; i < 1000000; i++ { + i := i + go func() { + defer wg.Done() + myMap.Delete(i) + }() + } + + wg.Wait() + wg.Add(1000000) + + for i := 0; i < 1000000; i++ { + i := i + go func() { + defer wg.Done() + if myMap.Has(i) { + t.Error(strconv.Itoa(i) + " should not exist") + return + } + }() + } + + wg.Wait() +} + +func TestClear(t *testing.T) { + myMap := csmap.New[int, string]() + loop := 10000 + for i := 0; i < loop; i++ { + myMap.Store(i, "test") + } + + myMap.Clear() + + if !myMap.IsEmpty() { + t.Fatal("count should be true") + } + + // store again + for i := 0; i < loop; i++ { + myMap.Store(i, "test") + } + + // get again + for i := 0; i < loop; i++ { + val, ok := myMap.Load(i) + if ok != true { + t.Fatal("ok should be true") + } + + if val != "test" { + t.Fatal("val should be test") + } + } + + // check again + count := myMap.Count() + if count != loop { + t.Fatal("count should be 1000") + } +} diff --git a/common/utils/concurrent-swiss-map/example/base/base.go b/common/utils/concurrent-swiss-map/example/base/base.go new file mode 100644 index 000000000..b3b7e7cb3 --- /dev/null +++ b/common/utils/concurrent-swiss-map/example/base/base.go @@ -0,0 +1,57 @@ +package main + +import ( + "hash/fnv" + + csmap "github.com/mhmtszr/concurrent-swiss-map" +) + +func main() { + myMap := csmap.New[string, int]( + // set the number of map shards. the default value is 32. + csmap.WithShardCount[string, int](32), + + // if don't set custom hasher, use the built-in maphash. + csmap.WithCustomHasher[string, int](func(key string) uint64 { + hash := fnv.New64a() + hash.Write([]byte(key)) + return hash.Sum64() + }), + + // set the total capacity, every shard map has total capacity/shard count capacity. the default value is 0. + csmap.WithSize[string, int](1000), + ) + + key := "swiss-map" + myMap.Store(key, 10) + + val, ok := myMap.Load(key) + println("load val:", val, "exists:", ok) + + deleted := myMap.Delete(key) + println("deleted:", deleted) + + ok = myMap.Has(key) + println("has:", ok) + + empty := myMap.IsEmpty() + println("empty:", empty) + + myMap.SetIfAbsent(key, 11) + + myMap.Range(func(key string, value int) (stop bool) { + println("range:", key, value) + return true + }) + + count := myMap.Count() + println("count:", count) + + // Output: + // load val: 10 exists: true + // deleted: true + // has: false + // empty: true + // range: swiss-map 11 + // count: 1 +} diff --git a/common/utils/concurrent-swiss-map/go.mod b/common/utils/concurrent-swiss-map/go.mod new file mode 100644 index 000000000..83da03a77 --- /dev/null +++ b/common/utils/concurrent-swiss-map/go.mod @@ -0,0 +1,3 @@ +module github.com/mhmtszr/concurrent-swiss-map + +go 1.18 diff --git a/common/utils/concurrent-swiss-map/go.sum b/common/utils/concurrent-swiss-map/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/common/utils/concurrent-swiss-map/img.png b/common/utils/concurrent-swiss-map/img.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e638275a8366b831a2465a0315e420104db554 GIT binary patch literal 120647 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 vix4X}#!sIE(1h3V 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 literal 0 HcmV?d00001 diff --git a/common/utils/limit/LICENSE b/common/utils/concurrent-swiss-map/maphash/LICENSE similarity index 99% rename from common/utils/limit/LICENSE rename to common/utils/concurrent-swiss-map/maphash/LICENSE index 800f2c7c2..261eeb9e9 100644 --- a/common/utils/limit/LICENSE +++ b/common/utils/concurrent-swiss-map/maphash/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 肖其顿 + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/common/utils/concurrent-swiss-map/maphash/README.md b/common/utils/concurrent-swiss-map/maphash/README.md new file mode 100644 index 000000000..d91530f99 --- /dev/null +++ b/common/utils/concurrent-swiss-map/maphash/README.md @@ -0,0 +1,4 @@ +# maphash + +Hash any `comparable` type using Golang's fast runtime hash. +Uses [AES](https://en.wikipedia.org/wiki/AES_instruction_set) instructions when available. \ No newline at end of file diff --git a/common/utils/concurrent-swiss-map/maphash/hasher.go b/common/utils/concurrent-swiss-map/maphash/hasher.go new file mode 100644 index 000000000..ef53596a2 --- /dev/null +++ b/common/utils/concurrent-swiss-map/maphash/hasher.go @@ -0,0 +1,48 @@ +// Copyright 2022 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package maphash + +import "unsafe" + +// Hasher hashes values of type K. +// Uses runtime AES-based hashing. +type Hasher[K comparable] struct { + hash hashfn + seed uintptr +} + +// NewHasher creates a new Hasher[K] with a random seed. +func NewHasher[K comparable]() Hasher[K] { + return Hasher[K]{ + hash: getRuntimeHasher[K](), + seed: newHashSeed(), + } +} + +// NewSeed returns a copy of |h| with a new hash seed. +func NewSeed[K comparable](h Hasher[K]) Hasher[K] { + return Hasher[K]{ + hash: h.hash, + seed: newHashSeed(), + } +} + +// Hash hashes |key|. +func (h Hasher[K]) Hash(key K) uint64 { + // promise to the compiler that pointer + // |p| does not escape the stack. + p := noescape(unsafe.Pointer(&key)) + return uint64(h.hash(p, h.seed)) +} diff --git a/common/utils/concurrent-swiss-map/maphash/runtime.go b/common/utils/concurrent-swiss-map/maphash/runtime.go new file mode 100644 index 000000000..b192dde8b --- /dev/null +++ b/common/utils/concurrent-swiss-map/maphash/runtime.go @@ -0,0 +1,117 @@ +// Copyright 2022 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 || go1.19 +// +build go1.18 go1.19 + +package maphash + +import ( + "math/rand" + "unsafe" +) + +type hashfn func(unsafe.Pointer, uintptr) uintptr + +func getRuntimeHasher[K comparable]() (h hashfn) { + a := any(make(map[K]struct{})) + i := (*mapiface)(unsafe.Pointer(&a)) + h = i.typ.hasher + return +} + +//nolint:gosec +var hashSeed = rand.Int() + +func newHashSeed() uintptr { + return uintptr(hashSeed) +} + +// noescape hides a pointer from escape analysis. It is the identity function +// but escape analysis doesn't think the output depends on the input. +// noescape is inlined and currently compiles down to zero instructions. +// USE CAREFULLY! +// This was copied from the runtime (via pkg "strings"); see issues 23382 and 7921. +// +//go:nosplit +//go:nocheckptr +//nolint:staticcheck +func noescape(p unsafe.Pointer) unsafe.Pointer { + x := uintptr(p) + return unsafe.Pointer(x ^ 0) +} + +type mapiface struct { + typ *maptype + val *hmap +} + +// go/src/runtime/type.go +type maptype struct { + typ _type + key *_type + elem *_type + bucket *_type + // function for hashing keys (ptr to key, seed) -> hash + hasher func(unsafe.Pointer, uintptr) uintptr + keysize uint8 + elemsize uint8 + bucketsize uint16 + flags uint32 +} + +// go/src/runtime/map.go +type hmap struct { + count int + flags uint8 + B uint8 + noverflow uint16 + // hash seed + hash0 uint32 + buckets unsafe.Pointer + oldbuckets unsafe.Pointer + nevacuate uintptr + // true type is *mapextra + // but we don't need this data + extra unsafe.Pointer +} + +// go/src/runtime/type.go +type ( + tflag uint8 + nameOff int32 + typeOff int32 +) + +// go/src/runtime/type.go +type _type struct { + size uintptr + ptrdata uintptr + hash uint32 + tflag tflag + align uint8 + fieldAlign uint8 + kind uint8 + equal func(unsafe.Pointer, unsafe.Pointer) bool + gcdata *byte + str nameOff + ptrToThis typeOff +} diff --git a/common/utils/concurrent-swiss-map/swiss/LICENSE b/common/utils/concurrent-swiss-map/swiss/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/common/utils/concurrent-swiss-map/swiss/README.md b/common/utils/concurrent-swiss-map/swiss/README.md new file mode 100644 index 000000000..cfb41531b --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/README.md @@ -0,0 +1,2 @@ +# swiss +Golang port of Abseil's flat_hash_map diff --git a/common/utils/concurrent-swiss-map/swiss/bits.go b/common/utils/concurrent-swiss-map/swiss/bits.go new file mode 100644 index 000000000..e296ea6e4 --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/bits.go @@ -0,0 +1,59 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//go:build !amd64 || nosimd + +//nolint:all + +package swiss + +import ( + "math/bits" + "unsafe" +) + +const ( + groupSize = 8 + maxAvgGroupLoad = 7 + + loBits uint64 = 0x0101010101010101 + hiBits uint64 = 0x8080808080808080 +) + +type bitset uint64 + +func metaMatchH2(m *metadata, h h2) bitset { + // https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord + return hasZeroByte(castUint64(m) ^ (loBits * uint64(h))) +} + +func metaMatchEmpty(m *metadata) bitset { + return hasZeroByte(castUint64(m) ^ hiBits) +} + +func nextMatch(b *bitset) uint32 { + s := uint32(bits.TrailingZeros64(uint64(*b))) + *b &= ^(1 << s) // clear bit |s| + return s >> 3 // div by 8 +} + +func hasZeroByte(x uint64) bitset { + return bitset(((x - loBits) & ^(x)) & hiBits) +} + +func castUint64(m *metadata) uint64 { + return *(*uint64)((unsafe.Pointer)(m)) +} + +//go:linkname fastrand runtime.fastrand +func fastrand() uint32 diff --git a/common/utils/concurrent-swiss-map/swiss/bits_amd64.go b/common/utils/concurrent-swiss-map/swiss/bits_amd64.go new file mode 100644 index 000000000..a46474d77 --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/bits_amd64.go @@ -0,0 +1,52 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//nolint:all +//go:build amd64 && !nosimd + +package swiss + +import ( + "github.com/mhmtszr/concurrent-swiss-map/swiss/simd" + "math/bits" + _ "unsafe" +) + +const ( + groupSize = 16 + maxAvgGroupLoad = 14 +) + +type bitset uint16 + +//nolint:all +func metaMatchH2(m *metadata, h h2) bitset { + b := simd.MatchMetadata((*[16]int8)(m), int8(h)) + return bitset(b) +} + +//nolint:all +func metaMatchEmpty(m *metadata) bitset { + b := simd.MatchMetadata((*[16]int8)(m), empty) + return bitset(b) +} + +//nolint:all +func nextMatch(b *bitset) (s uint32) { + s = uint32(bits.TrailingZeros16(uint16(*b))) + *b &= ^(1 << s) // clear bit |s| + return +} + +//go:linkname fastrand runtime.fastrand +func fastrand() uint32 diff --git a/common/utils/concurrent-swiss-map/swiss/map.go b/common/utils/concurrent-swiss-map/swiss/map.go new file mode 100644 index 000000000..8fea5235c --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/map.go @@ -0,0 +1,357 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swiss + +import ( + "github.com/mhmtszr/concurrent-swiss-map/maphash" +) + +const ( + maxLoadFactor = float32(maxAvgGroupLoad) / float32(groupSize) +) + +// Map is an open-addressing hash map +// based on Abseil's flat_hash_map. +type Map[K comparable, V any] struct { + ctrl []metadata + groups []group[K, V] + hash maphash.Hasher[K] + resident uint32 + dead uint32 + limit uint32 +} + +// metadata is the h2 metadata array for a group. +// find operations first probe the controls bytes +// to filter candidates before matching keys +type metadata [groupSize]int8 + +// group is a group of 16 key-value pairs +type group[K comparable, V any] struct { + keys [groupSize]K + values [groupSize]V +} + +const ( + h1Mask uint64 = 0xffff_ffff_ffff_ff80 + h2Mask uint64 = 0x0000_0000_0000_007f + empty int8 = -128 // 0b1000_0000 + tombstone int8 = -2 // 0b1111_1110 +) + +// h1 is a 57 bit hash prefix +type h1 uint64 + +// h2 is a 7 bit hash suffix +type h2 int8 + +// NewMap constructs a Map. +func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) { + groups := numGroups(sz) + m = &Map[K, V]{ + ctrl: make([]metadata, groups), + groups: make([]group[K, V], groups), + hash: maphash.NewHasher[K](), + limit: groups * maxAvgGroupLoad, + } + for i := range m.ctrl { + m.ctrl[i] = newEmptyMetadata() + } + return +} + +func (m *Map[K, V]) HasWithHash(key K, hash uint64) (ok bool) { + hi, lo := splitHash(hash) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + ok = true + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { + ok = false + return + } + g++ // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +func (m *Map[K, V]) GetWithHash(key K, hash uint64) (value V, ok bool) { + hi, lo := splitHash(hash) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + value, ok = m.groups[g].values[s], true + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { + ok = false + return + } + g++ // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Put attempts to insert |key| and |value| +func (m *Map[K, V]) Put(key K, value V) { + if m.resident >= m.limit { + m.rehash(m.nextSize()) + } + hi, lo := splitHash(m.hash.Hash(key)) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { // update + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { // insert + s := nextMatch(&matches) + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + m.ctrl[g][s] = int8(lo) + m.resident++ + return + } + g++ // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Put attempts to insert |key| and |value| +func (m *Map[K, V]) PutWithHash(key K, value V, hash uint64) { + if m.resident >= m.limit { + m.rehash(m.nextSize()) + } + hi, lo := splitHash(hash) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { // update + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { // insert + s := nextMatch(&matches) + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + m.ctrl[g][s] = int8(lo) + m.resident++ + return + } + g++ // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +func (m *Map[K, V]) DeleteWithHash(key K, hash uint64) (ok bool) { + hi, lo := splitHash(hash) + g := probeStart(hi, len(m.groups)) + for { + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + ok = true + // optimization: if |m.ctrl[g]| contains any empty + // metadata bytes, we can physically delete |key| + // rather than placing a tombstone. + // The observation is that any probes into group |g| + // would already be terminated by the existing empty + // slot, and therefore reclaiming slot |s| will not + // cause premature termination of probes into |g|. + if metaMatchEmpty(&m.ctrl[g]) != 0 { + m.ctrl[g][s] = empty + m.resident-- + } else { + m.ctrl[g][s] = tombstone + m.dead++ + } + var k K + var v V + m.groups[g].keys[s] = k + m.groups[g].values[s] = v + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { // |key| absent + ok = false + return + } + g++ // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Clear removes all elements from the Map. +func (m *Map[K, V]) Clear() { + for i, c := range m.ctrl { + for j := range c { + m.ctrl[i][j] = empty + } + } + var k K + var v V + for i := range m.groups { + g := &m.groups[i] + for i := range g.keys { + g.keys[i] = k + g.values[i] = v + } + } + m.resident, m.dead = 0, 0 +} + +// Iter iterates the elements of the Map, passing them to the callback. +// It guarantees that any key in the Map will be visited only once, and +// for un-mutated Maps, every key will be visited once. If the Map is +// Mutated during iteration, mutations will be reflected on return from +// Iter, but the set of keys visited by Iter is non-deterministic. +// +//nolint:gosec +func (m *Map[K, V]) Iter(cb func(k K, v V) (stop bool)) bool { + // take a consistent view of the table in case + // we rehash during iteration + ctrl, groups := m.ctrl, m.groups + // pick a random starting group + g := randIntN(len(groups)) + for n := 0; n < len(groups); n++ { + for s, c := range ctrl[g] { + if c == empty || c == tombstone { + continue + } + k, v := groups[g].keys[s], groups[g].values[s] + if stop := cb(k, v); stop { + return stop + } + } + g++ + if g >= uint32(len(groups)) { + g = 0 + } + } + return false +} + +// Count returns the number of elements in the Map. +func (m *Map[K, V]) Count() int { + return int(m.resident - m.dead) +} + +func (m *Map[K, V]) nextSize() (n uint32) { + n = uint32(len(m.groups)) * 2 + if m.dead >= (m.resident / 2) { + n = uint32(len(m.groups)) + } + return +} + +func (m *Map[K, V]) rehash(n uint32) { + groups, ctrl := m.groups, m.ctrl + m.groups = make([]group[K, V], n) + m.ctrl = make([]metadata, n) + for i := range m.ctrl { + m.ctrl[i] = newEmptyMetadata() + } + m.hash = maphash.NewSeed(m.hash) + m.limit = n * maxAvgGroupLoad + m.resident, m.dead = 0, 0 + for g := range ctrl { + for s := range ctrl[g] { + c := ctrl[g][s] + if c == empty || c == tombstone { + continue + } + m.Put(groups[g].keys[s], groups[g].values[s]) + } + } +} + +// numGroups returns the minimum number of groups needed to store |n| elems. +func numGroups(n uint32) (groups uint32) { + groups = (n + maxAvgGroupLoad - 1) / maxAvgGroupLoad + if groups == 0 { + groups = 1 + } + return +} + +func newEmptyMetadata() (meta metadata) { + for i := range meta { + meta[i] = empty + } + return +} + +func splitHash(h uint64) (h1, h2) { + return h1((h & h1Mask) >> 7), h2(h & h2Mask) +} + +func probeStart(hi h1, groups int) uint32 { + return fastModN(uint32(hi), uint32(groups)) +} + +// lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +func fastModN(x, n uint32) uint32 { + return uint32((uint64(x) * uint64(n)) >> 32) +} + +// randIntN returns a random number in the interval [0, n). +func randIntN(n int) uint32 { + return fastModN(fastrand(), uint32(n)) +} diff --git a/common/utils/concurrent-swiss-map/swiss/simd/match.s b/common/utils/concurrent-swiss-map/swiss/simd/match.s new file mode 100644 index 000000000..a87a806b1 --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/simd/match.s @@ -0,0 +1,19 @@ +// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT. +//nolint +//go:build amd64 + +#include "textflag.h" + +// func MatchMetadata(metadata *[16]int8, hash int8) uint16 +// Requires: SSE2, SSSE3 +TEXT ·MatchMetadata(SB), NOSPLIT, $0-18 + MOVQ metadata+0(FP), AX + MOVBLSX hash+8(FP), CX + MOVD CX, X0 + PXOR X1, X1 + PSHUFB X1, X0 + MOVOU (AX), X1 + PCMPEQB X1, X0 + PMOVMSKB X0, AX + MOVW AX, ret+16(FP) + RET diff --git a/common/utils/concurrent-swiss-map/swiss/simd/match_amd64.go b/common/utils/concurrent-swiss-map/swiss/simd/match_amd64.go new file mode 100644 index 000000000..1dcf6f578 --- /dev/null +++ b/common/utils/concurrent-swiss-map/swiss/simd/match_amd64.go @@ -0,0 +1,9 @@ +// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT. +//nolint:all +//go:build amd64 + +package simd + +// MatchMetadata performs a 16-way probe of |metadata| using SSE instructions +// nb: |metadata| must be an aligned pointer +func MatchMetadata(metadata *[16]int8, hash int8) uint16 diff --git a/common/utils/limit/NOTICE b/common/utils/limit/NOTICE deleted file mode 100644 index 73d68a92f..000000000 --- a/common/utils/limit/NOTICE +++ /dev/null @@ -1,13 +0,0 @@ - Copyright 2025 肖其顿 - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/common/utils/limit/README.md b/common/utils/limit/README.md deleted file mode 100644 index cb911dbaf..000000000 --- a/common/utils/limit/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# limit [![PkgGoDev](https://pkg.go.dev/badge/github.com/xiaoqidun/limit)](https://pkg.go.dev/github.com/xiaoqidun/limit) -一个高性能、并发安全的 Go 语言动态速率限制器 - -# 安装指南 -```shell -go get -u github.com/xiaoqidun/limit -``` - -# 快速开始 -```go -package main - -import ( - "fmt" - - "github.com/xiaoqidun/limit" - "golang.org/x/time/rate" -) - -func main() { - // 1. 创建一个新的 Limiter 实例 - limiter := limit.New() - // 2. 确保在程序退出前优雅地停止后台任务,这非常重要 - defer limiter.Stop() - // 3. 为任意键 "some-key" 获取一个速率限制器 - // - rate.Limit(2): 表示速率为 "每秒2个请求" - // - 2: 表示桶的容量 (Burst),允许瞬时处理2个请求 - rateLimiter := limiter.Get("some-key", rate.Limit(2), 2) - // 4. 模拟3次连续的突发请求 - // 由于速率和容量都为2,只有前两次请求能立即成功 - for i := 0; i < 3; i++ { - if rateLimiter.Allow() { - fmt.Printf("请求 %d: 已允许\n", i+1) - } else { - fmt.Printf("请求 %d: 已拒绝\n", i+1) - } - } -} -``` - -# 授权协议 -本项目使用 [Apache License 2.0](https://github.com/xiaoqidun/limit/blob/main/LICENSE) 授权协议 \ No newline at end of file diff --git a/common/utils/limit/example_test.go b/common/utils/limit/example_test.go deleted file mode 100644 index 5f96cbba9..000000000 --- a/common/utils/limit/example_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2025 肖其顿 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package limit_test - -import ( - "fmt" - "time" - - "github.com/xiaoqidun/limit" - "golang.org/x/time/rate" -) - -// ExampleLimiter 演示了 limit 包的基本用法。 -func ExampleLimiter() { - // 创建一个使用默认配置的 Limiter 实例 - limiter := limit.New() - // 程序退出前,优雅地停止后台任务,这非常重要 - defer limiter.Stop() - // 为一个特定的测试键获取一个速率限制器 - // 限制为每秒2个请求,最多允许3个并发(桶容量) - testKey := "testKey" - rateLimiter := limiter.Get(testKey, rate.Limit(2), 3) - // 模拟连续的请求 - for i := 0; i < 5; i++ { - if rateLimiter.Allow() { - fmt.Printf("请求 %d: 已允许\n", i+1) - } else { - fmt.Printf("请求 %d: 已拒绝\n", i+1) - } - time.Sleep(100 * time.Millisecond) - } - // 手动移除一个不再需要的限制器 - limiter.Del(testKey) - // Output: - // 请求 1: 已允许 - // 请求 2: 已允许 - // 请求 3: 已允许 - // 请求 4: 已拒绝 - // 请求 5: 已拒绝 -} - -// ExampleNewWithConfig 展示了如何使用自定义配置。 -func ExampleNewWithConfig() { - // 自定义配置 - config := limit.Config{ - ShardCount: 64, // 分片数量,必须是2的幂 - GCInterval: 5 * time.Minute, // GC 检查周期 - Expiration: 15 * time.Minute, // 限制器过期时间 - } - // 使用自定义配置创建一个 Limiter 实例 - customLimiter := limit.NewWithConfig(config) - defer customLimiter.Stop() - fmt.Println("使用自定义配置的限制器已成功创建") - // Output: - // 使用自定义配置的限制器已成功创建 -} diff --git a/common/utils/limit/go.mod b/common/utils/limit/go.mod deleted file mode 100644 index f291a2948..000000000 --- a/common/utils/limit/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/xiaoqidun/limit - -go 1.20 - -require golang.org/x/time v0.8.0 diff --git a/common/utils/limit/go.sum b/common/utils/limit/go.sum deleted file mode 100644 index d06eb417b..000000000 --- a/common/utils/limit/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/common/utils/limit/limit.go b/common/utils/limit/limit.go deleted file mode 100644 index fd89e16f3..000000000 --- a/common/utils/limit/limit.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2025 肖其顿 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package limit 提供了一个高性能、并发安全的动态速率限制器。 -// 它使用分片锁来减少高并发下的锁竞争,并能自动清理长期未使用的限制器。 -package limit - -import ( - "hash" - "hash/fnv" - "sync" - "sync/atomic" - "time" - - "golang.org/x/time/rate" -) - -// defaultShardCount 是默认的分片数量,设为2的幂可以优化哈希计算。 -const defaultShardCount = 32 - -// Config 定义了 Limiter 的可配置项。 -type Config struct { - // ShardCount 指定分片数量,必须是2的幂。如果为0或无效值,则使用默认值32。 - ShardCount int - // GCInterval 指定GC周期,即检查并清理过期限制器的间隔。如果为0,则使用默认值10分钟。 - GCInterval time.Duration - // Expiration 指定过期时间,即限制器在最后一次使用后能存活多久。如果为0,则使用默认值30分钟。 - Expiration time.Duration -} - -// Limiter 是一个高性能、分片实现的动态速率限制器。 -// 它的实例在并发使用时是安全的。 -type Limiter struct { - // 存储所有分片 - shards []*shard - // 配置信息 - config Config - // 标记限制器是否已停止 - stopped atomic.Bool - // 确保Stop方法只执行一次 - stopOnce sync.Once -} - -// New 使用默认配置创建一个新的 Limiter 实例。 -func New() *Limiter { - return NewWithConfig(Config{}) -} - -// NewWithConfig 根据提供的配置创建一个新的 Limiter 实例。 -func NewWithConfig(config Config) *Limiter { - // 如果未设置,则使用默认值 - if config.ShardCount == 0 { - config.ShardCount = defaultShardCount - } - if config.GCInterval == 0 { - config.GCInterval = 10 * time.Minute - } - if config.Expiration == 0 { - config.Expiration = 30 * time.Minute - } - // 确保分片数量是2的幂,以便进行高效的位运算 - if config.ShardCount <= 0 || (config.ShardCount&(config.ShardCount-1)) != 0 { - config.ShardCount = defaultShardCount - } - l := &Limiter{ - shards: make([]*shard, config.ShardCount), - config: config, - } - // 初始化所有分片 - for i := 0; i < config.ShardCount; i++ { - l.shards[i] = newShard(config.GCInterval, config.Expiration) - } - return l -} - -// Get 获取或创建一个与指定键关联的速率限制器。 -// 如果限制器已存在,它会根据传入的 r (速率) 和 b (并发数) 更新其配置。 -// 如果 Limiter 实例已被 Stop 方法关闭,此方法将返回 nil。 -func (l *Limiter) Get(k string, r rate.Limit, b int) *rate.Limiter { - // 快速路径检查,避免在已停止时进行哈希和查找 - if l.stopped.Load() { - return nil - } - // 定位到具体分片进行操作 - return l.getShard(k).get(k, r, b) -} - -// Del 手动移除一个与指定键关联的速率限制器。 -// 如果 Limiter 实例已被 Stop 方法关闭,此方法不执行任何操作。 -func (l *Limiter) Del(k string) { - // 快速路径检查 - if l.stopped.Load() { - return - } - // 定位到具体分片进行操作 - l.getShard(k).del(k) -} - -// Stop 停止 Limiter 的所有后台清理任务,并释放相关资源。 -// 此方法对于并发调用是安全的,并且可以被多次调用。 -func (l *Limiter) Stop() { - l.stopOnce.Do(func() { - l.stopped.Store(true) - for _, s := range l.shards { - s.stop() - } - }) -} - -// getShard 根据key的哈希值获取对应的分片。 -func (l *Limiter) getShard(key string) *shard { - hasher := fnvHasherPool.Get().(hash.Hash32) - defer func() { - hasher.Reset() - fnvHasherPool.Put(hasher) - }() - _, _ = hasher.Write([]byte(key)) // FNV-1a never returns an error. - // 使用位运算代替取模,提高效率 - return l.shards[hasher.Sum32()&(uint32(l.config.ShardCount)-1)] -} - -// shard 代表 Limiter 的一个分片,它包含独立的锁和数据,以减少全局锁竞争。 -type shard struct { - mutex sync.Mutex - stopCh chan struct{} - limiter map[string]*session - stopOnce sync.Once - waitGroup sync.WaitGroup -} - -// newShard 创建一个新的分片实例,并启动其gc任务。 -func newShard(gcInterval, expiration time.Duration) *shard { - s := &shard{ - // mutex 会被自动初始化为其零值(未锁定状态) - stopCh: make(chan struct{}), - limiter: make(map[string]*session), - } - s.waitGroup.Add(1) - go s.gc(gcInterval, expiration) - return s -} - -// gc 定期清理分片中过期的限制器。 -func (s *shard) gc(interval, expiration time.Duration) { - defer s.waitGroup.Done() - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - // 优先检查停止信号,确保能快速响应 - select { - case <-s.stopCh: - return - default: - } - select { - case <-ticker.C: - s.mutex.Lock() - // 再次检查分片是否已停止,防止在等待锁期间被停止 - if s.limiter == nil { - s.mutex.Unlock() - return - } - for k, v := range s.limiter { - // 清理过期的限制器 - if time.Since(v.lastGet) > expiration { - // 将 session 对象放回池中前,重置其状态 - v.limiter = nil - v.lastGet = time.Time{} - sessionPool.Put(v) - delete(s.limiter, k) - } - } - s.mutex.Unlock() - case <-s.stopCh: - // 收到停止信号,退出goroutine - return - } - } -} - -// get 获取或创建一个新的速率限制器,如果已存在则更新其配置。 -func (s *shard) get(k string, r rate.Limit, b int) *rate.Limiter { - s.mutex.Lock() - defer s.mutex.Unlock() - // 检查分片是否已停止 - if s.limiter == nil { - return nil - } - sess, ok := s.limiter[k] - if !ok { - // 从池中获取 session 对象 - sess = sessionPool.Get().(*session) - sess.limiter = rate.NewLimiter(r, b) - s.limiter[k] = sess - } else { - // 如果已存在,则更新其速率和并发数 - sess.limiter.SetLimit(r) - sess.limiter.SetBurst(b) - } - sess.lastGet = time.Now() - return sess.limiter -} - -// del 从分片中移除一个键的速率限制器。 -func (s *shard) del(k string) { - s.mutex.Lock() - defer s.mutex.Unlock() - // 检查分片是否已停止 - if s.limiter == nil { - return - } - if sess, ok := s.limiter[k]; ok { - // 将 session 对象放回池中前,重置其状态 - sess.limiter = nil - sess.lastGet = time.Time{} - sessionPool.Put(sess) - delete(s.limiter, k) - } -} - -// stop 停止分片的gc任务,并同步等待其完成后再清理资源。 -func (s *shard) stop() { - // 使用 sync.Once 确保 channel 只被关闭一次,彻底避免并发风险 - s.stopOnce.Do(func() { - close(s.stopCh) - }) - // 等待 gc goroutine 完全退出 - s.waitGroup.Wait() - // 锁定并进行最终的资源清理 - // 因为 gc 已经退出,所以此时只有 Get/Del 会竞争锁 - s.mutex.Lock() - defer s.mutex.Unlock() - // 检查是否已被清理,防止重复操作 - if s.limiter == nil { - return - } - // 将所有 session 对象放回对象池 - for _, sess := range s.limiter { - sess.limiter = nil - sess.lastGet = time.Time{} - sessionPool.Put(sess) - } - // 清理map,释放内存,并作为停止标记 - s.limiter = nil -} - -// session 存储每个键的速率限制器实例和最后访问时间。 -type session struct { - // 最后一次访问时间 - lastGet time.Time - // 速率限制器 - limiter *rate.Limiter -} - -// sessionPool 使用 sync.Pool 来复用 session 对象,以减少 GC 压力。 -var sessionPool = sync.Pool{ - New: func() interface{} { - return new(session) - }, -} - -// fnvHasherPool 使用 sync.Pool 来复用 FNV-1a 哈希对象,以减少高并发下的内存分配。 -var fnvHasherPool = sync.Pool{ - New: func() interface{} { - return fnv.New32a() - }, -} diff --git a/common/utils/limit/limit_test.go b/common/utils/limit/limit_test.go deleted file mode 100644 index 9b5e43e53..000000000 --- a/common/utils/limit/limit_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2025 肖其顿 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package limit - -import ( - "fmt" - "sync" - "testing" - "time" - - "golang.org/x/time/rate" -) - -// TestLimiter 覆盖了 Limiter 的主要功能。 -func TestLimiter(t *testing.T) { - // 子测试:验证基本的允许/拒绝逻辑 - t.Run("基本功能测试", func(t *testing.T) { - limiter := New() - defer limiter.Stop() - key := "测试键" - // 创建一个每秒2个令牌,桶容量为1的限制器 - rl := limiter.Get(key, rate.Limit(2), 1) - if rl == nil { - t.Fatal("limiter.Get() 意外返回 nil,测试无法继续") - } - if !rl.Allow() { - t.Error("rl.Allow(): 首次调用应返回 true, 实际为 false") - } - if rl.Allow() { - t.Error("rl.Allow(): 超出突发容量的调用应返回 false, 实际为 true") - } - time.Sleep(500 * time.Millisecond) - if !rl.Allow() { - t.Error("rl.Allow(): 令牌补充后的调用应返回 true, 实际为 false") - } - }) - - // 子测试:验证 Del 方法的功能 - t.Run("删除功能测试", func(t *testing.T) { - limiter := New() - defer limiter.Stop() - key := "测试键" - rl1 := limiter.Get(key, rate.Limit(2), 1) - if !rl1.Allow() { - t.Fatal("获取限制器后的首次 Allow() 调用失败") - } - limiter.Del(key) - rl2 := limiter.Get(key, rate.Limit(2), 1) - if !rl2.Allow() { - t.Error("Del() 后重新获取的限制器未能允许请求") - } - }) - - // 子测试:验证 Stop 方法的功能 - t.Run("停止功能测试", func(t *testing.T) { - limiter := New() - limiter.Stop() - if rl := limiter.Get("任意键", 1, 1); rl != nil { - t.Error("Stop() 后 Get() 应返回 nil, 实际返回了有效实例") - } - // 多次调用 Stop 不应引发 panic - limiter.Stop() - }) - - // 子测试:验证并发安全性 - t.Run("并发安全测试", func(t *testing.T) { - limiter := New() - defer limiter.Stop() - var wg sync.WaitGroup - numGoroutines := 100 - for i := 0; i < numGoroutines; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - key := fmt.Sprintf("并发测试键-%d", i) - if limiter.Get(key, rate.Limit(10), 5) == nil { - t.Errorf("并发获取键 '%s' 时, Get() 意外返回 nil", key) - } - }(i) - } - wg.Wait() - }) -} diff --git a/common/utils/timer/LICENSE b/common/utils/timer/LICENSE new file mode 100644 index 000000000..beb8af51b --- /dev/null +++ b/common/utils/timer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 蚂蚁实验室 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/utils/timer/README.md b/common/utils/timer/README.md new file mode 100644 index 000000000..46b1141eb --- /dev/null +++ b/common/utils/timer/README.md @@ -0,0 +1,130 @@ +## timer +[![Go](https://github.com/antlabs/timer/workflows/Go/badge.svg)](https://github.com/antlabs/timer/actions) +[![codecov](https://codecov.io/gh/antlabs/timer/branch/master/graph/badge.svg)](https://codecov.io/gh/antlabs/timer) + +timer是高性能定时器库 +## feature +* 支持一次性定时器 +* 支持周期性定时器 +* 支持多种数据结构后端,最小堆,5级时间轮 + +## 一次性定时器 +```go +import ( + "github.com/antlabs/timer" + "log" +) + +func main() { + tm := timer.NewTimer() + + tm.AfterFunc(1*time.Second, func() { + log.Printf("after\n") + }) + + tm.AfterFunc(10*time.Second, func() { + log.Printf("after\n") + }) + tm.Run() +} +``` +## 周期性定时器 +```go +import ( + "github.com/antlabs/timer" + "log" +) + +func main() { + tm := timer.NewTimer() + + tm.ScheduleFunc(1*time.Second, func() { + log.Printf("schedule\n") + }) + + tm.Run() +} +``` +## 自定义周期性定时器 +实现时间翻倍定时的例子 +```go +type curstomTest struct { + count int +} +// 只要实现Next接口就行 +func (c *curstomTest) Next(now time.Time) (rv time.Time) { + rv = now.Add(time.Duration(c.count) * time.Millisecond * 10) + c.count++ + return +} + +func main() { + tm := timer.NewTimer(timer.WithMinHeap()) + node := tm.CustomFunc(&curstomTest{count: 1}, func() { + log.Printf("%v\n", time.Now()) + }) + tm.Run() +} +``` +## 取消某一个定时器 +```go +import ( + "log" + "time" + + "github.com/antlabs/timer" +) + +func main() { + + tm := timer.NewTimer() + + // 只会打印2 time.Second + tm.AfterFunc(2*time.Second, func() { + log.Printf("2 time.Second") + }) + + // tk3 会被 tk3.Stop()函数调用取消掉 + tk3 := tm.AfterFunc(3*time.Second, func() { + log.Printf("3 time.Second") + }) + + tk3.Stop() //取消tk3 + + tm.Run() +} +``` +## 选择不同的的数据结构 +```go +import ( + "github.com/antlabs/timer" + "log" +) + +func main() { + tm := timer.NewTimer(timer.WithMinHeap())// 选择最小堆,默认时间轮 +} +``` +## benchmark + +github.com/antlabs/timer 性能最高 +``` +goos: linux +goarch: amd64 +pkg: benchmark +Benchmark_antlabs_Timer_AddTimer/N-1m-16 9177537 124 ns/op +Benchmark_antlabs_Timer_AddTimer/N-5m-16 10152950 128 ns/op +Benchmark_antlabs_Timer_AddTimer/N-10m-16 9955639 127 ns/op +Benchmark_RussellLuo_Timingwheel_AddTimer/N-1m-16 5316916 222 ns/op +Benchmark_RussellLuo_Timingwheel_AddTimer/N-5m-16 5848843 218 ns/op +Benchmark_RussellLuo_Timingwheel_AddTimer/N-10m-16 5872621 231 ns/op +Benchmark_ouqiang_Timewheel/N-1m-16 720667 1622 ns/op +Benchmark_ouqiang_Timewheel/N-5m-16 807018 1573 ns/op +Benchmark_ouqiang_Timewheel/N-10m-16 666183 1557 ns/op +Benchmark_Stdlib_AddTimer/N-1m-16 8031864 144 ns/op +Benchmark_Stdlib_AddTimer/N-5m-16 8437442 151 ns/op +Benchmark_Stdlib_AddTimer/N-10m-16 8080659 167 ns/op + +``` +* 压测代码位于 +https://github.com/junelabs/timer-benchmark \ No newline at end of file diff --git a/common/utils/timer/_long-time-test/build.sh b/common/utils/timer/_long-time-test/build.sh new file mode 100755 index 000000000..a86bcc3e1 --- /dev/null +++ b/common/utils/timer/_long-time-test/build.sh @@ -0,0 +1 @@ +go build -race long-time-test.go diff --git a/common/utils/timer/_long-time-test/long-time-test.go b/common/utils/timer/_long-time-test/long-time-test.go new file mode 100644 index 000000000..06eba2188 --- /dev/null +++ b/common/utils/timer/_long-time-test/long-time-test.go @@ -0,0 +1,157 @@ +package main + +import ( + "log" + "sync" + "time" + + "github.com/antlabs/timer" +) + +// 这是一个长时间测试代码 + +// 测试周期执行 +func schedule(tm timer.Timer) { + tm.ScheduleFunc(200*time.Millisecond, func() { + log.Printf("schedule 200 milliseconds\n") + }) + + tm.ScheduleFunc(time.Second, func() { + log.Printf("schedule second\n") + }) + + tm.ScheduleFunc(1*time.Minute, func() { + log.Printf("schedule minute\n") + }) + + tm.ScheduleFunc(1*time.Hour, func() { + log.Printf("schedule hour\n") + }) + + tm.ScheduleFunc(24*time.Hour, func() { + log.Printf("schedule day\n") + }) +} + +// 测试一次性定时器 +func after(tm timer.Timer) { + var wg sync.WaitGroup + wg.Add(4) + defer wg.Wait() + + go func() { + defer wg.Done() + for i := 0; i < 3600*24; i++ { + i := i + tm.AfterFunc(time.Second, func() { + log.Printf("after second:%d\n", i) + }) + time.Sleep(900 * time.Millisecond) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 60*24; i++ { + i := i + tm.AfterFunc(time.Minute, func() { + log.Printf("after minute:%d\n", i) + }) + time.Sleep(50 * time.Second) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 24; i++ { + i := i + tm.AfterFunc(time.Hour, func() { + log.Printf("after hour:%d\n", i) + }) + time.Sleep(59 * time.Minute) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1; i++ { + i := i + tm.AfterFunc(24*time.Hour, func() { + log.Printf("after day:%d\n", i) + }) + time.Sleep(59 * time.Minute) + } + }() +} + +// 检测 stop after 消息,没有打印是正确的行为 +func stopNode(tm timer.Timer) { + + var wg sync.WaitGroup + wg.Add(4) + defer wg.Wait() + + go func() { + defer wg.Done() + for i := 0; i < 3600*24; i++ { + i := i + node := tm.AfterFunc(time.Second, func() { + log.Printf("stop after second:%d\n", i) + }) + time.Sleep(900 * time.Millisecond) + node.Stop() + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 60*24; i++ { + i := i + node := tm.AfterFunc(time.Minute, func() { + log.Printf("stop after minute:%d\n", i) + }) + time.Sleep(50 * time.Second) + node.Stop() + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 24; i++ { + i := i + node := tm.AfterFunc(time.Hour, func() { + log.Printf("stop after hour:%d\n", i) + }) + time.Sleep(59 * time.Minute) + node.Stop() + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1; i++ { + i := i + node := tm.AfterFunc(23*time.Hour, func() { + log.Printf("stop after day:%d\n", i) + }) + time.Sleep(22 * time.Hour) + node.Stop() + } + }() +} + +func main() { + + log.SetFlags(log.Ldate | log.Lmicroseconds) + tm := timer.NewTimer() + + go schedule(tm) + go after(tm) + go stopNode(tm) + + go func() { + time.Sleep(time.Hour*24 + time.Hour) + tm.Stop() + }() + tm.Run() +} diff --git a/common/utils/timer/go.mod b/common/utils/timer/go.mod new file mode 100644 index 000000000..7f06e3d7e --- /dev/null +++ b/common/utils/timer/go.mod @@ -0,0 +1,5 @@ +module github.com/antlabs/timer + +go 1.19 + +require github.com/antlabs/stl v0.0.2 diff --git a/common/utils/timer/go.sum b/common/utils/timer/go.sum new file mode 100644 index 000000000..46a6f2770 --- /dev/null +++ b/common/utils/timer/go.sum @@ -0,0 +1,2 @@ +github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s= +github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac= diff --git a/common/utils/timer/min_heap.go b/common/utils/timer/min_heap.go new file mode 100644 index 000000000..d855e9d62 --- /dev/null +++ b/common/utils/timer/min_heap.go @@ -0,0 +1,221 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "container/heap" + "context" + "sync" + "sync/atomic" + "time" + + "github.com/panjf2000/ants/v2" +) + +var _ Timer = (*minHeap)(nil) + +var defaultTimeout = time.Hour + +type minHeap struct { + mu sync.Mutex + minHeaps + chAdd chan struct{} + ctx context.Context + cancel context.CancelFunc + wait sync.WaitGroup + tm *time.Timer + runCount int32 // 单元测试时使用 +} + +// 一次性定时器 +func (m *minHeap) AfterFunc(expire time.Duration, callback func()) TimeNoder { + return m.addCallback(expire, nil, callback, false) +} + +// 周期性定时器 +func (m *minHeap) ScheduleFunc(expire time.Duration, callback func()) TimeNoder { + return m.addCallback(expire, nil, callback, true) +} + +// 自定义下次的时间 +func (m *minHeap) CustomFunc(n Next, callback func()) TimeNoder { + return m.addCallback(time.Duration(0), n, callback, true) +} + +// 加任务 +func (m *minHeap) addCallback(expire time.Duration, n Next, callback func(), isSchedule bool) TimeNoder { + select { + case <-m.ctx.Done(): + panic("cannot add a task to a closed timer") + default: + } + + node := minHeapNode{ + callback: callback, + userExpire: expire, + next: n, + absExpire: time.Now().Add(expire), + isSchedule: isSchedule, + root: m, + } + + if n != nil { + node.absExpire = n.Next(time.Now()) + } + + m.mu.Lock() + heap.Push(&m.minHeaps, &node) + m.wait.Add(1) + m.mu.Unlock() + + select { + case m.chAdd <- struct{}{}: + default: + } + + return &node +} + +func (m *minHeap) removeTimeNode(node *minHeapNode) { + m.mu.Lock() + if node.index < 0 || node.index > int32(len(m.minHeaps)) || int32(len(m.minHeaps)) == 0 { + m.mu.Unlock() + return + } + + heap.Remove(&m.minHeaps, int(node.index)) + m.wait.Done() + m.mu.Unlock() +} + +func (m *minHeap) resetTimeNode(node *minHeapNode, d time.Duration) { + m.mu.Lock() + node.userExpire = d + node.absExpire = time.Now().Add(d) + heap.Fix(&m.minHeaps, int(node.index)) + select { + case m.chAdd <- struct{}{}: + default: + } + m.mu.Unlock() +} + +func (m *minHeap) getNewSleepTime() time.Duration { + if m.minHeaps.Len() == 0 { + return time.Hour + } + + timeout := time.Until(m.minHeaps[0].absExpire) + if timeout < 0 { + timeout = 0 + } + return timeout +} + +var pool, _ = ants.NewPool(-1) + +func (m *minHeap) process() { + for { + m.mu.Lock() + now := time.Now() + // 如果堆中没有元素,就等待 + // 这时候设置一个相对长的时间,避免空转cpu + if m.minHeaps.Len() == 0 { + m.tm.Reset(time.Hour) + m.mu.Unlock() + return + } + + for { + // 取出最小堆的第一个元素 + first := m.minHeaps[0] + + // 时间未到直接过滤掉 + // 只是跳过最近的循环 + if !now.After(first.absExpire) { + break + } + + // 取出待执行的callback + callback := first.callback + // 如果是周期性任务 + if first.isSchedule { + // 计算下次触发的绝对时间点 + first.absExpire = first.Next(now) + // 修改下在堆中的位置 + heap.Fix(&m.minHeaps, int(first.index)) + } else { + // 从堆中删除 + heap.Pop(&m.minHeaps) + m.wait.Done() + } + + // 正在运行的任务数加1 + atomic.AddInt32(&m.runCount, 1) + pool.Submit(func() { + callback() + // 对正在运行的任务数减1 + atomic.AddInt32(&m.runCount, -1) + }) + + // 如果堆中没有元素,就等待 + if m.minHeaps.Len() == 0 { + m.tm.Reset(defaultTimeout) + m.mu.Unlock() + return + } + } + + // 取出第一个元素 + first := m.minHeaps[0] + // 如果第一个元素的时间还没到,就计算下次触发的时间 + if time.Now().Before(first.absExpire) { + to := m.getNewSleepTime() + m.tm.Reset(to) + // fmt.Printf("### now=%v, to = %v, m.minHeaps[0].absExpire = %v\n", time.Now(), to, m.minHeaps[0].absExpire) + m.mu.Unlock() + return + } + m.mu.Unlock() + } +} + +// 运行 +// 为了避免空转cpu, 会等待一个chan, 只要AfterFunc或者ScheduleFunc被调用就会往这个chan里面写值 +func (m *minHeap) Run() { + m.tm = time.NewTimer(time.Hour) + m.process() + for { + select { + case <-m.tm.C: + m.process() + case <-m.chAdd: + m.mu.Lock() + // 极端情况,加完任务立即给删除了, 判断下当前堆中是否有元素 + if m.minHeaps.Len() > 0 { + m.tm.Reset(m.getNewSleepTime()) + } + m.mu.Unlock() + // 进入事件循环,如果为空就会从事件循环里面退出 + case <-m.ctx.Done(): + // 等待所有任务结束 + m.wait.Wait() + return + } + + } +} + +// 停止所有定时器 +func (m *minHeap) Stop() { + m.cancel() +} + +func newMinHeap() (mh *minHeap) { + mh = &minHeap{} + heap.Init(&mh.minHeaps) + mh.chAdd = make(chan struct{}, 1024) + mh.ctx, mh.cancel = context.WithCancel(context.TODO()) + return +} diff --git a/common/utils/timer/min_heap_node.go b/common/utils/timer/min_heap_node.go new file mode 100644 index 000000000..939e690ef --- /dev/null +++ b/common/utils/timer/min_heap_node.go @@ -0,0 +1,62 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "time" +) + +type minHeapNode struct { + callback func() // 用户的callback + absExpire time.Time // 绝对时间 + userExpire time.Duration // 过期时间段 + root *minHeap // 指向最小堆 + next Next // 自定义下个触发的时间点, cronex项目用到了 + index int32 // 在min heap中的索引,方便删除或者重新推入堆中 + isSchedule bool // 是否是周期性任务 +} + +func (m *minHeapNode) Stop() bool { + m.root.removeTimeNode(m) + return true +} +func (m *minHeapNode) Reset(d time.Duration) bool { + m.root.resetTimeNode(m, d) + return true +} + +func (m *minHeapNode) Next(now time.Time) time.Time { + if m.next != nil { + return (m.next).Next(now) + } + return now.Add(m.userExpire) +} + +type minHeaps []*minHeapNode + +func (m minHeaps) Len() int { return len(m) } + +func (m minHeaps) Less(i, j int) bool { return m[i].absExpire.Before(m[j].absExpire) } + +func (m minHeaps) Swap(i, j int) { + m[i], m[j] = m[j], m[i] + m[i].index = int32(i) + m[j].index = int32(j) +} + +func (m *minHeaps) Push(x any) { + // Push and Pop use pointer receivers because they modify the slice's length, + // not just its contents. + *m = append(*m, x.(*minHeapNode)) + lastIndex := int32(len(*m) - 1) + (*m)[lastIndex].index = lastIndex +} + +func (m *minHeaps) Pop() any { + old := *m + n := len(old) + x := old[n-1] + *m = old[0 : n-1] + return x +} diff --git a/common/utils/timer/min_heap_node_test.go b/common/utils/timer/min_heap_node_test.go new file mode 100644 index 000000000..6489d4367 --- /dev/null +++ b/common/utils/timer/min_heap_node_test.go @@ -0,0 +1,64 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license + +package timer + +import ( + "container/heap" + "testing" + "time" +) + +func Test_NodeSizeof(t *testing.T) { + t.Run("输出最小堆node的sizeof", func(t *testing.T) { + // t.Logf("minHeapNode size: %d, %d\n", unsafe.Sizeof(minHeapNode{}), unsafe.Sizeof(time.Timer{})) + }) +} +func Test_MinHeap(t *testing.T) { + t.Run("", func(t *testing.T) { + var mh minHeaps + now := time.Now() + n1 := minHeapNode{ + absExpire: now.Add(time.Second), + userExpire: 1 * time.Second, + } + + n2 := minHeapNode{ + absExpire: now.Add(2 * time.Second), + userExpire: 2 * time.Second, + } + + n3 := minHeapNode{ + absExpire: now.Add(3 * time.Second), + userExpire: 3 * time.Second, + } + + n6 := minHeapNode{ + absExpire: now.Add(6 * time.Second), + userExpire: 6 * time.Second, + } + n5 := minHeapNode{ + absExpire: now.Add(5 * time.Second), + userExpire: 5 * time.Second, + } + n4 := minHeapNode{ + absExpire: now.Add(4 * time.Second), + userExpire: 4 * time.Second, + } + mh.Push(&n1) + mh.Push(&n2) + mh.Push(&n3) + mh.Push(&n6) + mh.Push(&n5) + mh.Push(&n4) + + for i := 1; len(mh) > 0; i++ { + v := heap.Pop(&mh).(*minHeapNode) + + if v.userExpire != time.Duration(i)*time.Second { + t.Errorf("index(%d) v.userExpire(%v) != %v", i, v.userExpire, time.Duration(i)*time.Second) + } + } + }) +} diff --git a/common/utils/timer/min_heap_test.go b/common/utils/timer/min_heap_test.go new file mode 100644 index 000000000..e0788c012 --- /dev/null +++ b/common/utils/timer/min_heap_test.go @@ -0,0 +1,329 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "log" + "sync" + "sync/atomic" + "testing" + "time" +) + +// 测试AfterFunc有没有运行以及时间间隔可对 +func Test_MinHeap_AfterFunc_Run(t *testing.T) { + t.Run("1ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + + go tm.Run() + count := int32(0) + + tc := make(chan time.Duration, 2) + + var mu sync.Mutex + isClose := false + now := time.Now() + node1 := tm.AfterFunc(time.Millisecond, func() { + + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + + node2 := tm.AfterFunc(time.Millisecond, func() { + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + + time.Sleep(time.Millisecond * 3) + mu.Lock() + isClose = true + close(tc) + node1.Stop() + node2.Stop() + mu.Unlock() + for tv := range tc { + if tv < time.Millisecond || tv > 2*time.Millisecond { + t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond") + + } + } + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) + + t.Run("10ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + + go tm.Run() // 运行事件循环 + count := int32(0) + tc := make(chan time.Duration, 2) + + var mu sync.Mutex + isClosed := false + now := time.Now() + node1 := tm.AfterFunc(time.Millisecond*10, func() { + now2 := time.Now() + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClosed { + tc <- time.Since(now) + } + mu.Unlock() + log.Printf("node1.Lock:%v\n", time.Since(now2)) + }) + node2 := tm.AfterFunc(time.Millisecond*10, func() { + now2 := time.Now() + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClosed { + tc <- time.Since(now) + } + mu.Unlock() + log.Printf("node2.Lock:%v\n", time.Since(now2)) + }) + + time.Sleep(time.Millisecond * 24) + now3 := time.Now() + mu.Lock() + node1.Stop() + node2.Stop() + isClosed = true + close(tc) + mu.Unlock() + + log.Printf("node1.Stop:%v\n", time.Since(now3)) + cnt := 1 + for tv := range tc { + left := time.Millisecond * 10 * time.Duration(cnt) + right := time.Duration(cnt) * 2 * 10 * time.Millisecond + if tv < left || tv > right { + t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right) + } + // cnt++ + } + if atomic.LoadInt32(&count) != 2 { + + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) + + t.Run("90ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + go tm.Run() + count := int32(0) + tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 1) }) + tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 2) }) + + time.Sleep(time.Millisecond * 180) + if atomic.LoadInt32(&count) != 3 { + t.Errorf("count != 3") + } + + }) +} + +// 测试Schedule 运行的周期可对 +func Test_MinHeap_ScheduleFunc_Run(t *testing.T) { + t.Run("1ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + go tm.Run() + count := int32(0) + + _ = tm.ScheduleFunc(2*time.Millisecond, func() { + log.Printf("%v\n", time.Now()) + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) == 2 { + tm.Stop() + } + }) + + time.Sleep(time.Millisecond * 5) + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) + + t.Run("10ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + go tm.Run() + count := int32(0) + tc := make(chan time.Duration, 2) + var mu sync.Mutex + isClosed := false + now := time.Now() + + node := tm.ScheduleFunc(time.Millisecond*10, func() { + mu.Lock() + atomic.AddInt32(&count, 1) + + if atomic.LoadInt32(&count) <= 2 && !isClosed { + tc <- time.Since(now) + } + mu.Unlock() + }) + + time.Sleep(time.Millisecond * 25) + + mu.Lock() + close(tc) + isClosed = true + node.Stop() + mu.Unlock() + + cnt := 1 + for tv := range tc { + left := time.Millisecond * 10 * time.Duration(cnt) + right := time.Duration(cnt) * 2 * 10 * time.Millisecond + if tv < left || tv > right { + t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right) + } + cnt++ + } + + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) + + t.Run("30ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + go tm.Run() + count := int32(0) + c := make(chan bool, 1) + + node := tm.ScheduleFunc(time.Millisecond*30, func() { + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) == 2 { + c <- true + } + }) + go func() { + <-c + node.Stop() + }() + + time.Sleep(time.Millisecond * 70) + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) +} + +// 测试Stop是否会等待正在运行的任务结束 +func Test_Run_Stop(t *testing.T) { + t.Run("1ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + count := uint32(0) + tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) }) + tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) }) + go func() { + time.Sleep(time.Millisecond * 4) + tm.Stop() + }() + tm.Run() + if atomic.LoadUint32(&count) != 2 { + t.Errorf("count != 2") + } + }) +} + +type curstomTest struct { + count int32 +} + +func (c *curstomTest) Next(now time.Time) (rv time.Time) { + rv = now.Add(time.Duration(c.count) * time.Millisecond * 10) + atomic.AddInt32(&c.count, 1) + return +} + +// 验证自定义函数的运行间隔时间 +func Test_CustomFunc(t *testing.T) { + t.Run("custom", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + // mh := tm.(*minHeap) // 最小堆 + tc := make(chan time.Duration, 2) + now := time.Now() + count := uint32(1) + stop := make(chan bool, 1) + // 自定义函数 + node := tm.CustomFunc(&curstomTest{count: 1}, func() { + + if atomic.LoadUint32(&count) == 2 { + return + } + // 计算运行次数 + atomic.AddUint32(&count, 1) + tc <- time.Since(now) + // 关闭这个任务 + close(stop) + }) + + go func() { + <-stop + node.Stop() + tm.Stop() + }() + + tm.Run() + close(tc) + cnt := 1 + for tv := range tc { + left := time.Millisecond * 10 * time.Duration(cnt) + right := time.Duration(cnt) * 2 * 10 * time.Millisecond + if tv < left || tv > right { + t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right) + } + cnt++ + } + if atomic.LoadUint32(&count) != 2 { + t.Errorf("count != 2") + } + + // 正在运行的任务是比较短暂的,所以外部很难 + // if mh.runCount != int32(1) { + // t.Errorf("mh.runCount:%d != 1", mh.runCount) + // } + + }) +} + +// 验证运行次数是符合预期的 +func Test_RunCount(t *testing.T) { + t.Run("runcount-10ms", func(t *testing.T) { + tm := NewTimer(WithMinHeap()) + max := 10 + go func() { + tm.Run() + }() + + count := uint32(0) + for i := 0; i < max; i++ { + tm.ScheduleFunc(time.Millisecond*10, func() { + atomic.AddUint32(&count, 1) + }) + } + + time.Sleep(time.Millisecond * 15) + tm.Stop() + if count != uint32(max) { + t.Errorf("count:%d != %d", count, max) + } + + }) +} diff --git a/common/utils/timer/option.go b/common/utils/timer/option.go new file mode 100644 index 000000000..b2606b6a9 --- /dev/null +++ b/common/utils/timer/option.go @@ -0,0 +1,39 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +type option struct { + timeWheel bool + minHeap bool + skiplist bool + rbtree bool +} + +type Option func(c *option) + +func WithTimeWheel() Option { + return func(o *option) { + o.timeWheel = true + } +} + +func WithMinHeap() Option { + return func(o *option) { + o.minHeap = true + } +} + +// TODO +func WithSkipList() Option { + return func(o *option) { + o.skiplist = true + } +} + +// TODO +func WithRbtree() Option { + return func(o *option) { + o.rbtree = true + } +} diff --git a/common/utils/timer/t_test.go b/common/utils/timer/t_test.go new file mode 100644 index 000000000..90b7b9ace --- /dev/null +++ b/common/utils/timer/t_test.go @@ -0,0 +1,17 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "fmt" + "testing" + "unsafe" +) + +func Test_Look(t *testing.T) { + + tmp := newTimeHead(0, 0) + offset := unsafe.Offsetof(tmp.Head) + fmt.Printf("%d\n", offset) +} diff --git a/common/utils/timer/time_wheel.go b/common/utils/timer/time_wheel.go new file mode 100644 index 000000000..77fc907c6 --- /dev/null +++ b/common/utils/timer/time_wheel.go @@ -0,0 +1,294 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "context" + "fmt" + "sync/atomic" + "time" + "unsafe" + + "github.com/antlabs/stl/list" +) + +const ( + nearShift = 8 + + nearSize = 1 << nearShift + + levelShift = 6 + + levelSize = 1 << levelShift + + nearMask = nearSize - 1 + + levelMask = levelSize - 1 +) + +type timeWheel struct { + // 单调递增累加值, 走过一个时间片就+1 + jiffies uint64 + + // 256个槽位 + t1 [nearSize]*Time + + // 4个64槽位, 代表不同的刻度 + t2Tot5 [4][levelSize]*Time + + // 时间只精确到10ms + // curTimePoint 为1就是10ms 为2就是20ms + curTimePoint time.Duration + + // 上下文 + ctx context.Context + + // 取消函数 + cancel context.CancelFunc +} + +func newTimeWheel() *timeWheel { + ctx, cancel := context.WithCancel(context.Background()) + + t := &timeWheel{ctx: ctx, cancel: cancel} + + t.init() + + return t +} + +func (t *timeWheel) init() { + for i := 0; i < nearSize; i++ { + t.t1[i] = newTimeHead(1, uint64(i)) + } + + for i := 0; i < 4; i++ { + for j := 0; j < levelSize; j++ { + t.t2Tot5[i][j] = newTimeHead(uint64(i+2), uint64(j)) + } + } + + // t.curTimePoint = get10Ms() +} + +func maxVal() uint64 { + return (1 << (nearShift + 4*levelShift)) - 1 +} + +func levelMax(index int) uint64 { + return 1 << (nearShift + index*levelShift) +} + +func (t *timeWheel) index(n int) uint64 { + return (t.jiffies >> (nearShift + levelShift*n)) & levelMask +} + +func (t *timeWheel) add(node *timeNode, jiffies uint64) *timeNode { + var head *Time + expire := node.expire + idx := expire - jiffies + + level, index := uint64(1), uint64(0) + + if idx < nearSize { + + index = uint64(expire) & nearMask + head = t.t1[index] + + } else { + + max := maxVal() + for i := 0; i <= 3; i++ { + + if idx > max { + idx = max + expire = idx + jiffies + } + + if uint64(idx) < levelMax(i+1) { + index = uint64(expire >> (nearShift + i*levelShift) & levelMask) + head = t.t2Tot5[i][index] + level = uint64(i) + 2 + break + } + } + } + + if head == nil { + panic("not found head") + } + + head.lockPushBack(node, level, index) + + return node +} + +func (t *timeWheel) AfterFunc(expire time.Duration, callback func()) TimeNoder { + jiffies := atomic.LoadUint64(&t.jiffies) + + expire = expire/(time.Millisecond*10) + time.Duration(jiffies) + + node := &timeNode{ + expire: uint64(expire), + callback: callback, + root: t, + } + + return t.add(node, jiffies) +} + +func getExpire(expire time.Duration, jiffies uint64) time.Duration { + return expire/(time.Millisecond*10) + time.Duration(jiffies) +} + +func (t *timeWheel) ScheduleFunc(userExpire time.Duration, callback func()) TimeNoder { + jiffies := atomic.LoadUint64(&t.jiffies) + + expire := getExpire(userExpire, jiffies) + + node := &timeNode{ + userExpire: userExpire, + expire: uint64(expire), + callback: callback, + isSchedule: true, + root: t, + } + + return t.add(node, jiffies) +} + +func (t *timeWheel) Stop() { + t.cancel() +} + +// 移动链表 +func (t *timeWheel) cascade(levelIndex int, index int) { + tmp := newTimeHead(0, 0) + + l := t.t2Tot5[levelIndex][index] + l.Lock() + if l.Len() == 0 { + l.Unlock() + return + } + + l.ReplaceInit(&tmp.Head) + + // 每次链表的元素被移动走,都修改version + l.version.Add(1) + l.Unlock() + + offset := unsafe.Offsetof(tmp.Head) + tmp.ForEachSafe(func(pos *list.Head) { + node := (*timeNode)(pos.Entry(offset)) + t.add(node, atomic.LoadUint64(&t.jiffies)) + }) +} + +// moveAndExec函数功能 +// 1. 先移动到near链表里面 +// 2. near链表节点为空时,从上一层里面移动一些节点到下一层 +// 3. 再执行 +func (t *timeWheel) moveAndExec() { + // 这里时间溢出 + if uint32(t.jiffies) == 0 { + // TODO + // return + } + + // 如果本层的盘子没有定时器,这时候从上层的盘子移动一些过来 + index := t.jiffies & nearMask + if index == 0 { + for i := 0; i <= 3; i++ { + index2 := t.index(i) + t.cascade(i, int(index2)) + if index2 != 0 { + break + } + } + } + + atomic.AddUint64(&t.jiffies, 1) + + t.t1[index].Lock() + if t.t1[index].Len() == 0 { + t.t1[index].Unlock() + return + } + + head := newTimeHead(0, 0) + t1 := t.t1[index] + t1.ReplaceInit(&head.Head) + t1.version.Add(1) + t.t1[index].Unlock() + + // 执行,链表中的定时器 + offset := unsafe.Offsetof(head.Head) + + head.ForEachSafe(func(pos *list.Head) { + val := (*timeNode)(pos.Entry(offset)) + head.Del(pos) + + if val.stop.Load() == haveStop { + return + } + + go val.callback() + + if val.isSchedule { + jiffies := t.jiffies + // 这里的jiffies必须要减去1 + // 当前的callback被调用,已经包含一个时间片,如果不把这个时间片减去, + // 每次多一个时间片,就变成累加器, 最后周期定时器慢慢会变得不准 + val.expire = uint64(getExpire(val.userExpire, jiffies-1)) + t.add(val, jiffies) + } + }) +} + +// get10Ms函数通过参数传递,为了方便测试 +func (t *timeWheel) run(get10Ms func() time.Duration) { + // 先判断是否需要更新 + // 内核里面实现使用了全局jiffies和本地的jiffies比较,应用层没有jiffies,直接使用时间比较 + // 这也是skynet里面的做法 + + ms10 := get10Ms() + + if ms10 < t.curTimePoint { + + fmt.Printf("github.com/antlabs/timer:Time has been called back?from(%d)(%d)\n", + ms10, t.curTimePoint) + + t.curTimePoint = ms10 + return + } + + diff := ms10 - t.curTimePoint + t.curTimePoint = ms10 + + for i := 0; i < int(diff); i++ { + t.moveAndExec() + } +} + +// 自定义, TODO +func (t *timeWheel) CustomFunc(n Next, callback func()) TimeNoder { + return &timeNode{} +} + +func (t *timeWheel) Run() { + t.curTimePoint = get10Ms() + // 10ms精度 + tk := time.NewTicker(time.Millisecond * 10) + defer tk.Stop() + + for { + select { + case <-tk.C: + t.run(get10Ms) + case <-t.ctx.Done(): + return + } + } +} diff --git a/common/utils/timer/time_wheel_node.go b/common/utils/timer/time_wheel_node.go new file mode 100644 index 000000000..c029513e0 --- /dev/null +++ b/common/utils/timer/time_wheel_node.go @@ -0,0 +1,114 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/antlabs/stl/list" +) + +const ( + haveStop = uint32(1) +) + +// 先使用sync.Mutex实现功能 +// 后面使用cas优化 +type Time struct { + timeNode + sync.Mutex + + // |---16bit---|---16bit---|------32bit-----| + // |---level---|---index---|-------seq------| + // level 在near盘子里就是1, 在T2ToTt[0]盘子里就是2起步 + // index 就是各自盘子的索引值 + // seq 自增id + version atomic.Uint64 +} + +func newTimeHead(level uint64, index uint64) *Time { + head := &Time{} + head.version.Store(genVersionHeight(level, index)) + head.Init() + return head +} + +func genVersionHeight(level uint64, index uint64) uint64 { + return level<<(32+16) | index<<32 +} + +func (t *Time) lockPushBack(node *timeNode, level uint64, index uint64) { + t.Lock() + defer t.Unlock() + if node.stop.Load() == haveStop { + return + } + + t.AddTail(&node.Head) + atomic.StorePointer(&node.list, unsafe.Pointer(t)) + //更新节点的version信息 + node.version.Store(t.version.Load()) +} + +type timeNode struct { + expire uint64 + userExpire time.Duration + callback func() + stop atomic.Uint32 + list unsafe.Pointer //存放表头信息 + version atomic.Uint64 //保存节点版本信息 + isSchedule bool + root *timeWheel + list.Head +} + +// 一个timeNode节点有4个状态 +// 1.存在于初始化链表中 +// 2.被移动到tmp链表 +// 3.1 和 3.2是if else的状态 +// +// 3.1被移动到new链表 +// 3.2直接执行 +// +// 1和3.1状态是没有问题的 +// 2和3.2状态会是没有锁保护下的操作,会有数据竞争 +func (t *timeNode) Stop() bool { + + t.stop.Store(haveStop) + + // 使用版本号算法让timeNode知道自己是否被移动了 + // timeNode的version和表头的version一样表示没有被移动可以直接删除 + // 如果不一样,可能在第2或者3.2状态,使用惰性删除 + cpyList := (*Time)(atomic.LoadPointer(&t.list)) + cpyList.Lock() + defer cpyList.Unlock() + if t.version.Load() != cpyList.version.Load() { + return false + } + + cpyList.Del(&t.Head) + return true +} + +// warning: 该函数目前没有稳定 +func (t *timeNode) Reset(expire time.Duration) bool { + cpyList := (*Time)(atomic.LoadPointer(&t.list)) + cpyList.Lock() + defer cpyList.Unlock() + // TODO: 这里有一个问题,如果在执行Reset的时候,这个节点已经被移动到tmp链表 + // if atomic.LoadUint64(&t.version) != atomic.LoadUint64(&cpyList.version) { + // return + // } + cpyList.Del(&t.Head) + jiffies := atomic.LoadUint64(&t.root.jiffies) + + expire = expire/(time.Millisecond*10) + time.Duration(jiffies) + t.expire = uint64(expire) + + t.root.add(t, jiffies) + return true +} diff --git a/common/utils/timer/time_wheel_test.go b/common/utils/timer/time_wheel_test.go new file mode 100644 index 000000000..520f04f1f --- /dev/null +++ b/common/utils/timer/time_wheel_test.go @@ -0,0 +1,189 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "context" + "math" + "sync/atomic" + "testing" + "time" +) + +func Test_maxVal(t *testing.T) { + + if maxVal() != uint64(math.MaxUint32) { + t.Error("maxVal() != uint64(math.MaxUint32)") + } +} + +func Test_LevelMax(t *testing.T) { + if levelMax(1) != uint64(1<<(nearShift+levelShift)) { + t.Error("levelMax(1) != uint64(1<<(nearShift+levelShift))") + } + + if levelMax(2) != uint64(1<<(nearShift+2*levelShift)) { + t.Error("levelMax(2) != uint64(1<<(nearShift+2*levelShift))") + } + + if levelMax(3) != uint64(1<<(nearShift+3*levelShift)) { + t.Error("levelMax(3) != uint64(1<<(nearShift+3*levelShift))") + } + + if levelMax(4) != uint64(1<<(nearShift+4*levelShift)) { + t.Error("levelMax(4) != uint64(1<<(nearShift+4*levelShift))") + } + +} + +func Test_GenVersion(t *testing.T) { + if genVersionHeight(1, 0xf) != uint64(0x0001000f00000000) { + t.Error("genVersionHeight(1, 0xf) != uint64(0x0001000f00000000)") + } + + if genVersionHeight(1, 64) != uint64(0x0001004000000000) { + t.Error("genVersionHeight(2, 0xf) != uint64(0x0001004000000000)") + } + +} + +// 测试1小时 +func Test_hour(t *testing.T) { + tw := newTimeWheel() + + testHour := new(bool) + done := make(chan struct{}, 1) + tw.AfterFunc(time.Hour, func() { + *testHour = true + done <- struct{}{} + }) + + expire := getExpire(time.Hour, 0) + for i := 0; i < int(expire)+10; i++ { + get10Ms := func() time.Duration { + return tw.curTimePoint + 1 + } + tw.run(get10Ms) + } + + select { + case <-done: + case <-time.After(time.Second / 100): + } + + if *testHour == false { + t.Error("testHour == false") + } + +} + +// 测试周期性定时器, 5s +func Test_ScheduleFunc_5s(t *testing.T) { + tw := newTimeWheel() + + var first5 int32 + ctx, cancel := context.WithCancel(context.Background()) + + const total = int32(1000) + + testTime := time.Second * 5 + + tw.ScheduleFunc(testTime, func() { + atomic.AddInt32(&first5, 1) + if atomic.LoadInt32(&first5) == total { + cancel() + } + + }) + + expire := getExpire(testTime*time.Duration(total), 0) + for i := 0; i <= int(expire)+10; i++ { + get10Ms := func() time.Duration { + return tw.curTimePoint + 1 + } + tw.run(get10Ms) + } + + select { + case <-ctx.Done(): + case <-time.After(time.Second / 100): + } + + if total != first5 { + t.Errorf("total:%d != first5:%d\n", total, first5) + } +} + +// 测试周期性定时器, 1hour +func Test_ScheduleFunc_hour(t *testing.T) { + tw := newTimeWheel() + + var first5 int32 + ctx, cancel := context.WithCancel(context.Background()) + + const total = int32(100) + testTime := time.Hour + + tw.ScheduleFunc(testTime, func() { + atomic.AddInt32(&first5, 1) + if atomic.LoadInt32(&first5) == total { + cancel() + } + + }) + + expire := getExpire(testTime*time.Duration(total), 0) + for i := 0; i <= int(expire)+10; i++ { + get10Ms := func() time.Duration { + return tw.curTimePoint + 1 + } + tw.run(get10Ms) + } + + select { + case <-ctx.Done(): + case <-time.After(time.Second / 100): + } + + if total != first5 { + t.Errorf("total:%d != first5:%d\n", total, first5) + } + +} + +// 测试周期性定时器, 1day +func Test_ScheduleFunc_day(t *testing.T) { + tw := newTimeWheel() + + var first5 int32 + ctx, cancel := context.WithCancel(context.Background()) + + const total = int32(10) + testTime := time.Hour * 24 + + tw.ScheduleFunc(testTime, func() { + atomic.AddInt32(&first5, 1) + if atomic.LoadInt32(&first5) == total { + cancel() + } + + }) + + expire := getExpire(testTime*time.Duration(total), 0) + for i := 0; i <= int(expire)+10; i++ { + get10Ms := func() time.Duration { + return tw.curTimePoint + 1 + } + tw.run(get10Ms) + } + + select { + case <-ctx.Done(): + case <-time.After(time.Second / 100): + } + + if total != first5 { + t.Errorf("total:%d != first5:%d\n", total, first5) + } +} diff --git a/common/utils/timer/time_wheel_utils.go b/common/utils/timer/time_wheel_utils.go new file mode 100644 index 000000000..66f15bca7 --- /dev/null +++ b/common/utils/timer/time_wheel_utils.go @@ -0,0 +1,10 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import "time" + +func get10Ms() time.Duration { + return time.Duration(int64(time.Now().UnixNano() / int64(time.Millisecond) / 10)) +} diff --git a/common/utils/timer/timer.go b/common/utils/timer/timer.go new file mode 100644 index 000000000..9dc952cc8 --- /dev/null +++ b/common/utils/timer/timer.go @@ -0,0 +1,53 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import "time" + +type Next interface { + Next(time.Time) time.Time +} + +// 定时器接口 +type Timer interface { + // 一次性定时器 + AfterFunc(expire time.Duration, callback func()) TimeNoder + + // 周期性定时器 + ScheduleFunc(expire time.Duration, callback func()) TimeNoder + + // 自定义下次的时间 + CustomFunc(n Next, callback func()) TimeNoder + + // 运行 + Run() + + // 停止所有定时器 + Stop() +} + +// 停止单个定时器 +type TimeNoder interface { + Stop() bool + // 重置时间器 + Reset(expire time.Duration) bool +} + +// 定时器构造函数 +func NewTimer(opt ...Option) Timer { + var o option + for _, cb := range opt { + cb(&o) + } + + if o.timeWheel { + return newTimeWheel() + } + + if o.minHeap { + return newMinHeap() + } + + return newTimeWheel() +} diff --git a/common/utils/timer/timer_test.go b/common/utils/timer/timer_test.go new file mode 100644 index 000000000..7936bc613 --- /dev/null +++ b/common/utils/timer/timer_test.go @@ -0,0 +1,219 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "log" + "sync" + "sync/atomic" + "testing" + "time" +) + +func Test_ScheduleFunc(t *testing.T) { + tm := NewTimer() + + log.SetFlags(log.Ldate | log.Lmicroseconds) + count := uint32(0) + log.Printf("start\n") + + tm.ScheduleFunc(time.Millisecond*100, func() { + log.Printf("schedule\n") + atomic.AddUint32(&count, 1) + }) + + go func() { + time.Sleep(570 * time.Millisecond) + log.Printf("stop\n") + tm.Stop() + }() + + tm.Run() + if count != 5 { + t.Errorf("count:%d != 5\n", count) + } + +} + +func Test_AfterFunc(t *testing.T) { + tm := NewTimer() + go tm.Run() + log.Printf("start\n") + + count := uint32(0) + tm.AfterFunc(time.Millisecond*20, func() { + log.Printf("after Millisecond * 20") + atomic.AddUint32(&count, 1) + }) + + tm.AfterFunc(time.Second, func() { + log.Printf("after second") + atomic.AddUint32(&count, 1) + }) + + /* + tm.AfterFunc(time.Minute, func() { + log.Printf("after Minute") + }) + */ + /* + tm.AfterFunc(time.Hour, nil) + tm.AfterFunc(time.Hour*24, nil) + tm.AfterFunc(time.Hour*24*365, nil) + tm.AfterFunc(time.Hour*24*365*12, nil) + */ + + time.Sleep(time.Second + time.Millisecond*100) + tm.Stop() + + if count != 2 { + t.Errorf("count:%d != 2\n", count) + } + +} + +func Test_Node_Stop_1(t *testing.T) { + tm := NewTimer() + count := uint32(0) + node := tm.AfterFunc(time.Millisecond*10, func() { + atomic.AddUint32(&count, 1) + }) + go func() { + time.Sleep(time.Millisecond * 30) + node.Stop() + tm.Stop() + }() + + tm.Run() + if count != 1 { + t.Errorf("count:%d == 1\n", count) + } +} + +func Test_Node_Stop(t *testing.T) { + tm := NewTimer() + count := uint32(0) + node := tm.AfterFunc(time.Millisecond*100, func() { + atomic.AddUint32(&count, 1) + }) + node.Stop() + go func() { + time.Sleep(time.Millisecond * 200) + tm.Stop() + }() + tm.Run() + + if count == 1 { + t.Errorf("count:%d == 1\n", count) + } + +} + +// 测试重置定时器 +func Test_Reset(t *testing.T) { + t.Run("min heap reset", func(t *testing.T) { + + tm := NewTimer(WithMinHeap()) + + go tm.Run() + count := int32(0) + + tc := make(chan time.Duration, 2) + + var mu sync.Mutex + isClose := false + now := time.Now() + node1 := tm.AfterFunc(time.Millisecond*100, func() { + + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + + node2 := tm.AfterFunc(time.Millisecond*100, func() { + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + node1.Reset(time.Millisecond) + node2.Reset(time.Millisecond) + + time.Sleep(time.Millisecond * 3) + mu.Lock() + isClose = true + close(tc) + node1.Stop() + node2.Stop() + mu.Unlock() + for tv := range tc { + if tv < time.Millisecond || tv > 2*time.Millisecond { + t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond") + + } + } + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + + }) + + t.Run("time wheel reset", func(t *testing.T) { + tm := NewTimer() + + go func() { + tm.Run() + }() + + count := int32(0) + + tc := make(chan time.Duration, 2) + + var mu sync.Mutex + isClose := false + now := time.Now() + node1 := tm.AfterFunc(time.Millisecond*10, func() { + + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + + node2 := tm.AfterFunc(time.Millisecond*10, func() { + mu.Lock() + atomic.AddInt32(&count, 1) + if atomic.LoadInt32(&count) <= 2 && !isClose { + tc <- time.Since(now) + } + mu.Unlock() + }) + + node1.Reset(time.Millisecond * 20) + node2.Reset(time.Millisecond * 20) + + time.Sleep(time.Millisecond * 40) + mu.Lock() + isClose = true + close(tc) + node1.Stop() + node2.Stop() + mu.Unlock() + for tv := range tc { + if tv < time.Millisecond*20 || tv > 2*time.Millisecond*20 { + t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond") + } + } + if atomic.LoadInt32(&count) != 2 { + t.Errorf("count:%d != 2", atomic.LoadInt32(&count)) + } + }) +} diff --git a/common/utils/timer/timer_wheel_utils_test.go b/common/utils/timer/timer_wheel_utils_test.go new file mode 100644 index 000000000..0c4cdd62e --- /dev/null +++ b/common/utils/timer/timer_wheel_utils_test.go @@ -0,0 +1,14 @@ +// Copyright 2020-2024 guonaihong, antlabs. All rights reserved. +// +// mit license +package timer + +import ( + "fmt" + "testing" +) + +func Test_Get10Ms(t *testing.T) { + + fmt.Printf("%v:%d", get10Ms(), get10Ms()) +} diff --git a/go.work b/go.work index c34248269..682455af7 100644 --- a/go.work +++ b/go.work @@ -8,24 +8,25 @@ use ( ./common/cool ./common/utils/bitset ./common/utils/bytearray + ./common/utils/concurrent-swiss-map ./common/utils/cronex ./common/utils/event ./common/utils/go-jsonrpc ./common/utils/go-sensitive-word-1.3.3 ./common/utils/goja - ./common/utils/limit ./common/utils/lockfree-1.1.3 ./common/utils/log ./common/utils/qqwry ./common/utils/sturc + ./common/utils/timer ./common/utils/xml ./logic ./login ./modules ./modules/base - ./modules/player ./modules/config ./modules/dict + ./modules/player ./modules/space ./modules/task ) diff --git a/logic/controller/login_getserver.go b/logic/controller/login_getserver.go index 1227435f5..52aca8d45 100644 --- a/logic/controller/login_getserver.go +++ b/logic/controller/login_getserver.go @@ -41,7 +41,7 @@ func (h Controller) GetServerOnline(data *user.SidInfo, c gnet.Conn) (result *rp ser := playerservice.NewUserService(data.Head.UserID) f, b := ser.Friend.Get() for _, v := range f { - result.FriendInfo = append(result.FriendInfo, rpc.FriendInfo{v, 1}) + result.FriendInfo = append(result.FriendInfo, rpc.FriendInfo{Userid: v, TimePoke: 1}) } result.BlackInfo = b defer func() { diff --git a/logic/controller/pet_egg.go b/logic/controller/pet_egg.go index 2ed762915..db96d2fc8 100644 --- a/logic/controller/pet_egg.go +++ b/logic/controller/pet_egg.go @@ -5,22 +5,20 @@ import ( "blazing/logic/service/fight" "blazing/logic/service/pet" "blazing/logic/service/player" + "blazing/modules/player/model" ) // GetBreedInfo 获取繁殖信息协议 // 前端到后端无数据 请求协议 func (ctl Controller) GetBreedInfo( - data *pet.C2S_GET_BREED_INFO, playerObj *player.Player) (result *pet.S2C_GET_BREED_INFO, err errorcode.ErrorCode) { //这个时候player应该是空的 + data *pet.C2S_GET_BREED_INFO, player *player.Player) (result *model.S2C_GET_BREED_INFO, err errorcode.ErrorCode) { //这个时候player应该是空的 - result = &pet.S2C_GET_BREED_INFO{} - result.BreedLeftTime = 5000 - result.HatchLeftTime = 5000 - result.HatchState = 1 - result.BreedState = 1 - result.EggID = 1 - result.Intimacy = 1 - result.FeMalePetID = 1 - result.MalePetID = 3 + result = &model.S2C_GET_BREED_INFO{} + r := player.Service.Egg.Get() + if r == nil { + return + } + result = &r.Data // TODO: 实现获取繁殖信息的具体逻辑 return result, 0 @@ -45,10 +43,21 @@ func (ctl Controller) GetBreedPet( // StartBreed 开始繁殖协议 // 前端到后端 func (ctl Controller) StartBreed( - data *pet.C2S_START_BREED, playerObj *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - + data *pet.C2S_START_BREED, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 + _, MalePet, found := player.FindPet(data.Male) + if !found { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + _, Female, found := player.FindPet(data.Female) + if !found { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } // TODO: 实现开始繁殖的具体逻辑 result = &fight.NullOutboundInfo{} + r := player.Service.Egg.StartBreed(MalePet, Female) + if !r { + return nil, errorcode.ErrorCodes.ErrCannotPerformAction + } return result, 0 } @@ -56,15 +65,20 @@ func (ctl Controller) StartBreed( // GetEggList 获取精灵蛋数组 // 前端到后端无数据 请求协议 func (ctl Controller) GetEggList( - data *pet.C2S_GET_EGG_LIST, playerObj *player.Player) (result *pet.S2C_GET_EGG_LIST, err errorcode.ErrorCode) { //这个时候player应该是空的 + data *pet.C2S_GET_EGG_LIST, player *player.Player) (result *pet.S2C_GET_EGG_LIST, err errorcode.ErrorCode) { //这个时候player应该是空的 result = &pet.S2C_GET_EGG_LIST{} // TODO: 实现获取精灵蛋列表的逻辑 // 示例数据,实际应从玩家数据中获取 - result.EggList = append(result.EggList, pet.EggInfo{EggID: 1, OwnerID: 10001, EggCatchTime: 122123, - MalePetID: 1, - FeMalePetID: 3, - }) + r := player.Service.Egg.Get() + if r == nil { + return + } + + for _, v := range r.EggList { + result.EggList = append(result.EggList, v) + } + return result, 0 } diff --git a/logic/controller/user_info.go b/logic/controller/user_info.go index 416b0bff4..81ca8b5d1 100644 --- a/logic/controller/user_info.go +++ b/logic/controller/user_info.go @@ -16,7 +16,7 @@ import ( func (h Controller) GetUserSimInfo(data *user.SimUserInfoInboundInfo, player *player.Player) (result *user.SimUserInfoOutboundInfo, err errorcode.ErrorCode) { result = &user.SimUserInfoOutboundInfo{} - copier.Copy(result, player.Service.Info.Person(data.UserId)) + copier.Copy(result, player.Service.Info.Person(data.UserId).Data) return result, 0 } @@ -27,7 +27,7 @@ func (h Controller) GetUserSimInfo(data *user.SimUserInfoInboundInfo, player *pl func (h Controller) GetUserMoreInfo(data *user.MoreUserInfoInboundInfo, player *player.Player) (result *user.MoreUserInfoOutboundInfo, err errorcode.ErrorCode) { result = &user.MoreUserInfoOutboundInfo{} info := player.Service.Info.Person(data.UserId) - copier.CopyWithOption(result, info, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + copier.CopyWithOption(result, info.Data, copier.Option{IgnoreEmpty: true, DeepCopy: true}) //todo 待实现 return result, 0 @@ -56,4 +56,4 @@ func (h Controller) GetPlayerExp(data *item.ExpTotalRemainInboundInfo, player *p TotalExp: uint32(player.Info.ExpPool), }, 0 -} \ No newline at end of file +} diff --git a/logic/main.go b/logic/main.go index f8f900ce7..b6a6ede4a 100644 --- a/logic/main.go +++ b/logic/main.go @@ -46,6 +46,21 @@ func signalHandlerForMain(sig os.Signal) { // main 程序主入口函数 func main() { + // item := model.NeweggConfig() + // item.GeneratedPetIDs = []model.GeneratedPetID{ + // {PetID: 1, Prob: 0.01}, + // {PetID: 2, Prob: 0.01}, + // {PetID: 3, Prob: 0.01}, + // {PetID: 4, Prob: 0.01}, + // {PetID: 5, Prob: 0.01}, + // {PetID: 6, Prob: 0.01}, + // } + // item.MalePet = 1 + // item.FemalePet = 2 + // _, err := g.DB(item.GroupName()).Model(item.TableName()).FieldsEx("id").Data(item).Insert() + // if err != nil { + // panic(err) + // } //loadAccounts() // if cool.IsRedisMode { // go cool.ListenFunc(gctx.New()) diff --git a/logic/service/pet/egg.go b/logic/service/pet/egg.go index 3ea49190b..4af10a64d 100644 --- a/logic/service/pet/egg.go +++ b/logic/service/pet/egg.go @@ -1,6 +1,9 @@ package pet -import "blazing/logic/service/common" +import ( + "blazing/logic/service/common" + "blazing/modules/player/model" +) // C2S_GET_BREED_PET 获取繁殖精灵协议 // 前端到后端 @@ -16,33 +19,6 @@ type S2C_GET_BREED_PET struct { FemaleList []uint32 `json:"femaleList"` // 可繁殖雌性的精灵数组 参数为精灵捕获时间 } -// S2C_GET_BREED_INFO 获取繁殖信息协议 -// 后端到前端 -type S2C_GET_BREED_INFO struct { - // BreedState 繁殖状态 - BreedState uint32 `json:"breedState"` - // BreedLeftTime 繁殖剩余时间 - BreedLeftTime uint32 `json:"breedLeftTime"` - // BreedCoolTime 繁殖冷却时间 - BreedCoolTime uint32 `json:"breedCoolTime"` - // MalePetCatchTime 雄性精灵捕捉时间 - MalePetCatchTime uint32 `json:"malePetCatchTime"` - // MalePetID 雄性精灵ID - MalePetID uint32 `json:"malePetID"` - // FeMalePetCatchTime 雌性精灵捕捉时间 - FeMalePetCatchTime uint32 `json:"feMalePetCatchTime"` - // FeMalePetID 雌性精灵ID - FeMalePetID uint32 `json:"feMalePetID"` - // HatchState 孵化状态 - HatchState uint32 `json:"hatchState"` - // HatchLeftTime 孵化剩余时间 - HatchLeftTime uint32 `json:"hatchLeftTime"` - // EggID 当前孵化的精灵蛋ID - EggID uint32 `json:"eggID"` - // Intimacy 亲密度 1 = 悲伤 以此类推 ["悲伤","冷淡","平淡","友好","亲密无间"] - Intimacy uint32 `json:"intimacy"` -} - // C2S_GET_BREED_INFO 获取繁殖信息协议 // 前端到后端无数据 请求协议 type C2S_GET_BREED_INFO struct { @@ -58,17 +34,8 @@ type C2S_GET_EGG_LIST struct { // S2C_GET_EGG_LIST 获取精灵蛋数组协议 // 后端到前端 type S2C_GET_EGG_LIST struct { - EggListLen uint32 `struc:"sizeof=EggList"` - EggList []EggInfo `json:"eggList"` // 精灵蛋数组 跟其他数组一样 需要给有数量 -} - -// EggInfo 精灵蛋信息 -type EggInfo struct { - OwnerID uint32 `json:"ownerID"` // 所属人ID - EggCatchTime uint32 `json:"eggCatchTime"` // 精灵蛋获得时间 - EggID uint32 `json:"eggID"` // 精灵蛋ID - MalePetID uint32 `json:"male"` // 雄性精灵ID - FeMalePetID uint32 `json:"female"` // 雌性精灵ID + EggListLen uint32 `struc:"sizeof=EggList"` + EggList []model.EggInfo `json:"eggList"` // 精灵蛋数组 跟其他数组一样 需要给有数量 } // C2S_START_HATCH 开始孵化精灵蛋协议 diff --git a/logic/service/player/player.go b/logic/service/player/player.go index d28c00a2b..c82191e55 100644 --- a/logic/service/player/player.go +++ b/logic/service/player/player.go @@ -169,9 +169,9 @@ func (p *Player) SendPack(b []byte) error { if p.MainConn == nil { return nil } - _, ok := p.MainConn.Context().(*ClientData) + psocket, ok := p.MainConn.Context().(*ClientData) if ok { - return p.MainConn.Context().(*ClientData).SendPack(b) + return psocket.SendPack(b) } return nil } diff --git a/modules/config/model/boss_effect.go b/modules/config/model/boss_effect.go index e374289d7..e85a0e026 100644 --- a/modules/config/model/boss_effect.go +++ b/modules/config/model/boss_effect.go @@ -16,7 +16,7 @@ type PlayerPetSpecialEffect struct { SeIdx uint32 `gorm:"not null;uniqueIndex:idx_se_idx;comment:'精灵特效索引(XML中的Idx)'" json:"se_idx"` //Stat uint32 `gorm:"not null;default:0;comment:'精灵特效状态(XML中的Stat)'" json:"stat"` Eid uint32 `gorm:"not null;index:idx_eid;comment:'精灵特效Eid(XML中的Eid)'" json:"eid"` - Args []int `gorm:"type:json;comment:'精灵特效参数(XML中的Args)'" json:"args"` + Args []int `gorm:"type:jsonb;comment:'精灵特效参数(XML中的Args)'" json:"args"` Desc string `gorm:"type:varchar(255);default:'';comment:'精灵特效描述(XML中的Desc)'" json:"desc"` } diff --git a/modules/config/model/cdk.go b/modules/config/model/cdk.go index 6ce3de644..819575507 100644 --- a/modules/config/model/cdk.go +++ b/modules/config/model/cdk.go @@ -19,8 +19,8 @@ type CDKConfig struct { //cdk可兑换次数,where不等于0 ExchangeRemainCount int64 `gorm:"not null;default:1;comment:'CDK剩余可兑换次数(不能为0才允许兑换,支持查询where !=0)'" json:"exchange_remain_count" description:"剩余可兑换次数"` - ItemRewardIds []uint32 `gorm:"not null;type:json;default:'[]';comment:'绑定奖励物品ID数组,关联item_gift表主键'" json:"item_reward_ids" description:"奖励物品数组"` - ElfRewardIds []uint32 `gorm:"not null;type:json;default:'[]';comment:'绑定奖励精灵ID数组,关联config_pet_boss表主键'" json:"elf_reward_ids" description:"奖励精灵数组"` + ItemRewardIds []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'绑定奖励物品ID数组,关联item_gift表主键'" json:"item_reward_ids" description:"奖励物品数组"` + ElfRewardIds []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'绑定奖励精灵ID数组,关联config_pet_boss表主键'" json:"elf_reward_ids" description:"奖励精灵数组"` TitleRewardIds uint32 `gorm:"not null;default:0;comment:'绑定奖励称号'" json:"title_reward_ids" description:"绑定奖励称号"` ValidEndTime time.Time `gorm:"not null;comment:'CDK有效结束时间'" json:"valid_end_time" description:"有效结束时间"` diff --git a/modules/config/model/egg.go b/modules/config/model/egg.go new file mode 100644 index 000000000..63d1382ce --- /dev/null +++ b/modules/config/model/egg.go @@ -0,0 +1,52 @@ +package model + +import ( + "blazing/cool" +) + +// 表名常量定义:egg配置表 +const ( + TableNameeggConfig = "config_pet_egg" // egg配置表(记录egg编号、可兑换次数、奖励配置等核心信息) +) + +// EggConfig egg核心配置模型(含可兑换次数,满足查询`where 可兑换次数 != 0`需求) +type EggConfig struct { + *cool.Model + + //雄性 + + MalePet int32 `gorm:"not null;comment:'雄性宠物ID'" json:"male_pet"` + //雌性 + FemalePet int32 `gorm:"not null;comment:'雌性宠物ID'" json:"female_pet"` + + // 生成的精灵ID及对应概率 + GeneratedPetIDs []GeneratedPetID `gorm:"type:jsonb;comment:'生成的精灵ID及概率配置'" json:"generated_pet_ids"` + + Remark string `gorm:"size:512;default:'';comment:'egg备注'" json:"remark" description:"备注信息"` + //ItemGift []*ItemGift `gorm:"-" orm:"with:item_id=id"` +} +type GeneratedPetID struct { + PetID int32 `json:"pet_id" comment:"生成的精灵ID"` + Prob float64 `json:"prob" comment:"该精灵生成概率"` +} + +// -------------------------- 核心配套方法(遵循项目规范)-------------------------- +func (*EggConfig) TableName() string { + return TableNameeggConfig +} + +func (*EggConfig) GroupName() string { + return "default" +} + +func NeweggConfig() *EggConfig { + return &EggConfig{ + Model: cool.NewModel(), + } +} + +// -------------------------- 表结构自动同步 -------------------------- +func init() { + + cool.CreateTable(&EggConfig{}) +} diff --git a/modules/config/model/task.go b/modules/config/model/task.go index fb54e1c1a..e3272c0c3 100644 --- a/modules/config/model/task.go +++ b/modules/config/model/task.go @@ -32,7 +32,7 @@ type TaskConfig struct { IsAcceptable uint32 `gorm:"not null;default:1;comment:'是否可以被接受'" json:"is_acceptable" description:"是否可以被接受"` // 奖励配置 - ItemRewardIds []uint32 `gorm:"not null;type:json;default:'[]';comment:'绑定奖励物品ID数组,关联item_gift表主键'" json:"item_reward_ids" description:"奖励物品数组"` + ItemRewardIds []uint32 `gorm:"not null;type:jsonb;default:'[]';comment:'绑定奖励物品ID数组,关联item_gift表主键'" json:"item_reward_ids" description:"奖励物品数组"` ElfRewardIds uint32 `gorm:"not null;default:0;comment:'绑定奖励精灵ID,关联elf_gift表主键'" json:"elf_reward_ids" description:"绑定奖励精灵ID"` //绑定奖励 diff --git a/modules/player/model/egg.go b/modules/player/model/egg.go new file mode 100644 index 000000000..c9a39299a --- /dev/null +++ b/modules/player/model/egg.go @@ -0,0 +1,76 @@ +package model + +import ( + "blazing/cool" +) + +// 表名常量 +const TableNamePlayerEgg = "player_egg" + +// Egg 对应数据库表 player_cdk_log,用于记录CDK兑换日志 +type Egg struct { + Base + PlayerID uint64 `gorm:"not null;index:idx_player_Egg_by_player_id;comment:'所属玩家ID'" json:"player_id"` + Data S2C_GET_BREED_INFO `gorm:"type:jsonb;not null;comment:'全部数据'" json:"data"` + CurEgg EggInfo `gorm:"type:jsonb;not null;comment:'当前蛋'" json:"cur_egg"` + EggList []EggInfo `gorm:"type:jsonb;not null;comment:'蛋列表'" json:"egg_list"` +} + +// S2C_GET_BREED_INFO 获取繁殖信息协议 +// 后端到前端 +type S2C_GET_BREED_INFO struct { + // BreedState 繁殖状态 + BreedState uint32 `json:"breedState"` + StartTime uint32 `struc:"skip"` //返回记录 + // BreedLeftTime 繁殖剩余时间 + BreedLeftTime uint32 `json:"breedLeftTime"` + // BreedCoolTime 繁殖冷却时间 + BreedCoolTime uint32 `json:"breedCoolTime"` + // MalePetCatchTime 雄性精灵捕捉时间 + MalePetCatchTime uint32 `json:"malePetCatchTime"` + // MalePetID 雄性精灵ID + MalePetID uint32 `json:"malePetID"` + // FeMalePetCatchTime 雌性精灵捕捉时间 + FeMalePetCatchTime uint32 `json:"feMalePetCatchTime"` + // FeMalePetID 雌性精灵ID + FeMalePetID uint32 `json:"feMalePetID"` + // HatchState 孵化状态 ,0=未孵化 1=孵化中 2=已孵化 + HatchState uint32 `json:"hatchState"` + // HatchLeftTime 孵化剩余时间 + HatchLeftTime uint32 `json:"hatchLeftTime"` + // EggID 当前孵化的精灵蛋ID + EggID uint32 `json:"eggID"` + // Intimacy 亲密度 1 = 悲伤 以此类推 ["悲伤","冷淡","平淡","友好","亲密无间"] + Intimacy uint32 `json:"intimacy"` +} + +// EggInfo 精灵蛋信息 +type EggInfo struct { + OwnerID uint32 `json:"ownerID"` // 所属人ID + EggCatchTime uint32 `json:"eggCatchTime"` // 精灵蛋获得时间 + EggID uint32 `json:"eggID"` // 精灵蛋ID + MalePetID uint32 `json:"male"` // 雄性精灵ID + FeMalePetID uint32 `json:"female"` // 雌性精灵ID +} + +// TableName 返回表名 +func (*Egg) TableName() string { + return TableNamePlayerEgg +} + +// GroupName 返回表组名 +func (*Egg) GroupName() string { + return "default" +} + +// NewEgg 创建一个新的CDK记录 +func NewEgg() *Egg { + return &Egg{ + Base: *NewBase(), + } +} + +// init 程序启动时自动创建表 +func init() { + cool.CreateTable(&Egg{}) +} diff --git a/modules/player/model/FRIEND.go b/modules/player/model/friend.go similarity index 100% rename from modules/player/model/FRIEND.go rename to modules/player/model/friend.go diff --git a/modules/player/model/player.go b/modules/player/model/info.go similarity index 100% rename from modules/player/model/player.go rename to modules/player/model/info.go diff --git a/modules/player/model/sign.go b/modules/player/model/sign.go index c936a671d..20dc281cb 100644 --- a/modules/player/model/sign.go +++ b/modules/player/model/sign.go @@ -18,7 +18,7 @@ type SignInRecord struct { IsCompleted bool `gorm:"not null;default:false;comment:'签到是否完成(0-未完成 1-已完成)'" json:"is_completed"` //通过bitset来实现签到的进度记录 - SignInProgress []uint32 `gorm:"type:json;not null;comment:'签到进度(状压实现,存储每日签到状态)'" json:"sign_in_progress"` + SignInProgress []uint32 `gorm:"type:jsonb;not null;comment:'签到进度(状压实现,存储每日签到状态)'" json:"sign_in_progress"` } // TableName 指定表名(遵循现有规范) diff --git a/modules/player/service/egg.go b/modules/player/service/egg.go new file mode 100644 index 000000000..c0b4e4e74 --- /dev/null +++ b/modules/player/service/egg.go @@ -0,0 +1,48 @@ +package service + +import ( + "blazing/cool" + "blazing/modules/player/model" + "time" +) + +type EggService struct { + BaseService +} + +func NewEggService(id uint32) *EggService { + return &EggService{ + + BaseService: BaseService{userid: id, + + Service: &cool.Service{Model: model.NewEgg()}, + }, + } + +} +func (s *EggService) Get() (out *model.Egg) { + + s.TestModel(s.Model).Scan(&out) + + return + +} +func (s *EggService) StartBreed(m, f *model.PetInfo) bool { + + var tt *model.Egg + s.TestModel(s.Model).Scan(&tt) + if tt == nil { + tt = &model.Egg{} + } + if tt.Data.HatchState != 0 { + return false + + } + tt.Data.StartTime = uint32(time.Now().Unix()) + tt.Data.HatchState = 1 + tt.Data.FeMalePetCatchTime = f.CatchTime + tt.Data.MalePetCatchTime = m.CatchTime + tt.Data.FeMalePetID = f.ID + tt.Data.MalePetID = m.ID + return true +} diff --git a/modules/player/service/info.go b/modules/player/service/info.go index 1af56ad97..5d2d4fdc5 100644 --- a/modules/player/service/info.go +++ b/modules/player/service/info.go @@ -53,17 +53,11 @@ func (s *InfoService) Reg(nick string, color uint32) { //go s.InitTask() } -func (s *InfoService) Person(userid uint32) *model.PlayerInfo { +func (s *InfoService) Person(userid uint32) (out *model.PlayerEX) { - m := cool.DBM(s.Model).Where("player_id", userid) - var tt model.PlayerEX - err := m.Scan(&tt) - if err != nil { - return nil - } + cool.DBM(s.Model).Where("player_id", userid).Scan(&out) - ret := tt.Data - return &ret + return } func (s *InfoService) GetCache() *model.PlayerInfo { diff --git a/modules/player/service/pet.go b/modules/player/service/pet.go index 665024416..ab879d194 100644 --- a/modules/player/service/pet.go +++ b/modules/player/service/pet.go @@ -116,7 +116,7 @@ RETURNING max_ts; `, service.NewBaseSysUserService().Model.TableName()) // 执行 Raw SQL 并扫描返回值 - ret, err := cool.DBM(service.NewBaseSysUserService().Model).Raw(sql, s.userid).All() + ret, _ := cool.DBM(service.NewBaseSysUserService().Model).Raw(sql, s.userid).All() //fmt.Println(ret, err) y.CatchTime = ret.Array()[0].Uint32() m1 := cool.DBM(s.Model).Where("player_id", s.userid) @@ -127,7 +127,7 @@ RETURNING max_ts; player.Free = 0 player.IsVip = cool.Config.ServerInfo.IsVip - _, err = m1.Insert(player) + _, err := m1.Insert(player) if err != nil { panic(err) } diff --git a/modules/player/service/user.go b/modules/player/service/user.go index 296f4d6b3..01433f73b 100644 --- a/modules/player/service/user.go +++ b/modules/player/service/user.go @@ -22,6 +22,7 @@ type UserService struct { Title *TitleService Cdk *CdkService Friend *FriendService + Egg *EggService } func NewUserService(id uint32) *UserService { @@ -38,6 +39,7 @@ func NewUserService(id uint32) *UserService { Title: NewTitleService(id), Cdk: NewCdkService(id), Friend: NewFriendService(id), + Egg: NewEggService(id), } }