From a2695f4fcf2a266d3fc535d67d05d07259168d2f Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 5 Feb 2024 11:23:12 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=20stream=20?= =?UTF-8?q?=E5=8C=85=E5=AF=B9=E4=BA=8E=20[]string=20=E7=9A=84=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/stream/string.go | 8 +- utils/stream/string_test.go | 6 +- utils/stream/strings.go | 162 +++++++++++++++-------------------- utils/stream/strings_test.go | 90 +++++++++++++++++++ 4 files changed, 166 insertions(+), 100 deletions(-) diff --git a/utils/stream/string.go b/utils/stream/string.go index cc79616..e4446a0 100644 --- a/utils/stream/string.go +++ b/utils/stream/string.go @@ -15,7 +15,7 @@ type String[S ~string] struct { str S } -// Elem 返回字符串 +// Elem 返回原始元素 func (s *String[S]) Elem() S { return s.str } @@ -287,7 +287,7 @@ func (s *String[S]) Update(f func(S) S) *String[S] { } // Split 返回字符串切片 -func (s *String[S]) Split(sep string) Strings[S] { +func (s *String[S]) Split(sep string) *Strings[S] { slice := strings.Split(string(s.str), sep) rep := make([]S, len(slice)) for i, v := range slice { @@ -297,7 +297,7 @@ func (s *String[S]) Split(sep string) Strings[S] { } // SplitN 返回字符串切片 -func (s *String[S]) SplitN(sep string, n int) Strings[S] { +func (s *String[S]) SplitN(sep string, n int) *Strings[S] { slice := strings.SplitN(string(s.str), sep, n) rep := make([]S, len(slice)) for i, v := range slice { @@ -307,7 +307,7 @@ func (s *String[S]) SplitN(sep string, n int) Strings[S] { } // Batched 将字符串按照指定长度分组,最后一组可能小于指定长度 -func (s *String[S]) Batched(size int) Strings[S] { +func (s *String[S]) Batched(size int) *Strings[S] { var str = string(s.str) var result = make([]S, 0, len(str)/size+1) for len(str) >= size { diff --git a/utils/stream/string_test.go b/utils/stream/string_test.go index 331c7ad..9e0fb08 100644 --- a/utils/stream/string_test.go +++ b/utils/stream/string_test.go @@ -767,7 +767,7 @@ func TestString_Split(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := stream.NewString(c.in).Split(c.sep) - for i, v := range got { + for i, v := range got.Elem() { if v != c.want[i] { t.Fatalf("NewString(%s).Split(%s) = %v; want %v", c.in, c.sep, got, c.want) } @@ -791,7 +791,7 @@ func TestString_SplitN(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := stream.NewString(c.in).SplitN(c.sep, c.n) - for i, v := range got { + for i, v := range got.Elem() { if v != c.want[i] { t.Fatalf("NewString(%s).SplitN(%s, %d) = %v; want %v", c.in, c.sep, c.n, got, c.want) } @@ -814,7 +814,7 @@ func TestString_Batched(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := stream.NewString(c.in).Batched(c.size) - for i, v := range got { + for i, v := range got.Elem() { if v != c.want[i] { t.Fatalf("NewString(%s).Batched(%d) = %v; want %v", c.in, c.size, got, c.want) } diff --git a/utils/stream/strings.go b/utils/stream/strings.go index 981e04f..9551d2f 100644 --- a/utils/stream/strings.go +++ b/utils/stream/strings.go @@ -1,123 +1,99 @@ package stream -import ( - "sort" -) +import "strings" // NewStrings 创建字符串切片 -func NewStrings[S ~string](s ...S) Strings[S] { - var slice = make(Strings[S], len(s)) - for i, v := range s { - slice[i] = v - } - return slice +func NewStrings[S ~string](s ...S) *Strings[S] { + return &Strings[S]{s} } // Strings 字符串切片 -type Strings[S ~string] []S +type Strings[S ~string] struct { + s []S +} + +// Elem 返回原始元素 +func (s *Strings[S]) Elem() []S { + return s.s +} // Len 返回切片长度 func (s *Strings[S]) Len() int { - return len(*s) + return len(s.s) } // Append 添加字符串 func (s *Strings[S]) Append(ss ...S) *Strings[S] { - *s = append(*s, NewStrings(ss...)...) + s.s = append(s.s, ss...) return s } -// Clear 清空切片 -func (s *Strings[S]) Clear() *Strings[S] { - *s = make(Strings[S], 0) - return s -} - -// Copy 复制切片 -func (s *Strings[S]) Copy() *Strings[S] { - ss := make(Strings[S], len(*s)) - copy(ss, *s) - return &ss -} - -// Range 返回指定范围的切片 -func (s *Strings[S]) Range(start, end int) *Strings[S] { - *s = (*s)[start:end] - return s -} - -// First 返回第一个元素 -func (s *Strings[S]) First() *String[S] { - return NewString((*s)[0]) -} - -// Last 返回最后一个元素 -func (s *Strings[S]) Last() *String[S] { - return NewString((*s)[len(*s)-1]) -} - -// Index 返回指定的元素 -func (s *Strings[S]) Index(i int) *String[S] { - return NewString((*s)[i]) -} - -// Reverse 反转切片 -func (s *Strings[S]) Reverse() *Strings[S] { - for i, j := 0, len(*s)-1; i < j; i, j = i+1, j-1 { - (*s)[i], (*s)[j] = (*s)[j], (*s)[i] +// Join 连接字符串 +func (s *Strings[S]) Join(sep S) *String[S] { + var cast = make([]string, len(s.s)) + for i, v := range s.s { + cast[i] = string(v) } - return s + return NewString(S(strings.Join(cast, string(sep)))) } -// Desc 降序排序 -func (s *Strings[S]) Desc() *Strings[S] { - sort.Slice(*s, func(i, j int) bool { - return (*s)[i] > (*s)[j] - }) - return s +// Choice 选择字符串 +func (s *Strings[S]) Choice(i int) *String[S] { + return NewString(s.s[i]) } -// Asc 升序排序 -func (s *Strings[S]) Asc() *Strings[S] { - sort.Slice(*s, func(i, j int) bool { - return (*s)[i] < (*s)[j] - }) - return s -} - -// Sort 自定义排序 -func (s *Strings[S]) Sort(f func(int, int) bool) *Strings[S] { - sort.Slice(*s, func(i, j int) bool { - return f(i, j) - }) - return s -} - -// Unique 去重 -func (s *Strings[S]) Unique() *Strings[S] { - m := map[S]struct{}{} - for _, v := range *s { - m[v] = struct{}{} - } - *s = make(Strings[S], 0, len(m)) - for k := range m { - *s = append(*s, k) +// Choices 选择多个字符串 +func (s *Strings[S]) Choices(i ...int) *Strings[S] { + var ss = make([]S, len(i)) + for j, v := range i { + ss[j] = s.s[v] } + return NewStrings(ss...) +} + +// ChoiceInRange 选择范围内的字符串 +func (s *Strings[S]) ChoiceInRange(start, end int) *Strings[S] { + return NewStrings(s.s[start:end]...) +} + +// Remove 移除字符串 +func (s *Strings[S]) Remove(i int) *Strings[S] { + s.s = append(s.s[:i], s.s[i+1:]...) return s } -// Delete 删除指定位置的字符串 -func (s *Strings[S]) Delete(i int) *Strings[S] { - *s = append((*s)[:i], (*s)[i+1:]...) - return s -} - -// Each 遍历切片 -func (s *Strings[S]) Each(f func(int, S) bool) *Strings[S] { - for i, v := range *s { - if !f(i, v) { - break +// Removes 移除多个字符串 +func (s *Strings[S]) Removes(i ...int) *Strings[S] { + var ss = make([]S, 0, len(s.s)-len(i)) + for j, v := range s.s { + for _, i := range i { + if j != i { + ss = append(ss, v) + } } } + s.s = ss return s } + +// RemoveInRange 移除范围内的字符串 +func (s *Strings[S]) RemoveInRange(start, end int) *Strings[S] { + s.s = append(s.s[:start], s.s[end:]...) + return s +} + +// Clear 清空字符串 +func (s *Strings[S]) Clear() *Strings[S] { + s.s = []S{} + return s +} + +// First 第一个字符串 +func (s *Strings[S]) First() *String[S] { + return NewString(s.s[0]) +} + +// Last 最后一个字符串 +func (s *Strings[S]) Last() *String[S] { + return NewString(s.s[len(s.s)-1]) +} diff --git a/utils/stream/strings_test.go b/utils/stream/strings_test.go index 8d4d002..fb9d9d2 100644 --- a/utils/stream/strings_test.go +++ b/utils/stream/strings_test.go @@ -1 +1,91 @@ package stream_test + +import ( + "github.com/kercylan98/minotaur/utils/stream" + "testing" +) + +func TestNewStrings(t *testing.T) { + var cases = []struct { + name string + in []string + want []string + }{ + {name: "empty", in: []string{}, want: []string{}}, + {name: "one", in: []string{"a"}, want: []string{"a"}}, + {name: "two", in: []string{"a", "b"}, want: []string{"a", "b"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...) + if got.Len() != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +func TestStrings_Elem(t *testing.T) { + var cases = []struct { + name string + in []string + want []string + }{ + {name: "empty", in: []string{}, want: []string{}}, + {name: "one", in: []string{"a"}, want: []string{"a"}}, + {name: "two", in: []string{"a", "b"}, want: []string{"a", "b"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...).Elem() + if len(got) != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +func TestStrings_Len(t *testing.T) { + var cases = []struct { + name string + in []string + want int + }{ + {name: "empty", in: []string{}, want: 0}, + {name: "one", in: []string{"a"}, want: 1}, + {name: "two", in: []string{"a", "b"}, want: 2}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...) + if got.Len() != c.want { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +func TestStrings_Append(t *testing.T) { + var cases = []struct { + name string + in []string + append []string + want []string + }{ + {name: "empty", in: []string{}, append: []string{"a"}, want: []string{"a"}}, + {name: "one", in: []string{"a"}, append: []string{"b"}, want: []string{"a", "b"}}, + {name: "two", in: []string{"a", "b"}, append: []string{"c"}, want: []string{"a", "b", "c"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...).Append(c.append...) + if got.Len() != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} From 3549fcca11691299e311928fb79ee15863a276cf Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 5 Feb 2024 12:06:17 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20modular=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20Block=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=BD=93?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8C=96=E6=9C=8D=E5=8A=A1=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=20modular.Service=20=E5=90=8E=E5=8F=AF=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0=20Block=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E8=AF=A5=E6=8E=A5=E5=8F=A3=E5=B0=86=E9=80=82=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E5=85=B7=E6=9C=89=E9=98=BB=E5=A1=9E=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E7=9A=84=E6=9C=8D=E5=8A=A1=EF=BC=8C=E4=BE=8B?= =?UTF-8?q?=E5=A6=82=E7=BD=91=E7=BB=9C=E6=9C=8D=E5=8A=A1=E5=99=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modular/block.go | 10 +++++++ .../service/services/server/server.go | 28 +++++++++++++++++++ modular/example/main.go | 2 ++ modular/modular.go | 16 +++++++++++ 4 files changed, 56 insertions(+) create mode 100644 modular/block.go create mode 100644 modular/example/internal/service/services/server/server.go diff --git a/modular/block.go b/modular/block.go new file mode 100644 index 0000000..06df717 --- /dev/null +++ b/modular/block.go @@ -0,0 +1,10 @@ +package modular + +// Block 标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数 +// +// 该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行 +type Block interface { + Service + // OnBlock 阻塞进程 + OnBlock() +} diff --git a/modular/example/internal/service/services/server/server.go b/modular/example/internal/service/services/server/server.go new file mode 100644 index 0000000..9c080a3 --- /dev/null +++ b/modular/example/internal/service/services/server/server.go @@ -0,0 +1,28 @@ +package server + +import ( + "github.com/kercylan98/minotaur/server" + "time" +) + +type Service struct { + srv *server.Server +} + +func (s *Service) OnInit() { + s.srv = server.New(server.NetworkNone, server.WithLimitLife(time.Second*3)) +} + +func (s *Service) OnPreload() { + +} + +func (s *Service) OnMount() { + +} + +func (s *Service) OnBlock() { + if err := s.srv.RunNone(); err != nil { + panic(err) + } +} diff --git a/modular/example/main.go b/modular/example/main.go index 35d7a1c..3ff1e99 100644 --- a/modular/example/main.go +++ b/modular/example/main.go @@ -4,11 +4,13 @@ import ( "github.com/kercylan98/minotaur/modular" "github.com/kercylan98/minotaur/modular/example/internal/service/services/attack" "github.com/kercylan98/minotaur/modular/example/internal/service/services/login" + "github.com/kercylan98/minotaur/modular/example/internal/service/services/server" ) func main() { modular.RegisterServices( new(attack.Service), + new(server.Service), new(login.Service), ) modular.Run() diff --git a/modular/modular.go b/modular/modular.go index a0d5011..e700368 100644 --- a/modular/modular.go +++ b/modular/modular.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/kercylan98/minotaur/utils/log" "reflect" + "sync" ) var application *modular @@ -56,4 +57,19 @@ func Run() { s := m.services[i] s.instance.OnMount() } + + // OnBlock + var wait = new(sync.WaitGroup) + for i := 0; i < len(m.services); i++ { + s := m.services[i] + if block, ok := s.instance.(Block); ok { + wait.Add(1) + go func(wait *sync.WaitGroup) { + defer wait.Done() + block.OnBlock() + }(wait) + } + } + wait.Wait() + log.Info("all services exited") } From 40acb567a70ff3f577fb6f009ef7ec5faa52de8b Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 5 Feb 2024 12:11:11 +0800 Subject: [PATCH 3/7] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modular/README.md | 73 + modular/example/README.md | 15 + .../example/internal/service/expose/README.md | 42 + .../service/services/attack/README.md | 54 + .../internal/service/services/login/README.md | 54 + .../service/services/server/README.md | 53 + utils/generator/astgo/type.go | 2 +- utils/stream/README.md | 1595 +++++++++++++++++ 8 files changed, 1887 insertions(+), 1 deletion(-) create mode 100644 modular/README.md create mode 100644 modular/example/README.md create mode 100644 modular/example/internal/service/expose/README.md create mode 100644 modular/example/internal/service/services/attack/README.md create mode 100644 modular/example/internal/service/services/login/README.md create mode 100644 modular/example/internal/service/services/server/README.md create mode 100644 utils/stream/README.md diff --git a/modular/README.md b/modular/README.md new file mode 100644 index 0000000..92c6c17 --- /dev/null +++ b/modular/README.md @@ -0,0 +1,73 @@ +# Modular + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Run](#Run)|运行模块化应用程序 +|[RegisterServices](#RegisterServices)|注册服务 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[Block](#struct_Block)|标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数 +|`INTERFACE`|[Service](#struct_Service)|模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行 + +
+ + +*** +## 详情信息 +#### func Run() + +> 运行模块化应用程序 + +*** +#### func RegisterServices(s ...Service) + +> 注册服务 + +*** + +### Block `INTERFACE` +标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数 + +该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行 +```go +type Block interface { + Service + OnBlock() +} +``` + +### Service `INTERFACE` +模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行 + - 生命周期示例: OnInit -> OnPreload -> OnMount + +在 Golang 中,包与包之间互相引用会导致循环依赖,因此在模块化应用程序中,所有的服务均不应该直接引用其他服务。 + +服务应该在 OnInit 阶段将不依赖其他服务的内容初始化完成,并且如果服务需要暴露给其他服务调用,那么也应该在 OnInit 阶段完成对外暴露。 + - 暴露方式可参考 modular/example + +在 OnPreload 阶段,服务应该完成对其依赖服务的依赖注入,最终在 OnMount 阶段完成对服务功能的定义、路由的声明等。 +```go +type Service interface { + OnInit() + OnPreload() + OnMount() +} +``` diff --git a/modular/example/README.md b/modular/example/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/modular/example/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/modular/example/internal/service/expose/README.md b/modular/example/internal/service/expose/README.md new file mode 100644 index 0000000..0536fdd --- /dev/null +++ b/modular/example/internal/service/expose/README.md @@ -0,0 +1,42 @@ +# Expose + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[Attack](#struct_Attack)|暂无描述... +|`INTERFACE`|[Login](#struct_Login)|暂无描述... + +
+ + +*** +## 详情信息 + +### Attack `INTERFACE` + +```go +type Attack interface { + Name() string +} +``` + +### Login `INTERFACE` + +```go +type Login interface { + Name() string +} +``` diff --git a/modular/example/internal/service/services/attack/README.md b/modular/example/internal/service/services/attack/README.md new file mode 100644 index 0000000..a78a2b2 --- /dev/null +++ b/modular/example/internal/service/services/attack/README.md @@ -0,0 +1,54 @@ +# Attack + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Service](#struct_Service)|暂无描述... + +
+ + +*** +## 详情信息 + +### Service `STRUCT` + +```go +type Service struct { + Login expose.Login + name string +} +``` + + +#### func (*Service) OnInit() + +*** + + +#### func (*Service) OnPreload() + +*** + + +#### func (*Service) OnMount() + +*** + + +#### func (*Service) Name() string + +*** diff --git a/modular/example/internal/service/services/login/README.md b/modular/example/internal/service/services/login/README.md new file mode 100644 index 0000000..f00438a --- /dev/null +++ b/modular/example/internal/service/services/login/README.md @@ -0,0 +1,54 @@ +# Login + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Service](#struct_Service)|暂无描述... + +
+ + +*** +## 详情信息 + +### Service `STRUCT` + +```go +type Service struct { + Attack expose.Attack + name string +} +``` + + +#### func (*Service) OnInit() + +*** + + +#### func (*Service) OnPreload() + +*** + + +#### func (*Service) OnMount() + +*** + + +#### func (*Service) Name() string + +*** diff --git a/modular/example/internal/service/services/server/README.md b/modular/example/internal/service/services/server/README.md new file mode 100644 index 0000000..b3dbb5b --- /dev/null +++ b/modular/example/internal/service/services/server/README.md @@ -0,0 +1,53 @@ +# Server + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Service](#struct_Service)|暂无描述... + +
+ + +*** +## 详情信息 + +### Service `STRUCT` + +```go +type Service struct { + srv *server.Server +} +``` + + +#### func (*Service) OnInit() + +*** + + +#### func (*Service) OnPreload() + +*** + + +#### func (*Service) OnMount() + +*** + + +#### func (*Service) OnBlock() + +*** diff --git a/utils/generator/astgo/type.go b/utils/generator/astgo/type.go index c30a7a4..4e4baf9 100644 --- a/utils/generator/astgo/type.go +++ b/utils/generator/astgo/type.go @@ -50,7 +50,7 @@ func newType(expr ast.Expr) *Type { params = append(params, fmt.Sprintf("%s %s", f.Name, f.Type.Sign)) } s = strings.Join(params, ", ") - if brackets { + if brackets && strings.HasSuffix(s, ")") && strings.HasPrefix(s, "(") { s = "(" + s + ")" } } diff --git a/utils/stream/README.md b/utils/stream/README.md new file mode 100644 index 0000000..7c636fd --- /dev/null +++ b/utils/stream/README.md @@ -0,0 +1,1595 @@ +# Stream + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewString](#NewString)|创建字符串流 +|[NewStrings](#NewStrings)|创建字符串切片 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[String](#struct_String)|字符串流 +|`STRUCT`|[Strings](#struct_Strings)|字符串切片 + +
+ + +*** +## 详情信息 +#### func NewString\[S ~string\](s S) *String[S] + +> 创建字符串流 + +
+查看 / 收起单元测试 + + +```go + +func TestNewString(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "hello"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in) + if got.String() != c.want { + t.Fatalf("NewString(%s) = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** +#### func NewStrings\[S ~string\](s ...S) *Strings[S] + +> 创建字符串切片 + +
+查看 / 收起单元测试 + + +```go + +func TestNewStrings(t *testing.T) { + var cases = []struct { + name string + in []string + want []string + }{{name: "empty", in: []string{}, want: []string{}}, {name: "one", in: []string{"a"}, want: []string{"a"}}, {name: "two", in: []string{"a", "b"}, want: []string{"a", "b"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...) + if got.Len() != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + +### String `STRUCT` +字符串流 +```go +type String[S ~string] struct { + str S +} +``` + + +#### func (*String) Elem() S +> 返回原始元素 + +*** + + +#### func (*String) String() string +> 返回字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_String(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "hello"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).String() + if got != c.want { + t.Fatalf("String(%s).String() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Index(i int) *String[S] +> 返回字符串指定位置的字符,当索引超出范围时将会触发 panic + +
+查看 / 收起单元测试 + + +```go + +func TestString_Index(t *testing.T) { + var cases = []struct { + name string + in string + i int + want string + shouldPanic bool + }{{name: "case1", in: "hello", i: 0, want: "h", shouldPanic: false}, {name: "case2", in: "world", i: 2, want: "r", shouldPanic: false}, {name: "case3", in: "world", i: 5, want: "", shouldPanic: true}, {name: "case4", in: "world", i: -1, want: "", shouldPanic: true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); (r != nil) != c.shouldPanic { + t.Fatalf("NewString(%s).Index(%d) should panic", c.in, c.i) + } + }() + got := stream.NewString(c.in).Index(c.i) + if got.String() != c.want { + t.Fatalf("NewString(%s).Index(%d) = %s; want %s", c.in, c.i, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Range(start int, end int) *String[S] +> 返回字符串指定范围的字符 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Range(t *testing.T) { + var cases = []struct { + name string + in string + start int + end int + want string + shouldPanic bool + }{{name: "case1", in: "hello", start: 0, end: 2, want: "he", shouldPanic: false}, {name: "case2", in: "world", start: 2, end: 5, want: "rld", shouldPanic: false}, {name: "case3", in: "world", start: 5, end: 6, want: "", shouldPanic: true}, {name: "case4", in: "world", start: -1, end: 6, want: "", shouldPanic: true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); (r != nil) != c.shouldPanic { + t.Fatalf("NewString(%s).Range(%d, %d) should panic", c.in, c.start, c.end) + } + }() + got := stream.NewString(c.in).Range(c.start, c.end) + if got.String() != c.want { + t.Fatalf("NewString(%s).Range(%d, %d) = %s; want %s", c.in, c.start, c.end, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) TrimSpace() *String[S] +> 返回去除字符串首尾空白字符的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_TrimSpace(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: " hello ", want: "hello"}, {name: "case2", in: " world ", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimSpace() + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimSpace() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Trim(cs string) *String[S] +> 返回去除字符串首尾指定字符的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Trim(t *testing.T) { + var cases = []struct { + name string + in string + cs string + want string + }{{name: "case1", in: "hello", cs: "h", want: "ello"}, {name: "case2", in: "world", cs: "d", want: "worl"}, {name: "none", in: "world", cs: "", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Trim(c.cs) + if got.String() != c.want { + t.Fatalf("NewString(%s).Trim(%s) = %s; want %s", c.in, c.cs, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) TrimPrefix(prefix string) *String[S] +> 返回去除字符串前缀的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_TrimPrefix(t *testing.T) { + var cases = []struct { + name string + in string + prefix string + want string + isEqual bool + }{{name: "case1", in: "hello", prefix: "h", want: "ello", isEqual: false}, {name: "case2", in: "world", prefix: "w", want: "orld", isEqual: false}, {name: "none", in: "world", prefix: "x", want: "world", isEqual: true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimPrefix(c.prefix) + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimPrefix(%s) = %s; want %s", c.in, c.prefix, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) TrimSuffix(suffix string) *String[S] +> 返回去除字符串后缀的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_TrimSuffix(t *testing.T) { + var cases = []struct { + name string + in string + suffix string + want string + isEqual bool + }{{name: "case1", in: "hello", suffix: "o", want: "hell", isEqual: false}, {name: "case2", in: "world", suffix: "d", want: "worl", isEqual: false}, {name: "none", in: "world", suffix: "x", want: "world", isEqual: true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimSuffix(c.suffix) + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimSuffix(%s) = %s; want %s", c.in, c.suffix, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) ToUpper() *String[S] +> 返回字符串的大写形式 + +
+查看 / 收起单元测试 + + +```go + +func TestString_ToUpper(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "HELLO"}, {name: "case2", in: "world", want: "WORLD"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ToUpper() + if got.String() != c.want { + t.Fatalf("NewString(%s).ToUpper() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) ToLower() *String[S] +> 返回字符串的小写形式 + +
+查看 / 收起单元测试 + + +```go + +func TestString_ToLower(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "HELLO", want: "hello"}, {name: "case2", in: "WORLD", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ToLower() + if got.String() != c.want { + t.Fatalf("NewString(%s).ToLower() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Equal(ss S) bool +> 返回字符串是否相等 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Equal(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want bool + }{{name: "case1", in: "hello", ss: "hello", want: true}, {name: "case2", in: "world", ss: "world", want: true}, {name: "case3", in: "world", ss: "worldx", want: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Equal(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Equal(%s) = %t; want %t", c.in, c.ss, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) HasPrefix(prefix S) bool +> 返回字符串是否包含指定前缀 + +
+查看 / 收起单元测试 + + +```go + +func TestString_HasPrefix(t *testing.T) { + var cases = []struct { + name string + in string + prefix string + want bool + }{{name: "case1", in: "hello", prefix: "h", want: true}, {name: "case2", in: "world", prefix: "w", want: true}, {name: "case3", in: "world", prefix: "x", want: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).HasPrefix(c.prefix) + if got != c.want { + t.Fatalf("NewString(%s).HasPrefix(%s) = %t; want %t", c.in, c.prefix, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) HasSuffix(suffix S) bool +> 返回字符串是否包含指定后缀 + +*** + + +#### func (*String) Len() int +> 返回字符串长度 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Len(t *testing.T) { + var cases = []struct { + name string + in string + want int + }{{name: "case1", in: "hello", want: 5}, {name: "case2", in: "world", want: 5}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Len() + if got != c.want { + t.Fatalf("NewString(%s).Len() = %d; want %d", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Contains(sub S) bool +> 返回字符串是否包含指定子串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Contains(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want bool + }{{name: "case1", in: "hello", ss: "he", want: true}, {name: "case2", in: "world", ss: "or", want: true}, {name: "case3", in: "world", ss: "x", want: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Contains(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Contains(%s) = %t; want %t", c.in, c.ss, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Count(sub S) int +> 返回字符串包含指定子串的次数 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Count(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want int + }{{name: "case1", in: "hello", ss: "l", want: 2}, {name: "case2", in: "world", ss: "o", want: 1}, {name: "case3", in: "world", ss: "x", want: 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Count(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Count(%s) = %d; want %d", c.in, c.ss, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Repeat(count int) *String[S] +> 返回重复 count 次的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Repeat(t *testing.T) { + var cases = []struct { + name string + in string + count int + want string + }{{name: "case1", in: "hello", count: 2, want: "hellohello"}, {name: "case2", in: "world", count: 3, want: "worldworldworld"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Repeat(c.count) + if got.String() != c.want { + t.Fatalf("NewString(%s).Repeat(%d) = %s; want %s", c.in, c.count, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Replace(old S, new S, n int) *String[S] +> 返回替换指定子串后的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Replace(t *testing.T) { + var cases = []struct { + name string + in string + old string + new string + want string + }{{name: "case1", in: "hello", old: "l", new: "x", want: "hexxo"}, {name: "case2", in: "world", old: "o", new: "x", want: "wxrld"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Replace(c.old, c.new, -1) + if got.String() != c.want { + t.Fatalf("NewString(%s).Replace(%s, %s) = %s; want %s", c.in, c.old, c.new, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) ReplaceAll(old S, new S) *String[S] +> 返回替换所有指定子串后的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_ReplaceAll(t *testing.T) { + var cases = []struct { + name string + in string + old string + new string + want string + }{{name: "case1", in: "hello", old: "l", new: "x", want: "hexxo"}, {name: "case2", in: "world", old: "o", new: "x", want: "wxrld"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ReplaceAll(c.old, c.new) + if got.String() != c.want { + t.Fatalf("NewString(%s).ReplaceAll(%s, %s) = %s; want %s", c.in, c.old, c.new, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Append(ss S) *String[S] +> 返回追加指定字符串后的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Append(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want string + }{{name: "case1", in: "hello", ss: " world", want: "hello world"}, {name: "case2", in: "world", ss: " hello", want: "world hello"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Append(c.ss) + if got.String() != c.want { + t.Fatalf("NewString(%s).Append(%s) = %s; want %s", c.in, c.ss, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Prepend(ss S) *String[S] +> 返回追加指定字符串后的字符串,追加的字符串在前 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Prepend(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want string + }{{name: "case1", in: "hello", ss: "world ", want: "world hello"}, {name: "case2", in: "world", ss: "hello ", want: "hello world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Prepend(c.ss) + if got.String() != c.want { + t.Fatalf("NewString(%s).Prepend(%s) = %s; want %s", c.in, c.ss, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Clear() *String[S] +> 返回清空字符串后的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Clear(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: ""}, {name: "case2", in: "world", want: ""}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Clear() + if got.String() != c.want { + t.Fatalf("NewString(%s).Clear() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Reverse() *String[S] +> 返回反转字符串后的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Reverse(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "olleh"}, {name: "case2", in: "world", want: "dlrow"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Reverse() + if got.String() != c.want { + t.Fatalf("NewString(%s).Reverse() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Queto() *String[S] +> 返回带引号的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Queto(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "\"hello\""}, {name: "case2", in: "world", want: "\"world\""}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Queto() + if got.String() != c.want { + t.Fatalf("NewString(%s).Queto() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) QuetoToASCII() *String[S] +> 返回带引号的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_QuetoToASCII(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "\"hello\""}, {name: "case2", in: "world", want: "\"world\""}, {name: "case3", in: "你好", want: "\"\\u4f60\\u597d\""}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).QuetoToASCII() + if got.String() != c.want { + t.Fatalf("NewString(%s).QuetoToASCII() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) FirstUpper() *String[S] +> 返回首字母大写的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_FirstUpper(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "Hello"}, {name: "case2", in: "world", want: "World"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).FirstUpper() + if got.String() != c.want { + t.Fatalf("NewString(%s).FirstUpper() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) FirstLower() *String[S] +> 返回首字母小写的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_FirstLower(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "Hello", want: "hello"}, {name: "case2", in: "World", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).FirstLower() + if got.String() != c.want { + t.Fatalf("NewString(%s).FirstLower() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) SnakeCase() *String[S] +> 返回蛇形命名的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_SnakeCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "HelloWorld", want: "hello_world"}, {name: "case2", in: "HelloWorldHello", want: "hello_world_hello"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).SnakeCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).SnakeCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) CamelCase() *String[S] +> 返回驼峰命名的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_CamelCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello_world", want: "helloWorld"}, {name: "case2", in: "hello_world_hello", want: "helloWorldHello"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).CamelCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).CamelCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) KebabCase() *String[S] +> 返回短横线命名的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_KebabCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "HelloWorld", want: "hello-world"}, {name: "case2", in: "HelloWorldHello", want: "hello-world-hello"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).KebabCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).KebabCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) TitleCase() *String[S] +> 返回标题命名的字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_TitleCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello_world", want: "HelloWorld"}, {name: "case2", in: "hello_world_hello", want: "HelloWorldHello"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TitleCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).TitleCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Bytes() []byte +> 返回字符串的字节数组 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Bytes(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "hello"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Bytes() + if string(got) != c.want { + t.Fatalf("NewString(%s).Bytes() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Runes() []rune +> 返回字符串的字符数组 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Runes(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "hello"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Runes() + if string(got) != c.want { + t.Fatalf("NewString(%s).Runes() = %v; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Default(def S) *String[S] +> 当字符串为空时设置默认值 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Default(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "", want: "default"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Default("default") + if got.String() != c.want { + t.Fatalf("NewString(%s).Default() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Handle(f func ( S)) *String[S] +> 处理字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Handle(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "hello"}, {name: "case2", in: "world", want: "world"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var w string + got := stream.NewString(c.in).Handle(func(s string) { + w = s + }) + if w != c.want { + t.Fatalf("NewString(%s).Handle() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Update(f func ( S) S) *String[S] +> 更新字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Update(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "case1", in: "hello", want: "HELLO"}, {name: "case2", in: "world", want: "WORLD"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Update(func(s string) string { + return stream.NewString(s).ToUpper().String() + }) + if got.String() != c.want { + t.Fatalf("NewString(%s).Update() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Split(sep string) *Strings[S] +> 返回字符串切片 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Split(t *testing.T) { + var cases = []struct { + name string + in string + sep string + want []string + }{{name: "case1", in: "hello world", sep: " ", want: []string{"hello", "world"}}, {name: "case2", in: "hello,world", sep: ",", want: []string{"hello", "world"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Split(c.sep) + for i, v := range got.Elem() { + if v != c.want[i] { + t.Fatalf("NewString(%s).Split(%s) = %v; want %v", c.in, c.sep, got, c.want) + } + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) SplitN(sep string, n int) *Strings[S] +> 返回字符串切片 + +
+查看 / 收起单元测试 + + +```go + +func TestString_SplitN(t *testing.T) { + var cases = []struct { + name string + in string + sep string + n int + want []string + }{{name: "case1", in: "hello world", sep: " ", n: 2, want: []string{"hello", "world"}}, {name: "case2", in: "hello,world", sep: ",", n: 2, want: []string{"hello", "world"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).SplitN(c.sep, c.n) + for i, v := range got.Elem() { + if v != c.want[i] { + t.Fatalf("NewString(%s).SplitN(%s, %d) = %v; want %v", c.in, c.sep, c.n, got, c.want) + } + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*String) Batched(size int) *Strings[S] +> 将字符串按照指定长度分组,最后一组可能小于指定长度 + +
+查看 / 收起单元测试 + + +```go + +func TestString_Batched(t *testing.T) { + var cases = []struct { + name string + in string + size int + want []string + }{{name: "case1", in: "hello world", size: 5, want: []string{"hello", " worl", "d"}}, {name: "case2", in: "hello,world", size: 5, want: []string{"hello", ",worl", "d"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Batched(c.size) + for i, v := range got.Elem() { + if v != c.want[i] { + t.Fatalf("NewString(%s).Batched(%d) = %v; want %v", c.in, c.size, got, c.want) + } + } + }) + } +} + +``` + + +
+ + +*** + +### Strings `STRUCT` +字符串切片 +```go +type Strings[S ~string] struct { + s []S +} +``` + + +#### func (*Strings) Elem() []S +> 返回原始元素 + +
+查看 / 收起单元测试 + + +```go + +func TestStrings_Elem(t *testing.T) { + var cases = []struct { + name string + in []string + want []string + }{{name: "empty", in: []string{}, want: []string{}}, {name: "one", in: []string{"a"}, want: []string{"a"}}, {name: "two", in: []string{"a", "b"}, want: []string{"a", "b"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...).Elem() + if len(got) != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Strings) Len() int +> 返回切片长度 + +
+查看 / 收起单元测试 + + +```go + +func TestStrings_Len(t *testing.T) { + var cases = []struct { + name string + in []string + want int + }{{name: "empty", in: []string{}, want: 0}, {name: "one", in: []string{"a"}, want: 1}, {name: "two", in: []string{"a", "b"}, want: 2}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...) + if got.Len() != c.want { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Strings) Append(ss ...S) *Strings[S] +> 添加字符串 + +
+查看 / 收起单元测试 + + +```go + +func TestStrings_Append(t *testing.T) { + var cases = []struct { + name string + in []string + append []string + want []string + }{{name: "empty", in: []string{}, append: []string{"a"}, want: []string{"a"}}, {name: "one", in: []string{"a"}, append: []string{"b"}, want: []string{"a", "b"}}, {name: "two", in: []string{"a", "b"}, append: []string{"c"}, want: []string{"a", "b", "c"}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewStrings(c.in...).Append(c.append...) + if got.Len() != len(c.want) { + t.Errorf("got %v, want %v", got, c.want) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Strings) Join(sep S) *String[S] +> 连接字符串 + +*** + + +#### func (*Strings) Choice(i int) *String[S] +> 选择字符串 + +*** + + +#### func (*Strings) Choices(i ...int) *Strings[S] +> 选择多个字符串 + +*** + + +#### func (*Strings) ChoiceInRange(start int, end int) *Strings[S] +> 选择范围内的字符串 + +*** + + +#### func (*Strings) Remove(i int) *Strings[S] +> 移除字符串 + +*** + + +#### func (*Strings) Removes(i ...int) *Strings[S] +> 移除多个字符串 + +*** + + +#### func (*Strings) RemoveInRange(start int, end int) *Strings[S] +> 移除范围内的字符串 + +*** + + +#### func (*Strings) Clear() *Strings[S] +> 清空字符串 + +*** + + +#### func (*Strings) First() *String[S] +> 第一个字符串 + +*** + + +#### func (*Strings) Last() *String[S] +> 最后一个字符串 + +*** From 7fa0e6863613bbd137be98fa5f4d57345622e0c2 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 20 Feb 2024 09:28:54 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20super=20=E5=8C=85=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20StopWatch=20=E5=92=8C=20StopWatchAndPrintln=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E7=94=A8=E4=BA=8E=E8=BF=BD=E8=B8=AA=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=BF=90=E8=A1=8C=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/loss_counter.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/utils/super/loss_counter.go b/utils/super/loss_counter.go index 8cfc5e5..56aec47 100644 --- a/utils/super/loss_counter.go +++ b/utils/super/loss_counter.go @@ -38,3 +38,17 @@ func (slf *LossCounter) String() string { }) return strings.Join(lines, "\n") } + +// StopWatch 计时器,返回 fn 执行耗时 +func StopWatch(fn func()) time.Duration { + start := time.Now() + fn() + return time.Since(start) +} + +// StopWatchAndPrintln 计时器,返回 fn 执行耗时,并打印耗时 +func StopWatchAndPrintln(name string, fn func()) time.Duration { + loss := StopWatch(fn) + fmt.Println(fmt.Sprintf("%s cost: %s", name, loss.String())) + return loss +} From 6846c9dfc70b8eb6b326529908ef18f29e4a2a30 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 21 Feb 2024 16:58:00 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20geometry=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20SimpleCircle=20=E7=BB=93=E6=9E=84=E4=BD=93=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E8=A1=A8=E7=A4=BA=E4=BB=85=E7=94=B1=E5=9C=86?= =?UTF-8?q?=E5=BF=83=E5=8F=8A=E5=8D=8A=E5=BE=84=E7=BB=84=E6=88=90=E7=9A=84?= =?UTF-8?q?=E5=9C=86=E5=BD=A2=EF=BC=8C=E5=8C=85=E5=90=AB=E6=8A=95=E5=BD=B1?= =?UTF-8?q?=E3=80=81=E8=B7=9D=E7=A6=BB=E7=AD=89=E5=B8=B8=E7=94=A8=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E3=80=82=E4=BC=98=E5=8C=96=20geometry=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E8=AE=A1=E7=AE=97=E5=87=BD=E6=95=B0=EF=BC=8C=E6=89=80?= =?UTF-8?q?=E6=9C=89=E8=AE=A1=E7=AE=97=E5=85=A5=E5=8F=82=E5=9D=87=E4=BC=9A?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E4=B8=BA=20float64=20=E8=BF=90=E7=AE=97?= =?UTF-8?q?=EF=BC=8C=E8=BE=93=E5=87=BA=E6=97=B6=E8=BD=AC=E6=8D=A2=E5=9B=9E?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E7=9A=84=E6=B3=9B=E5=9E=8B=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/geometry/circle.go | 2 +- utils/geometry/geometry.go | 35 +++--- utils/geometry/line.go | 2 +- utils/geometry/position.go | 12 ++ utils/geometry/rectangle.go | 52 ++++---- utils/geometry/shape.go | 7 +- utils/geometry/simple_circle.go | 175 +++++++++++++++++++++++++++ utils/geometry/simple_circle_test.go | 17 +++ 8 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 utils/geometry/simple_circle.go create mode 100644 utils/geometry/simple_circle_test.go diff --git a/utils/geometry/circle.go b/utils/geometry/circle.go index c2d725b..c43ddcd 100644 --- a/utils/geometry/circle.go +++ b/utils/geometry/circle.go @@ -5,7 +5,7 @@ import ( "math" ) -// Circle 圆形 +// Circle 由多个点组成的圆形数据结构 type Circle[V generic.SignedNumber] struct { Shape[V] } diff --git a/utils/geometry/geometry.go b/utils/geometry/geometry.go index 4ab1fb4..9e76228 100644 --- a/utils/geometry/geometry.go +++ b/utils/geometry/geometry.go @@ -32,6 +32,8 @@ func GetOppositionDirection(direction Direction) Direction { return DirectionRight case DirectionRight: return DirectionLeft + case DirectionUnknown: + return DirectionUnknown } return DirectionUnknown } @@ -89,21 +91,14 @@ func GetDirectionNextWithPos[V generic.SignedNumber](direction Direction, width, // CalcDirection 计算点2位于点1的方向 func CalcDirection[V generic.SignedNumber](x1, y1, x2, y2 V) Direction { - var oneEighty = 180 - var fortyFive = 45 - var oneThirtyFive = 135 - var twoTwentyFive = 225 - var threeFifteen = 315 - var end = 360 - var start = 0 - angle := CalcAngle(x1, y1, x2, y2) + V(oneEighty) - if angle > V(oneThirtyFive) && angle <= V(twoTwentyFive) { + angle := CalcAngle(float64(x1), float64(y1), float64(x2), float64(y2)) + 180 + if angle > 45 && angle <= 225 { return DirectionRight - } else if (angle > V(threeFifteen) && angle <= V(end)) || (angle >= V(start) && angle <= V(fortyFive)) { + } else if (angle > 315 && angle <= 360) || (angle >= 0 && angle <= 45) { return DirectionLeft - } else if angle > V(twoTwentyFive) && angle <= V(threeFifteen) { + } else if angle > 225 && angle <= 315 { return DirectionUp - } else if angle > V(fortyFive) && angle <= V(oneThirtyFive) { + } else if angle > 45 && angle <= 134 { return DirectionDown } return DirectionUnknown @@ -141,20 +136,18 @@ func CalcNewCoordinate[V generic.SignedNumber](x, y, angle, distance V) (newX, n // CalcRadianWithAngle 根据角度 angle 计算弧度 func CalcRadianWithAngle[V generic.SignedNumber](angle V) V { - var pi = math.Pi - var dividend = 180.0 - return angle * V(pi) / V(dividend) + return V(float64(angle) * math.Pi / 180) } // CalcAngleDifference 计算两个角度之间的最小角度差 func CalcAngleDifference[V generic.SignedNumber](angleA, angleB V) V { pi := math.Pi - t := angleA - angleB - a := t + V(pi) - b := V(pi) * 2 - t = V(math.Floor(float64(a/b))) * b - t -= V(pi) - return t + t := float64(angleA) - float64(angleB) + a := t + math.Pi + b := math.Pi * 2 + t = math.Floor(a/b) * b + t -= pi + return V(t) } // CalcRayIsIntersect 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞 diff --git a/utils/geometry/line.go b/utils/geometry/line.go index e1283a6..61b2753 100644 --- a/utils/geometry/line.go +++ b/utils/geometry/line.go @@ -172,7 +172,7 @@ func CalcLineSegmentIsIntersect[V generic.SignedNumber](line1, line2 LineSegment // CalcLineSegmentSlope 计算线段的斜率 func CalcLineSegmentSlope[V generic.SignedNumber](line LineSegment[V]) V { - return (line.GetEnd().GetY() - line.GetStart().GetY()) / (line.GetEnd().GetX() - line.GetStart().GetX()) + return V((float64(line.GetEnd().GetY()) - float64(line.GetStart().GetY())) / (float64(line.GetEnd().GetX()) - float64(line.GetStart().GetX()))) } // CalcLineSegmentIntercept 计算线段的截距 diff --git a/utils/geometry/position.go b/utils/geometry/position.go index a82ea64..c50caca 100644 --- a/utils/geometry/position.go +++ b/utils/geometry/position.go @@ -84,6 +84,18 @@ func (slf Point[V]) Abs() Point[V] { return NewPoint(V(math.Abs(float64(slf.GetX()))), V(math.Abs(float64(slf.GetY())))) } +// Distance 返回两个点之间的距离 +func (slf Point[V]) Distance(point Point[V]) float64 { + return math.Sqrt(float64(slf.DistanceSquared(point))) +} + +// DistanceSquared 返回两个点之间的距离的平方 +func (slf Point[V]) DistanceSquared(point Point[V]) V { + x, y := slf.GetXY() + px, py := point.GetXY() + return (x-px)*(x-px) + (y-py)*(y-py) +} + // Max 返回两个位置中每个维度的最大值组成的新的位置 func (slf Point[V]) Max(point Point[V]) Point[V] { x, y := slf.GetXY() diff --git a/utils/geometry/rectangle.go b/utils/geometry/rectangle.go index 0e288fe..c0a13ed 100644 --- a/utils/geometry/rectangle.go +++ b/utils/geometry/rectangle.go @@ -4,19 +4,20 @@ import "github.com/kercylan98/minotaur/utils/generic" // GetAdjacentTranslatePos 获取一个连续位置的矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 func GetAdjacentTranslatePos[T any, P generic.SignedNumber](matrix []T, width, pos P) (result []P) { - size := P(len(matrix)) - currentRow := pos / width - if up := pos - width; up >= 0 { - result = append(result, up) + wf, pf := float64(width), float64(pos) + size := float64(len(matrix)) + currentRow := pf / wf + if up := -wf; up >= 0 { + result = append(result, P(up)) } - if down := pos + width; down < size { - result = append(result, down) + if down := pf + wf; down < size { + result = append(result, P(down)) } - if left := pos - 1; left >= 0 && currentRow == (left/width) { - result = append(result, left) + if left := pf - 1; left >= 0 && currentRow == (left/wf) { + result = append(result, P(left)) } - if right := pos + 1; right < size && currentRow == (right/width) { - result = append(result, right) + if right := pf + 1; right < size && currentRow == (right/wf) { + result = append(result, P(right)) } return } @@ -61,19 +62,20 @@ func GetAdjacentTranslateCoordinateYX[T any, P generic.SignedNumber](matrix [][] // GetAdjacentDiagonalsPos 获取一个连续位置的矩阵中,特定位置相邻的对角线最多四个方向的位置 func GetAdjacentDiagonalsPos[T any, P generic.SignedNumber](matrix []T, width, pos P) (result []P) { - size := P(len(matrix)) - currentRow := pos / width - if topLeft := pos - width - 1; topLeft >= 0 && currentRow-1 == (topLeft/width) { - result = append(result, topLeft) + size := float64(len(matrix)) + wf, pf := float64(width), float64(pos) + currentRow := pf / wf + if topLeft := pf - wf - 1; topLeft >= 0 && currentRow-1 == (topLeft/wf) { + result = append(result, P(topLeft)) } - if topRight := pos - width + 1; topRight >= 0 && currentRow-1 == (topRight/width) { - result = append(result, topRight) + if topRight := pf - wf + 1; topRight >= 0 && currentRow-1 == (topRight/wf) { + result = append(result, P(topRight)) } - if bottomLeft := pos + width - 1; bottomLeft < size && currentRow+1 == (bottomLeft/width) { - result = append(result, bottomLeft) + if bottomLeft := pf + wf - 1; bottomLeft < size && currentRow+1 == (bottomLeft/wf) { + result = append(result, P(bottomLeft)) } - if bottomRight := pos + width + 1; bottomRight < size && currentRow+1 == (bottomRight/width) { - result = append(result, bottomRight) + if bottomRight := pf + wf + 1; bottomRight < size && currentRow+1 == (bottomRight/wf) { + result = append(result, P(bottomRight)) } return } @@ -326,13 +328,13 @@ func GetRectangleFullPos[V generic.SignedNumber](width, height V) (result []V) { // CalcRectangleCentroid 计算矩形质心 // - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心 func CalcRectangleCentroid[V generic.SignedNumber](shape Shape[V]) Point[V] { - x, y := V(0), V(0) - length := V(shape.PointCount()) + var x, y float64 + length := float64(shape.PointCount()) for _, point := range shape.Points() { - x += point.GetX() - y += point.GetY() + x += float64(point.GetX()) + y += float64(point.GetY()) } x /= length y /= length - return NewPoint(x, y) + return NewPoint(V(x), V(x)) } diff --git a/utils/geometry/shape.go b/utils/geometry/shape.go index 97e3271..f88b90a 100644 --- a/utils/geometry/shape.go +++ b/utils/geometry/shape.go @@ -413,7 +413,7 @@ func (slf Shape[V]) getAllGraphicCompositionWithDesc(opt *shapeSearchOptions) (r return } -// CalcBoundingRadius 计算多边形转换为圆的半径 +// CalcBoundingRadius 计算多边形转换为圆的半径,即外接圆的半径 func CalcBoundingRadius[V generic.SignedNumber](shape Shape[V]) V { var boundingRadius V var centroid = CalcRectangleCentroid(shape) @@ -426,6 +426,11 @@ func CalcBoundingRadius[V generic.SignedNumber](shape Shape[V]) V { return boundingRadius } +// CalcBoundingRadiusWithWidthAndHeight 计算多边形转换为圆的半径,即外接圆的半径 +func CalcBoundingRadiusWithWidthAndHeight[V generic.SignedNumber](width, height V) V { + return V(math.Sqrt(float64(width*width+height*height)) / 2) +} + // CalcBoundingRadiusWithCentroid 计算多边形在特定质心下圆的半径 func CalcBoundingRadiusWithCentroid[V generic.SignedNumber](shape Shape[V], centroid Point[V]) V { var boundingRadius V diff --git a/utils/geometry/simple_circle.go b/utils/geometry/simple_circle.go new file mode 100644 index 0000000..9cc93a7 --- /dev/null +++ b/utils/geometry/simple_circle.go @@ -0,0 +1,175 @@ +package geometry + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/random" + "math" +) + +// NewSimpleCircle 通过传入圆的半径和圆心位置,生成一个圆 +func NewSimpleCircle[V generic.SignedNumber](radius V, centroid Point[V]) SimpleCircle[V] { + if radius <= 0 { + panic(fmt.Errorf("radius must be greater than 0, but got %v", radius)) + } + return SimpleCircle[V]{ + centroid: centroid, + radius: radius, + } +} + +// SimpleCircle 仅由位置和半径组成的圆形数据结构 +type SimpleCircle[V generic.SignedNumber] struct { + centroid Point[V] // 圆心位置 + radius V // 半径 +} + +// String 获取圆形的字符串表示 +func (sc SimpleCircle[V]) String() string { + return fmt.Sprintf("SimpleCircle{centroid: %v, %v, radius: %v}", sc.centroid.GetX(), sc.centroid.GetY(), sc.radius) +} + +// Centroid 获取圆形质心位置 +func (sc SimpleCircle[V]) Centroid() Point[V] { + return sc.centroid +} + +// CentroidX 获取圆形质心位置的 X 坐标 +func (sc SimpleCircle[V]) CentroidX() V { + return sc.centroid.GetX() +} + +// CentroidY 获取圆形质心位置的 Y 坐标 +func (sc SimpleCircle[V]) CentroidY() V { + return sc.centroid.GetY() +} + +// CentroidXY 获取圆形质心位置的 X、Y 坐标 +func (sc SimpleCircle[V]) CentroidXY() (V, V) { + return sc.centroid.GetXY() +} + +// PointIsIn 检查特定点是否位于圆内 +func (sc SimpleCircle[V]) PointIsIn(pos Point[V]) bool { + return V(pos.Distance(sc.centroid)) <= sc.radius +} + +// CentroidDistance 计算与另一个圆的质心距离 +func (sc SimpleCircle[V]) CentroidDistance(circle SimpleCircle[V]) float64 { + return sc.centroid.Distance(circle.centroid) +} + +// Radius 获取圆形半径 +func (sc SimpleCircle[V]) Radius() V { + return sc.radius +} + +// ZoomRadius 获取缩放后的半径 +func (sc SimpleCircle[V]) ZoomRadius(zoom float64) V { + return V(float64(sc.radius) * zoom) +} + +// Area 获取圆形面积 +func (sc SimpleCircle[V]) Area() V { + return sc.radius * sc.radius +} + +// Projection 获取圆形投影到另一个圆形的特定比例下的位置和半径 +func (sc SimpleCircle[V]) Projection(circle SimpleCircle[V], ratio float64) SimpleCircle[V] { + // 计算圆心朝目标按比例移动后的位置 + distance := float64(sc.Centroid().Distance(circle.centroid)) + moveDistance := distance * ratio + newX := float64(sc.CentroidX()) + moveDistance*(float64(circle.CentroidX())-float64(sc.CentroidX()))/distance + newY := float64(sc.CentroidY()) + moveDistance*(float64(circle.CentroidY())-float64(sc.CentroidY()))/distance + + return NewSimpleCircle(V(float64(sc.radius)*ratio), NewPoint(V(newX), V(newY))) +} + +// Length 获取圆的周长 +func (sc SimpleCircle[V]) Length() V { + return 2 * sc.radius +} + +// Overlap 与另一个圆是否发生重叠 +func (sc SimpleCircle[V]) Overlap(circle SimpleCircle[V]) bool { + return sc.centroid.Distance(circle.centroid) < float64(sc.radius+circle.radius) +} + +// RandomPoint 获取圆内随机点 +func (sc SimpleCircle[V]) RandomPoint() Point[V] { + rx := V(random.Float64() * float64(sc.radius)) + ry := V(random.Float64() * float64(sc.radius)) + if random.Bool() { + rx = -rx + } + if random.Bool() { + ry = -ry + } + return sc.centroid.GetOffset(rx, ry) +} + +// RandomPointWithinCircle 获取圆内随机点,且该圆在 radius 小于父圆时不会超出父圆 +func (sc SimpleCircle[V]) RandomPointWithinCircle(radius V) Point[V] { + // 生成随机角度 + angle := random.Float64() * 2 * math.Pi + + // 限制坐标随机范围 + var rx, ry float64 + if radius < sc.radius { + r := float64(sc.radius - radius) + rx = random.Float64() * r + ry = random.Float64() * r + } else { + r := float64(sc.radius) + rx = random.Float64() * r + ry = random.Float64() * r + } + + // 生成随机点 + return sc.centroid.GetOffset(V(rx*math.Cos(angle)), V(ry*math.Sin(angle))) +} + +// RandomPointWithinRadius 获取圆内随机点,且该点与圆心的距离小于等于指定半径 +func (sc SimpleCircle[V]) RandomPointWithinRadius(radius V) Point[V] { + if radius > sc.radius { + panic("radius must be less than or equal to the circle radius") + } + rx := V(random.Float64() * float64(radius)) + ry := V(random.Float64() * float64(radius)) + if random.Bool() { + rx = -rx + } + if random.Bool() { + ry = -ry + } + return sc.centroid.GetOffset(rx, ry) +} + +// RandomPointWithinRadiusAndSector 获取圆内随机点,且距离圆心小于等于指定半径,且角度在指定范围内 +// - startAngle: 起始角度,取值范围为 0 到 360 度 +// - endAngle: 结束角度,取值范围为 0 到 360 度,且大于起始角度 +func (sc SimpleCircle[V]) RandomPointWithinRadiusAndSector(radius, startAngle, endAngle V) Point[V] { + var full = 360 + if radius > sc.radius { + panic("radius must be less than or equal to the circle radius") + } + if startAngle < 0 || startAngle > V(full) { + panic("startAngle must be in the range 0 to 360 degrees") + } + if endAngle < 0 || endAngle > V(full) { + panic("endAngle must be in the range 0 to 360 degrees") + } + if startAngle > endAngle { + panic("startAngle must be less than or equal to endAngle") + } + angle := V(random.Float64() * float64(endAngle-startAngle)) + return sc.centroid.GetOffset(radius*V(math.Cos(float64(angle))), radius*V(math.Sin(float64(angle)))) +} + +// RandomCircleWithinParent 根据指定半径,生成一个圆内随机子圆,该圆不会超出父圆 +func (sc SimpleCircle[V]) RandomCircleWithinParent(radius V) SimpleCircle[V] { + if radius > sc.radius { + panic("radius must be less than or equal to the circle radius") + } + return NewSimpleCircle(radius, sc.RandomPointWithinCircle(radius)) +} diff --git a/utils/geometry/simple_circle_test.go b/utils/geometry/simple_circle_test.go new file mode 100644 index 0000000..f8b9657 --- /dev/null +++ b/utils/geometry/simple_circle_test.go @@ -0,0 +1,17 @@ +package geometry_test + +import ( + "github.com/kercylan98/minotaur/utils/geometry" + "testing" +) + +func TestSimpleCircle_RandomSubCircle(t *testing.T) { + for i := 0; i < 10; i++ { + sc := geometry.NewSimpleCircle(10, geometry.NewPoint(0, 0)) + sub := sc.RandomCircleWithinParent(8) + + t.Log(sc) + t.Log(sub) + t.Log(sc.CentroidDistance(sub)) + } +} From b81f972fdadb5fb1e5d13667f558ef4c58788036 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 22 Feb 2024 10:31:59 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20server=20?= =?UTF-8?q?=E5=8C=85=E6=AD=BB=E9=94=81=E6=A3=80=E6=B5=8B=E4=B8=AD=20Messag?= =?UTF-8?q?e=20=E8=AF=BB=E5=86=99=E7=9A=84=E7=AB=9E=E6=80=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/message.go | 6 ++++++ server/server.go | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/message.go b/server/message.go index 05efd04..e4c2028 100644 --- a/server/message.go +++ b/server/message.go @@ -5,6 +5,7 @@ import ( "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/super" + "sync" ) const ( @@ -87,6 +88,7 @@ type Message struct { producer string name string t MessageType + l *sync.RWMutex } // bindDispatcher 绑定分发器 @@ -101,6 +103,10 @@ func (slf *Message) GetProducer() string { // reset 重置消息结构体 func (slf *Message) reset() { + if slf.l != nil { + slf.l.Lock() + defer slf.l.Unlock() + } slf.conn = nil slf.ordinaryHandler = nil slf.exceptionHandler = nil diff --git a/server/server.go b/server/server.go index 2379348..ad6cc94 100644 --- a/server/server.go +++ b/server/server.go @@ -22,6 +22,7 @@ import ( "os" "os/signal" "runtime/debug" + "sync" "sync/atomic" "syscall" "time" @@ -437,13 +438,16 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher.Dispatcher[string, cancel context.CancelFunc ) if srv.deadlockDetect > 0 { + msg.l = new(sync.RWMutex) ctx, cancel = context.WithTimeout(context.Background(), srv.deadlockDetect) go func(ctx context.Context, srv *Server, msg *Message) { select { case <-ctx.Done(): if err := ctx.Err(); errors.Is(err, context.DeadlineExceeded) { - log.Warn("Server", log.String("MessageType", messageNames[msg.t]), log.String("Info", msg.String()), log.Any("SuspectedDeadlock", msg)) + msg.l.RLock() + log.Warn("Server", log.String("SuspectedDeadlock", msg.String())) srv.OnDeadlockDetectEvent(msg) + msg.l.RUnlock() } } }(ctx, srv, msg) From 7333101dc68142b02c28279306b3faa667d27b77 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 22 Feb 2024 10:37:31 +0800 Subject: [PATCH 7/7] =?UTF-8?q?other:=20=E5=8D=87=E7=BA=A7=20go=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=87=B3=201.22.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8533405..001ceaf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kercylan98/minotaur -go 1.21 +go 1.22.0 require ( github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108