From 5fb1dcbcea0c56aeafd271e3d7ff3c8cd1eece9b Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 6 Sep 2023 20:09:25 +0800 Subject: [PATCH 01/13] =?UTF-8?q?style:=20=E5=B8=B8=E9=87=8F=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E4=B8=BA=E4=BB=8E=201=20=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/task/state.go | 8 ++++---- server/message.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/game/task/state.go b/game/task/state.go index f2a8807..017126f 100644 --- a/game/task/state.go +++ b/game/task/state.go @@ -1,10 +1,10 @@ package task const ( - StateAccept State = iota // 已接受 - StateFinish // 已完成 - StateReward // 已领取 - StateFail // 已失败 + StateAccept State = iota + 1 // 已接受 + StateFinish // 已完成 + StateReward // 已领取 + StateFail // 已失败 ) type State uint16 diff --git a/server/message.go b/server/message.go index 7f027e9..3a2f5c2 100644 --- a/server/message.go +++ b/server/message.go @@ -42,8 +42,8 @@ var messageNames = map[MessageType]string{ } const ( - MessageErrorActionNone MessageErrorAction = iota // 错误消息类型操作:将不会被进行任何特殊处理,仅进行日志输出 - MessageErrorActionShutdown // 错误消息类型操作:当接收到该类型的操作时,服务器将执行 Server.shutdown 函数 + MessageErrorActionNone MessageErrorAction = iota + 1 // 错误消息类型操作:将不会被进行任何特殊处理,仅进行日志输出 + MessageErrorActionShutdown // 错误消息类型操作:当接收到该类型的操作时,服务器将执行 Server.shutdown 函数 ) var messageErrorActionNames = map[MessageErrorAction]string{ From a4ba3f1fa86ab2ad682c28f6e3ab0258099b4ac6 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 7 Sep 2023 20:01:14 +0800 Subject: [PATCH 02/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20survey.Analyz?= =?UTF-8?q?er=20=E5=8E=BB=E9=87=8D=20BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/log/survey/analyzer.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/log/survey/analyzer.go b/utils/log/survey/analyzer.go index b510965..e338cff 100644 --- a/utils/log/survey/analyzer.go +++ b/utils/log/survey/analyzer.go @@ -70,11 +70,12 @@ func (slf *Analyzer) IncreaseNonRepeat(key string, record R, recordKey string, d for _, v := range dimension { dvs = append(dvs, record.GetString(v)) } - dk := strings.Join(dvs, "_") + dk := strings.Join(append([]string{key}, dvs...), "_") if _, e := slf.repeat[dk]; e { slf.m.Unlock() return } + slf.repeat[dk] = struct{}{} slf.m.Unlock() slf.Increase(key, record, recordKey) } @@ -89,11 +90,12 @@ func (slf *Analyzer) IncreaseValueNonRepeat(key string, record R, value float64, for _, v := range dimension { dvs = append(dvs, record.GetString(v)) } - dk := strings.Join(dvs, "_") + dk := strings.Join(append([]string{key}, dvs...), "_") if _, e := slf.repeat[dk]; e { slf.m.Unlock() return } + slf.repeat[dk] = struct{}{} slf.m.Unlock() slf.IncreaseValue(key, value) } From 5ab990246ddb7059bc83ec65f485cb7bbb1ded22 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 7 Sep 2023 20:05:01 +0800 Subject: [PATCH 03/13] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20slice=20?= =?UTF-8?q?=E5=8C=85=20Filter=20=E5=92=8C=20Map=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=20Reduce=20=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/slice/filter.go | 64 ++++++++++++++++++++++++++++++++++++++ utils/slice/filter_test.go | 34 ++++++++++++++++++++ utils/slice/map.go | 14 +++++++++ utils/slice/reduce.go | 28 +++++++++++++++++ utils/slice/reduce_test.go | 17 ++++++++++ utils/slice/slice.go | 25 ++------------- 6 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 utils/slice/filter.go create mode 100644 utils/slice/filter_test.go create mode 100644 utils/slice/map.go create mode 100644 utils/slice/reduce.go create mode 100644 utils/slice/reduce_test.go diff --git a/utils/slice/filter.go b/utils/slice/filter.go new file mode 100644 index 0000000..bae96ca --- /dev/null +++ b/utils/slice/filter.go @@ -0,0 +1,64 @@ +package slice + +// Filter 根据特定的表达式过滤切片成员 +// - reserve: 是否保留符合条件的成员 +// - slice: 待过滤的切片 +// - expression: 过滤表达式 +// +// 这个函数的作用是遍历输入切片 slice,然后根据 expression 函数的返回值来决定是否保留每个元素。具体来说 +// - 如果 expression 返回 true 并且 reserve 也是 true,那么元素会被保留 +// - 如果 expression 返回 false 并且 reserve 是 false,那么元素也会被保留 +// +// 该没有创建新的内存空间或进行元素复制,所以整个操作相当高效。同时,由于 s 和 slice 实际上共享底层的数组,因此这个函数会改变传入的 slice。如果不希望改变原切片,需要在函数调用之前手动复制一份或者使用 FilterCopy 函数。 +func Filter[V any](reserve bool, slice []V, expression func(index int, item V) bool) []V { + if len(slice) == 0 { + return slice + } + + var ( + i int + j int + ) + for i = 0; i < len(slice); i++ { + if expression(i, slice[i]) { + if reserve { + slice[j] = slice[i] + j++ + } + } else { + if !reserve { + slice[j] = slice[i] + j++ + } + } + } + + return slice[:j] +} + +// FilterT 与 Filter 的功能相同,但是 reserve 被默认为 true +func FilterT[V any](slice []V, expression func(index int, item V) bool) []V { + return Filter(true, slice, expression) +} + +// FilterF 与 Filter 的功能相同,但是 reserve 被默认为 false +func FilterF[V any](slice []V, expression func(index int, item V) bool) []V { + return Filter(false, slice, expression) +} + +// FilterCopy 与 Filter 的功能相同,但是不会改变原切片,而是返回一个新的切片 +func FilterCopy[V any](reserve bool, slice []V, expression func(index int, item V) bool) []V { + var s = make([]V, len(slice)) + copy(s, slice) + return Filter(reserve, s, expression) +} + +// FilterCopyT 与 FilterCopy 的功能相同,但是 reserve 被默认为 true +func FilterCopyT[V any](slice []V, expression func(index int, item V) bool) []V { + return FilterCopy(true, slice, expression) +} + +// FilterCopyF 与 FilterCopy 的功能相同,但是 reserve 被默认为 false +func FilterCopyF[V any](slice []V, expression func(index int, item V) bool) []V { + return FilterCopy(false, slice, expression) +} diff --git a/utils/slice/filter_test.go b/utils/slice/filter_test.go new file mode 100644 index 0000000..1249821 --- /dev/null +++ b/utils/slice/filter_test.go @@ -0,0 +1,34 @@ +package slice_test + +import ( + "github.com/kercylan98/minotaur/utils/slice" + "testing" +) + +func TestFilter(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + s = slice.Filter(true, s, func(index int, item int) bool { + return item%2 == 0 + }) + if len(s) != 2 { + t.Error("Filter failed") + } + if s[0] != 2 { + t.Error("Filter failed") + } + if s[1] != 4 { + t.Error("Filter failed") + } +} + +func TestFilterCopy(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + cp := slice.FilterCopy(true, s, func(index int, item int) bool { + return item%2 == 0 + }) + if len(s) != 5 { + t.Error("FilterCopy failed") + } else { + t.Log(s, cp) + } +} diff --git a/utils/slice/map.go b/utils/slice/map.go new file mode 100644 index 0000000..9d3af4a --- /dev/null +++ b/utils/slice/map.go @@ -0,0 +1,14 @@ +package slice + +// Map 将切片中的每一个元素转换为另一种类型的元素 +// - slice: 待转换的切片 +// - converter: 转换函数 +// +// 这不会改变原有的切片,而是返回一个新的切片 +func Map[V any, T any](slice []V, converter func(index int, item V) T) []T { + var s = make([]T, len(slice)) + for i := 0; i < len(slice); i++ { + s[i] = converter(i, slice[i]) + } + return s +} diff --git a/utils/slice/reduce.go b/utils/slice/reduce.go new file mode 100644 index 0000000..ae31edd --- /dev/null +++ b/utils/slice/reduce.go @@ -0,0 +1,28 @@ +package slice + +import ( + "github.com/kercylan98/minotaur/utils/generic" +) + +// Reduce 将切片中的多个元素组合成一个单一值 +// - start: 开始索引,如果为负数则从后往前计算,例如:-1 表示从最后一个元素开始向左遍历,1 表示从第二个元素开始 +// - slice: 待组合的切片 +// - reducer: 组合函数 +func Reduce[V any, R generic.Number](start int, slice []V, reducer func(index int, item V, current R) R) (result R) { + length := len(slice) + + if start >= length || -start > length { + return + } + + if start < 0 { + for i := length + start; i >= 0; i-- { + result = reducer(i, slice[i], result) + } + } else { + for i := start; i < length; i++ { + result = reducer(i, slice[i], result) + } + } + return result +} diff --git a/utils/slice/reduce_test.go b/utils/slice/reduce_test.go new file mode 100644 index 0000000..3553fd9 --- /dev/null +++ b/utils/slice/reduce_test.go @@ -0,0 +1,17 @@ +package slice_test + +import ( + "github.com/kercylan98/minotaur/utils/slice" + "testing" +) + +func TestReduce(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + sum := slice.Reduce(0, s, func(index int, item int, current int) int { + return current + item + }) + t.Log(sum) + if sum != 15 { + t.Error("Reduce failed") + } +} diff --git a/utils/slice/slice.go b/utils/slice/slice.go index 90f3473..23c386f 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -5,6 +5,9 @@ import ( "reflect" ) +// Slice 切片类型 +type Slice[V any] []V + // GetValue 获取特定索引的元素,如果索引超出范围则返回零值 func GetValue[V any](slice []V, i int) (v V) { if i >= 0 && i < len(slice) { @@ -333,25 +336,3 @@ func SubWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) []T { } return result } - -// Filter 过滤切片中的元素 -// - filterHandle 返回 true 表示需要保留 -func Filter[T any](a []T, filterHandle func(a T) bool) []T { - var result []T - for _, a := range a { - if filterHandle(a) { - result = append(result, a) - } - } - return result -} - -// Mapping 将切片中的元素进行转换 -// - mappingHandle 返回转换后的元素 -func Mapping[T any, R any](a []T, mappingHandle func(a T) R) []R { - var result []R - for _, a := range a { - result = append(result, mappingHandle(a)) - } - return result -} From d7724094d19943303b9bbe2b61fa8cb3e595c7c8 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 13:13:01 +0800 Subject: [PATCH 04/13] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=A7?= =?UTF-8?q?=E9=87=8F=20slice=20=E5=8C=85=E5=92=8C=20hash=20=E5=8C=85?= =?UTF-8?q?=E7=9A=84=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/hash/chunk.go | 27 +++++++++++++ utils/hash/drop.go | 56 ++++++++++++++++++++++++++ utils/hash/each.go | 47 ++++++++++++++++++++++ utils/hash/filter.go | 56 ++++++++++++++++++++++++++ utils/slice/chunk.go | 18 +++++++++ utils/slice/drop.go | 40 +++++++++++++++++++ utils/slice/drop_test.go | 11 ++++++ utils/slice/each.go | 85 ++++++++++++++++++++++++++++++++++++++++ utils/slice/fill.go | 58 +++++++++++++++++++++++++++ utils/slice/filter.go | 8 ++-- utils/slice/group.go | 17 ++++++++ utils/slice/shuffle.go | 30 ++++++++++++++ utils/slice/slice.go | 9 ----- utils/slice/unique.go | 44 +++++++++++++++++++++ 14 files changed, 493 insertions(+), 13 deletions(-) create mode 100644 utils/hash/chunk.go create mode 100644 utils/hash/drop.go create mode 100644 utils/hash/each.go create mode 100644 utils/hash/filter.go create mode 100644 utils/slice/chunk.go create mode 100644 utils/slice/drop.go create mode 100644 utils/slice/drop_test.go create mode 100644 utils/slice/each.go create mode 100644 utils/slice/fill.go create mode 100644 utils/slice/group.go create mode 100644 utils/slice/shuffle.go create mode 100644 utils/slice/unique.go diff --git a/utils/hash/chunk.go b/utils/hash/chunk.go new file mode 100644 index 0000000..0f690c8 --- /dev/null +++ b/utils/hash/chunk.go @@ -0,0 +1,27 @@ +package hash + +// Chunk 将哈希表按照指定大小分块 +// - m: 待分块的哈希表 +// - size: 每块的大小 +func Chunk[K comparable, V any](m map[K]V, size int) []map[K]V { + if len(m) == 0 { + return nil + } + + var ( + i int + j int + ) + chunks := make([]map[K]V, (len(m)-1)/size+1) + for i = 0; i < len(m); i += size { + chunks[j] = make(map[K]V, size) + for key, value := range m { + if i <= j*size && j*size < i+size { + chunks[j][key] = value + } + } + j++ + } + + return chunks +} diff --git a/utils/hash/drop.go b/utils/hash/drop.go new file mode 100644 index 0000000..050f712 --- /dev/null +++ b/utils/hash/drop.go @@ -0,0 +1,56 @@ +package hash + +// RandomDrop 随机删除哈希表中的指定数量的元素 +// - 该函数会修改原哈希表,如果不想修改原哈希表,请使用 RandomDropCopy +func RandomDrop[K comparable, V any](n int, hash map[K]V) map[K]V { + if n <= 0 || len(hash) <= n { + return hash + } + for k := range hash { + if n <= 0 { + break + } + delete(hash, k) + n-- + } + return hash +} + +// RandomDropCopy 随机删除哈希表中的指定数量的元素 +// - 该函数不会修改原哈希表,如果想修改原哈希表,请使用 RandomDrop +func RandomDropCopy[K comparable, V any](n int, m map[K]V) map[K]V { + if n <= 0 || len(m) <= n { + return map[K]V{} + } + var nm = make(map[K]V, len(m)) + for k, v := range m { + if n <= 0 { + break + } + nm[k] = v + n-- + } + return nm +} + +// DropBy 从哈希表中删除指定的元素 +func DropBy[K comparable, V any](m map[K]V, fn func(key K, value V) bool) map[K]V { + for k, v := range m { + if fn(k, v) { + delete(m, k) + } + } + return m +} + +// DropByCopy 与 DropBy 功能相同,但是该函数不会修改原哈希表 +func DropByCopy[K comparable, V any](m map[K]V, fn func(key K, value V) bool) map[K]V { + var nm = make(map[K]V, len(m)) + for k, v := range m { + if fn(k, v) { + continue + } + nm[k] = v + } + return nm +} diff --git a/utils/hash/each.go b/utils/hash/each.go new file mode 100644 index 0000000..85324b4 --- /dev/null +++ b/utils/hash/each.go @@ -0,0 +1,47 @@ +package hash + +// Each 根据传入的 abort 遍历 m,如果 iterator 返回值与 abort 相同,则停止遍历 +func Each[K comparable, V any](abort bool, m map[K]V, iterator func(i int, key K, item V) bool) { + i := 0 + for k, v := range m { + if iterator(i, k, v) == abort { + break + } + i++ + } +} + +// EachT 与 Each 的功能相同,但是 abort 被默认为 true +func EachT[K comparable, V any](m map[K]V, iterator func(i int, key K, item V) bool) { + Each(true, m, iterator) +} + +// EachF 与 Each 的功能相同,但是 abort 被默认为 false +func EachF[K comparable, V any](m map[K]V, iterator func(i int, key K, item V) bool) { + Each(false, m, iterator) +} + +// EachResult 根据传入的 abort 遍历 m,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 +func EachResult[K comparable, V any, R any](abort bool, m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { + var result []R + i := 0 + for k, v := range m { + r, ok := iterator(i, k, v) + result = append(result, r) + if ok == abort { + break + } + i++ + } + return result +} + +// EachResultT 与 EachResult 的功能相同,但是 abort 被默认为 true +func EachResultT[K comparable, V any, R any](m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { + return EachResult(true, m, iterator) +} + +// EachResultF 与 EachResult 的功能相同,但是 abort 被默认为 false +func EachResultF[K comparable, V any, R any](m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { + return EachResult(false, m, iterator) +} diff --git a/utils/hash/filter.go b/utils/hash/filter.go new file mode 100644 index 0000000..50bed1d --- /dev/null +++ b/utils/hash/filter.go @@ -0,0 +1,56 @@ +package hash + +// Filter 根据特定的表达式过滤哈希表成员 +// - reserve: 是否保留符合条件的成员 +// - m: 待过滤的哈希表 +// - expression: 过滤表达式 +// +// 这个函数的作用是遍历输入哈希表 m,然后根据 expression 函数的返回值来决定是否保留每个元素。具体来说 +// - 如果 expression 返回 true 并且 reserve 也是 true,那么元素会被保留 +// - 如果 expression 返回 false 并且 reserve 是 false,那么元素也会被保留 +// +// 该没有创建新的内存空间或进行元素复制,所以整个操作相当高效。同时,由于 m 和 map 实际上共享底层的数组,因此这个函数会改变传入的 map。如果不希望改变原哈希表,需要在函数调用之前手动复制一份或者使用 FilterCopy 函数。 +func Filter[K comparable, V any](reserve bool, m map[K]V, expression func(key K, value V) bool) map[K]V { + if len(m) == 0 { + return m + } + + for key, value := range m { + if !expression(key, value) { + delete(m, key) + } + } + + return m +} + +// FilterT 与 Filter 的功能相同,但是 reserve 被默认为 true +func FilterT[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { + return Filter(true, m, expression) +} + +// FilterF 与 Filter 的功能相同,但是 reserve 被默认为 false +func FilterF[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { + return Filter(false, m, expression) +} + +// FilterCopy 与 Filter 的功能相同,但是不会改变原哈希表,而是返回一个新的哈希表 +func FilterCopy[K comparable, V any](reserve bool, m map[K]V, expression func(key K, value V) bool) map[K]V { + var res = map[K]V{} + for key, value := range m { + if expression(key, value) { + res[key] = value + } + } + return res +} + +// FilterTCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 true +func FilterTCopy[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { + return FilterCopy(true, m, expression) +} + +// FilterFCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 false +func FilterFCopy[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { + return FilterCopy(false, m, expression) +} diff --git a/utils/slice/chunk.go b/utils/slice/chunk.go new file mode 100644 index 0000000..311d2f5 --- /dev/null +++ b/utils/slice/chunk.go @@ -0,0 +1,18 @@ +package slice + +// Chunk 返回分块后的切片 +func Chunk[T any](collection []T, size int) [][]T { + if len(collection) == 0 { + return nil + } + + if size < 1 { + panic("size must be greater than 0") + } + + result := make([][]T, 0, (len(collection)+size-1)/size) + for size < len(collection) { + collection, result = collection[size:], append(result, collection[0:size]) + } + return append(result, collection) +} diff --git a/utils/slice/drop.go b/utils/slice/drop.go new file mode 100644 index 0000000..3883121 --- /dev/null +++ b/utils/slice/drop.go @@ -0,0 +1,40 @@ +package slice + +// Drop 从切片中删除指定数量的元素 +// - start: 起始位置 +// - n: 删除的元素数量 +// - slice: 待删除元素的切片 +// +// 关于 start 的取值: +// - 当 start < 0 时,start 将会从右至左计数,即 -1 表示最后一个元素,-2 表示倒数第二个元素,以此类推 +func Drop[V any](start, n int, slice []V) []V { + var s = make([]V, len(slice)) + copy(s, slice) + if start < 0 { + start = len(s) + start - n + 1 + if start < 0 { + start = 0 + } + } + + end := start + n + if end > len(s) { + end = len(s) + } + + return append(s[:start], s[end:]...) +} + +// DropBy 从切片中删除指定的元素 +// - slice: 待删除元素的切片 +func DropBy[V any](slice []V, fn func(index int, value V) bool) []V { + var s = make([]V, len(slice)) + copy(s, slice) + for i := 0; i < len(s); i++ { + if fn(i, s[i]) { + s = append(s[:i], s[i+1:]...) + i-- + } + } + return s +} diff --git a/utils/slice/drop_test.go b/utils/slice/drop_test.go new file mode 100644 index 0000000..33fbe0d --- /dev/null +++ b/utils/slice/drop_test.go @@ -0,0 +1,11 @@ +package slice_test + +import ( + "github.com/kercylan98/minotaur/utils/slice" + "testing" +) + +func TestDrop(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + t.Log(s, slice.Drop(1, 3, s)) +} diff --git a/utils/slice/each.go b/utils/slice/each.go new file mode 100644 index 0000000..9174bf1 --- /dev/null +++ b/utils/slice/each.go @@ -0,0 +1,85 @@ +package slice + +// Each 根据传入的 abort 遍历 slice,如果 iterator 返回值与 abort 相同,则停止遍历 +func Each[V any](abort bool, slice []V, iterator func(index int, item V) bool) { + for i := 0; i < len(slice); i++ { + if iterator(i, slice[i]) == abort { + break + } + } +} + +// EachT 与 Each 的功能相同,但是 abort 被默认为 true +func EachT[V any](slice []V, iterator func(index int, item V) bool) { + Each(true, slice, iterator) +} + +// EachF 与 Each 的功能相同,但是 abort 被默认为 false +func EachF[V any](slice []V, iterator func(index int, item V) bool) { + Each(false, slice, iterator) +} + +// EachReverse 根据传入的 abort 从后往前遍历 slice,如果 iterator 返回值与 abort 相同,则停止遍历 +func EachReverse[V any](abort bool, slice []V, iterator func(index int, item V) bool) { + for i := len(slice) - 1; i >= 0; i-- { + if iterator(i, slice[i]) == abort { + break + } + } +} + +// EachReverseT 与 EachReverse 的功能相同,但是 abort 被默认为 true +func EachReverseT[V any](slice []V, iterator func(index int, item V) bool) { + EachReverse(true, slice, iterator) +} + +// EachReverseF 与 EachReverse 的功能相同,但是 abort 被默认为 false +func EachReverseF[V any](slice []V, iterator func(index int, item V) bool) { + EachReverse(false, slice, iterator) +} + +// EachResult 根据传入的 abort 遍历 slice,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 +func EachResult[V any, R any](abort bool, slice []V, iterator func(index int, item V) (R, bool)) []R { + var result []R + for i := 0; i < len(slice); i++ { + r, ok := iterator(i, slice[i]) + result = append(result, r) + if ok == abort { + break + } + } + return result +} + +// EachResultT 与 EachResult 的功能相同,但是 abort 被默认为 true +func EachResultT[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { + return EachResult(true, slice, iterator) +} + +// EachResultF 与 EachResult 的功能相同,但是 abort 被默认为 false +func EachResultF[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { + return EachResult(false, slice, iterator) +} + +// EachResultReverse 根据传入的 abort 从后往前遍历 slice,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 +func EachResultReverse[V any, R any](abort bool, slice []V, iterator func(index int, item V) (R, bool)) []R { + var result []R + for i := len(slice) - 1; i >= 0; i-- { + r, ok := iterator(i, slice[i]) + result = append(result, r) + if ok == abort { + break + } + } + return result +} + +// EachResultReverseT 与 EachResultReverse 的功能相同,但是 abort 被默认为 true +func EachResultReverseT[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { + return EachResultReverse(true, slice, iterator) +} + +// EachResultReverseF 与 EachResultReverse 的功能相同,但是 abort 被默认为 false +func EachResultReverseF[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { + return EachResultReverse(false, slice, iterator) +} diff --git a/utils/slice/fill.go b/utils/slice/fill.go new file mode 100644 index 0000000..a345315 --- /dev/null +++ b/utils/slice/fill.go @@ -0,0 +1,58 @@ +package slice + +// FillBy 用指定的值填充切片 +// - slice: 待填充的切片 +func FillBy[V any](slice []V, fn func(index int, value V) V) []V { + for i, v := range slice { + slice[i] = fn(i, v) + } + return slice +} + +// FillByCopy 与 FillBy 的功能相同,但是不会改变原切片,而是返回一个新的切片 +func FillByCopy[V any](slice []V, fn func(index int, value V) V) []V { + var s = make([]V, len(slice)) + copy(s, slice) + return FillBy(s, fn) +} + +// FillUntil 用指定的值填充切片,如果 fn 返回的 bool 值与 abort 相同,则停止填充 +// - abort: 填充中止条件 +// - slice: 待填充的切片 +func FillUntil[V any](abort bool, slice []V, fn func(index int, value V) (V, bool)) []V { + for i, v := range slice { + if value, b := fn(i, v); b == abort { + break + } else { + slice[i] = value + } + } + return slice +} + +// FillUntilCopy 与 FillUntil 的功能相同,但不会改变原切片,而是返回一个新的切片 +func FillUntilCopy[V any](abort bool, slice []V, fn func(index int, value V) (V, bool)) []V { + var s = make([]V, len(slice)) + copy(s, slice) + return FillUntil(abort, s, fn) +} + +// FillUntilT 是 FillUntil 的简化版本,其中 abort 参数为 true +func FillUntilT[V any](slice []V, fn func(index int, value V) (V, bool)) []V { + return FillUntil(true, slice, fn) +} + +// FillUntilF 是 FillUntil 的简化版本,其中 abort 参数为 false +func FillUntilF[V any](slice []V, fn func(index int, value V) (V, bool)) []V { + return FillUntil(false, slice, fn) +} + +// FillUntilTCopy 是 FillUntilCopy 的简化版本,其中 abort 参数为 true +func FillUntilTCopy[V any](slice []V, fn func(index int, value V) (V, bool)) []V { + return FillUntilCopy(true, slice, fn) +} + +// FillUntilFCopy 是 FillUntilCopy 的简化版本,其中 abort 参数为 false +func FillUntilFCopy[V any](slice []V, fn func(index int, value V) (V, bool)) []V { + return FillUntilCopy(false, slice, fn) +} diff --git a/utils/slice/filter.go b/utils/slice/filter.go index bae96ca..2a8a806 100644 --- a/utils/slice/filter.go +++ b/utils/slice/filter.go @@ -53,12 +53,12 @@ func FilterCopy[V any](reserve bool, slice []V, expression func(index int, item return Filter(reserve, s, expression) } -// FilterCopyT 与 FilterCopy 的功能相同,但是 reserve 被默认为 true -func FilterCopyT[V any](slice []V, expression func(index int, item V) bool) []V { +// FilterTCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 true +func FilterTCopy[V any](slice []V, expression func(index int, item V) bool) []V { return FilterCopy(true, slice, expression) } -// FilterCopyF 与 FilterCopy 的功能相同,但是 reserve 被默认为 false -func FilterCopyF[V any](slice []V, expression func(index int, item V) bool) []V { +// FilterFCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 false +func FilterFCopy[V any](slice []V, expression func(index int, item V) bool) []V { return FilterCopy(false, slice, expression) } diff --git a/utils/slice/group.go b/utils/slice/group.go new file mode 100644 index 0000000..503333f --- /dev/null +++ b/utils/slice/group.go @@ -0,0 +1,17 @@ +package slice + +// GroupBy 返回分组后的切片 +func GroupBy[T any, K comparable](collection []T, fn func(T) K) map[K][]T { + if len(collection) == 0 { + return nil + } + + result := make(map[K][]T, len(collection)) + + for _, item := range collection { + key := fn(item) + result[key] = append(result[key], item) + } + + return result +} diff --git a/utils/slice/shuffle.go b/utils/slice/shuffle.go new file mode 100644 index 0000000..bb99be8 --- /dev/null +++ b/utils/slice/shuffle.go @@ -0,0 +1,30 @@ +package slice + +import "math/rand" + +// Shuffle 随机打乱切片 +// - 该函数会改变传入的切片,如果不希望改变原切片,需要在函数调用之前手动复制一份或者使用 ShuffleCopy 函数 +func Shuffle[T any](collection []T) []T { + rand.Shuffle(len(collection), func(i, j int) { + collection[i], collection[j] = collection[j], collection[i] + }) + + return collection +} + +// ShuffleCopy 返回随机打乱后的切片 +// - 该函数不会改变原切片 +func ShuffleCopy[T any](collection []T) []T { + if len(collection) == 0 { + return nil + } + + result := make([]T, len(collection)) + perm := rand.Perm(len(collection)) + + for i, randIndex := range perm { + result[i] = collection[randIndex] + } + + return result +} diff --git a/utils/slice/slice.go b/utils/slice/slice.go index 23c386f..7948a1a 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -1,7 +1,6 @@ package slice import ( - "math/rand" "reflect" ) @@ -111,14 +110,6 @@ func Reverse[V any](slice []V) { } } -// Shuffle 随机打乱数组 -func Shuffle[V any](slice []V) { - for i := 0; i < len(slice); i++ { - j := rand.Intn(len(slice)) - slice[i], slice[j] = slice[j], slice[i] - } -} - // Distinct 去重 func Distinct[V any](slice []V) []V { var result []V diff --git a/utils/slice/unique.go b/utils/slice/unique.go new file mode 100644 index 0000000..cca9197 --- /dev/null +++ b/utils/slice/unique.go @@ -0,0 +1,44 @@ +package slice + +// Unique 返回去重后的切片 +func Unique[T comparable](collection []T) []T { + if len(collection) == 0 { + return nil + } + + result := make([]T, 0, len(collection)) + seen := make(map[T]struct{}, len(collection)) + + for _, item := range collection { + if _, ok := seen[item]; ok { + continue + } + + seen[item] = struct{}{} + result = append(result, item) + } + + return result +} + +// UniqueBy 返回去重后的切片 +func UniqueBy[T any](collection []T, fn func(T) any) []T { + if len(collection) == 0 { + return nil + } + + result := make([]T, 0, len(collection)) + seen := make(map[any]struct{}, len(collection)) + + for _, item := range collection { + key := fn(item) + if _, ok := seen[key]; ok { + continue + } + + seen[key] = struct{}{} + result = append(result, item) + } + + return result +} From d72f18590bec72f6321fb990f1428a12c30c00e6 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 13:13:34 +0800 Subject: [PATCH 05/13] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20stream?= =?UTF-8?q?=20=E5=8C=85=EF=BC=8C=E6=8F=90=E4=BE=9B=E6=9B=B4=E4=BE=BF?= =?UTF-8?q?=E6=8D=B7=E7=9A=84=E4=BD=BF=E7=94=A8=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/stream/map.go | 235 ++++++++-------------------- utils/stream/map_example_test.go | 26 --- utils/stream/map_test.go | 90 ----------- utils/stream/slice.go | 261 +++++++++++++++---------------- utils/stream/slice_test.go | 16 +- utils/stream/slices.go | 145 +++++++++++++++++ 6 files changed, 350 insertions(+), 423 deletions(-) delete mode 100644 utils/stream/map_example_test.go delete mode 100644 utils/stream/map_test.go create mode 100644 utils/stream/slices.go diff --git a/utils/stream/map.go b/utils/stream/map.go index a770933..1d28fbf 100644 --- a/utils/stream/map.go +++ b/utils/stream/map.go @@ -1,185 +1,88 @@ package stream import ( - "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hash" - "reflect" ) -// WithMap 使用传入的 map 执行链式操作 -// - 该函数将会直接影响到传入的 map -func WithMap[K comparable, V any](m map[K]V) Map[K, V] { - return m -} - -// WithMapCopy 使用传入的 map 执行链式操作 -// - 该函数不会影响到传入的 map -func WithMapCopy[K comparable, V any](m map[K]V) Map[K, V] { - return hash.Copy(m) -} - -// Map 提供了 map 的链式操作 type Map[K comparable, V any] map[K]V -// Set 设置一个值 -func (slf Map[K, V]) Set(key K, value V) Map[K, V] { - slf[key] = value +// Map 返回映射 +func (slf Map[K, V]) Map() map[K]V { return slf } -// Delete 删除一个值 -func (slf Map[K, V]) Delete(key K) Map[K, V] { - delete(slf, key) +// Keys 返回键列表 +func (slf Map[K, V]) Keys() Slice[K] { + var res = make([]K, 0, len(slf)) + for key := range slf { + res = append(res, key) + } + return res +} + +// Values 返回值列表 +func (slf Map[K, V]) Values() Slice[V] { + var res = make([]V, 0, len(slf)) + for _, value := range slf { + res = append(res, value) + } + return res +} + +// Filter 是 hash.Filter 的快捷方式 +func (slf Map[K, V]) Filter(reserve bool, expression func(key K, value V) bool) Map[K, V] { + return hash.Filter(reserve, slf, expression) +} + +// FilterT 是 hash.FilterT 的快捷方式 +func (slf Map[K, V]) FilterT(expression func(key K, value V) bool) Map[K, V] { + return hash.FilterT(slf, expression) +} + +// FilterF 是 hash.FilterF 的快捷方式 +func (slf Map[K, V]) FilterF(expression func(key K, value V) bool) Map[K, V] { + return hash.FilterF(slf, expression) +} + +// FilterCopy 是 hash.FilterCopy 的快捷方式 +func (slf Map[K, V]) FilterCopy(reserve bool, expression func(key K, value V) bool) Map[K, V] { + return hash.FilterCopy(reserve, slf, expression) +} + +// FilterTCopy 是 hash.FilterTCopy 的快捷方式 +func (slf Map[K, V]) FilterTCopy(expression func(key K, value V) bool) Map[K, V] { + return hash.FilterTCopy(slf, expression) +} + +// FilterFCopy 是 hash.FilterFCopy 的快捷方式 +func (slf Map[K, V]) FilterFCopy(expression func(key K, value V) bool) Map[K, V] { + return hash.FilterFCopy(slf, expression) +} + +// Chunk 是 hash.Chunk 的快捷方式 +func (slf Map[K, V]) Chunk(size int) Slice[Map[K, V]] { + chunks := hash.Chunk(slf, size) + var res = make([]Map[K, V], 0, len(chunks)) + for i := range chunks { + res = append(res, chunks[i]) + } + return res +} + +// Each 是 hash.Each 的快捷方式 +func (slf Map[K, V]) Each(abort bool, iterator func(i int, key K, item V) bool) Map[K, V] { + hash.Each(abort, slf, iterator) return slf } -// Filter 过滤 handle 返回 false 的元素 -func (slf Map[K, V]) Filter(handle func(key K, value V) bool) Map[K, V] { - for k, v := range slf { - if !handle(k, v) { - delete(slf, k) - } - } +// EachT 是 hash.EachT 的快捷方式 +func (slf Map[K, V]) EachT(iterator func(i int, key K, item V) bool) Map[K, V] { + hash.EachT(slf, iterator) return slf } -// FilterKey 过滤特定的 key -func (slf Map[K, V]) FilterKey(keys ...K) Map[K, V] { - for _, key := range keys { - delete(slf, key) - } - return slf -} - -// FilterValue 过滤特定的 value -func (slf Map[K, V]) FilterValue(values ...V) Map[K, V] { - for k, v := range slf { - for _, value := range values { - if reflect.DeepEqual(v, value) { - delete(slf, k) - } - } - } - return slf -} - -// RandomKeep 随机保留 n 个元素 -func (slf Map[K, V]) RandomKeep(n int) Map[K, V] { - length := len(slf) - if n >= length { - return slf - } - for k := range slf { - if n > 0 { - n-- - } else { - delete(slf, k) - } - } - return slf -} - -// RandomDelete 随机删除 n 个元素 -func (slf Map[K, V]) RandomDelete(n int) Map[K, V] { - var count int - for k := range slf { - if count < n { - count++ - delete(slf, k) - } else { - return slf - } - } - return slf -} - -// RandomReplace 将 values 覆盖到当前的 map 中 -// - 如果 values 的长度大于当前 map 的长度,则只会覆盖当前 map 的长度 -func (slf Map[K, V]) RandomReplace(values ...V) Map[K, V] { - var record []K - var valuesLen = len(values) - for k := range slf { - record = append(record, k) - if len(record) >= valuesLen { - break - } - } - for i, k := range record { - slf.Set(k, values[i]) - } - return slf -} - -// Distinct 去重,如果 handle 返回 true,则认为是重复的元素 -func (slf Map[K, V]) Distinct(handle func(key K, value V) bool) Map[K, V] { - for k, v := range slf { - if handle(k, v) { - delete(slf, k) - } - } - return slf -} - -// Range 遍历当前 Map, handle 返回 false 则停止遍历 -func (slf Map[K, V]) Range(handle func(key K, value V) bool) Map[K, V] { - for k, v := range slf { - if !handle(k, v) { - break - } - } - return slf -} - -// ValueOr 当 key 不存在时,设置一个默认值 -func (slf Map[K, V]) ValueOr(key K, value V) Map[K, V] { - if _, ok := slf[key]; !ok { - slf[key] = value - } - return slf -} - -// GetValueOr 当 key 不存在时,返回一个默认值 -func (slf Map[K, V]) GetValueOr(key K, value V) V { - if v, ok := slf[key]; ok { - return v - } - return value -} - -// Clear 清空当前 Map -func (slf Map[K, V]) Clear() Map[K, V] { - for k := range slf { - delete(slf, k) - } - return slf -} - -// Merge 合并多个 Map -func (slf Map[K, V]) Merge(maps ...map[K]V) Map[K, V] { - for _, m := range maps { - for k, v := range m { - slf[k] = v - } - } - return slf -} - -// ToSliceStream 将当前 Map stream 转换为 Slice stream -func (slf Map[K, V]) ToSliceStream() Slice[V] { - return hash.ToSlice(slf) -} - -// ToSliceStreamWithKey 将当前 Map stream 转换为 Slice stream,key 为 Slice 的元素 -func (slf Map[K, V]) ToSliceStreamWithKey() Slice[K] { - return hash.KeyToSlice(slf) -} - -// ToSyncMap 将当前 Map 转换为 concurrent.BalanceMap -func (slf Map[K, V]) ToSyncMap() *concurrent.BalanceMap[K, V] { - return concurrent.NewBalanceMap[K, V](concurrent.WithBalanceMapSource(slf)) -} - -// ToMap 将当前 Map 转换为 map -func (slf Map[K, V]) ToMap() map[K]V { +// EachF 是 hash.EachF 的快捷方式 +func (slf Map[K, V]) EachF(iterator func(i int, key K, item V) bool) Map[K, V] { + hash.EachF(slf, iterator) return slf } diff --git a/utils/stream/map_example_test.go b/utils/stream/map_example_test.go deleted file mode 100644 index 1989c21..0000000 --- a/utils/stream/map_example_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package stream_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/stream" -) - -func ExampleWithMap() { - m := stream.WithMap(map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "d"}).Filter(func(key int, value string) bool { - return key > 3 - }) - fmt.Println(len(m)) - - // Output: - // 2 -} - -func ExampleWithMapCopy() { - m := stream.WithMapCopy(map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "d"}).Filter(func(key int, value string) bool { - return key > 3 - }) - fmt.Println(len(m)) - - // Output: - // 2 -} diff --git a/utils/stream/map_test.go b/utils/stream/map_test.go deleted file mode 100644 index 270ca2f..0000000 --- a/utils/stream/map_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package stream_test - -import ( - "github.com/kercylan98/minotaur/utils/stream" - . "github.com/smartystreets/goconvey/convey" - "testing" -) - -func initMap() map[int]int { - return map[int]int{ - 1: 100, - 2: 200, - 3: 300, - } -} - -func TestWithMap(t *testing.T) { - Convey("TestWithMap", t, func() { - var s = initMap() - var m = stream.WithMap(s).RandomDelete(1) - So(m, ShouldNotBeNil) - So(len(s), ShouldEqual, 2) - }) -} - -func TestWithMapCopy(t *testing.T) { - Convey("TestWithMapCopy", t, func() { - var s = initMap() - var m = stream.WithMapCopy(s).RandomDelete(1) - So(m, ShouldNotBeNil) - So(len(s), ShouldEqual, 3) - }) -} - -func TestMap_Set(t *testing.T) { - Convey("TestMap_Set", t, func() { - var m = stream.WithMap(initMap()).Set(4, 400) - So(m[4], ShouldEqual, 400) - }) -} - -func TestMap_Filter(t *testing.T) { - Convey("TestMap_Filter", t, func() { - var m = stream.WithMap(initMap()).Filter(func(key int, value int) bool { - return key == 1 - }) - So(len(m), ShouldEqual, 1) - }) -} - -func TestMap_FilterKey(t *testing.T) { - Convey("TestMap_FilterKey", t, func() { - var m = stream.WithMap(initMap()).FilterKey(1) - So(len(m), ShouldEqual, 2) - }) -} - -func TestMap_FilterValue(t *testing.T) { - Convey("TestMap_FilterValue", t, func() { - var m = stream.WithMap(initMap()).FilterValue(100) - So(len(m), ShouldEqual, 2) - }) -} - -func TestMap_RandomKeep(t *testing.T) { - Convey("TestMap_RandomKeep", t, func() { - var m = stream.WithMap(initMap()).RandomKeep(1) - So(len(m), ShouldEqual, 1) - }) -} - -func TestMap_RandomDelete(t *testing.T) { - Convey("TestMap_RandomDelete", t, func() { - var m = stream.WithMap(initMap()).RandomDelete(1) - So(len(m), ShouldEqual, 2) - }) -} - -func TestMap_Distinct(t *testing.T) { - Convey("TestMap_Distinct", t, func() { - var m = stream.WithMap(map[int]int{ - 1: 100, - 2: 200, - 3: 100, - }).Distinct(func(key int, value int) bool { - return value == 100 - }) - So(len(m), ShouldEqual, 1) - }) -} diff --git a/utils/stream/slice.go b/utils/stream/slice.go index 34d21ba..00fe381 100644 --- a/utils/stream/slice.go +++ b/utils/stream/slice.go @@ -1,152 +1,151 @@ package stream -import ( - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/slice" - "reflect" -) +import "github.com/kercylan98/minotaur/utils/slice" -// WithSlice 创建一个 Slice -// - 该函数不会影响到传入的 slice -func WithSlice[V any](values []V) Slice[V] { - return slice.Copy(values) -} - -// Slice 提供了 slice 的链式操作 type Slice[V any] []V -// Filter 过滤 handle 返回 false 的元素 -func (slf Slice[V]) Filter(handle func(index int, value V) bool) Slice[V] { - var ns = make([]V, 0, len(slf)) - for i, v := range slf { - if handle(i, v) { - ns = append(ns, v) - } - } - return ns +// Slice 返回切片 +func (slf Slice[V]) Slice() []V { + return slf } -// FilterValue 过滤特定的 value -func (slf Slice[V]) FilterValue(values ...V) Slice[V] { - return slf.Filter(func(index int, value V) bool { - for _, v := range values { - if reflect.DeepEqual(v, value) { - return false - } - } - return true - }) +// Chunk 的快捷方式 +func (slf Slice[V]) Chunk(size int) Slices[V] { + chunks := slice.Chunk(slf, size) + result := make(Slices[V], len(chunks)) + for i, chunk := range chunks { + result[i] = chunk + } + return result } -// FilterIndex 过滤特定的 index -func (slf Slice[V]) FilterIndex(indexes ...int) Slice[V] { - return slf.Filter(func(index int, value V) bool { - return !slice.Contains(indexes, index) - }) +// Drop 是 slice.Drop 的快捷方式 +func (slf Slice[V]) Drop(start, n int) Slice[V] { + return slice.Drop(start, n, slf) } -// RandomKeep 随机保留 n 个元素 -func (slf Slice[V]) RandomKeep(n int) Slice[V] { - length := len(slf) - if n >= length { - return slf - } - var hit = make([]int, length) - for i := 0; i < n; i++ { - hit[i] = 1 - } - slice.Shuffle(hit) - var ns = make([]V, 0, n) - for i, v := range slf { - if hit[i] == 1 { - ns = append(ns, v) - } - } - return ns +// DropBy 是 slice.DropBy 的快捷方式 +func (slf Slice[V]) DropBy(fn func(index int, value V) bool) Slice[V] { + return slice.DropBy(slf, fn) } -// RandomDelete 随机删除 n 个元素 -func (slf Slice[V]) RandomDelete(n int) Slice[V] { - length := len(slf) - if n >= length { - return slf[:0] - } - var hit = make([]int, length) - for i := 0; i < n; i++ { - hit[i] = 1 - } - slice.Shuffle(hit) - var ns = make([]V, 0, n) - for i, v := range slf { - if hit[i] == 0 { - ns = append(ns, v) - } - } - return ns +// Each 是 slice.Each 的快捷方式 +func (slf Slice[V]) Each(abort bool, iterator func(index int, item V) bool) Slice[V] { + slice.Each(abort, slf, iterator) + return slf } -// Shuffle 随机打乱 +// EachT 是 slice.EachT 的快捷方式 +func (slf Slice[V]) EachT(iterator func(index int, item V) bool) Slice[V] { + slice.EachT(slf, iterator) + return slf +} + +// EachF 是 slice.EachF 的快捷方式 +func (slf Slice[V]) EachF(iterator func(index int, item V) bool) Slice[V] { + slice.EachF(slf, iterator) + return slf +} + +// EachReverse 是 slice.EachReverse 的快捷方式 +func (slf Slice[V]) EachReverse(abort bool, iterator func(index int, item V) bool) Slice[V] { + slice.EachReverse(abort, slf, iterator) + return slf +} + +// EachReverseT 是 slice.EachReverseT 的快捷方式 +func (slf Slice[V]) EachReverseT(iterator func(index int, item V) bool) Slice[V] { + slice.EachReverseT(slf, iterator) + return slf +} + +// EachReverseF 是 slice.EachReverseF 的快捷方式 +func (slf Slice[V]) EachReverseF(iterator func(index int, item V) bool) Slice[V] { + slice.EachReverseF(slf, iterator) + return slf +} + +// FillBy 是 slice.FillBy 的快捷方式 +func (slf Slice[V]) FillBy(fn func(index int, value V) V) Slice[V] { + return slice.FillBy(slf, fn) +} + +// FillByCopy 是 slice.FillByCopy 的快捷方式 +func (slf Slice[V]) FillByCopy(fn func(index int, value V) V) Slice[V] { + return slice.FillByCopy(slf, fn) +} + +// FillUntil 是 slice.FillUntil 的快捷方式 +func (slf Slice[V]) FillUntil(abort bool, fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntil(abort, slf, fn) +} + +// FillUntilCopy 是 slice.FillUntilCopy 的快捷方式 +func (slf Slice[V]) FillUntilCopy(abort bool, fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntilCopy(abort, slf, fn) +} + +// FillUntilT 是 slice.FillUntilT 的快捷方式 +func (slf Slice[V]) FillUntilT(fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntilT(slf, fn) +} + +// FillUntilF 是 slice.FillUntilF 的快捷方式 +func (slf Slice[V]) FillUntilF(fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntilF(slf, fn) +} + +// FillUntilTCopy 是 slice.FillUntilTCopy 的快捷方式 +func (slf Slice[V]) FillUntilTCopy(fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntilTCopy(slf, fn) +} + +// FillUntilFCopy 是 slice.FillUntilFCopy 的快捷方式 +func (slf Slice[V]) FillUntilFCopy(fn func(index int, value V) (V, bool)) Slice[V] { + return slice.FillUntilFCopy(slf, fn) +} + +// Filter 是 slice.Filter 的快捷方式 +func (slf Slice[V]) Filter(reserve bool, expression func(index int, item V) bool) Slice[V] { + return slice.Filter(reserve, slf, expression) +} + +// FilterT 是 slice.FilterT 的快捷方式 +func (slf Slice[V]) FilterT(expression func(index int, item V) bool) Slice[V] { + return slice.FilterT(slf, expression) +} + +// FilterF 是 slice.FilterF 的快捷方式 +func (slf Slice[V]) FilterF(expression func(index int, item V) bool) Slice[V] { + return slice.FilterF(slf, expression) +} + +// FilterCopy 是 slice.FilterCopy 的快捷方式 +func (slf Slice[V]) FilterCopy(reserve bool, expression func(index int, item V) bool) Slice[V] { + return slice.FilterCopy(reserve, slf, expression) +} + +// FilterTCopy 是 slice.FilterTCopy 的快捷方式 +func (slf Slice[V]) FilterTCopy(expression func(index int, item V) bool) Slice[V] { + return slice.FilterTCopy(slf, expression) +} + +// FilterFCopy 是 slice.FilterFCopy 的快捷方式 +func (slf Slice[V]) FilterFCopy(expression func(index int, item V) bool) Slice[V] { + return slice.FilterFCopy(slf, expression) +} + +// Shuffle 是 slice.Shuffle 的快捷方式 func (slf Slice[V]) Shuffle() Slice[V] { - slice.Shuffle(slf) - return slf + return slice.Shuffle(slf) } -// Reverse 反转 -func (slf Slice[V]) Reverse() Slice[V] { - slice.Reverse(slf) - return slf +// ShuffleCopy 是 slice.ShuffleCopy 的快捷方式 +func (slf Slice[V]) ShuffleCopy() Slice[V] { + return slice.ShuffleCopy(slf) } -// Clear 清空 -func (slf Slice[V]) Clear() Slice[V] { - return slf[:0] -} - -// Distinct 去重,如果 handle 返回 true 则认为是重复元素 -func (slf Slice[V]) Distinct() Slice[V] { - return slice.Distinct(slf) -} - -// Merge 合并 -func (slf Slice[V]) Merge(values ...V) Slice[V] { - return append(slf, values...) -} - -// GetStartPart 获取前 n 个元素 -func (slf Slice[V]) GetStartPart(n int) Slice[V] { - return slf[:n] -} - -// GetEndPart 获取后 n 个元素 -func (slf Slice[V]) GetEndPart(n int) Slice[V] { - return slice.GetEndPart(slf, n) -} - -// GetPart 获取指定区间的元素 -func (slf Slice[V]) GetPart(start, end int) Slice[V] { - return slice.GetPart(slf, start, end) -} - -// ContainsHandle 如果包含指定的元素则执行 handle -func (slf Slice[V]) ContainsHandle(value V, handle func(slice Slice[V]) Slice[V]) Slice[V] { - if slice.ContainsAny(slf, value) { - return handle(slf) - } - return slf -} - -// Set 设置指定位置的元素 -func (slf Slice[V]) Set(index int, value V) Slice[V] { - slf[index] = value - return slf -} - -// Delete 删除指定位置的元素 -func (slf Slice[V]) Delete(index int) Slice[V] { - return append(slf, slf[index+1:]...) -} - -// ToMapStream 将当前的 Slice stream 转换为 Map stream -func (slf Slice[V]) ToMapStream() Map[int, V] { - return hash.ToMap(slf) +// UniqueBy 是 slice.UniqueBy 的快捷方式 +func (slf Slice[V]) UniqueBy(fn func(V) any) Slice[V] { + return slice.UniqueBy(slf, fn) } diff --git a/utils/stream/slice_test.go b/utils/stream/slice_test.go index 1dff0e4..41921c8 100644 --- a/utils/stream/slice_test.go +++ b/utils/stream/slice_test.go @@ -1,18 +1,14 @@ package stream_test import ( - "fmt" "github.com/kercylan98/minotaur/utils/stream" - . "github.com/smartystreets/goconvey/convey" "testing" ) -func TestSlice_Filter(t *testing.T) { - Convey("TestSlice_Filter", t, func() { - d := []int{1, 2, 3, 4, 5} - var s = stream.WithSlice(d).Reverse() - fmt.Println(s) - fmt.Println(d) - So(len(s), ShouldEqual, 2) - }) +func TestStream_Chunk(t *testing.T) { + var s = stream.Slice[int]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + var chunks = s.Chunk(3) + for _, chunk := range chunks { + t.Log(chunk) + } } diff --git a/utils/stream/slices.go b/utils/stream/slices.go new file mode 100644 index 0000000..e0d7ca1 --- /dev/null +++ b/utils/stream/slices.go @@ -0,0 +1,145 @@ +package stream + +import "github.com/kercylan98/minotaur/utils/slice" + +type Slices[V any] []Slice[V] + +// Merge 合并为一个切片 +func (slf Slices[V]) Merge() Slice[V] { + var s = make([]V, 0) + for _, stream := range slf { + s = append(s, stream...) + } + return s +} + +// Drop 的快捷方式 +func (slf Slices[V]) Drop(start, n int) Slices[V] { + return slice.Drop(start, n, slf) +} + +// DropBy 的快捷方式 +func (slf Slices[V]) DropBy(fn func(index int, value Slice[V]) bool) Slices[V] { + return slice.DropBy(slf, fn) +} + +// Each 的快捷方式 +func (slf Slices[V]) Each(abort bool, iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.Each(abort, slf, iterator) + return slf +} + +// EachT 的快捷方式 +func (slf Slices[V]) EachT(iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.EachT(slf, iterator) + return slf +} + +// EachF 的快捷方式 +func (slf Slices[V]) EachF(iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.EachF(slf, iterator) + return slf +} + +// EachReverse 的快捷方式 +func (slf Slices[V]) EachReverse(abort bool, iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.EachReverse(abort, slf, iterator) + return slf +} + +// EachReverseT 的快捷方式 +func (slf Slices[V]) EachReverseT(iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.EachReverseT(slf, iterator) + return slf +} + +// EachReverseF 的快捷方式 +func (slf Slices[V]) EachReverseF(iterator func(index int, item Slice[V]) bool) Slices[V] { + slice.EachReverseF(slf, iterator) + return slf +} + +// FillBy 的快捷方式 +func (slf Slices[V]) FillBy(fn func(index int, value Slice[V]) Slice[V]) Slices[V] { + return slice.FillBy(slf, fn) +} + +// FillByCopy 的快捷方式 +func (slf Slices[V]) FillByCopy(fn func(index int, value Slice[V]) Slice[V]) Slices[V] { + return slice.FillByCopy(slf, fn) +} + +// FillUntil 的快捷方式 +func (slf Slices[V]) FillUntil(abort bool, fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntil(abort, slf, fn) +} + +// FillUntilCopy 的快捷方式 +func (slf Slices[V]) FillUntilCopy(abort bool, fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntilCopy(abort, slf, fn) +} + +// FillUntilT 的快捷方式 +func (slf Slices[V]) FillUntilT(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntilT(slf, fn) +} + +// FillUntilF 的快捷方式 +func (slf Slices[V]) FillUntilF(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntilF(slf, fn) +} + +// FillUntilTCopy 的快捷方式 +func (slf Slices[V]) FillUntilTCopy(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntilTCopy(slf, fn) +} + +// FillUntilFCopy 的快捷方式 +func (slf Slices[V]) FillUntilFCopy(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { + return slice.FillUntilFCopy(slf, fn) +} + +// Filter 的快捷方式 +func (slf Slices[V]) Filter(reserve bool, expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.Filter(reserve, slf, expression) +} + +// FilterT 的快捷方式 +func (slf Slices[V]) FilterT(expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.FilterT(slf, expression) +} + +// FilterF 的快捷方式 +func (slf Slices[V]) FilterF(expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.FilterF(slf, expression) +} + +// FilterCopy 的快捷方式 +func (slf Slices[V]) FilterCopy(reserve bool, expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.FilterCopy(reserve, slf, expression) +} + +// FilterTCopy 的快捷方式 +func (slf Slices[V]) FilterTCopy(expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.FilterTCopy(slf, expression) +} + +// FilterFCopy 的快捷方式 +func (slf Slices[V]) FilterFCopy(expression func(index int, item Slice[V]) bool) Slices[V] { + return slice.FilterFCopy(slf, expression) +} + +// Shuffle 的快捷方式 +func (slf Slices[V]) Shuffle() Slices[V] { + return slice.Shuffle(slf) +} + +// ShuffleCopy 的快捷方式 +func (slf Slices[V]) ShuffleCopy() Slices[V] { + return slice.ShuffleCopy(slf) +} + +// UniqueBy 的快捷方式 +func (slf Slices[V]) UniqueBy(fn func(Slice[V]) any) Slices[V] { + return slice.UniqueBy(slf, fn) +} From 62ef35a518c259142679d171f53060d0cef79d13 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 13:27:25 +0800 Subject: [PATCH 06/13] =?UTF-8?q?feat:=20slice=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20Zoom=20=E5=87=BD=E6=95=B0=EF=BC=8C=20stream=20?= =?UTF-8?q?=E5=8C=85=E6=94=AF=E6=8C=81=20Zoom=20=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/slice/zoom.go | 11 +++++++++++ utils/stream/slice.go | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 utils/slice/zoom.go diff --git a/utils/slice/zoom.go b/utils/slice/zoom.go new file mode 100644 index 0000000..567d3d3 --- /dev/null +++ b/utils/slice/zoom.go @@ -0,0 +1,11 @@ +package slice + +// Zoom 将切片的长度缩放到指定的大小,如果 newSize 小于 slice 的长度,则会截断 slice,如果 newSize 大于 slice 的长度,则会在 slice 的末尾添加零值数据 +func Zoom[V any](newSize int, slice []V) []V { + if newSize < 0 { + newSize = 0 + } + var s = make([]V, newSize) + copy(s, slice) + return s +} diff --git a/utils/stream/slice.go b/utils/stream/slice.go index 00fe381..460e9ea 100644 --- a/utils/stream/slice.go +++ b/utils/stream/slice.go @@ -9,7 +9,17 @@ func (slf Slice[V]) Slice() []V { return slf } -// Chunk 的快捷方式 +// Copy 复制一份切片 +func (slf Slice[V]) Copy() Slice[V] { + return slice.Copy(slf) +} + +// Zoom 是 slice.Zoom 的快捷方式 +func (slf Slice[V]) Zoom(newSize int) Slice[V] { + return slice.Zoom(newSize, slf) +} + +// Chunk 是 slice.Chunk 的快捷方式 func (slf Slice[V]) Chunk(size int) Slices[V] { chunks := slice.Chunk(slf, size) result := make(Slices[V], len(chunks)) From d9b68fc037a5fdf068c9d3f3d42785ccf12a8928 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 13:27:42 +0800 Subject: [PATCH 07/13] =?UTF-8?q?test:=20=E6=96=B0=E5=A2=9E=20stream.Slice?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/stream/slice_test.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/utils/stream/slice_test.go b/utils/stream/slice_test.go index 41921c8..83e5cac 100644 --- a/utils/stream/slice_test.go +++ b/utils/stream/slice_test.go @@ -5,10 +5,30 @@ import ( "testing" ) -func TestStream_Chunk(t *testing.T) { +func TestStream(t *testing.T) { var s = stream.Slice[int]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} - var chunks = s.Chunk(3) - for _, chunk := range chunks { - t.Log(chunk) - } + t.Log(s, s. + Copy(). + Shuffle(). + Filter(true, func(index int, item int) bool { + return item%2 == 0 + }). + Zoom(20). + Each(true, func(index int, item int) bool { + t.Log(index, item) + return false + }). + Chunk(3). + EachT(func(index int, item stream.Slice[int]) bool { + t.Log(item) + return false + }). + Merge(). + FillBy(func(index int, value int) int { + if value == 0 { + return 999 + } + return value + }), + ) } From 5024022366aaa52cfdd36afc5440266baa633021 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 14:44:48 +0800 Subject: [PATCH 08/13] =?UTF-8?q?feat:=20stream.Slice=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20Indexes=20=E5=92=8C=20Map=20=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/stream/slice.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/utils/stream/slice.go b/utils/stream/slice.go index 460e9ea..66e0827 100644 --- a/utils/stream/slice.go +++ b/utils/stream/slice.go @@ -19,6 +19,24 @@ func (slf Slice[V]) Zoom(newSize int) Slice[V] { return slice.Zoom(newSize, slf) } +// Indexes 将切片转换为索引切片 +func (slf Slice[V]) Indexes() Slice[int] { + var s = make([]int, len(slf)) + for i := 0; i < len(s); i++ { + s[i] = i + } + return s +} + +// Map 将切片转为 map +func (slf Slice[V]) Map() Map[int, V] { + var m = make(map[int]V, len(slf)) + for k, v := range slf { + m[k] = v + } + return m +} + // Chunk 是 slice.Chunk 的快捷方式 func (slf Slice[V]) Chunk(size int) Slices[V] { chunks := slice.Chunk(slf, size) From 376ff779e129f2ced628f48e4cffdad507def19d Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 15:20:24 +0800 Subject: [PATCH 09/13] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=20server?= =?UTF-8?q?=20=E5=8C=85=E6=B6=88=E6=81=AF=E6=97=A5=E5=BF=97=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=20server.Conn.Reuse=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=EF=BC=88=E4=B8=8D=E5=90=88=E7=90=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/conn.go | 15 --------------- server/message.go | 34 ++++++++++++++++++---------------- server/server.go | 2 +- server/server_test.go | 8 ++++++++ 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/server/conn.go b/server/conn.go index 3437d83..3f97832 100644 --- a/server/conn.go +++ b/server/conn.go @@ -143,21 +143,6 @@ func (slf *Conn) IsEmpty() bool { return slf.ws == nil && slf.gn == nil && slf.kcp == nil && slf.gw == nil } -// Reuse 重用连接 -// - 重用连接时,会将当前连接的数据复制到新连接中 -// - 通常在于连接断开后,重新连接时使用 -func (slf *Conn) Reuse(conn *Conn) { - slf.Close() - slf.remoteAddr = conn.remoteAddr - slf.ip = conn.ip - slf.ws = conn.ws - slf.gn = conn.gn - slf.kcp = conn.kcp - slf.data = conn.data - slf.packetPool = conn.packetPool - slf.packets = conn.packets -} - // RemoteAddr 获取远程地址 func (slf *Conn) RemoteAddr() net.Addr { return slf.remoteAddr diff --git a/server/message.go b/server/message.go index 3a2f5c2..dbd701e 100644 --- a/server/message.go +++ b/server/message.go @@ -2,9 +2,9 @@ package server import ( "context" - "encoding/json" "fmt" "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/super" "reflect" ) @@ -85,6 +85,22 @@ func (slf *Message) MessageType() MessageType { return slf.t } +// AttrsString 返回消息属性的字符串表示 +func (slf *Message) AttrsString() string { + var attrs = make([]any, 0, len(slf.attrs)) + for _, attr := range slf.attrs { + if tof := reflect.TypeOf(attr); tof.Kind() == reflect.Func { + attrs = append(attrs, tof.String()) + continue + } + attrs = append(attrs, attr) + } + if len(attrs) == 0 { + return "NoneAttr" + } + return string(super.MarshalJSON(attrs)) +} + // String 返回消息的字符串表示 func (slf *Message) String() string { var attrs = make([]any, 0, len(slf.attrs)) @@ -94,22 +110,8 @@ func (slf *Message) String() string { } attrs = append(attrs, attr) } - var s string - switch slf.t { - case MessageTypePacket: - if len(attrs) > 1 { - s = messagePacketVisualization(attrs[1].([]byte)) - } - default: - if len(slf.attrs) == 0 { - s = "NoneAttr" - } else { - raw, _ := json.Marshal(attrs) - s = string(raw) - } - } - return fmt.Sprintf("[%s] %s", slf.t, s) + return fmt.Sprintf("[%s] %s", slf.t, slf.AttrsString()) } // String 返回消息类型的字符串表示 diff --git a/server/server.go b/server/server.go index d682d97..f91e000 100644 --- a/server/server.go +++ b/server/server.go @@ -623,7 +623,7 @@ func (slf *Server) dispatchMessage(msg *Message) { defer func(msg *Message) { if err := recover(); err != nil { stack := string(debug.Stack()) - log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("MessageAttrs", msg.attrs), log.Any("error", err), log.String("stack", stack)) + log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("MessageAttrs", msg.AttrsString()), log.Any("error", err), log.String("stack", stack)) fmt.Println(stack) if e, ok := err.(error); ok { slf.OnMessageErrorEvent(msg, e) diff --git a/server/server_test.go b/server/server_test.go index d7ca8b1..8d0032c 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -21,6 +21,14 @@ func TestNew(t *testing.T) { } return true }) + var current *server.Conn + srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) { + if current != nil { + current.Reuse(conn) + } else { + current = conn + } + }) srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { conn.Write(packet) }) From 483ace2fa9e1d60069fb6dff234505efd0fc4cd6 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 15:54:51 +0800 Subject: [PATCH 10/13] =?UTF-8?q?perf:=20server=20=E5=8C=85=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E5=85=B3=E9=97=AD=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/conn.go | 50 +++++++++++++++++++++++++++---------------- server/event.go | 3 +-- server/gnet.go | 3 ++- server/server.go | 22 +++++++++++++++---- server/server_test.go | 11 +++++----- 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/server/conn.go b/server/conn.go index 3f97832..afa73d7 100644 --- a/server/conn.go +++ b/server/conn.go @@ -127,6 +127,8 @@ type Conn struct { type connection struct { server *Server mutex *sync.Mutex + close sync.Once + closed bool remoteAddr net.Addr ip string ws *websocket.Conn @@ -159,23 +161,36 @@ func (slf *Conn) GetIP() string { return slf.ip } +// IsClosed 是否已经关闭 +func (slf *Conn) IsClosed() bool { + return slf.closed +} + // Close 关闭连接 -func (slf *Conn) Close() { - if slf.ws != nil { - _ = slf.ws.Close() - } else if slf.gn != nil { - _ = slf.gn.Close() - } else if slf.kcp != nil { - _ = slf.kcp.Close() - } - if slf.packetPool != nil { - slf.packetPool.Close() - } - slf.packetPool = nil - if slf.packets != nil { - close(slf.packets) - slf.packets = nil - } +func (slf *Conn) Close(err ...error) { + slf.close.Do(func() { + slf.closed = true + if slf.ws != nil { + _ = slf.ws.Close() + } else if slf.gn != nil { + _ = slf.gn.Close() + } else if slf.kcp != nil { + _ = slf.kcp.Close() + } + if slf.packetPool != nil { + slf.packetPool.Close() + } + slf.packetPool = nil + if slf.packets != nil { + close(slf.packets) + slf.packets = nil + } + if len(err) > 0 { + slf.server.OnConnectionClosedEvent(slf, err[0]) + return + } + slf.server.OnConnectionClosedEvent(slf, nil) + }) } // SetData 设置连接数据,该数据将在连接关闭前始终存在 @@ -261,9 +276,6 @@ func (slf *Conn) writeLoop(wait *sync.WaitGroup) { defer func() { if err := recover(); err != nil { slf.Close() - // TODO: 以下代码是否需要? - // log.Error("WriteLoop", log.Any("Error", err)) - // debug.PrintStack() } }() wait.Done() diff --git a/server/event.go b/server/event.go index 1ae7241..e81e988 100644 --- a/server/event.go +++ b/server/event.go @@ -184,12 +184,11 @@ func (slf *event) RegConnectionClosedEvent(handle ConnectionClosedEventHandle, p func (slf *event) OnConnectionClosedEvent(conn *Conn, err any) { PushSystemMessage(slf.Server, func() { + slf.Server.online.Delete(conn.GetID()) slf.connectionClosedEventHandles.RangeValue(func(index int, value ConnectionClosedEventHandle) bool { value(slf.Server, conn, err) return true }) - conn.Close() - slf.Server.online.Delete(conn.GetID()) }, "ConnectionClosedEvent") } diff --git a/server/gnet.go b/server/gnet.go index f207568..4d7b61f 100644 --- a/server/gnet.go +++ b/server/gnet.go @@ -26,7 +26,8 @@ func (slf *gNet) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) { } func (slf *gNet) OnClosed(c gnet.Conn, err error) (action gnet.Action) { - slf.OnConnectionClosedEvent(c.Context().(*Conn), err) + conn := c.Context().(*Conn) + conn.Close(err) return } diff --git a/server/server.go b/server/server.go index f91e000..f6fb3ac 100644 --- a/server/server.go +++ b/server/server.go @@ -205,14 +205,21 @@ func (slf *Server) Run(addr string) error { go func(conn *Conn) { defer func() { if err := recover(); err != nil { - slf.OnConnectionClosedEvent(conn, err) + e, ok := err.(error) + if !ok { + e = fmt.Errorf("%v", err) + } + conn.Close(e) } }() buf := make([]byte, 4096) - for { + for !conn.IsClosed() { n, err := conn.kcp.Read(buf) if err != nil { + if conn.IsClosed() { + break + } panic(err) } PushPacketMessage(slf, conn, 0, buf[:n]) @@ -292,15 +299,22 @@ func (slf *Server) Run(addr string) error { defer func() { if err := recover(); err != nil { - slf.OnConnectionClosedEvent(conn, err) + e, ok := err.(error) + if !ok { + e = fmt.Errorf("%v", err) + } + conn.Close(e) } }() - for { + for !conn.IsClosed() { if err := ws.SetReadDeadline(super.If(slf.websocketReadDeadline <= 0, times.Zero, time.Now().Add(slf.websocketReadDeadline))); err != nil { panic(err) } messageType, packet, readErr := ws.ReadMessage() if readErr != nil { + if conn.IsClosed() { + break + } panic(readErr) } if len(slf.supportMessageTypes) > 0 && !slf.supportMessageTypes[messageType] { diff --git a/server/server_test.go b/server/server_test.go index 8d0032c..a85dfed 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -21,14 +21,15 @@ func TestNew(t *testing.T) { } return true }) - var current *server.Conn + srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { + fmt.Println("关闭", conn.GetID(), err, "Count", srv.GetOnlineCount()) + }) srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) { - if current != nil { - current.Reuse(conn) - } else { - current = conn + if srv.GetOnlineCount() > 1 { + conn.Close() } }) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { conn.Write(packet) }) From 6c882edb09dcd3d7979da42d951eddb63bc6f240 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 16:47:23 +0800 Subject: [PATCH 11/13] =?UTF-8?q?fix:=20server=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E8=AE=A1=E6=95=B0=E5=A7=8B=E7=BB=88=E4=B8=BA?= =?UTF-8?q?1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index f6fb3ac..d1b9f66 100644 --- a/server/server.go +++ b/server/server.go @@ -592,12 +592,13 @@ func (slf *Server) pushMessage(message *Message) { defer slf.OnShuntChannelCreatedEvent(channelGuid) } if channel != nil { + slf.messageCounter.Add(1) channel <- message return } } + slf.messageCounter.Add(1) slf.messageChannel <- message - } func (slf *Server) low(message *Message, present time.Time, expect time.Duration, messageReplace ...string) { @@ -616,7 +617,6 @@ func (slf *Server) low(message *Message, present time.Time, expect time.Duration // dispatchMessage 消息分发 func (slf *Server) dispatchMessage(msg *Message) { - slf.messageCounter.Add(1) var ( ctx context.Context cancel context.CancelFunc From 19df61b97fc17f5dc7fdcf04d6d23cb72aaa1500 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 16:59:16 +0800 Subject: [PATCH 12/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20server=20?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E7=AD=89=E5=BE=85=E6=B6=88=E6=81=AF=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E8=BF=87=E7=A8=8B=E4=B8=AD=EF=BC=8C=E6=96=B0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=B0=86=E9=98=BB=E5=A1=9E=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index d1b9f66..ebee8bc 100644 --- a/server/server.go +++ b/server/server.go @@ -573,7 +573,7 @@ func (slf *Server) ShuntChannelFreed(channelGuid int64) { // pushMessage 向服务器中写入特定类型的消息,需严格遵守消息属性要求 func (slf *Server) pushMessage(message *Message) { - if slf.messagePool.IsClose() || slf.isShutdown.Load() || !slf.OnMessageExecBeforeEvent(message) { + if slf.messagePool.IsClose() || !slf.OnMessageExecBeforeEvent(message) { slf.messagePool.Release(message) return } From 3c3dc83830e7843ba09fdc3ed2a9ad9d7e099d95 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 8 Sep 2023 20:14:43 +0800 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20survey=20=E5=88=86=E6=9E=90?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87=20GetTim?= =?UTF-8?q?e=20=E5=87=BD=E6=95=B0=E8=8E=B7=E5=8F=96=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/log/survey/record.go | 11 ++++++++++- utils/log/survey/survey.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/utils/log/survey/record.go b/utils/log/survey/record.go index 1f28a59..2cd162b 100644 --- a/utils/log/survey/record.go +++ b/utils/log/survey/record.go @@ -1,6 +1,10 @@ package survey -import "github.com/tidwall/gjson" +import ( + "github.com/kercylan98/minotaur/utils/times" + "github.com/tidwall/gjson" + "time" +) type ( Result = gjson.Result @@ -9,6 +13,11 @@ type ( // R 记录器所记录的一条数据 type R string +// GetTime 获取该记录的时间 +func (slf R) GetTime(layout string) time.Time { + return times.GetTimeFromString(string(slf)[:len(layout)], layout) +} + // Get 获取指定 key 的值 // - 当 key 为嵌套 key 时,使用 . 进行分割,例如:a.b.c // - 更多用法参考:https://github.com/tidwall/gjson diff --git a/utils/log/survey/survey.go b/utils/log/survey/survey.go index af23124..1153b3d 100644 --- a/utils/log/survey/survey.go +++ b/utils/log/survey/survey.go @@ -127,3 +127,18 @@ func Analyze(filePath string, handle func(analyzer *Analyzer, record R)) *Report return newReport(analyzer) } + +// AnalyzeMulti 与 Analyze 类似,但是可以分析多个文件 +func AnalyzeMulti(filePaths []string, handle func(analyzer *Analyzer, record R)) *Report { + analyzer := new(Analyzer) + for _, filePath := range filePaths { + err := file.ReadLineWithParallel(filePath, 1*1024*1024*1024, func(s string) { + handle(analyzer, R(s)) + }) + if err != nil { + panic(err) + } + } + + return newReport(analyzer) +}