Merge branch 'develop'
This commit is contained in:
commit
4421d07cdf
2
go.mod
2
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
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# Modular
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||
<details>
|
||||
<summary>展开 / 折叠目录导航</summary>
|
||||
|
||||
|
||||
> 包级函数定义
|
||||
|
||||
|函数名称|描述
|
||||
|:--|:--
|
||||
|[Run](#Run)|运行模块化应用程序
|
||||
|[RegisterServices](#RegisterServices)|注册服务
|
||||
|
||||
|
||||
> 类型定义
|
||||
|
||||
|类型|名称|描述
|
||||
|:--|:--|:--
|
||||
|`INTERFACE`|[Block](#struct_Block)|标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||
|`INTERFACE`|[Service](#struct_Service)|模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
||||
## 详情信息
|
||||
#### func Run()
|
||||
<span id="Run"></span>
|
||||
> 运行模块化应用程序
|
||||
|
||||
***
|
||||
#### func RegisterServices(s ...Service)
|
||||
<span id="RegisterServices"></span>
|
||||
> 注册服务
|
||||
|
||||
***
|
||||
<span id="struct_Block"></span>
|
||||
### Block `INTERFACE`
|
||||
标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||
|
||||
该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行
|
||||
```go
|
||||
type Block interface {
|
||||
Service
|
||||
OnBlock()
|
||||
}
|
||||
```
|
||||
<span id="struct_Service"></span>
|
||||
### Service `INTERFACE`
|
||||
模块化服务接口,所有的服务均需要实现该接口,在服务的生命周期内发生任何错误均应通过 panic 阻止服务继续运行
|
||||
- 生命周期示例: OnInit -> OnPreload -> OnMount
|
||||
|
||||
在 Golang 中,包与包之间互相引用会导致循环依赖,因此在模块化应用程序中,所有的服务均不应该直接引用其他服务。
|
||||
|
||||
服务应该在 OnInit 阶段将不依赖其他服务的内容初始化完成,并且如果服务需要暴露给其他服务调用,那么也应该在 OnInit 阶段完成对外暴露。
|
||||
- 暴露方式可参考 modular/example
|
||||
|
||||
在 OnPreload 阶段,服务应该完成对其依赖服务的依赖注入,最终在 OnMount 阶段完成对服务功能的定义、路由的声明等。
|
||||
```go
|
||||
type Service interface {
|
||||
OnInit()
|
||||
OnPreload()
|
||||
OnMount()
|
||||
}
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
package modular
|
||||
|
||||
// Block 标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
|
||||
//
|
||||
// 该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行
|
||||
type Block interface {
|
||||
Service
|
||||
// OnBlock 阻塞进程
|
||||
OnBlock()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# Main
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
|
@ -0,0 +1,42 @@
|
|||
# Expose
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||
<details>
|
||||
<summary>展开 / 折叠目录导航</summary>
|
||||
|
||||
|
||||
> 类型定义
|
||||
|
||||
|类型|名称|描述
|
||||
|:--|:--|:--
|
||||
|`INTERFACE`|[Attack](#struct_Attack)|暂无描述...
|
||||
|`INTERFACE`|[Login](#struct_Login)|暂无描述...
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
||||
## 详情信息
|
||||
<span id="struct_Attack"></span>
|
||||
### Attack `INTERFACE`
|
||||
|
||||
```go
|
||||
type Attack interface {
|
||||
Name() string
|
||||
}
|
||||
```
|
||||
<span id="struct_Login"></span>
|
||||
### Login `INTERFACE`
|
||||
|
||||
```go
|
||||
type Login interface {
|
||||
Name() string
|
||||
}
|
||||
```
|
|
@ -0,0 +1,54 @@
|
|||
# Attack
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||
<details>
|
||||
<summary>展开 / 折叠目录导航</summary>
|
||||
|
||||
|
||||
> 类型定义
|
||||
|
||||
|类型|名称|描述
|
||||
|:--|:--|:--
|
||||
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
||||
## 详情信息
|
||||
<span id="struct_Service"></span>
|
||||
### Service `STRUCT`
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
Login expose.Login
|
||||
name string
|
||||
}
|
||||
```
|
||||
<span id="struct_Service_OnInit"></span>
|
||||
|
||||
#### func (*Service) OnInit()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnPreload"></span>
|
||||
|
||||
#### func (*Service) OnPreload()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnMount"></span>
|
||||
|
||||
#### func (*Service) OnMount()
|
||||
|
||||
***
|
||||
<span id="struct_Service_Name"></span>
|
||||
|
||||
#### func (*Service) Name() string
|
||||
|
||||
***
|
|
@ -0,0 +1,54 @@
|
|||
# Login
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||
<details>
|
||||
<summary>展开 / 折叠目录导航</summary>
|
||||
|
||||
|
||||
> 类型定义
|
||||
|
||||
|类型|名称|描述
|
||||
|:--|:--|:--
|
||||
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
||||
## 详情信息
|
||||
<span id="struct_Service"></span>
|
||||
### Service `STRUCT`
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
Attack expose.Attack
|
||||
name string
|
||||
}
|
||||
```
|
||||
<span id="struct_Service_OnInit"></span>
|
||||
|
||||
#### func (*Service) OnInit()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnPreload"></span>
|
||||
|
||||
#### func (*Service) OnPreload()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnMount"></span>
|
||||
|
||||
#### func (*Service) OnMount()
|
||||
|
||||
***
|
||||
<span id="struct_Service_Name"></span>
|
||||
|
||||
#### func (*Service) Name() string
|
||||
|
||||
***
|
|
@ -0,0 +1,53 @@
|
|||
# Server
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
|
||||
<details>
|
||||
<summary>展开 / 折叠目录导航</summary>
|
||||
|
||||
|
||||
> 类型定义
|
||||
|
||||
|类型|名称|描述
|
||||
|:--|:--|:--
|
||||
|`STRUCT`|[Service](#struct_Service)|暂无描述...
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
***
|
||||
## 详情信息
|
||||
<span id="struct_Service"></span>
|
||||
### Service `STRUCT`
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
srv *server.Server
|
||||
}
|
||||
```
|
||||
<span id="struct_Service_OnInit"></span>
|
||||
|
||||
#### func (*Service) OnInit()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnPreload"></span>
|
||||
|
||||
#### func (*Service) OnPreload()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnMount"></span>
|
||||
|
||||
#### func (*Service) OnMount()
|
||||
|
||||
***
|
||||
<span id="struct_Service_OnBlock"></span>
|
||||
|
||||
#### func (*Service) OnBlock()
|
||||
|
||||
***
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 + ")"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
// Circle 圆形
|
||||
// Circle 由多个点组成的圆形数据结构
|
||||
type Circle[V generic.SignedNumber] struct {
|
||||
Shape[V]
|
||||
}
|
||||
|
|
|
@ -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 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞
|
||||
|
|
|
@ -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 计算线段的截距
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue