Merge branch 'develop'

This commit is contained in:
kercylan98 2024-02-22 10:50:53 +08:00
commit 4421d07cdf
28 changed files with 2388 additions and 152 deletions

2
go.mod
View File

@ -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

73
modular/README.md Normal file
View File

@ -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` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<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()
}
```

10
modular/block.go Normal file
View File

@ -0,0 +1,10 @@
package modular
// Block 标识模块化服务为阻塞进程的服务,当实现了 Service 且实现了 Block 接口时,模块化应用程序会在 Service.OnMount 阶段完成后执行 OnBlock 函数
//
// 该接口适用于 Http 服务、WebSocket 服务等需要阻塞进程的服务。需要注意的是, OnBlock 的执行不能保证按照 Service 的注册顺序执行
type Block interface {
Service
// OnBlock 阻塞进程
OnBlock()
}

15
modular/example/README.md Normal file
View File

@ -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)
暂无介绍...
</details>
***

View File

@ -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` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<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
}
```

View File

@ -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` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<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
***

View File

@ -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` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<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
***

View File

@ -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` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<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()
***

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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")
}

View File

@ -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

View File

@ -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)

View File

@ -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 + ")"
}
}

View File

@ -5,7 +5,7 @@ import (
"math"
)
// Circle 圆形
// Circle 由多个点组成的圆形数据结构
type Circle[V generic.SignedNumber] struct {
Shape[V]
}

View File

@ -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 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞

View File

@ -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 计算线段的截距

View File

@ -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()

View File

@ -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))
}

View File

@ -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

View File

@ -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))
}

View File

@ -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))
}
}

1595
utils/stream/README.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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)
}

View File

@ -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])
}

View File

@ -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)
}
})
}
}

View File

@ -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
}