From bd7a3fee6b658c509f9e255cdba058b0eac8f209 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 10:37:51 +0800 Subject: [PATCH 01/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/README.md | 12 +- game/fight/README.md | 2 +- game/space/README.md | 4 +- game/task/README.md | 16 +-- notify/README.md | 2 +- notify/notifies/README.md | 28 ++--- notify/senders/README.md | 2 +- planner/pce/README.md | 12 +- planner/pce/cs/README.md | 2 +- planner/pce/tmpls/README.md | 8 +- server/README.md | 64 +++++------ server/client/README.md | 10 +- server/gateway/README.md | 22 ++-- server/internal/dispatcher/README.md | 4 +- server/lockstep/README.md | 10 +- server/router/README.md | 4 +- server/writeloop/README.md | 4 +- utils/aoi/README.md | 2 +- utils/arrangement/README.md | 20 ++-- utils/buffer/README.md | 8 +- utils/collection/README.md | 130 +++++++++++----------- utils/collection/listings/README.md | 10 +- utils/collection/mappings/README.md | 2 +- utils/combination/README.md | 60 +++++----- utils/compress/README.md | 12 +- utils/crypto/README.md | 20 ++-- utils/deck/README.md | 4 +- utils/file/README.md | 20 ++-- utils/fsm/README.md | 12 +- utils/generator/astgo/README.md | 2 +- utils/generator/genreadme/README.md | 6 +- utils/generator/genreadme/builder.go | 16 ++- utils/generator/genreadme/builder_test.go | 4 +- utils/generic/README.md | 6 +- utils/geometry/README.md | 122 ++++++++++---------- utils/geometry/astar/README.md | 2 +- utils/geometry/dp/README.md | 2 +- utils/geometry/matrix/README.md | 2 +- utils/geometry/navmesh/README.md | 2 +- utils/hub/README.md | 2 +- utils/huge/README.md | 8 +- utils/leaderboard/README.md | 6 +- utils/log/README.md | 88 +++++++-------- utils/log/survey/README.md | 10 +- utils/maths/README.md | 40 +++---- utils/memory/README.md | 6 +- utils/moving/README.md | 10 +- utils/offset/README.md | 8 +- utils/random/README.md | 46 ++++---- utils/reflects/README.md | 6 +- utils/runtimes/README.md | 10 +- utils/sole/README.md | 22 ++-- utils/sorts/README.md | 2 +- utils/str/README.md | 42 +++---- utils/super/README.md | 130 +++++++++++----------- utils/timer/README.md | 8 +- utils/times/README.md | 88 +++++++-------- utils/xlsxtool/README.md | 2 +- 58 files changed, 608 insertions(+), 596 deletions(-) diff --git a/game/activity/README.md b/game/activity/README.md index ec9848b4..77dd01e3 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -75,28 +75,28 @@ activity 活动状态管理 > 加载所有活动实体数据 *** -#### func LoadOrRefreshActivity(activityType Type, activityId ID, options ...*Options) error +#### func LoadOrRefreshActivity(activityType Type, activityId ID, options ...*Options) error > 加载或刷新活动 > - 通常在活动配置刷新时候将活动通过该方法注册或刷新 *** -#### func DefineNoneDataActivity(activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] +#### func DefineNoneDataActivity(activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] > 声明无数据的活动类型 *** -#### func DefineGlobalDataActivity(activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] +#### func DefineGlobalDataActivity(activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] > 声明拥有全局数据的活动类型 *** -#### func DefineEntityDataActivity(activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] +#### func DefineEntityDataActivity(activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] > 声明拥有实体数据的活动类型 *** -#### func DefineGlobalAndEntityDataActivity(activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] +#### func DefineGlobalAndEntityDataActivity(activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] > 声明拥有全局数据和实体数据的活动类型 @@ -161,7 +161,7 @@ activity 活动状态管理 > 新的一天事件 *** -#### func NewOptions() *Options +#### func NewOptions() *Options > 创建活动选项 diff --git a/game/fight/README.md b/game/fight/README.md index 2b977fc3..144feeb9 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewTurnBased(calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] +#### func NewTurnBased(calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] > 创建一个新的回合制 > - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间 diff --git a/game/space/README.md b/game/space/README.md index 58e3d322..55dc53c7 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -34,7 +34,7 @@ space 游戏中常见的空间设计,例如房间、地图等 *** ## 详情信息 -#### func NewRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] +#### func NewRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] > 创建房间管理器 RoomManager 的实例 @@ -49,7 +49,7 @@ func ExampleNewRoomManager() { ``` *** -#### func NewRoomControllerOptions() *RoomControllerOptions[EntityID, RoomID, Entity, Room] +#### func NewRoomControllerOptions() *RoomControllerOptions[EntityID, RoomID, Entity, Room] > 创建房间控制器选项 diff --git a/game/task/README.md b/game/task/README.md index 3d5d02fd..9c116b9e 100644 --- a/game/task/README.md +++ b/game/task/README.md @@ -45,7 +45,7 @@ *** ## 详情信息 -#### func Cond(k any, v any) Condition +#### func Cond(k any, v any) Condition > 创建任务条件 @@ -108,19 +108,19 @@ func TestCond(t *testing.T) { > 触发特定任务类型的刷新任务条件事件 *** -#### func WithType(taskType string) Option +#### func WithType(taskType string) Option > 设置任务类型 *** -#### func WithCondition(condition Condition) Option +#### func WithCondition(condition Condition) Option > 设置任务完成条件,当满足条件时,任务状态为完成 > - 任务条件值需要变更时可通过 Task.AssignConditionValueAndRefresh 方法变更 > - 当多次设置该选项时,后面的设置会覆盖之前的设置 *** -#### func WithCounter(counter int64, initCount ...int64) Option +#### func WithCounter(counter int64, initCount ...int64) Option > 设置任务计数器,当计数器达到要求时,任务状态为完成 > - 一些场景下,任务计数器可能会溢出,此时可通过 WithOverflowCounter 设置可溢出的任务计数器 @@ -128,24 +128,24 @@ func TestCond(t *testing.T) { > - 如果需要初始化计数器的值,可通过 initCount 参数设置 *** -#### func WithOverflowCounter(counter int64, initCount ...int64) Option +#### func WithOverflowCounter(counter int64, initCount ...int64) Option > 设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成 > - 当多次设置该选项时,后面的设置会覆盖之前的设置 > - 如果需要初始化计数器的值,可通过 initCount 参数设置 *** -#### func WithDeadline(deadline time.Time) Option +#### func WithDeadline(deadline time.Time) Option > 设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败 *** -#### func WithLimitedDuration(start time.Time, duration time.Duration) Option +#### func WithLimitedDuration(start time.Time, duration time.Duration) Option > 设置任务限时,超过限时时间并且任务未完成时,任务状态为失败 *** -#### func NewTask(options ...Option) *Task +#### func NewTask(options ...Option) *Task > 生成任务 diff --git a/notify/README.md b/notify/README.md index 284040b3..4d1c98e6 100644 --- a/notify/README.md +++ b/notify/README.md @@ -32,7 +32,7 @@ notify 包含了对外部第三方通知的实现,如机器人消息等 *** ## 详情信息 -#### func NewManager(senders ...Sender) *Manager +#### func NewManager(senders ...Sender) *Manager > 通过指定的 Sender 创建一个通知管理器, senders 包中提供了一些内置的 Sender diff --git a/notify/notifies/README.md b/notify/notifies/README.md index 7b99d6e3..21dfa210 100644 --- a/notify/notifies/README.md +++ b/notify/notifies/README.md @@ -45,12 +45,12 @@ notifies 包含了内置通知内容的实现 *** ## 详情信息 -#### func NewFeiShu(message FeiShuMessage) *FeiShu +#### func NewFeiShu(message FeiShuMessage) *FeiShu > 创建飞书通知消息 *** -#### func FeiShuMessageWithText(text string) FeiShuMessage +#### func FeiShuMessageWithText(text string) FeiShuMessage > 飞书文本消息 > - 支持通过换行符进行消息换行 @@ -68,24 +68,24 @@ notifies 包含了内置通知内容的实现 > - 请确保链接是合法的,否则会以原始内容发送消息。 *** -#### func FeiShuMessageWithRichText(richText *FeiShuRichText) FeiShuMessage +#### func FeiShuMessageWithRichText(richText *FeiShuRichText) FeiShuMessage > 飞书富文本消息 *** -#### func FeiShuMessageWithImage(imageKey string) FeiShuMessage +#### func FeiShuMessageWithImage(imageKey string) FeiShuMessage > 飞书图片消息 > - imageKey 可通过上传图片接口获取 *** -#### func FeiShuMessageWithInteractive(json string) FeiShuMessage +#### func FeiShuMessageWithInteractive(json string) FeiShuMessage > 飞书卡片消息 > - json 表示卡片的 json 数据或者消息模板的 json 数据 *** -#### func FeiShuMessageWithShareChat(chatId string) FeiShuMessage +#### func FeiShuMessageWithShareChat(chatId string) FeiShuMessage > 飞书分享群名片 > - chatId 群ID获取方式请参见群ID说明 @@ -93,7 +93,7 @@ notifies 包含了内置通知内容的实现 > 群ID说明:https://open.feishu.cn/document/server-docs/group/chat/chat-id-description *** -#### func FeiShuMessageWithShareUser(userId string) FeiShuMessage +#### func FeiShuMessageWithShareUser(userId string) FeiShuMessage > 飞书分享个人名片 > - userId 表示用户的 OpenID 获取方式请参见了解更多:如何获取 Open ID @@ -101,7 +101,7 @@ notifies 包含了内置通知内容的实现 > 如何获取 Open ID:https://open.feishu.cn/document/faq/trouble-shooting/how-to-obtain-openid *** -#### func FeiShuMessageWithAudio(fileKey string) FeiShuMessage +#### func FeiShuMessageWithAudio(fileKey string) FeiShuMessage > 飞书语音消息 > - fileKey 语音文件Key,可通过上传文件接口获取 @@ -109,7 +109,7 @@ notifies 包含了内置通知内容的实现 > 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create *** -#### func FeiShuMessageWithMedia(fileKey string) FeiShuMessage +#### func FeiShuMessageWithMedia(fileKey string) FeiShuMessage > 飞书视频消息 > - fileKey 视频文件Key,可通过上传文件接口获取 @@ -117,7 +117,7 @@ notifies 包含了内置通知内容的实现 > 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create *** -#### func FeiShuMessageWithMediaAndCover(fileKey string, imageKey string) FeiShuMessage +#### func FeiShuMessageWithMediaAndCover(fileKey string, imageKey string) FeiShuMessage > 飞书带封面的视频消息 > - fileKey 视频文件Key,可通过上传文件接口获取 @@ -126,7 +126,7 @@ notifies 包含了内置通知内容的实现 > 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create *** -#### func FeiShuMessageWithFile(fileKey string) FeiShuMessage +#### func FeiShuMessageWithFile(fileKey string) FeiShuMessage > 飞书文件消息 > - fileKey 文件Key,可通过上传文件接口获取 @@ -134,7 +134,7 @@ notifies 包含了内置通知内容的实现 > 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create *** -#### func FeiShuMessageWithSticker(fileKey string) FeiShuMessage +#### func FeiShuMessageWithSticker(fileKey string) FeiShuMessage > 飞书表情包消息 > - fileKey 表情包文件Key,目前仅支持发送机器人收到的表情包,可通过接收消息事件的推送获取表情包 file_key。 @@ -142,7 +142,7 @@ notifies 包含了内置通知内容的实现 > 接收消息事件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/events/receive *** -#### func NewFeiShuRichText() *FeiShuRichText +#### func NewFeiShuRichText() *FeiShuRichText > 创建一个飞书富文本 @@ -155,7 +155,7 @@ type FeiShu struct { MsgType string } ``` -#### func (*FeiShu) Format() string, error +#### func (*FeiShu) Format() ( string, error) > 格式化通知内容 *** ### FeiShuMessage `STRUCT` diff --git a/notify/senders/README.md b/notify/senders/README.md index a446930e..5038d8bd 100644 --- a/notify/senders/README.md +++ b/notify/senders/README.md @@ -30,7 +30,7 @@ senders Package 包含了内置通知发送器的实现 *** ## 详情信息 -#### func NewFeiShu(webhook string) *FeiShu +#### func NewFeiShu(webhook string) *FeiShu > 根据特定的 webhook 地址创建飞书发送器 diff --git a/planner/pce/README.md b/planner/pce/README.md index dd10bdda..2d07abb8 100644 --- a/planner/pce/README.md +++ b/planner/pce/README.md @@ -69,12 +69,12 @@ *** ## 详情信息 -#### func NewExporter() *Exporter +#### func NewExporter() *Exporter > 创建导出器 *** -#### func GetFieldGolangType(field Field) string +#### func GetFieldGolangType(field Field) string > 获取字段的 Golang 类型 @@ -95,12 +95,12 @@ func TestGetFieldGolangType(t *testing.T) { *** -#### func GetFields() []Field +#### func GetFields() []Field > 获取所有内置支持的字段 *** -#### func NewLoader(fields []Field) *Loader +#### func NewLoader(fields []Field) *Loader > 创建加载器 > - 加载器被用于加载配置表的数据和结构信息 @@ -132,10 +132,10 @@ type DataTmpl interface { ```go type Exporter struct{} ``` -#### func (*Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) []byte, error +#### func (*Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) ( []byte, error) > 导出结构 *** -#### func (*Exporter) ExportData(tmpl DataTmpl, data map[any]any) []byte, error +#### func (*Exporter) ExportData(tmpl DataTmpl, data map[any]any) ( []byte, error) > 导出数据 *** ### Field `INTERFACE` diff --git a/planner/pce/cs/README.md b/planner/pce/cs/README.md index 87e0cded..85c23328 100644 --- a/planner/pce/cs/README.md +++ b/planner/pce/cs/README.md @@ -31,7 +31,7 @@ *** ## 详情信息 -#### func NewXlsx(sheet *xlsx.Sheet, exportType XlsxExportType) *Xlsx +#### func NewXlsx(sheet *xlsx.Sheet, exportType XlsxExportType) *Xlsx *** diff --git a/planner/pce/tmpls/README.md b/planner/pce/tmpls/README.md index e0b1bb76..dab54517 100644 --- a/planner/pce/tmpls/README.md +++ b/planner/pce/tmpls/README.md @@ -32,12 +32,12 @@ *** ## 详情信息 -#### func NewGolang(packageName string) *Golang +#### func NewGolang(packageName string) *Golang > 创建一个 Golang 配置导出模板 *** -#### func NewJSON() *JSON +#### func NewJSON() *JSON *** @@ -49,7 +49,7 @@ type Golang struct { Templates []*pce.TmplStruct } ``` -#### func (*Golang) Render(templates ...*pce.TmplStruct) string, error +#### func (*Golang) Render(templates ...*pce.TmplStruct) ( string, error) *** #### func (*Golang) GetVariable(config *pce.TmplStruct) string *** @@ -64,5 +64,5 @@ type JSON struct { jsonIter.API } ``` -#### func (*JSON) Render(data map[any]any) string, error +#### func (*JSON) Render(data map[any]any) ( string, error) *** diff --git a/server/README.md b/server/README.md index a79cad69..02ca621b 100644 --- a/server/README.md +++ b/server/README.md @@ -80,7 +80,7 @@ server 提供了包含多种网络类型的服务器实现 *** ## 详情信息 -#### func NewBot(srv *Server, options ...BotOption) *Bot +#### func NewBot(srv *Server, options ...BotOption) *Bot > 创建一个机器人,目前仅支持 Socket 服务器 @@ -122,151 +122,151 @@ func TestNewBot(t *testing.T) { *** -#### func WithBotNetworkDelay(delay time.Duration, fluctuation time.Duration) BotOption +#### func WithBotNetworkDelay(delay time.Duration, fluctuation time.Duration) BotOption > 设置机器人网络延迟及波动范围 > - delay 延迟 > - fluctuation 波动范围 *** -#### func WithBotWriter(construction func (bot *Bot) io.Writer) BotOption +#### func WithBotWriter(construction func (bot *Bot) io.Writer) BotOption > 设置机器人写入器,默认为 os.Stdout *** -#### func DefaultWebsocketUpgrader() *websocket.Upgrader +#### func DefaultWebsocketUpgrader() *websocket.Upgrader *** -#### func NewHttpHandleWrapper(srv *Server, packer ContextPacker[Context]) *Http[Context] +#### func NewHttpHandleWrapper(srv *Server, packer ContextPacker[Context]) *Http[Context] > 创建一个新的 http 处理程序包装器 > - 默认使用 server.HttpContext 作为上下文,如果需要依赖其作为新的上下文,可以通过 NewHttpContext 创建 *** -#### func NewHttpContext(ctx *gin.Context) *HttpContext +#### func NewHttpContext(ctx *gin.Context) *HttpContext > 基于 gin.Context 创建一个新的 HttpContext *** -#### func NewGinWrapper(server *gin.Engine, pack func (ctx *gin.Context) CTX) *HttpWrapper[CTX] +#### func NewGinWrapper(server *gin.Engine, pack func (ctx *gin.Context) CTX) *HttpWrapper[CTX] > 创建 gin 包装器,用于对 NewHttpWrapper 函数的替代 *** -#### func HasMessageType(mt MessageType) bool +#### func HasMessageType(mt MessageType) bool > 检查是否存在指定的消息类型 *** -#### func NewMultipleServer(serverHandle ...func () ((addr string, srv *Server))) *MultipleServer +#### func NewMultipleServer(serverHandle ...func () ((addr string, srv *Server))) *MultipleServer *** -#### func GetNetworks() []Network +#### func GetNetworks() []Network > 获取所有支持的网络模式 *** -#### func WithLowMessageDuration(duration time.Duration) Option +#### func WithLowMessageDuration(duration time.Duration) Option > 通过指定慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 > - 默认值为 DefaultLowMessageDuration > - 当 duration <= 0 时,表示关闭慢消息检测 *** -#### func WithAsyncLowMessageDuration(duration time.Duration) Option +#### func WithAsyncLowMessageDuration(duration time.Duration) Option > 通过指定异步消息的慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 > - 默认值为 DefaultAsyncLowMessageDuration > - 当 duration <= 0 时,表示关闭慢消息检测 *** -#### func WithWebsocketConnInitializer(initializer func (writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error) Option +#### func WithWebsocketConnInitializer(initializer func (writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error) Option > 通过 websocket 连接初始化的方式创建服务器,当 initializer 返回错误时,服务器将不会处理该连接的后续逻辑 > - 该选项仅在创建 NetworkWebsocket 服务器时有效 *** -#### func WithWebsocketUpgrade(upgrader *websocket.Upgrader) Option +#### func WithWebsocketUpgrade(upgrader *websocket.Upgrader) Option > 通过指定 websocket.Upgrader 的方式创建服务器 > - 默认值为 DefaultWebsocketUpgrader > - 该选项仅在创建 NetworkWebsocket 服务器时有效 *** -#### func WithConnWriteBufferSize(size int) Option +#### func WithConnWriteBufferSize(size int) Option > 通过连接写入缓冲区大小的方式创建服务器 > - 默认值为 DefaultConnWriteBufferSize > - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存 *** -#### func WithDispatcherBufferSize(size int) Option +#### func WithDispatcherBufferSize(size int) Option > 通过消息分发器缓冲区大小的方式创建服务器 > - 默认值为 DefaultDispatcherBufferSize > - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存 *** -#### func WithMessageStatistics(duration time.Duration, limit int) Option +#### func WithMessageStatistics(duration time.Duration, limit int) Option > 通过消息统计的方式创建服务器 > - 默认不开启,当 duration 和 limit 均大于 0 的时候,服务器将记录每 duration 期间的消息数量,并保留最多 limit 条 *** -#### func WithPacketWarnSize(size int) Option +#### func WithPacketWarnSize(size int) Option > 通过数据包大小警告的方式创建服务器,当数据包大小超过指定大小时,将会输出 WARN 类型的日志 > - 默认值为 DefaultPacketWarnSize > - 当 size <= 0 时,表示不设置警告 *** -#### func WithLimitLife(t time.Duration) Option +#### func WithLimitLife(t time.Duration) Option > 通过限制最大生命周期的方式创建服务器 > - 通常用于测试服务器,服务器将在到达最大生命周期时自动关闭 *** -#### func WithWebsocketWriteCompression() Option +#### func WithWebsocketWriteCompression() Option > 通过数据写入压缩的方式创建Websocket服务器 > - 默认不开启数据压缩 *** -#### func WithWebsocketCompression(level int) Option +#### func WithWebsocketCompression(level int) Option > 通过数据压缩的方式创建Websocket服务器 > - 默认不开启数据压缩 *** -#### func WithDeadlockDetect(t time.Duration) Option +#### func WithDeadlockDetect(t time.Duration) Option > 通过死锁、死循环、永久阻塞检测的方式创建服务器 > - 当检测到死锁、死循环、永久阻塞时,服务器将会生成 WARN 类型的日志,关键字为 "SuspectedDeadlock" > - 默认不开启死锁检测 *** -#### func WithDisableAsyncMessage() Option +#### func WithDisableAsyncMessage() Option > 通过禁用异步消息的方式创建服务器 *** -#### func WithAsyncPoolSize(size int) Option +#### func WithAsyncPoolSize(size int) Option > 通过指定异步消息池大小的方式创建服务器 > - 当通过 WithDisableAsyncMessage 禁用异步消息时,此选项无效 > - 默认值为 DefaultAsyncPoolSize *** -#### func WithWebsocketReadDeadline(t time.Duration) Option +#### func WithWebsocketReadDeadline(t time.Duration) Option > 设置 Websocket 读取超时时间 > - 默认: DefaultWebsocketReadDeadline > - 当 t <= 0 时,表示不设置超时时间 *** -#### func WithTicker(poolSize int, size int, connSize int, autonomy bool) Option +#### func WithTicker(poolSize int, size int, connSize int, autonomy bool) Option > 通过定时器创建服务器,为服务器添加定时器功能 > - poolSize:指定服务器定时器池大小,当池子内的定时器数量超出该值后,多余的定时器在释放时将被回收,该值小于等于 0 时将使用 timer.DefaultTickerPoolSize @@ -275,28 +275,28 @@ func TestNewBot(t *testing.T) { > - autonomy:定时器是否独立运行(独立运行的情况下不会作为服务器消息运行,会导致并发问题) *** -#### func WithTLS(certFile string, keyFile string) Option +#### func WithTLS(certFile string, keyFile string) Option > 通过安全传输层协议TLS创建服务器 > - 支持:Http、Websocket *** -#### func WithGRPCServerOptions(options ...grpc.ServerOption) Option +#### func WithGRPCServerOptions(options ...grpc.ServerOption) Option > 通过GRPC的可选项创建GRPC服务器 *** -#### func WithWebsocketMessageType(messageTypes ...int) Option +#### func WithWebsocketMessageType(messageTypes ...int) Option > 设置仅支持特定类型的Websocket消息 *** -#### func WithPProf(pattern ...string) Option +#### func WithPProf(pattern ...string) Option > 通过性能分析工具PProf创建服务器 *** -#### func New(network Network, options ...Option) *Server +#### func New(network Network, options ...Option) *Server > 根据特定网络类型创建一个服务器 @@ -866,7 +866,7 @@ func ExampleServer_Run() { #### func (*Server) Context() context.Context > 获取服务器上下文 *** -#### func (*Server) TimeoutContext(timeout time.Duration) context.Context, context.CancelFunc +#### func (*Server) TimeoutContext(timeout time.Duration) ( context.Context, context.CancelFunc) > 获取服务器超时上下文,context.WithTimeout 的简写 *** #### func (*Server) Ticker() *timer.Ticker diff --git a/server/client/README.md b/server/client/README.md index ad20045b..ee5bc4cd 100644 --- a/server/client/README.md +++ b/server/client/README.md @@ -40,25 +40,25 @@ *** ## 详情信息 -#### func NewClient(core Core) *Client +#### func NewClient(core Core) *Client > 创建客户端 *** -#### func CloneClient(client *Client) *Client +#### func CloneClient(client *Client) *Client > 克隆客户端 *** -#### func NewTCP(addr string) *Client +#### func NewTCP(addr string) *Client *** -#### func NewUnixDomainSocket(addr string) *Client +#### func NewUnixDomainSocket(addr string) *Client *** -#### func NewWebsocket(addr string) *Client +#### func NewWebsocket(addr string) *Client > 创建 websocket 客户端 diff --git a/server/gateway/README.md b/server/gateway/README.md index 831a22f4..224b2bad 100644 --- a/server/gateway/README.md +++ b/server/gateway/README.md @@ -45,17 +45,17 @@ gateway 是用于处理服务器消息的网关模块,适用于对客户端消 *** ## 详情信息 -#### func NewEndpoint(name string, cli *client.Client, options ...EndpointOption) *Endpoint +#### func NewEndpoint(name string, cli *client.Client, options ...EndpointOption) *Endpoint > 创建网关端点 *** -#### func WithEndpointStateEvaluator(evaluator func (costUnixNano float64) float64) EndpointOption +#### func WithEndpointStateEvaluator(evaluator func (costUnixNano float64) float64) EndpointOption > 设置端点健康值评估函数 *** -#### func WithEndpointConnectionPoolSize(size int) EndpointOption +#### func WithEndpointConnectionPoolSize(size int) EndpointOption > 设置端点连接池大小 > - 默认为 DefaultEndpointConnectionPoolSize @@ -63,25 +63,25 @@ gateway 是用于处理服务器消息的网关模块,适用于对客户端消 > - 在网关服务器中,多个客户端在发送消息到端点服务器时,会共用一个连接,适当的增大连接池大小可以提高网关服务器的承载能力 *** -#### func WithEndpointReconnectInterval(interval time.Duration) EndpointOption +#### func WithEndpointReconnectInterval(interval time.Duration) EndpointOption > 设置端点重连间隔 > - 默认为 DefaultEndpointReconnectInterval > - 端点在连接失败后会在该间隔后重连,如果 <= 0 则不会重连 *** -#### func NewGateway(srv *server.Server, scanner Scanner, options ...Option) *Gateway +#### func NewGateway(srv *server.Server, scanner Scanner, options ...Option) *Gateway > 基于 server.Server 创建 Gateway 网关服务器 *** -#### func WithEndpointSelector(selector EndpointSelector) Option +#### func WithEndpointSelector(selector EndpointSelector) Option > 设置端点选择器 > - 默认情况下,网关会随机选择一个端点作为目标,如果需要自定义端点选择器,可以通过该选项设置 *** -#### func MarshalGatewayOutPacket(addr string, packet []byte) []byte, error +#### func MarshalGatewayOutPacket(addr string, packet []byte) ([]byte, error) > 将数据包转换为网关出网数据包 > - | identifier(4) | ipv4(4) | port(2) | packet | @@ -93,7 +93,7 @@ gateway 是用于处理服务器消息的网关模块,适用于对客户端消 > - | identifier(4) | ipv4(4) | port(2) | packet | *** -#### func MarshalGatewayInPacket(addr string, currentTime int64, packet []byte) []byte, error +#### func MarshalGatewayInPacket(addr string, currentTime int64, packet []byte) ([]byte, error) > 将数据包转换为网关入网数据包 > - | ipv4(4) | port(2) | cost(4) | packet | @@ -218,11 +218,11 @@ func TestGateway_Run(t *testing.T) { #### func (*Gateway) Server() *server.Server > 获取网关服务器核心 *** -#### func (*Gateway) GetEndpoint(name string) *Endpoint, error +#### func (*Gateway) GetEndpoint(name string) ( *Endpoint, error) > 获取一个可用的端点 > - name: 端点名称 *** -#### func (*Gateway) GetConnEndpoint(name string, conn *server.Conn) *Endpoint, error +#### func (*Gateway) GetConnEndpoint(name string, conn *server.Conn) ( *Endpoint, error) > 获取一个可用的端点,如果客户端已经连接到了某个端点,将优先返回该端点 > - 当连接到的端点不可用或没有连接记录时,效果同 GetEndpoint 相同 > - 当连接行为为有状态时,推荐使用该方法 @@ -243,7 +243,7 @@ type Scanner interface { GetInterval() time.Duration } ``` -#### func (*Scanner) GetEndpoints() []*gateway.Endpoint, error +#### func (*Scanner) GetEndpoints() ( []*gateway.Endpoint, error) *** #### func (*Scanner) GetInterval() time.Duration *** diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index ac41cf86..1d3bd6e5 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -36,7 +36,7 @@ *** ## 详情信息 -#### func NewDispatcher(bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] +#### func NewDispatcher(bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] > 创建一个新的消息分发器 Dispatcher 实例 @@ -107,7 +107,7 @@ func TestNewDispatcher(t *testing.T) { *** -#### func NewManager(bufferSize int, handler Handler[P, M]) *Manager[P, M] +#### func NewManager(bufferSize int, handler Handler[P, M]) *Manager[P, M] > 生成消息分发器管理器 diff --git a/server/lockstep/README.md b/server/lockstep/README.md index 5be645f7..f7a5a725 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -37,7 +37,7 @@ *** ## 详情信息 -#### func NewLockstep(options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] +#### func NewLockstep(options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] > 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回 @@ -78,19 +78,19 @@ func TestNewLockstep(t *testing.T) { *** -#### func WithFrameLimit(frameLimit int64) Option[ClientID, Command] +#### func WithFrameLimit(frameLimit int64) Option[ClientID, Command] > 通过特定逻辑帧上限创建锁步(帧)同步组件 > - 当达到上限时将停止广播 *** -#### func WithFrameRate(frameRate int64) Option[ClientID, Command] +#### func WithFrameRate(frameRate int64) Option[ClientID, Command] > 通过特定逻辑帧率创建锁步(帧)同步组件 > - 默认情况下为 15/s *** -#### func WithSerialization(handle func (frame int64, commands []Command) []byte) Option[ClientID, Command] +#### func WithSerialization(handle func (frame int64, commands []Command) []byte) Option[ClientID, Command] > 通过特定的序列化方式将每一帧的数据进行序列化 > @@ -102,7 +102,7 @@ func TestNewLockstep(t *testing.T) { > } *** -#### func WithInitFrame(initFrame int64) Option[ClientID, Command] +#### func WithInitFrame(initFrame int64) Option[ClientID, Command] > 通过特定的初始帧创建锁步(帧)同步组件 > - 默认情况下为 0,即第一帧索引为 0 diff --git a/server/router/README.md b/server/router/README.md index 2c289683..7a470bc2 100644 --- a/server/router/README.md +++ b/server/router/README.md @@ -33,7 +33,7 @@ *** ## 详情信息 -#### func NewMultistage(options ...MultistageOption[HandleFunc]) *Multistage[HandleFunc] +#### func NewMultistage(options ...MultistageOption[HandleFunc]) *Multistage[HandleFunc] > 创建一个支持多级分类的路由器 @@ -47,7 +47,7 @@ func ExampleNewMultistage() { ``` *** -#### func WithRouteTrim(handle func (route any) any) MultistageOption[HandleFunc] +#### func WithRouteTrim(handle func (route any) any) MultistageOption[HandleFunc] > 路由修剪选项 > - 将在路由注册前对路由进行对应处理 diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 73dfc13b..173ca785 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -33,7 +33,7 @@ *** ## 详情信息 -#### func NewChannel(pool *hub.ObjectPool[Message], channelSize int, writeHandler func (message Message) error, errorHandler func (err any)) *Channel[Message] +#### func NewChannel(pool *hub.ObjectPool[Message], channelSize int, writeHandler func (message Message) error, errorHandler func (err any)) *Channel[Message] > 创建基于 Channel 的写循环 > - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Channel 会在写入完成后将 Message 对象放回缓冲池 @@ -44,7 +44,7 @@ > 传入 writeHandler 的消息对象是从 Channel 中获取的,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 *** -#### func NewUnbounded(pool *hub.ObjectPool[Message], writeHandler func (message Message) error, errorHandler func (err any)) *Unbounded[Message] +#### func NewUnbounded(pool *hub.ObjectPool[Message], writeHandler func (message Message) error, errorHandler func (err any)) *Unbounded[Message] > 创建写循环 > - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Unbounded 会在写入完成后将 Message 对象放回缓冲池 diff --git a/utils/aoi/README.md b/utils/aoi/README.md index e2c74291..c03f1f6d 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -37,7 +37,7 @@ AOI 问题是在大规模多人在线游戏中常见的问题,它涉及到确 *** ## 详情信息 -#### func NewTwoDimensional(width int, height int, areaWidth int, areaHeight int) *TwoDimensional[EID, PosType, E] +#### func NewTwoDimensional(width int, height int, areaWidth int, areaHeight int) *TwoDimensional[EID, PosType, E]
diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index 93dcdb3c..8caab46b 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -50,54 +50,54 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 *** ## 详情信息 -#### func WithAreaConstraint(constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaConstraint(constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的约束条件 > - 该约束用于判断一个成员是否可以被添加到该编排区域中 > - 与 WithAreaConflict 不同的是,约束通常用于非成员关系导致的硬性约束,例如:成员的等级过滤、成员的性别等 *** -#### func WithAreaConflict(conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaConflict(conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的冲突条件,冲突处理函数需要返回造成冲突的成员列表 > - 该冲突用于判断一个成员是否可以被添加到该编排区域中 > - 与 WithAreaConstraint 不同的是,冲突通常用于成员关系导致的软性约束,例如:成员的职业唯一性、成员的种族唯一性等 *** -#### func WithAreaEvaluate(evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaEvaluate(evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的评估函数 > - 该评估函数将影响成员被编入区域的优先级 *** -#### func NewArrangement(options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] +#### func NewArrangement(options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] > 创建一个新的编排 *** -#### func WithItemFixed(matcher ItemFixedAreaHandle[AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemFixed(matcher ItemFixedAreaHandle[AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员的固定编排区域 *** -#### func WithItemPriority(priority ItemPriorityHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemPriority(priority ItemPriorityHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员的优先级 *** -#### func WithItemNotAllow(verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemNotAllow(verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员不允许的编排区域 *** -#### func WithRetryThreshold(threshold int) Option[ID, AreaInfo] +#### func WithRetryThreshold(threshold int) Option[ID, AreaInfo] > 设置编排时的重试阈值 > - 当每一轮编排结束任有成员未被编排时,将会进行下一轮编排,直到编排次数达到该阈值 > - 默认的阈值为 10 次 *** -#### func WithConstraintHandle(handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] +#### func WithConstraintHandle(handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] > 设置编排时触发约束时的处理函数 > - 当约束条件触发时,将会调用该函数。如果无法在该函数中处理约束,应该继续返回 err,尝试进行下一层的约束处理 @@ -107,7 +107,7 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 > 有意思的是,硬性约束应该永远是无解的,而当需要进行一些打破规则的操作时,则可以透过该函数传入的 editor 进行操作 *** -#### func WithConflictHandle(handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] +#### func WithConflictHandle(handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] > 设置编排时触发冲突时的处理函数 > - 当冲突条件触发时,将会调用该函数。如果无法在该函数中处理冲突,应该继续返回这一批成员,尝试进行下一层的冲突处理 diff --git a/utils/buffer/README.md b/utils/buffer/README.md index b56d19ee..b0f0d88b 100644 --- a/utils/buffer/README.md +++ b/utils/buffer/README.md @@ -40,7 +40,7 @@ buffer 提供了缓冲区相关的实用程序。 *** ## 详情信息 -#### func NewRing(initSize ...int) *Ring[T] +#### func NewRing(initSize ...int) *Ring[T] > 创建一个并发不安全的环形缓冲区 > - initSize: 初始容量 @@ -68,12 +68,12 @@ func TestNewRing(t *testing.T) { *** -#### func NewRingUnbounded(bufferSize int) *RingUnbounded[T] +#### func NewRingUnbounded(bufferSize int) *RingUnbounded[T] > 创建一个并发安全的基于环形缓冲区实现的无界缓冲区 *** -#### func NewUnbounded() *Unbounded[V] +#### func NewUnbounded() *Unbounded[V] > 创建一个无界缓冲区 > - generateNil: 生成空值的函数,该函数仅需始终返回 nil 即可 @@ -93,7 +93,7 @@ type Ring[T any] struct { w int } ``` -#### func (*Ring) Read() T, error +#### func (*Ring) Read() ( T, error) > 读取数据
查看 / 收起基准测试 diff --git a/utils/collection/README.md b/utils/collection/README.md index 1fa259f5..b9663e92 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -141,7 +141,7 @@ collection 用于对 input 和 map 操作的工具函数 *** ## 详情信息 -#### func CloneSlice(slice S) S +#### func CloneSlice(slice S) S > 克隆切片,该函数是 slices.Clone 的快捷方式 @@ -190,7 +190,7 @@ func TestCloneSlice(t *testing.T) { *** -#### func CloneMap(m M) M +#### func CloneMap(m M) M > 克隆 map @@ -239,7 +239,7 @@ func TestCloneMap(t *testing.T) { *** -#### func CloneSliceN(slice S, n int) []S +#### func CloneSliceN(slice S, n int) []S > 克隆 slice 为 n 个切片进行返回 @@ -294,7 +294,7 @@ func TestCloneSliceN(t *testing.T) { *** -#### func CloneMapN(m M, n int) []M +#### func CloneMapN(m M, n int) []M > 克隆 map 为 n 个 map 进行返回 @@ -349,7 +349,7 @@ func TestCloneMapN(t *testing.T) { *** -#### func CloneSlices(slices ...S) []S +#### func CloneSlices(slices ...S) []S > 克隆多个切片 @@ -401,7 +401,7 @@ func TestCloneSlices(t *testing.T) { *** -#### func CloneMaps(maps ...M) []M +#### func CloneMaps(maps ...M) []M > 克隆多个 map @@ -453,7 +453,7 @@ func TestCloneMaps(t *testing.T) { *** -#### func InSlice(slice S, v V, handler ComparisonHandler[V]) bool +#### func InSlice(slice S, v V, handler ComparisonHandler[V]) bool > 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 @@ -501,7 +501,7 @@ func TestInSlice(t *testing.T) { *** -#### func InComparableSlice(slice S, v V) bool +#### func InComparableSlice(slice S, v V) bool > 检查 v 是否被包含在 slice 中 @@ -545,7 +545,7 @@ func TestInComparableSlice(t *testing.T) { *** -#### func AllInSlice(slice S, values []V, handler ComparisonHandler[V]) bool +#### func AllInSlice(slice S, values []V, handler ComparisonHandler[V]) bool > 检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 > - 在所有 values 中的元素都被包含在 slice 中时,返回 true @@ -595,7 +595,7 @@ func TestAllInSlice(t *testing.T) { *** -#### func AllInComparableSlice(slice S, values []V) bool +#### func AllInComparableSlice(slice S, values []V) bool > 检查 values 中的所有元素是否均被包含在 slice 中 > - 在所有 values 中的元素都被包含在 slice 中时,返回 true @@ -641,7 +641,7 @@ func TestAllInComparableSlice(t *testing.T) { *** -#### func AnyInSlice(slice S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInSlice(slice S, values []V, handler ComparisonHandler[V]) bool > 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true @@ -690,7 +690,7 @@ func TestAnyInSlice(t *testing.T) { *** -#### func AnyInComparableSlice(slice S, values []V) bool +#### func AnyInComparableSlice(slice S, values []V) bool > 检查 values 中的任意一个元素是否被包含在 slice 中 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true @@ -735,7 +735,7 @@ func TestAnyInComparableSlice(t *testing.T) { *** -#### func InSlices(slices []S, v V, handler ComparisonHandler[V]) bool +#### func InSlices(slices []S, v V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true @@ -784,7 +784,7 @@ func TestInSlices(t *testing.T) { *** -#### func InComparableSlices(slices []S, v V) bool +#### func InComparableSlices(slices []S, v V) bool > 通过将多个切片合并后检查 v 是否被包含在 slices 中 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true @@ -829,7 +829,7 @@ func TestInComparableSlices(t *testing.T) { *** -#### func AllInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AllInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true @@ -878,7 +878,7 @@ func TestAllInSlices(t *testing.T) { *** -#### func AllInComparableSlices(slices []S, values []V) bool +#### func AllInComparableSlices(slices []S, values []V) bool > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true @@ -923,7 +923,7 @@ func TestAllInComparableSlices(t *testing.T) { *** -#### func AnyInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true @@ -972,7 +972,7 @@ func TestAnyInSlices(t *testing.T) { *** -#### func AnyInComparableSlices(slices []S, values []V) bool +#### func AnyInComparableSlices(slices []S, values []V) bool > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true @@ -1017,7 +1017,7 @@ func TestAnyInComparableSlices(t *testing.T) { *** -#### func InAllSlices(slices []S, v V, handler ComparisonHandler[V]) bool +#### func InAllSlices(slices []S, v V, handler ComparisonHandler[V]) bool > 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当 v 被包含在 slices 的每一项元素中时,返回 true @@ -1066,7 +1066,7 @@ func TestInAllSlices(t *testing.T) { *** -#### func InAllComparableSlices(slices []S, v V) bool +#### func InAllComparableSlices(slices []S, v V) bool > 检查 v 是否被包含在 slices 的每一项元素中 > - 当 v 被包含在 slices 的每一项元素中时,返回 true @@ -1111,7 +1111,7 @@ func TestInAllComparableSlices(t *testing.T) { *** -#### func AnyInAllSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInAllSlices(slices []S, values []V, handler ComparisonHandler[V]) bool > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true @@ -1160,7 +1160,7 @@ func TestAnyInAllSlices(t *testing.T) { *** -#### func AnyInAllComparableSlices(slices []S, values []V) bool +#### func AnyInAllComparableSlices(slices []S, values []V) bool > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true @@ -1205,7 +1205,7 @@ func TestAnyInAllComparableSlices(t *testing.T) { *** -#### func KeyInMap(m M, key K) bool +#### func KeyInMap(m M, key K) bool > 检查 m 中是否包含特定 key @@ -1249,7 +1249,7 @@ func TestKeyInMap(t *testing.T) { *** -#### func ValueInMap(m M, value V, handler ComparisonHandler[V]) bool +#### func ValueInMap(m M, value V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配 @@ -1296,7 +1296,7 @@ func TestValueInMap(t *testing.T) { *** -#### func AllKeyInMap(m M, keys ...K) bool +#### func AllKeyInMap(m M, keys ...K) bool > 检查 m 中是否包含 keys 中所有的元素 @@ -1340,7 +1340,7 @@ func TestAllKeyInMap(t *testing.T) { *** -#### func AllValueInMap(m M, values []V, handler ComparisonHandler[V]) bool +#### func AllValueInMap(m M, values []V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 @@ -1386,7 +1386,7 @@ func TestAllValueInMap(t *testing.T) { *** -#### func AnyKeyInMap(m M, keys ...K) bool +#### func AnyKeyInMap(m M, keys ...K) bool > 检查 m 中是否包含 keys 中任意一个元素 @@ -1430,7 +1430,7 @@ func TestAnyKeyInMap(t *testing.T) { *** -#### func AnyValueInMap(m M, values []V, handler ComparisonHandler[V]) bool +#### func AnyValueInMap(m M, values []V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 @@ -1476,7 +1476,7 @@ func TestAnyValueInMap(t *testing.T) { *** -#### func AllKeyInMaps(maps []M, keys ...K) bool +#### func AllKeyInMaps(maps []M, keys ...K) bool > 检查 maps 中的每一个元素是否均包含 keys 中所有的元素 @@ -1520,7 +1520,7 @@ func TestAllKeyInMaps(t *testing.T) { *** -#### func AllValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool +#### func AllValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool > 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 @@ -1566,7 +1566,7 @@ func TestAllValueInMaps(t *testing.T) { *** -#### func AnyKeyInMaps(maps []M, keys ...K) bool +#### func AnyKeyInMaps(maps []M, keys ...K) bool > 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中 > - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true @@ -1611,7 +1611,7 @@ func TestAnyKeyInMaps(t *testing.T) { *** -#### func AnyValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool +#### func AnyValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool > 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 > - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true @@ -1658,7 +1658,7 @@ func TestAnyValueInMaps(t *testing.T) { *** -#### func KeyInAllMaps(maps []M, key K) bool +#### func KeyInAllMaps(maps []M, key K) bool > 检查 key 是否被包含在 maps 的每一个元素中 @@ -1702,7 +1702,7 @@ func TestKeyInAllMaps(t *testing.T) { *** -#### func AnyKeyInAllMaps(maps []M, keys []K) bool +#### func AnyKeyInAllMaps(maps []M, keys []K) bool > 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 > - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true @@ -1747,7 +1747,7 @@ func TestAnyKeyInAllMaps(t *testing.T) { *** -#### func ConvertSliceToAny(s S) []any +#### func ConvertSliceToAny(s S) []any > 将切片转换为任意类型的切片 @@ -1799,7 +1799,7 @@ func TestConvertSliceToAny(t *testing.T) { *** -#### func ConvertSliceToIndexMap(s S) map[int]V +#### func ConvertSliceToIndexMap(s S) map[int]V > 将切片转换为索引为键的映射 @@ -1850,7 +1850,7 @@ func TestConvertSliceToIndexMap(t *testing.T) { *** -#### func ConvertSliceToIndexOnlyMap(s S) map[int]struct {} +#### func ConvertSliceToIndexOnlyMap(s S) map[int]struct {} > 将切片转换为索引为键的映射 @@ -1905,7 +1905,7 @@ func TestConvertSliceToIndexOnlyMap(t *testing.T) { *** -#### func ConvertSliceToMap(s S) map[V]struct {} +#### func ConvertSliceToMap(s S) map[V]struct {} > 将切片转换为值为键的映射 @@ -1957,7 +1957,7 @@ func TestConvertSliceToMap(t *testing.T) { *** -#### func ConvertSliceToBoolMap(s S) map[V]bool +#### func ConvertSliceToBoolMap(s S) map[V]bool > 将切片转换为值为键的映射 @@ -2008,7 +2008,7 @@ func TestConvertSliceToBoolMap(t *testing.T) { *** -#### func ConvertMapKeysToSlice(m M) []K +#### func ConvertMapKeysToSlice(m M) []K > 将映射的键转换为切片 @@ -2064,7 +2064,7 @@ func TestConvertMapKeysToSlice(t *testing.T) { *** -#### func ConvertMapValuesToSlice(m M) []V +#### func ConvertMapValuesToSlice(m M) []V > 将映射的值转换为切片 @@ -2121,7 +2121,7 @@ func TestConvertMapValuesToSlice(t *testing.T) { *** -#### func InvertMap(m M) N +#### func InvertMap(m M) N > 将映射的键和值互换 @@ -2169,7 +2169,7 @@ func TestInvertMap(t *testing.T) { *** -#### func ConvertMapValuesToBool(m M) N +#### func ConvertMapValuesToBool(m M) N > 将映射的值转换为布尔值 @@ -2592,7 +2592,7 @@ func TestDeduplicateSliceInPlace(t *testing.T) { *** -#### func DeduplicateSlice(s S) S +#### func DeduplicateSlice(s S) S > 去除切片中的重复元素,返回新切片 @@ -2695,7 +2695,7 @@ func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) { *** -#### func DeduplicateSliceWithCompare(s S, compare func (a V) bool) S +#### func DeduplicateSliceWithCompare(s S, compare func (a V) bool) S > 去除切片中的重复元素,使用自定义的比较函数,返回新的切片 @@ -2748,7 +2748,7 @@ func TestDeduplicateSliceWithCompare(t *testing.T) { *** -#### func FilterOutByIndices(slice S, indices ...int) S +#### func FilterOutByIndices(slice S, indices ...int) S > 过滤切片中特定索引的元素,返回过滤后的切片 @@ -2798,7 +2798,7 @@ func TestFilterOutByIndices(t *testing.T) { *** -#### func FilterOutByCondition(slice S, condition func (v V) bool) S +#### func FilterOutByCondition(slice S, condition func (v V) bool) S > 过滤切片中符合条件的元素,返回过滤后的切片 > - condition 的返回值为 true 时,将会过滤掉该元素 @@ -2857,7 +2857,7 @@ func TestFilterOutByCondition(t *testing.T) { *** -#### func FilterOutByKey(m M, key K) M +#### func FilterOutByKey(m M, key K) M > 过滤 map 中特定的 key,返回过滤后的 map @@ -2907,7 +2907,7 @@ func TestFilterOutByKey(t *testing.T) { *** -#### func FilterOutByValue(m M, value V, handler ComparisonHandler[V]) M +#### func FilterOutByValue(m M, value V, handler ComparisonHandler[V]) M > 过滤 map 中特定的 value,返回过滤后的 map @@ -2961,7 +2961,7 @@ func TestFilterOutByValue(t *testing.T) { *** -#### func FilterOutByKeys(m M, keys ...K) M +#### func FilterOutByKeys(m M, keys ...K) M > 过滤 map 中多个 key,返回过滤后的 map @@ -3011,7 +3011,7 @@ func TestFilterOutByKeys(t *testing.T) { *** -#### func FilterOutByValues(m M, values []V, handler ComparisonHandler[V]) M +#### func FilterOutByValues(m M, values []V, handler ComparisonHandler[V]) M > 过滤 map 中多个 values,返回过滤后的 map @@ -3067,7 +3067,7 @@ func TestFilterOutByValues(t *testing.T) { *** -#### func FilterOutByMap(m M, condition func (k K, v V) bool) M +#### func FilterOutByMap(m M, condition func (k K, v V) bool) M > 过滤 map 中符合条件的元素,返回过滤后的 map > - condition 的返回值为 true 时,将会过滤掉该元素 @@ -3212,7 +3212,7 @@ func TestFindLoopedPrevInSlice(t *testing.T) { *** -#### func FindCombinationsInSliceByRange(s S, minSize int, maxSize int) []S +#### func FindCombinationsInSliceByRange(s S, minSize int, maxSize int) []S > 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 @@ -3257,7 +3257,7 @@ func TestFindCombinationsInSliceByRange(t *testing.T) { *** -#### func FindFirstOrDefaultInSlice(slice S, defaultValue V) V +#### func FindFirstOrDefaultInSlice(slice S, defaultValue V) V > 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值 @@ -3437,7 +3437,7 @@ func TestFindInSlice(t *testing.T) { *** -#### func FindIndexInSlice(slice S, handler func (v V) bool) int +#### func FindIndexInSlice(slice S, handler func (v V) bool) int > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 @@ -3527,7 +3527,7 @@ func TestFindInComparableSlice(t *testing.T) { *** -#### func FindIndexInComparableSlice(slice S, v V) int +#### func FindIndexInComparableSlice(slice S, v V) int > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 @@ -4158,7 +4158,7 @@ func TestSwapSlice(t *testing.T) { *** -#### func MappingFromSlice(slice S, handler func (value V) N) NS +#### func MappingFromSlice(slice S, handler func (value V) N) NS > 将切片中的元素进行转换 @@ -4210,7 +4210,7 @@ func TestMappingFromSlice(t *testing.T) { *** -#### func MappingFromMap(m M, handler func (value V) N) NM +#### func MappingFromMap(m M, handler func (value V) N) NM > 将 map 中的元素进行转换 @@ -4744,7 +4744,7 @@ func TestChooseRandomMapValueRepeatN(t *testing.T) { *** -#### func ChooseRandomMapKeyAndValueRepeatN(m M, n int) M +#### func ChooseRandomMapKeyAndValueRepeatN(m M, n int) M > 获取 map 中的 n 个随机 key 和 v,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -4977,7 +4977,7 @@ func TestChooseRandomMapKeyAndValue(t *testing.T) { *** -#### func ChooseRandomMapKeyAndValueN(m M, n int) M +#### func ChooseRandomMapKeyAndValueN(m M, n int) M > 获取 map 中的 inputN 个随机 key 和 v > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -5023,7 +5023,7 @@ func TestChooseRandomMapKeyAndValueN(t *testing.T) { *** -#### func DescBy(a Sort, b Sort) bool +#### func DescBy(a Sort, b Sort) bool > 返回降序比较结果 @@ -5073,7 +5073,7 @@ func TestDescBy(t *testing.T) { *** -#### func AscBy(a Sort, b Sort) bool +#### func AscBy(a Sort, b Sort) bool > 返回升序比较结果 @@ -5173,7 +5173,7 @@ func TestDesc(t *testing.T) { *** -#### func DescByClone(slice S, getter func (index int) Sort) S +#### func DescByClone(slice S, getter func (index int) Sort) S > 对切片进行降序排序,返回排序后的切片 @@ -5273,7 +5273,7 @@ func TestAsc(t *testing.T) { *** -#### func AscByClone(slice S, getter func (index int) Sort) S +#### func AscByClone(slice S, getter func (index int) Sort) S > 对切片进行升序排序,返回排序后的切片 @@ -5367,7 +5367,7 @@ func TestShuffle(t *testing.T) { *** -#### func ShuffleByClone(slice S) S +#### func ShuffleByClone(slice S) S > 对切片进行随机排序,返回排序后的切片 diff --git a/utils/collection/listings/README.md b/utils/collection/listings/README.md index 57dd1d83..0ea0d9f7 100644 --- a/utils/collection/listings/README.md +++ b/utils/collection/listings/README.md @@ -36,22 +36,22 @@ *** ## 详情信息 -#### func NewMatrix(dimensions ...int) *Matrix[V] +#### func NewMatrix(dimensions ...int) *Matrix[V] > 创建一个新的 Matrix 实例。 *** -#### func NewPagedSlice(pageSize int) *PagedSlice[T] +#### func NewPagedSlice(pageSize int) *PagedSlice[T] > 创建一个新的 PagedSlice 实例。 *** -#### func NewPrioritySlice(lengthAndCap ...int) *PrioritySlice[V] +#### func NewPrioritySlice(lengthAndCap ...int) *PrioritySlice[V] > 创建一个优先级切片 *** -#### func NewSyncSlice(length int, cap int) *SyncSlice[V] +#### func NewSyncSlice(length int, cap int) *SyncSlice[V] > 创建一个 SyncSlice @@ -145,7 +145,7 @@ func TestPrioritySlice_Append(t *testing.T) { #### func (*PrioritySlice) Appends(priority int, vs ...V) > 添加元素 *** -#### func (*PrioritySlice) Get(index int) V, int +#### func (*PrioritySlice) Get(index int) ( V, int) > 获取元素 *** #### func (*PrioritySlice) GetValue(index int) V diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md index 0fa7236f..f4910eb0 100644 --- a/utils/collection/mappings/README.md +++ b/utils/collection/mappings/README.md @@ -30,7 +30,7 @@ *** ## 详情信息 -#### func NewSyncMap(source ...map[K]V) *SyncMap[K, V] +#### func NewSyncMap(source ...map[K]V) *SyncMap[K, V] > 创建一个 SyncMap diff --git a/utils/combination/README.md b/utils/combination/README.md index 0efb5275..2826bbe1 100644 --- a/utils/combination/README.md +++ b/utils/combination/README.md @@ -65,12 +65,12 @@ combination 包提供了一些实用的组合函数。 *** ## 详情信息 -#### func NewCombination(options ...Option[T]) *Combination[T] +#### func NewCombination(options ...Option[T]) *Combination[T] > 创建一个新的组合器 *** -#### func WithEvaluation(evaluate func (items []T) float64) Option[T] +#### func WithEvaluation(evaluate func (items []T) float64) Option[T] > 设置组合评估函数 > - 用于对组合进行评估,返回一个分值的评价函数 @@ -79,57 +79,57 @@ combination 包提供了一些实用的组合函数。 > - 默认的评估函数将返回一个随机数 *** -#### func NewMatcher(options ...MatcherOption[T]) *Matcher[T] +#### func NewMatcher(options ...MatcherOption[T]) *Matcher[T] > 创建一个新的匹配器 *** -#### func WithMatcherEvaluation(evaluate func (items []T) float64) MatcherOption[T] +#### func WithMatcherEvaluation(evaluate func (items []T) float64) MatcherOption[T] > 设置匹配器评估函数 > - 用于对组合进行评估,返回一个分值的评价函数 > - 通过该选项将覆盖匹配器的默认(WithEvaluation)评估函数 *** -#### func WithMatcherLeastLength(length int) MatcherOption[T] +#### func WithMatcherLeastLength(length int) MatcherOption[T] > 通过匹配最小长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合最小数量 *** -#### func WithMatcherLength(length int) MatcherOption[T] +#### func WithMatcherLength(length int) MatcherOption[T] > 通过匹配长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合数量 *** -#### func WithMatcherMostLength(length int) MatcherOption[T] +#### func WithMatcherMostLength(length int) MatcherOption[T] > 通过匹配最大长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合最大数量 *** -#### func WithMatcherIntervalLength(min int, max int) MatcherOption[T] +#### func WithMatcherIntervalLength(min int, max int) MatcherOption[T] > 通过匹配长度区间的组合创建匹配器 > - min: 组合的最小长度,表示需要匹配的组合最小数量 > - max: 组合的最大长度,表示需要匹配的组合最大数量 *** -#### func WithMatcherContinuity(getIndex func (item T) Index) MatcherOption[T] +#### func WithMatcherContinuity(getIndex func (item T) Index) MatcherOption[T] > 通过匹配连续的组合创建匹配器 > - index: 用于获取组合中元素的索引值,用于判断是否连续 *** -#### func WithMatcherSame(count int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherSame(count int, getType func (item T) E) MatcherOption[T] > 通过匹配相同的组合创建匹配器 > - count: 组合中相同元素的数量,当 count <= 0 时,表示相同元素的数量不限 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithMatcherNCarryM(n int, m int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherNCarryM(n int, m int, getType func (item T) E) MatcherOption[T] > 通过匹配 N 携带 M 的组合创建匹配器 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -137,7 +137,7 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithMatcherNCarryIndependentM(n int, m int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherNCarryIndependentM(n int, m int, getType func (item T) E) MatcherOption[T] > 通过匹配 N 携带独立 M 的组合创建匹配器 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -145,87 +145,87 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func NewValidator(options ...ValidatorOption[T]) *Validator[T] +#### func NewValidator(options ...ValidatorOption[T]) *Validator[T] > 创建一个新的校验器 *** -#### func WithValidatorHandle(handle func (items []T) bool) ValidatorOption[T] +#### func WithValidatorHandle(handle func (items []T) bool) ValidatorOption[T] > 通过特定的验证函数对组合进行验证 *** -#### func WithValidatorHandleLength(length int) ValidatorOption[T] +#### func WithValidatorHandleLength(length int) ValidatorOption[T] > 校验组合的长度是否符合要求 *** -#### func WithValidatorHandleLengthRange(min int, max int) ValidatorOption[T] +#### func WithValidatorHandleLengthRange(min int, max int) ValidatorOption[T] > 校验组合的长度是否在指定的范围内 *** -#### func WithValidatorHandleLengthMin(min int) ValidatorOption[T] +#### func WithValidatorHandleLengthMin(min int) ValidatorOption[T] > 校验组合的长度是否大于等于指定的最小值 *** -#### func WithValidatorHandleLengthMax(max int) ValidatorOption[T] +#### func WithValidatorHandleLengthMax(max int) ValidatorOption[T] > 校验组合的长度是否小于等于指定的最大值 *** -#### func WithValidatorHandleLengthNot(length int) ValidatorOption[T] +#### func WithValidatorHandleLengthNot(length int) ValidatorOption[T] > 校验组合的长度是否不等于指定的值 *** -#### func WithValidatorHandleTypeLength(length int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLength(length int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否为指定的值 *** -#### func WithValidatorHandleTypeLengthRange(min int, max int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthRange(min int, max int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否在指定的范围内 *** -#### func WithValidatorHandleTypeLengthMin(min int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthMin(min int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否大于等于指定的最小值 *** -#### func WithValidatorHandleTypeLengthMax(max int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthMax(max int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否小于等于指定的最大值 *** -#### func WithValidatorHandleTypeLengthNot(length int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthNot(length int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否不等于指定的值 *** -#### func WithValidatorHandleContinuous(getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleContinuous(getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否连续 *** -#### func WithValidatorHandleContinuousNot(getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleContinuousNot(getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否不连续 *** -#### func WithValidatorHandleGroupContinuous(getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleGroupContinuous(getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否能够按类型分组并且连续 *** -#### func WithValidatorHandleGroupContinuousN(n int, getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleGroupContinuousN(n int, getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否能够按分组为 n 组类型并且连续 *** -#### func WithValidatorHandleNCarryM(n int, m int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleNCarryM(n int, m int, getType func (item T) E) ValidatorOption[T] > 校验组合成员是否匹配 N 携带相同的 M 的组合 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -233,7 +233,7 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithValidatorHandleNCarryIndependentM(n int, m int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleNCarryIndependentM(n int, m int, getType func (item T) E) ValidatorOption[T] > 校验组合成员是否匹配 N 携带独立的 M 的组合 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 diff --git a/utils/compress/README.md b/utils/compress/README.md index d576d740..03f09b79 100644 --- a/utils/compress/README.md +++ b/utils/compress/README.md @@ -30,32 +30,32 @@ compress 提供了一些用于压缩和解压缩数据的函数。 *** ## 详情信息 -#### func GZipCompress(data []byte) bytes.Buffer, error +#### func GZipCompress(data []byte) (bytes.Buffer, error) > 对数据进行GZip压缩,返回bytes.Buffer和错误信息 *** -#### func GZipUnCompress(dataByte []byte) []byte, error +#### func GZipUnCompress(dataByte []byte) ([]byte, error) > 对已进行GZip压缩的数据进行解压缩,返回字节数组及错误信息 *** -#### func TARCompress(data []byte) bytes.Buffer, error +#### func TARCompress(data []byte) (bytes.Buffer, error) > 对数据进行TAR压缩,返回bytes.Buffer和错误信息 *** -#### func TARUnCompress(dataByte []byte) []byte, error +#### func TARUnCompress(dataByte []byte) ([]byte, error) > 对已进行TAR压缩的数据进行解压缩,返回字节数组及错误信息 *** -#### func ZIPCompress(data []byte) bytes.Buffer, error +#### func ZIPCompress(data []byte) (bytes.Buffer, error) > 对数据进行ZIP压缩,返回bytes.Buffer和错误信息 *** -#### func ZIPUnCompress(dataByte []byte) []byte, error +#### func ZIPUnCompress(dataByte []byte) ([]byte, error) > 对已进行ZIP压缩的数据进行解压缩,返回字节数组及错误信息 diff --git a/utils/crypto/README.md b/utils/crypto/README.md index 88f6719a..4864232e 100644 --- a/utils/crypto/README.md +++ b/utils/crypto/README.md @@ -34,52 +34,52 @@ *** ## 详情信息 -#### func EncryptBase64(data []byte) string +#### func EncryptBase64(data []byte) string > 对数据进行Base64编码 *** -#### func DecodedBase64(data string) []byte, error +#### func DecodedBase64(data string) ([]byte, error) > 对数据进行Base64解码 *** -#### func EncryptCRC32(str string) uint32 +#### func EncryptCRC32(str string) uint32 > 对字符串进行CRC加密并返回其结果。 *** -#### func DecodedCRC32(data []byte) uint32 +#### func DecodedCRC32(data []byte) uint32 > 对字节数组进行CRC加密并返回其结果。 *** -#### func EncryptMD5(str string) string +#### func EncryptMD5(str string) string > 对字符串进行MD5加密并返回其结果。 *** -#### func DecodedMD5(data []byte) string +#### func DecodedMD5(data []byte) string > 对字节数组进行MD5加密并返回其结果。 *** -#### func EncryptSHA1(str string) string +#### func EncryptSHA1(str string) string > 对字符串进行SHA1加密并返回其结果。 *** -#### func DecodedSHA1(data []byte) string +#### func DecodedSHA1(data []byte) string > 对字节数组进行SHA1加密并返回其结果。 *** -#### func EncryptSHA256(str string) string +#### func EncryptSHA256(str string) string > 对字符串进行SHA256加密并返回其结果。 *** -#### func DecodedSHA256(data []byte) string +#### func DecodedSHA256(data []byte) string > 对字节数组进行SHA256加密并返回其结果。 diff --git a/utils/deck/README.md b/utils/deck/README.md index f6f342df..3c95f23b 100644 --- a/utils/deck/README.md +++ b/utils/deck/README.md @@ -33,12 +33,12 @@ deck 包中的内容用于针对一堆内容的管理,适用但不限于牌堆 *** ## 详情信息 -#### func NewDeck() *Deck[I] +#### func NewDeck() *Deck[I] > 创建一个新的甲板 *** -#### func NewGroup(guid int64, fillHandle func (guid int64) []I) *Group[I] +#### func NewGroup(guid int64, fillHandle func (guid int64) []I) *Group[I] > 创建一个新的组 diff --git a/utils/file/README.md b/utils/file/README.md index aa2cf968..fc7e257d 100644 --- a/utils/file/README.md +++ b/utils/file/README.md @@ -35,46 +35,46 @@ *** ## 详情信息 -#### func PathExist(path string) bool, error +#### func PathExist(path string) (bool, error) > 路径是否存在 *** -#### func IsDir(path string) bool, error +#### func IsDir(path string) (bool, error) > 路径是否是文件夹 *** -#### func WriterFile(filePath string, content []byte) error +#### func WriterFile(filePath string, content []byte) error > 向特定文件写入内容 *** -#### func ReadOnce(filePath string) []byte, error +#### func ReadOnce(filePath string) ([]byte, error) > 单次读取文件 > - 一次性对整个文件进行读取,小文件读取可以很方便的一次性将文件内容读取出来,而大文件读取会造成性能影响。 *** -#### func ReadBlockHook(filePath string, bufferSize int, hook func (data []byte)) error +#### func ReadBlockHook(filePath string, bufferSize int, hook func (data []byte)) error > 分块读取文件 > - 将filePath路径对应的文件数据并将读到的每一部分传入hook函数中,当过程中如果产生错误则会返回error。 > - 分块读取可以在读取速度和内存消耗之间有一个很好的平衡。 *** -#### func ReadLine(filePath string, hook func (line string)) error +#### func ReadLine(filePath string, hook func (line string)) error > 分行读取文件 > - 将filePath路径对应的文件数据并将读到的每一行传入hook函数中,当过程中如果产生错误则会返回error。 *** -#### func LineCount(filePath string) int +#### func LineCount(filePath string) int > 统计文件行数 *** -#### func Paths(dir string) []string +#### func Paths(dir string) []string > 获取指定目录下的所有文件路径 > - 包括了子目录下的文件 @@ -88,7 +88,7 @@ > - 可通过 start 参数指定开始读取的位置,如果不指定则从文件开头开始读取。 *** -#### func FindLineChunks(file *os.File, chunkSize int64) [][2]int64 +#### func FindLineChunks(file *os.File, chunkSize int64) [][2]int64 > 查找文件按照每行划分的分块,每个分块的大小将在 chunkSize 和分割后的分块距离行首及行尾的距离中范围内 > - 使用该函数得到的分块是完整的行,不会出现行被分割的情况 @@ -96,7 +96,7 @@ > - 返回值的成员是一个长度为 2 的数组,第一个元素是分块的起始位置,第二个元素是分块的结束位置 *** -#### func FindLineChunksByOffset(file *os.File, offset int64, chunkSize int64) [][2]int64 +#### func FindLineChunksByOffset(file *os.File, offset int64, chunkSize int64) [][2]int64 > 该函数与 FindLineChunks 类似,不同的是该函数可以指定 offset 从指定位置开始读取文件 diff --git a/utils/fsm/README.md b/utils/fsm/README.md index f19ad85d..0661ad23 100644 --- a/utils/fsm/README.md +++ b/utils/fsm/README.md @@ -36,34 +36,34 @@ *** ## 详情信息 -#### func NewFSM(data Data) *FSM[State, Data] +#### func NewFSM(data Data) *FSM[State, Data] > 创建一个新的状态机 *** -#### func WithEnterBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithEnterBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态进入前的回调 > - 在首次设置状态时,状态机本身的当前状态为零值状态 *** -#### func WithEnterAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithEnterAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态进入后的回调 *** -#### func WithUpdateEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithUpdateEvent(fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态内刷新的回调 *** -#### func WithExitBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithExitBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态退出前的回调 > - 该阶段状态机的状态为退出前的状态,而非新的状态 *** -#### func WithExitAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithExitAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态退出后的回调 > - 该阶段状态机的状态为新的状态,而非退出前的状态 diff --git a/utils/generator/astgo/README.md b/utils/generator/astgo/README.md index 282a53c6..a8edb188 100644 --- a/utils/generator/astgo/README.md +++ b/utils/generator/astgo/README.md @@ -36,7 +36,7 @@ *** ## 详情信息 -#### func NewPackage(dir string) *Package, error +#### func NewPackage(dir string) (*Package, error)
diff --git a/utils/generator/genreadme/README.md b/utils/generator/genreadme/README.md index e4851acf..6ead52ec 100644 --- a/utils/generator/genreadme/README.md +++ b/utils/generator/genreadme/README.md @@ -30,7 +30,7 @@ *** ## 详情信息 -#### func New(pkgDirPath string, output string) *Builder, error +#### func New(pkgDirPath string, output string) (*Builder, error) *** @@ -51,11 +51,11 @@ type Builder struct { ```go func TestBuilder_Generate(t *testing.T) { - filepath.Walk("/Users/kercylan/Coding.localized/Go/minotaur", func(path string, info fs.FileInfo, err error) error { + filepath.Walk("D:/sources/minotaur", func(path string, info fs.FileInfo, err error) error { if !info.IsDir() { return nil } - if strings.Contains(strings.TrimPrefix(path, "/Users/kercylan/Coding.localized/Go/minotaur"), ".") { + if strings.Contains(strings.TrimPrefix(path, "D:/sources/minotaur"), ".") { return nil } b, err := New(path, filepath.Join(path, "README.md")) diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index 06cf694f..343656b1 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -163,7 +163,13 @@ func (b *Builder) genStructs() { } return f }(), - funcHandler(function.Results), + func() string { + f := strings.TrimSpace(funcHandler(function.Results)) + if len(function.Results) >= 2 && !strings.HasPrefix(f, "(") { + f = "(" + f + ")" + } + return f + }(), ))) b.newLine(fmt.Sprintf(``, function.Name)) b.quote() @@ -260,7 +266,13 @@ func (b *Builder) genStructs() { } return f }(), - funcHandler(function.Results), + func() string { + f := funcHandler(function.Results) + if len(function.Results) >= 2 && !strings.HasPrefix(strings.TrimSpace(f), "(") { + f = "(" + f + ")" + } + return f + }(), ))) b.quote() for _, comment := range function.Comments.Clear { diff --git a/utils/generator/genreadme/builder_test.go b/utils/generator/genreadme/builder_test.go index 9a420ab9..ded811b3 100644 --- a/utils/generator/genreadme/builder_test.go +++ b/utils/generator/genreadme/builder_test.go @@ -16,11 +16,11 @@ func TestBuilder_Generate(t *testing.T) { // panic(err) //} //return - filepath.Walk("/Users/kercylan/Coding.localized/Go/minotaur", func(path string, info fs.FileInfo, err error) error { + filepath.Walk("D:/sources/minotaur", func(path string, info fs.FileInfo, err error) error { if !info.IsDir() { return nil } - if strings.Contains(strings.TrimPrefix(path, "/Users/kercylan/Coding.localized/Go/minotaur"), ".") { + if strings.Contains(strings.TrimPrefix(path, "D:/sources/minotaur"), ".") { return nil } b, err := New( diff --git a/utils/generic/README.md b/utils/generic/README.md index c57f517a..90c71d60 100644 --- a/utils/generic/README.md +++ b/utils/generic/README.md @@ -48,17 +48,17 @@ generic 目的在于提供一组基于泛型的用于处理通用功能的函数 *** ## 详情信息 -#### func IsNil(v V) bool +#### func IsNil(v V) bool > 检查指定的值是否为 nil *** -#### func IsAllNil(v ...V) bool +#### func IsAllNil(v ...V) bool > 检查指定的值是否全部为 nil *** -#### func IsHasNil(v ...V) bool +#### func IsHasNil(v ...V) bool > 检查指定的值是否存在 nil diff --git a/utils/geometry/README.md b/utils/geometry/README.md index 177eb017..1d3055ea 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -132,7 +132,7 @@ geometry 旨在提供一组用于处理几何形状和计算几何属性的函 *** ## 详情信息 -#### func NewCircle(radius V, points int) Circle[V] +#### func NewCircle(radius V, points int) Circle[V] > 通过传入圆的半径和需要的点数量,生成一个圆 @@ -146,12 +146,12 @@ func ExampleNewCircle() { ``` *** -#### func CalcCircleCentroidDistance(circle1 Circle[V], circle2 Circle[V]) V +#### func CalcCircleCentroidDistance(circle1 Circle[V], circle2 Circle[V]) V > 计算两个圆质心距离 *** -#### func GetOppositionDirection(direction Direction) Direction +#### func GetOppositionDirection(direction Direction) Direction > 获取特定方向的对立方向 @@ -161,39 +161,39 @@ func ExampleNewCircle() { > 获取特定方向上的下一个坐标 *** -#### func GetDirectionNextWithPoint(direction Direction, point Point[V]) Point[V] +#### func GetDirectionNextWithPoint(direction Direction, point Point[V]) Point[V] > 获取特定方向上的下一个坐标 *** -#### func GetDirectionNextWithPos(direction Direction, width V, pos V) V +#### func GetDirectionNextWithPos(direction Direction, width V, pos V) V > 获取位置在特定宽度和特定方向上的下一个位置 > - 需要注意的是,在左右方向时,当下一个位置不在矩形区域内时,将会返回上一行的末位置或下一行的首位置 *** -#### func CalcDirection(x1 V, y1 V, x2 V, y2 V) Direction +#### func CalcDirection(x1 V, y1 V, x2 V, y2 V) Direction > 计算点2位于点1的方向 *** -#### func CalcDistanceWithCoordinate(x1 V, y1 V, x2 V, y2 V) V +#### func CalcDistanceWithCoordinate(x1 V, y1 V, x2 V, y2 V) V > 计算两点之间的距离 *** -#### func CalcDistanceWithPoint(point1 Point[V], point2 Point[V]) V +#### func CalcDistanceWithPoint(point1 Point[V], point2 Point[V]) V > 计算两点之间的距离 *** -#### func CalcDistanceSquared(x1 V, y1 V, x2 V, y2 V) V +#### func CalcDistanceSquared(x1 V, y1 V, x2 V, y2 V) V > 计算两点之间的平方距离 > - 这个函数的主要用途是在需要计算两点之间距离的情况下,但不需要得到实际的距离值,而只需要比较距离大小。因为平方根运算相对较为耗时,所以在只需要比较大小的情况下,通常会使用平方距离。 *** -#### func CalcAngle(x1 V, y1 V, x2 V, y2 V) V +#### func CalcAngle(x1 V, y1 V, x2 V, y2 V) V > 计算点2位于点1之间的角度 @@ -203,75 +203,75 @@ func ExampleNewCircle() { > 根据给定的x、y坐标、角度和距离计算新的坐标 *** -#### func CalcRadianWithAngle(angle V) V +#### func CalcRadianWithAngle(angle V) V > 根据角度 angle 计算弧度 *** -#### func CalcAngleDifference(angleA V, angleB V) V +#### func CalcAngleDifference(angleA V, angleB V) V > 计算两个角度之间的最小角度差 *** -#### func CalcRayIsIntersect(x V, y V, angle V, shape Shape[V]) bool +#### func CalcRayIsIntersect(x V, y V, angle V, shape Shape[V]) bool > 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞 *** -#### func NewLineSegment(start Point[V], end Point[V]) LineSegment[V] +#### func NewLineSegment(start Point[V], end Point[V]) LineSegment[V] > 创建一根线段 *** -#### func NewLineSegmentCap(start Point[V], end Point[V], data Data) LineSegmentCap[V, Data] +#### func NewLineSegmentCap(start Point[V], end Point[V], data Data) LineSegmentCap[V, Data] > 创建一根包含数据的线段 *** -#### func NewLineSegmentCapWithLine(line LineSegment[V], data Data) LineSegmentCap[V, Data] +#### func NewLineSegmentCapWithLine(line LineSegment[V], data Data) LineSegmentCap[V, Data] > 通过已有线段创建一根包含数据的线段 *** -#### func ConvertLineSegmentGeneric(line LineSegment[V]) LineSegment[TO] +#### func ConvertLineSegmentGeneric(line LineSegment[V]) LineSegment[TO] > 转换线段的泛型类型为特定类型 *** -#### func PointOnLineSegmentWithCoordinate(x1 V, y1 V, x2 V, y2 V, x V, y V) bool +#### func PointOnLineSegmentWithCoordinate(x1 V, y1 V, x2 V, y2 V, x V, y V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithPos(width V, pos1 V, pos2 V, pos V) bool +#### func PointOnLineSegmentWithPos(width V, pos1 V, pos2 V, pos V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithPoint(point1 Point[V], point2 Point[V], point Point[V]) bool +#### func PointOnLineSegmentWithPoint(point1 Point[V], point2 Point[V], point Point[V]) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithCoordinateInBounds(x1 V, y1 V, x2 V, y2 V, x V, y V) bool +#### func PointOnLineSegmentWithCoordinateInBounds(x1 V, y1 V, x2 V, y2 V, x V, y V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithCoordinate 不同的是, PointOnLineSegmentWithCoordinateInBounds 中会判断线段及点的位置是否正确 *** -#### func PointOnLineSegmentWithPosInBounds(width V, pos1 V, pos2 V, pos V) bool +#### func PointOnLineSegmentWithPosInBounds(width V, pos1 V, pos2 V, pos V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithPos 不同的是, PointOnLineSegmentWithPosInBounds 中会判断线段及点的位置是否正确 *** -#### func PointOnLineSegmentWithPointInBounds(point1 Point[V], point2 Point[V], point Point[V]) bool +#### func PointOnLineSegmentWithPointInBounds(point1 Point[V], point2 Point[V], point Point[V]) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithPoint 不同的是, PointOnLineSegmentWithPointInBounds 中会判断线段及点的位置是否正确 *** -#### func CalcLineSegmentIsCollinear(line1 LineSegment[V], line2 LineSegment[V], tolerance V) bool +#### func CalcLineSegmentIsCollinear(line1 LineSegment[V], line2 LineSegment[V], tolerance V) bool > 检查两条线段在一个误差内是否共线 > - 共线是指两条线段在同一直线上,即它们的延长线可以重合 @@ -282,7 +282,7 @@ func ExampleNewCircle() { > 通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段 *** -#### func CalcLineSegmentIsIntersect(line1 LineSegment[V], line2 LineSegment[V]) bool +#### func CalcLineSegmentIsIntersect(line1 LineSegment[V], line2 LineSegment[V]) bool > 计算两条线段是否相交 @@ -305,17 +305,17 @@ func TestCalcLineSegmentIsIntersect(t *testing.T) { *** -#### func CalcLineSegmentSlope(line LineSegment[V]) V +#### func CalcLineSegmentSlope(line LineSegment[V]) V > 计算线段的斜率 *** -#### func CalcLineSegmentIntercept(line LineSegment[V]) V +#### func CalcLineSegmentIntercept(line LineSegment[V]) V > 计算线段的截距 *** -#### func NewPoint(x V, y V) Point[V] +#### func NewPoint(x V, y V) Point[V] > 创建一个由 x、y 坐标组成的点 @@ -337,27 +337,27 @@ func TestNewPoint(t *testing.T) { *** -#### func NewPointCap(x V, y V) PointCap[V, D] +#### func NewPointCap(x V, y V) PointCap[V, D] > 创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 *** -#### func NewPointCapWithData(x V, y V, data D) PointCap[V, D] +#### func NewPointCapWithData(x V, y V, data D) PointCap[V, D] > 通过设置数据的方式创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 *** -#### func NewPointCapWithPoint(point Point[V], data D) PointCap[V, D] +#### func NewPointCapWithPoint(point Point[V], data D) PointCap[V, D] > 通过设置数据的方式创建一个由已有坐标组成的点,这个点具有一个数据容量 *** -#### func CoordinateToPoint(x V, y V) Point[V] +#### func CoordinateToPoint(x V, y V) Point[V] > 将坐标转换为x、y的坐标数组 *** -#### func CoordinateToPos(width V, x V, y V) V +#### func CoordinateToPos(width V, x V, y V) V > 将坐标转换为二维数组的顺序位置坐标 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 @@ -368,7 +368,7 @@ func TestNewPoint(t *testing.T) { > 将坐标数组转换为x和y坐标 *** -#### func PointToPos(width V, xy Point[V]) V +#### func PointToPos(width V, xy Point[V]) V > 将坐标转换为二维数组的顺序位置 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 @@ -379,38 +379,38 @@ func TestNewPoint(t *testing.T) { > 通过宽度将一个二维数组的顺序位置转换为xy坐标 *** -#### func PosToPoint(width V, pos V) Point[V] +#### func PosToPoint(width V, pos V) Point[V] > 通过宽度将一个二维数组的顺序位置转换为x、y的坐标数组 *** -#### func PosToCoordinateX(width V, pos V) V +#### func PosToCoordinateX(width V, pos V) V > 通过宽度将一个二维数组的顺序位置转换为X坐标 *** -#### func PosToCoordinateY(width V, pos V) V +#### func PosToCoordinateY(width V, pos V) V > 通过宽度将一个二维数组的顺序位置转换为Y坐标 *** -#### func PointCopy(point Point[V]) Point[V] +#### func PointCopy(point Point[V]) Point[V] > 复制一个坐标数组 *** -#### func PointToPosWithMulti(width V, points ...Point[V]) []V +#### func PointToPosWithMulti(width V, points ...Point[V]) []V > 将一组坐标转换为二维数组的顺序位置 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 *** -#### func PosToPointWithMulti(width V, positions ...V) []Point[V] +#### func PosToPointWithMulti(width V, positions ...V) []Point[V] > 将一组二维数组的顺序位置转换为一组数组坐标 *** -#### func PosSameRow(width V, pos1 V, pos2 V) bool +#### func PosSameRow(width V, pos1 V, pos2 V) bool > 返回两个顺序位置在同一宽度是否位于同一行 @@ -420,7 +420,7 @@ func TestNewPoint(t *testing.T) { > 将两个位置转换为 x1, y1, x2, y2 的坐标进行返回 *** -#### func CalcProjectionPoint(line LineSegment[V], point Point[V]) Point[V] +#### func CalcProjectionPoint(line LineSegment[V], point Point[V]) Point[V] > 计算一个点到一条线段的最近点(即投影点)的。这个函数接收一个点和一条线段作为输入,线段由两个端点组成。 > - 该函数的主要用于需要计算一个点到一条线段的最近点的情况下 @@ -675,7 +675,7 @@ func TestGenerateShapeOnRectangle(t *testing.T) { > 获取一个矩形填充满后包含的所有位置 *** -#### func CalcRectangleCentroid(shape Shape[V]) Point[V] +#### func CalcRectangleCentroid(shape Shape[V]) Point[V] > 计算矩形质心 > - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心 @@ -691,7 +691,7 @@ func TestGenerateShapeOnRectangle(t *testing.T) { > 设置 Shape.String 是没有边界的 *** -#### func NewShape(points ...Point[V]) Shape[V] +#### func NewShape(points ...Point[V]) Shape[V] > 通过多个点生成一个形状进行返回 @@ -775,91 +775,91 @@ func TestNewShapeWithString(t *testing.T) { *** -#### func CalcBoundingRadius(shape Shape[V]) V +#### func CalcBoundingRadius(shape Shape[V]) V > 计算多边形转换为圆的半径 *** -#### func CalcBoundingRadiusWithCentroid(shape Shape[V], centroid Point[V]) V +#### func CalcBoundingRadiusWithCentroid(shape Shape[V], centroid Point[V]) V > 计算多边形在特定质心下圆的半径 *** -#### func CalcTriangleTwiceArea(a Point[V], b Point[V], c Point[V]) V +#### func CalcTriangleTwiceArea(a Point[V], b Point[V], c Point[V]) V > 计算由 a、b、c 三个点组成的三角形的面积的两倍 *** -#### func IsPointOnEdge(edges []LineSegment[V], point Point[V]) bool +#### func IsPointOnEdge(edges []LineSegment[V], point Point[V]) bool > 检查点是否在 edges 的任意一条边上 *** -#### func ProjectionPointToShape(point Point[V], shape Shape[V]) Point[V], V +#### func ProjectionPointToShape(point Point[V], shape Shape[V]) (Point[V], V) > 将一个点投影到一个多边形上,找到离该点最近的投影点,并返回投影点和距离 *** -#### func WithShapeSearchRectangleLowerLimit(minWidth int, minHeight int) ShapeSearchOption +#### func WithShapeSearchRectangleLowerLimit(minWidth int, minHeight int) ShapeSearchOption > 通过矩形宽高下限的方式搜索 *** -#### func WithShapeSearchRectangleUpperLimit(maxWidth int, maxHeight int) ShapeSearchOption +#### func WithShapeSearchRectangleUpperLimit(maxWidth int, maxHeight int) ShapeSearchOption > 通过矩形宽高上限的方式搜索 *** -#### func WithShapeSearchRightAngle() ShapeSearchOption +#### func WithShapeSearchRightAngle() ShapeSearchOption > 通过直角的方式进行搜索 *** -#### func WithShapeSearchOppositionDirection(direction Direction) ShapeSearchOption +#### func WithShapeSearchOppositionDirection(direction Direction) ShapeSearchOption > 通过限制对立方向的方式搜索 > - 对立方向例如上不能与下共存 *** -#### func WithShapeSearchDirectionCount(count int) ShapeSearchOption +#### func WithShapeSearchDirectionCount(count int) ShapeSearchOption > 通过限制方向数量的方式搜索 *** -#### func WithShapeSearchDirectionCountLowerLimit(direction Direction, count int) ShapeSearchOption +#### func WithShapeSearchDirectionCountLowerLimit(direction Direction, count int) ShapeSearchOption > 通过限制特定方向数量下限的方式搜索 *** -#### func WithShapeSearchDirectionCountUpperLimit(direction Direction, count int) ShapeSearchOption +#### func WithShapeSearchDirectionCountUpperLimit(direction Direction, count int) ShapeSearchOption > 通过限制特定方向数量上限的方式搜索 *** -#### func WithShapeSearchDeduplication() ShapeSearchOption +#### func WithShapeSearchDeduplication() ShapeSearchOption > 通过去重的方式进行搜索 > - 去重方式中每个点仅会被使用一次 *** -#### func WithShapeSearchPointCountLowerLimit(lowerLimit int) ShapeSearchOption +#### func WithShapeSearchPointCountLowerLimit(lowerLimit int) ShapeSearchOption > 通过限制图形构成的最小点数进行搜索 > - 当搜索到的图形的点数量低于 lowerLimit 时,将被忽略 *** -#### func WithShapeSearchPointCountUpperLimit(upperLimit int) ShapeSearchOption +#### func WithShapeSearchPointCountUpperLimit(upperLimit int) ShapeSearchOption > 通过限制图形构成的最大点数进行搜索 > - 当搜索到的图形的点数量大于 upperLimit 时,将被忽略 *** -#### func WithShapeSearchAsc() ShapeSearchOption +#### func WithShapeSearchAsc() ShapeSearchOption > 通过升序的方式进行搜索 *** -#### func WithShapeSearchDesc() ShapeSearchOption +#### func WithShapeSearchDesc() ShapeSearchOption > 通过降序的方式进行搜索 diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md index 100b4ad2..c8e85403 100644 --- a/utils/geometry/astar/README.md +++ b/utils/geometry/astar/README.md @@ -35,7 +35,7 @@ astar 提供用于实现 A* 算法的函数和数据结构。A* 算法是一种 *** ## 详情信息 -#### func Find(graph Graph[Node], start Node, end Node, cost func (a Node) V, heuristic func (a Node) V) []Node +#### func Find(graph Graph[Node], start Node, end Node, cost func (a Node) V, heuristic func (a Node) V) []Node > 使用 A* 算法在导航网格上查找从起点到终点的最短路径,并返回路径上的节点序列。 > diff --git a/utils/geometry/dp/README.md b/utils/geometry/dp/README.md index 5be6dee3..3b22ddc2 100644 --- a/utils/geometry/dp/README.md +++ b/utils/geometry/dp/README.md @@ -35,7 +35,7 @@ dp (DistributionPattern) 提供用于在二维数组中根据不同的特征标 *** ## 详情信息 -#### func NewDistributionPattern(sameKindVerifyHandle func (itemA Item) bool) *DistributionPattern[Item] +#### func NewDistributionPattern(sameKindVerifyHandle func (itemA Item) bool) *DistributionPattern[Item] > 构建一个分布图实例 diff --git a/utils/geometry/matrix/README.md b/utils/geometry/matrix/README.md index 0981cdf6..691e139e 100644 --- a/utils/geometry/matrix/README.md +++ b/utils/geometry/matrix/README.md @@ -30,7 +30,7 @@ matrix 提供了一个简单的二维数组的实现 *** ## 详情信息 -#### func NewMatrix(width int, height int) *Matrix[T] +#### func NewMatrix(width int, height int) *Matrix[T] > 生成特定宽高的二维矩阵 > - 虽然提供了通过x、y坐标的操作函数,但是建议无论如何使用pos进行处理 diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md index 0374f762..9bdab61e 100644 --- a/utils/geometry/navmesh/README.md +++ b/utils/geometry/navmesh/README.md @@ -33,7 +33,7 @@ navmesh 提供了用于导航网格处理的函数和数据结构。导航网格 *** ## 详情信息 -#### func NewNavMesh(shapes []geometry.Shape[V], meshShrinkAmount V) *NavMesh[V] +#### func NewNavMesh(shapes []geometry.Shape[V], meshShrinkAmount V) *NavMesh[V] > 创建一个新的导航网格,并返回一个指向该导航网格的指针。 > diff --git a/utils/hub/README.md b/utils/hub/README.md index 3bb95775..d350d930 100644 --- a/utils/hub/README.md +++ b/utils/hub/README.md @@ -30,7 +30,7 @@ *** ## 详情信息 -#### func NewObjectPool(generator func () *T, releaser func (data *T)) *ObjectPool[*T] +#### func NewObjectPool(generator func () *T, releaser func (data *T)) *ObjectPool[*T] > 创建一个 ObjectPool diff --git a/utils/huge/README.md b/utils/huge/README.md index 54c94c76..0a762ceb 100644 --- a/utils/huge/README.md +++ b/utils/huge/README.md @@ -34,23 +34,23 @@ *** ## 详情信息 -#### func NewFloat(x T) *Float +#### func NewFloat(x T) *Float > 创建一个 Float *** -#### func NewFloatByString(i string) *Float +#### func NewFloatByString(i string) *Float > 通过字符串创建一个 Float > - 如果字符串不是一个合法的数字,则返回 0 *** -#### func NewInt(x T) *Int +#### func NewInt(x T) *Int > 创建一个 Int *** -#### func NewIntByString(i string) *Int +#### func NewIntByString(i string) *Int > 通过字符串创建一个 Int > - 如果字符串不是一个合法的数字,则返回 0 diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index 6be6bd6b..15bcb622 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewBinarySearch(options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] +#### func NewBinarySearch(options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] > 创建一个基于内存的二分查找排行榜 @@ -49,13 +49,13 @@ func ExampleNewBinarySearch() { ``` *** -#### func WithBinarySearchCount(rankCount int) BinarySearchOption[CompetitorID, Score] +#### func WithBinarySearchCount(rankCount int) BinarySearchOption[CompetitorID, Score] > 通过限制排行榜竞争者数量来创建排行榜 > - 默认情况下允许100位竞争者 *** -#### func WithBinarySearchASC() BinarySearchOption[CompetitorID, Score] +#### func WithBinarySearchASC() BinarySearchOption[CompetitorID, Score] > 通过升序的方式创建排行榜 > - 默认情况下为降序 diff --git a/utils/log/README.md b/utils/log/README.md index 7980ed3a..37be9bb3 100644 --- a/utils/log/README.md +++ b/utils/log/README.md @@ -96,7 +96,7 @@ *** -#### func Default() *Logger +#### func Default() *Logger > 获取默认的日志记录器 @@ -150,193 +150,193 @@ > - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用 *** -#### func Skip(vs ...any) slog.Attr +#### func Skip(vs ...any) slog.Attr > 构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用 > - 该函数还支持将其他字段快捷的转换为 Skip 字段 *** -#### func Duration(key string, val time.Duration) slog.Attr +#### func Duration(key string, val time.Duration) slog.Attr > 使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式 *** -#### func DurationP(key string, val *time.Duration) slog.Attr +#### func DurationP(key string, val *time.Duration) slog.Attr > 构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Bool(key string, val bool) slog.Attr +#### func Bool(key string, val bool) slog.Attr > 构造一个带有布尔值的字段 *** -#### func BoolP(key string, val *bool) slog.Attr +#### func BoolP(key string, val *bool) slog.Attr > 构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func String(key string, val string) slog.Attr +#### func String(key string, val string) slog.Attr > 构造一个带有字符串值的字段 *** -#### func StringP(key string, val *string) slog.Attr +#### func StringP(key string, val *string) slog.Attr > 构造一个带有字符串值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int(key string, val I) slog.Attr +#### func Int(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func IntP(key string, val *I) slog.Attr +#### func IntP(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int8(key string, val I) slog.Attr +#### func Int8(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int8P(key string, val *I) slog.Attr +#### func Int8P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int16(key string, val I) slog.Attr +#### func Int16(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int16P(key string, val *I) slog.Attr +#### func Int16P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int32(key string, val I) slog.Attr +#### func Int32(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int32P(key string, val *I) slog.Attr +#### func Int32P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int64(key string, val I) slog.Attr +#### func Int64(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int64P(key string, val *I) slog.Attr +#### func Int64P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint(key string, val I) slog.Attr +#### func Uint(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func UintP(key string, val *I) slog.Attr +#### func UintP(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint8(key string, val I) slog.Attr +#### func Uint8(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint8P(key string, val *I) slog.Attr +#### func Uint8P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint16(key string, val I) slog.Attr +#### func Uint16(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint16P(key string, val *I) slog.Attr +#### func Uint16P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint32(key string, val I) slog.Attr +#### func Uint32(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint32P(key string, val *I) slog.Attr +#### func Uint32P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint64(key string, val I) slog.Attr +#### func Uint64(key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint64P(key string, val *I) slog.Attr +#### func Uint64P(key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float(key string, val F) slog.Attr +#### func Float(key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func FloatP(key string, val *F) slog.Attr +#### func FloatP(key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float32(key string, val F) slog.Attr +#### func Float32(key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func Float32P(key string, val *F) slog.Attr +#### func Float32P(key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float64(key string, val F) slog.Attr +#### func Float64(key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func Float64P(key string, val *F) slog.Attr +#### func Float64P(key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Time(key string, val time.Time) slog.Attr +#### func Time(key string, val time.Time) slog.Attr > 构造一个带有时间值的字段 *** -#### func TimeP(key string, val *time.Time) slog.Attr +#### func TimeP(key string, val *time.Time) slog.Attr > 构造一个带有时间值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Any(key string, val any) slog.Attr +#### func Any(key string, val any) slog.Attr > 构造一个带有任意值的字段 *** -#### func Group(key string, args ...any) slog.Attr +#### func Group(key string, args ...any) slog.Attr > 返回分组字段 *** -#### func Stack(key string) slog.Attr +#### func Stack(key string) slog.Attr > 返回堆栈字段 @@ -368,27 +368,27 @@ func TestStack(t *testing.T) { *** -#### func Err(err error) slog.Attr +#### func Err(err error) slog.Attr > 构造一个带有错误值的字段 *** -#### func NewHandler(w io.Writer, opts *Options) slog.Handler +#### func NewHandler(w io.Writer, opts *Options) slog.Handler > 创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序 *** -#### func NewLogger(handlers ...slog.Handler) *Logger +#### func NewLogger(handlers ...slog.Handler) *Logger > 创建一个新的日志记录器 *** -#### func NewMultiHandler(handlers ...slog.Handler) slog.Handler +#### func NewMultiHandler(handlers ...slog.Handler) slog.Handler > 创建一个新的多处理程序 *** -#### func NewOptions() *Options +#### func NewOptions() *Options > 创建一个新的日志选项 diff --git a/utils/log/survey/README.md b/utils/log/survey/README.md index 7c2e721d..5e05d6fa 100644 --- a/utils/log/survey/README.md +++ b/utils/log/survey/README.md @@ -45,13 +45,13 @@ *** ## 详情信息 -#### func NewFileFlusher(filePath string, layout ...string) *FileFlusher +#### func NewFileFlusher(filePath string, layout ...string) *FileFlusher > 创建一个文件刷新器 > - layout 为日志文件名的时间戳格式 (默认为 time.DateOnly) *** -#### func WithFlushInterval(interval time.Duration) Option +#### func WithFlushInterval(interval time.Duration) Option > 设置日志文件刷新间隔 > - 默认为 3s,当日志文件刷新间隔 <= 0 时,将会在每次写入日志时刷新日志文件 @@ -83,19 +83,19 @@ > 关闭运营日志记录器 *** -#### func Analyze(filePath string, handle func (analyzer *Analyzer, record R)) *Report +#### func Analyze(filePath string, handle func (analyzer *Analyzer, record R)) *Report > 分析特定文件的记录,当发生错误时,会发生 panic > - handle 为并行执行的,需要自行处理并发安全 > - 适用于外部进程对于日志文件的读取,但是需要注意的是,此时日志文件可能正在被写入,所以可能会读取到错误的数据 *** -#### func AnalyzeMulti(filePaths []string, handle func (analyzer *Analyzer, record R)) *Report +#### func AnalyzeMulti(filePaths []string, handle func (analyzer *Analyzer, record R)) *Report > 与 Analyze 类似,但是可以分析多个文件 *** -#### func IncrementAnalyze(filePath string, handle func (analyzer *Analyzer, record R)) func () ( *Report, error) +#### func IncrementAnalyze(filePath string, handle func (analyzer *Analyzer, record R)) func () ( *Report, error) > 增量分析,返回一个函数,每次调用该函数都会分析文件中新增的内容 diff --git a/utils/maths/README.md b/utils/maths/README.md index 004d1fe1..5d9170ce 100644 --- a/utils/maths/README.md +++ b/utils/maths/README.md @@ -52,12 +52,12 @@ *** ## 详情信息 -#### func Compare(a V, expression CompareExpression, b V) bool +#### func Compare(a V, expression CompareExpression, b V) bool > 根据特定表达式比较两个值 *** -#### func IsContinuity(values S) bool +#### func IsContinuity(values S) bool > 检查一组值是否连续 @@ -72,32 +72,32 @@ func ExampleIsContinuity() { ``` *** -#### func IsContinuityWithSort(values S) bool +#### func IsContinuityWithSort(values S) bool > 检查一组值排序后是否连续 *** -#### func GetDefaultTolerance() float64 +#### func GetDefaultTolerance() float64 > 获取默认误差范围 *** -#### func Pow(a int, n int) int +#### func Pow(a int, n int) int > 整数幂运算 *** -#### func PowInt64(a int64, n int64) int64 +#### func PowInt64(a int64, n int64) int64 > 整数幂运算 *** -#### func Min(a V, b V) V +#### func Min(a V, b V) V > 返回两个数之中较小的值 *** -#### func Max(a V, b V) V +#### func Max(a V, b V) V > 返回两个数之中较大的值 @@ -112,17 +112,17 @@ func ExampleIsContinuity() { > 将两个数按照较大的和较小的顺序进行返回 *** -#### func Clamp(value V, min V, max V) V +#### func Clamp(value V, min V, max V) V > 将给定值限制在最小值和最大值之间 *** -#### func Tolerance(value1 V, value2 V, tolerance V) bool +#### func Tolerance(value1 V, value2 V, tolerance V) bool > 检查两个值是否在一个误差范围内 *** -#### func Merge(refer V, a V, b V) V +#### func Merge(refer V, a V, b V) V > 通过一个参考值合并两个数字 @@ -132,17 +132,17 @@ func ExampleIsContinuity() { > 通过一个参考值取消合并的两个数字 *** -#### func MergeToInt64(v1 V, v2 V) int64 +#### func MergeToInt64(v1 V, v2 V) int64 > 将两个数字合并为一个 int64 数字 *** -#### func UnMergeInt64(n int64) V, V +#### func UnMergeInt64(n int64) (V, V) > 将一个 int64 数字拆分为两个数字 *** -#### func ToContinuous(nums S) map[V]V +#### func ToContinuous(nums S) map[V]V > 将一组非连续的数字转换为从1开始的连续数字 > - 返回值是一个 map,key 是从 1 开始的连续数字,value 是原始数字 @@ -160,34 +160,34 @@ func ExampleToContinuous() { ``` *** -#### func CountDigits(num V) int +#### func CountDigits(num V) int > 接收一个整数 num 作为输入,并返回该数字的位数 *** -#### func GetDigitValue(num int64, digit int) int64 +#### func GetDigitValue(num int64, digit int) int64 > 接收一个整数 num 和一个表示目标位数的整数 digit 作为输入,并返 > 回数字 num 在指定位数上的数值。我们使用 math.Abs() 函数获取 num 的绝对值,并通 > 过除以10的操作将 num 移动到目标位数上。然后,通过取余运算得到位数上的数值 *** -#### func JoinNumbers(num1 V, n ...V) V +#### func JoinNumbers(num1 V, n ...V) V > 将一组数字连接起来 *** -#### func IsOdd(n V) bool +#### func IsOdd(n V) bool > 返回 n 是否为奇数 *** -#### func IsEven(n V) bool +#### func IsEven(n V) bool > 返回 n 是否为偶数 *** -#### func MakeLastDigitsZero(num T, digits int) T +#### func MakeLastDigitsZero(num T, digits int) T > 返回一个新的数,其中 num 的最后 digits 位数被设为零。 > - 函数首先创建一个 10 的 digits 次方的遮罩,然后通过整除和乘以这个遮罩来使 num 的最后 digits 位归零。 diff --git a/utils/memory/README.md b/utils/memory/README.md index 4d1c1804..25256182 100644 --- a/utils/memory/README.md +++ b/utils/memory/README.md @@ -38,7 +38,7 @@ > 运行持久化缓存程序 *** -#### func BindPersistCacheProgram(name string, handler OutputParamHandlerFunc, option ...*Option) func () +#### func BindPersistCacheProgram(name string, handler OutputParamHandlerFunc, option ...*Option) func () > 绑定持久化缓存程序 > - name 持久化缓存程序名称 @@ -52,7 +52,7 @@ > - 所有持久化程序绑定完成后,应该主动调用 Run 函数运行 *** -#### func BindAction(name string, handler Func) Func +#### func BindAction(name string, handler Func) Func > 绑定需要缓存的操作函数 > - name 缓存操作名称 @@ -92,7 +92,7 @@ func TestBindAction(t *testing.T) { *** -#### func NewOption() *Option +#### func NewOption() *Option *** diff --git a/utils/moving/README.md b/utils/moving/README.md index e2432178..8cefd3df 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -37,7 +37,7 @@ *** ## 详情信息 -#### func NewTwoDimensional(options ...TwoDimensionalOption[EID, PosType]) *TwoDimensional[EID, PosType] +#### func NewTwoDimensional(options ...TwoDimensionalOption[EID, PosType]) *TwoDimensional[EID, PosType] > 创建一个用于2D对象移动的实例(TwoDimensional) @@ -74,24 +74,24 @@ func TestNewTwoDimensional(t *testing.T) { *** -#### func WithTwoDimensionalTimeUnit(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalTimeUnit(duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定时间单位创建 > - 默认单位为1毫秒,最小单位也为1毫秒 *** -#### func WithTwoDimensionalIdleWaitTime(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalIdleWaitTime(duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定的空闲等待时间创建 > - 默认情况下在没有新的移动计划时将限制 100毫秒 + 移动间隔事件(默认100毫秒) *** -#### func WithTwoDimensionalInterval(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalInterval(duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定的移动间隔时间创建 *** -#### func NewEntity(guid int64, speed float64) *MoveEntity +#### func NewEntity(guid int64, speed float64) *MoveEntity *** diff --git a/utils/offset/README.md b/utils/offset/README.md index 3c359590..4dd1b328 100644 --- a/utils/offset/README.md +++ b/utils/offset/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewTime(offset time.Duration) *Time +#### func NewTime(offset time.Duration) *Time > 新建一个包含偏移的时间 @@ -44,17 +44,17 @@ > 设置全局偏移时间 *** -#### func GetGlobal() *Time +#### func GetGlobal() *Time > 获取全局偏移时间 *** -#### func Now() time.Time +#### func Now() time.Time > 获取当前时间偏移后的时间 *** -#### func Since(t time.Time) time.Duration +#### func Since(t time.Time) time.Duration > 获取当前时间偏移后的时间自从 t 以来经过的时间 diff --git a/utils/random/README.md b/utils/random/README.md index e6070429..4747b7fc 100644 --- a/utils/random/README.md +++ b/utils/random/README.md @@ -52,69 +52,69 @@ *** ## 详情信息 -#### func Dice() int +#### func Dice() int > 掷骰子 > - 常规掷骰子将返回 1-6 的随机数 *** -#### func DiceN(n int) int +#### func DiceN(n int) int > 掷骰子 > - 与 Dice 不同的是,将返回 1-N 的随机数 *** -#### func NetIP() net.IP +#### func NetIP() net.IP > 返回一个随机的IP地址 *** -#### func Port() int +#### func Port() int > 返回一个随机的端口号 *** -#### func IPv4() string +#### func IPv4() string > 返回一个随机产生的IPv4地址。 *** -#### func IPv4Port() string +#### func IPv4Port() string > 返回一个随机产生的IPv4地址和端口。 *** -#### func Int64(min int64, max int64) int64 +#### func Int64(min int64, max int64) int64 > 返回一个介于min和max之间的int64类型的随机数。 *** -#### func Int(min int, max int) int +#### func Int(min int, max int) int > 返回一个介于min和max之间的的int类型的随机数。 *** -#### func Duration(min int64, max int64) time.Duration +#### func Duration(min int64, max int64) time.Duration > 返回一个介于min和max之间的的Duration类型的随机数。 *** -#### func Float64() float64 +#### func Float64() float64 > 返回一个0~1的浮点数 *** -#### func Float32() float32 +#### func Float32() float32 > 返回一个0~1的浮点数 *** -#### func IntN(n int) int +#### func IntN(n int) int > 返回一个0~n的整数 *** -#### func Bool() bool +#### func Bool() bool > 返回一个随机的布尔值 @@ -153,13 +153,13 @@ func TestProbabilitySlice(t *testing.T) { > - 当总概率小于 1 将会发生未命中的情况 *** -#### func Probability(p int, full ...int) bool +#### func Probability(p int, full ...int) bool > 输入一个概率,返回是否命中 > - 当 full 不为空时,将以 full 为基数,p 为分子,计算命中概率 *** -#### func ProbabilityChooseOne(ps ...int) int +#### func ProbabilityChooseOne(ps ...int) int > 输入一组概率,返回命中的索引 @@ -168,39 +168,39 @@ func TestProbabilitySlice(t *testing.T) { *** -#### func ChineseName() string +#### func ChineseName() string > 返回一个随机组成的中文姓名。 *** -#### func EnglishName() string +#### func EnglishName() string > 返回一个随机组成的英文姓名。 *** -#### func Name() string +#### func Name() string > 返回一个随机组成的中文或英文姓名 > - 以1/2的概率决定生产的是中文还是英文姓名。 *** -#### func NumberString(min int, max int) string +#### func NumberString(min int, max int) string > 返回一个介于min和max之间的string类型的随机数。 *** -#### func NumberStringRepair(min int, max int) string +#### func NumberStringRepair(min int, max int) string > 返回一个介于min和max之间的string类型的随机数 > - 通过Int64生成一个随机数,当结果的字符串长度小于max的字符串长度的情况下,使用0在开头补齐。 *** -#### func HostName() string +#### func HostName() string > 返回一个随机产生的hostname。 *** -#### func WeightSlice(getWeightHandle func (data T) int64, data ...T) T +#### func WeightSlice(getWeightHandle func (data T) int64, data ...T) T > 按权重随机从切片中产生一个数据并返回 @@ -210,7 +210,7 @@ func TestProbabilitySlice(t *testing.T) { > 按权重随机从切片中产生一个数据并返回数据和对应索引 *** -#### func WeightMap(getWeightHandle func (data T) int64, data map[K]T) T +#### func WeightMap(getWeightHandle func (data T) int64, data map[K]T) T > 按权重随机从map中产生一个数据并返回 diff --git a/utils/reflects/README.md b/utils/reflects/README.md index bf685e0d..950e693b 100644 --- a/utils/reflects/README.md +++ b/utils/reflects/README.md @@ -52,7 +52,7 @@ > 包装函数,后置函数执行后 *** -#### func GetPtrUnExportFiled(s reflect.Value, filedIndex int) reflect.Value +#### func GetPtrUnExportFiled(s reflect.Value, filedIndex int) reflect.Value > 获取指针类型的未导出字段 @@ -62,12 +62,12 @@ > 设置指针类型的未导出字段 *** -#### func Copy(s reflect.Value) reflect.Value +#### func Copy(s reflect.Value) reflect.Value > 拷贝 *** -#### func GetPointer(src T) reflect.Value +#### func GetPointer(src T) reflect.Value > 获取指针 diff --git a/utils/runtimes/README.md b/utils/runtimes/README.md index 63c3494c..b716fdb6 100644 --- a/utils/runtimes/README.md +++ b/utils/runtimes/README.md @@ -29,27 +29,27 @@ *** ## 详情信息 -#### func GetWorkingDir() string +#### func GetWorkingDir() string > 获取工作目录绝对路径 *** -#### func GetTempDir() string +#### func GetTempDir() string > 获取系统临时目录 *** -#### func GetExecutablePathByBuild() string +#### func GetExecutablePathByBuild() string > 获取当前执行文件绝对路径(go build) *** -#### func GetExecutablePathByCaller() string +#### func GetExecutablePathByCaller() string > 获取当前执行文件绝对路径(go run) *** -#### func CurrentRunningFuncName(skip ...int) string +#### func CurrentRunningFuncName(skip ...int) string > 获取正在运行的函数名 diff --git a/utils/sole/README.md b/utils/sole/README.md index b02da323..786c068f 100644 --- a/utils/sole/README.md +++ b/utils/sole/README.md @@ -55,7 +55,7 @@ > 解除注销特定命名空间的唯一标识符 *** -#### func Get() int64 +#### func Get() int64 > 获取全局唯一标识符 @@ -65,7 +65,7 @@ > 重置全局唯一标识符 *** -#### func GetWith(name any) int64 +#### func GetWith(name any) int64 > 获取特定命名空间的唯一标识符 @@ -75,17 +75,17 @@ > 重置特定命名空间的唯一标识符 *** -#### func NewOnce() *Once[V] +#### func NewOnce() *Once[V] > 创建一个用于数据取值去重的结构实例 *** -#### func SonyflakeIDE() int64, error +#### func SonyflakeIDE() (int64, error) > 获取一个雪花id *** -#### func SonyflakeID() int64 +#### func SonyflakeID() int64 > 获取一个雪花id @@ -95,32 +95,32 @@ > 配置雪花id生成策略 *** -#### func AutoIncrementUint32() uint32 +#### func AutoIncrementUint32() uint32 > 获取一个自增的 uint32 值 *** -#### func AutoIncrementUint64() uint64 +#### func AutoIncrementUint64() uint64 > 获取一个自增的 uint64 值 *** -#### func AutoIncrementInt32() int32 +#### func AutoIncrementInt32() int32 > 获取一个自增的 int32 值 *** -#### func AutoIncrementInt64() int64 +#### func AutoIncrementInt64() int64 > 获取一个自增的 int64 值 *** -#### func AutoIncrementInt() int +#### func AutoIncrementInt() int > 获取一个自增的 int 值 *** -#### func AutoIncrementString() string +#### func AutoIncrementString() string > 获取一个自增的字符串 diff --git a/utils/sorts/README.md b/utils/sorts/README.md index 6375347c..bc1ef79d 100644 --- a/utils/sorts/README.md +++ b/utils/sorts/README.md @@ -25,7 +25,7 @@ *** ## 详情信息 -#### func Topological(slice S, queryIndexHandler func (item V) Index, queryDependsHandler func (item V) []Index) S, error +#### func Topological(slice S, queryIndexHandler func (item V) Index, queryDependsHandler func (item V) []Index) (S, error) > 拓扑排序是一种对有向图进行排序的算法,它可以用来解决一些依赖关系的问题,比如计算字段的依赖关系。拓扑排序会将存在依赖关系的元素进行排序,使得依赖关系的元素总是排在被依赖的元素之前。 > - slice: 需要排序的切片 diff --git a/utils/str/README.md b/utils/str/README.md index 04e35d04..a0d020ac 100644 --- a/utils/str/README.md +++ b/utils/str/README.md @@ -46,7 +46,7 @@ *** ## 详情信息 -#### func RangeLine(eachString string, eachFunc func (index int, line string) error) error +#### func RangeLine(eachString string, eachFunc func (index int, line string) error) error > 对传入的eachString进行按行切片后再进行遍历 > - 该函数会预先对“\r\n”进行处理替换为“\n”。 @@ -54,72 +54,72 @@ > - index表示了当前行的行号(由0开始),line表示了当前行的内容。 *** -#### func SplitTrimSpace(str string, sep string) []string +#### func SplitTrimSpace(str string, sep string) []string > 按照空格分割字符串并去除空格 *** -#### func FirstUpper(str string) string +#### func FirstUpper(str string) string > 首字母大写 *** -#### func FirstLower(str string) string +#### func FirstLower(str string) string > 首字母小写 *** -#### func FirstUpperBytes(str []byte) []byte +#### func FirstUpperBytes(str []byte) []byte > 首字母大写 *** -#### func FirstLowerBytes(str []byte) []byte +#### func FirstLowerBytes(str []byte) []byte > 首字母小写 *** -#### func IsEmpty(str string) bool +#### func IsEmpty(str string) bool > 判断字符串是否为空 *** -#### func IsEmptyBytes(str []byte) bool +#### func IsEmptyBytes(str []byte) bool > 判断字符串是否为空 *** -#### func IsNotEmpty(str string) bool +#### func IsNotEmpty(str string) bool > 判断字符串是否不为空 *** -#### func IsNotEmptyBytes(str []byte) bool +#### func IsNotEmptyBytes(str []byte) bool > 判断字符串是否不为空 *** -#### func SnakeString(str string) string +#### func SnakeString(str string) string > 蛇形字符串 *** -#### func SnakeStringBytes(str []byte) []byte +#### func SnakeStringBytes(str []byte) []byte > 蛇形字符串 *** -#### func CamelString(str string) string +#### func CamelString(str string) string > 驼峰字符串 *** -#### func CamelStringBytes(str []byte) []byte +#### func CamelStringBytes(str []byte) []byte > 驼峰字符串 *** -#### func SortJoin(delimiter string, s ...string) string +#### func SortJoin(delimiter string, s ...string) string > 将多个字符串排序后拼接 @@ -130,19 +130,19 @@ > - 隐藏身份证、邮箱、手机号等敏感信息用*号替代 *** -#### func ThousandsSeparator(str string) string +#### func ThousandsSeparator(str string) string > 返回将str进行千位分隔符处理后的字符串。 *** -#### func KV(str string, tag ...string) string, string +#### func KV(str string, tag ...string) (string, string) > 返回str经过转换后形成的key、value > - 这里tag表示使用什么字符串来区分key和value的分隔符。 > - 默认情况即不传入tag的情况下分隔符为“=”。 *** -#### func FormatSpeedyInt(numberStr string) int, error +#### func FormatSpeedyInt(numberStr string) (int, error) > 返回numberStr经过格式化后去除空格和“,”分隔符的结果 > - 当字符串为“123,456,789”的时候,返回结果为“123456789”。 @@ -150,7 +150,7 @@ > - 当字符串为“1 23, 45 6, 789”的时候,返回结果为“123456789”。 *** -#### func FormatSpeedyInt64(numberStr string) int64, error +#### func FormatSpeedyInt64(numberStr string) (int64, error) > 返回numberStr经过格式化后去除空格和“,”分隔符的结果 > - 当字符串为“123,456,789”的时候,返回结果为“123456789”。 @@ -158,7 +158,7 @@ > - 当字符串为“1 23, 45 6, 789”的时候,返回结果为“123456789”。 *** -#### func FormatSpeedyFloat32(numberStr string) float64, error +#### func FormatSpeedyFloat32(numberStr string) (float64, error) > 返回numberStr经过格式化后去除空格和“,”分隔符的结果 > - 当字符串为“123,456,789.123”的时候,返回结果为“123456789.123”。 @@ -166,7 +166,7 @@ > - 当字符串为“1 23, 45 6, 789.123”的时候,返回结果为“123456789.123”。 *** -#### func FormatSpeedyFloat64(numberStr string) float64, error +#### func FormatSpeedyFloat64(numberStr string) (float64, error) > 返回numberStr经过格式化后去除空格和“,”分隔符的结果 > - 当字符串为“123,456,789.123”的时候,返回结果为“123456789.123”。 diff --git a/utils/super/README.md b/utils/super/README.md index b3561fb2..a3feb3f5 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -104,12 +104,12 @@ *** ## 详情信息 -#### func NewBitSet(bits ...Bit) *BitSet[Bit] +#### func NewBitSet(bits ...Bit) *BitSet[Bit] > 通过指定的 Bit 位创建一个 BitSet *** -#### func TryWriteChannel(ch chan T, data T) bool +#### func TryWriteChannel(ch chan T, data T) bool > 尝试写入 channel,如果 channel 无法写入则忽略,返回是否写入成功 > - 无法写入的情况包括:channel 已满、channel 已关闭 @@ -121,18 +121,18 @@ > - 无法写入的情况包括:channel 已满、channel 已关闭 *** -#### func RegError(code int, message string) error +#### func RegError(code int, message string) error > 通过错误码注册错误,返回错误的引用 *** -#### func RegErrorRef(code int, message string, ref error) error +#### func RegErrorRef(code int, message string, ref error) error > 通过错误码注册错误,返回错误的引用 > - 引用将会被重定向到注册的错误信息 *** -#### func GetError(err error) int, error +#### func GetError(err error) (int, error) > 通过错误引用获取错误码和真实错误信息,如果错误不存在则返回 0,如果错误引用不存在则返回原本的错误 @@ -155,7 +155,7 @@ func TestGetError(t *testing.T) { *** -#### func RecoverTransform(a any) error +#### func RecoverTransform(a any) error > recover 错误转换 @@ -179,7 +179,7 @@ func ExampleRecoverTransform() { > 执行 f 函数,如果 f 为 nil,则不执行 *** -#### func HandleErr(f func () error) error +#### func HandleErr(f func () error) error > 执行 f 函数,如果 f 为 nil,则不执行 @@ -194,43 +194,43 @@ func ExampleRecoverTransform() { > go 代码格式化 *** -#### func If(expression bool, t T, f T) T +#### func If(expression bool, t T, f T) T *** -#### func MarshalJSON(v interface {}) []byte +#### func MarshalJSON(v interface {}) []byte > 将对象转换为 json > - 当转换失败时,将返回 json 格式的空对象 *** -#### func MarshalJSONE(v interface {}) []byte, error +#### func MarshalJSONE(v interface {}) ([]byte, error) > 将对象转换为 json > - 当转换失败时,将返回错误信息 *** -#### func UnmarshalJSON(data []byte, v interface {}) error +#### func UnmarshalJSON(data []byte, v interface {}) error > 将 json 转换为对象 *** -#### func MarshalIndentJSON(v interface {}, prefix string, indent string) []byte +#### func MarshalIndentJSON(v interface {}, prefix string, indent string) []byte > 将对象转换为 json *** -#### func MarshalToTargetWithJSON(src interface {}, dest interface {}) error +#### func MarshalToTargetWithJSON(src interface {}, dest interface {}) error > 将对象转换为目标对象 *** -#### func StartLossCounter() *LossCounter +#### func StartLossCounter() *LossCounter > 开始损耗计数 *** -#### func Match(value Value) *Matcher[Value, Result] +#### func Match(value Value) *Matcher[Value, Result] > 匹配 @@ -255,12 +255,12 @@ func TestMatch(t *testing.T) { *** -#### func IsNumber(v any) bool +#### func IsNumber(v any) bool > 判断是否为数字 *** -#### func NumberToRome(num int) string +#### func NumberToRome(num int) string > 将数字转换为罗马数字 @@ -290,167 +290,167 @@ func TestNumberToRome(t *testing.T) { *** -#### func StringToInt(value string) int +#### func StringToInt(value string) int > 字符串转换为整数 *** -#### func StringToFloat64(value string) float64 +#### func StringToFloat64(value string) float64 > 字符串转换为 float64 *** -#### func StringToBool(value string) bool +#### func StringToBool(value string) bool > 字符串转换为 bool *** -#### func StringToUint64(value string) uint64 +#### func StringToUint64(value string) uint64 > 字符串转换为 uint64 *** -#### func StringToUint(value string) uint +#### func StringToUint(value string) uint > 字符串转换为 uint *** -#### func StringToFloat32(value string) float32 +#### func StringToFloat32(value string) float32 > 字符串转换为 float32 *** -#### func StringToInt64(value string) int64 +#### func StringToInt64(value string) int64 > 字符串转换为 int64 *** -#### func StringToUint32(value string) uint32 +#### func StringToUint32(value string) uint32 > 字符串转换为 uint32 *** -#### func StringToInt32(value string) int32 +#### func StringToInt32(value string) int32 > 字符串转换为 int32 *** -#### func StringToUint16(value string) uint16 +#### func StringToUint16(value string) uint16 > 字符串转换为 uint16 *** -#### func StringToInt16(value string) int16 +#### func StringToInt16(value string) int16 > 字符串转换为 int16 *** -#### func StringToUint8(value string) uint8 +#### func StringToUint8(value string) uint8 > 字符串转换为 uint8 *** -#### func StringToInt8(value string) int8 +#### func StringToInt8(value string) int8 > 字符串转换为 int8 *** -#### func StringToByte(value string) byte +#### func StringToByte(value string) byte > 字符串转换为 byte *** -#### func StringToRune(value string) rune +#### func StringToRune(value string) rune > 字符串转换为 rune *** -#### func IntToString(value int) string +#### func IntToString(value int) string > 整数转换为字符串 *** -#### func Float64ToString(value float64) string +#### func Float64ToString(value float64) string > float64 转换为字符串 *** -#### func BoolToString(value bool) string +#### func BoolToString(value bool) string > bool 转换为字符串 *** -#### func Uint64ToString(value uint64) string +#### func Uint64ToString(value uint64) string > uint64 转换为字符串 *** -#### func UintToString(value uint) string +#### func UintToString(value uint) string > uint 转换为字符串 *** -#### func Float32ToString(value float32) string +#### func Float32ToString(value float32) string > float32 转换为字符串 *** -#### func Int64ToString(value int64) string +#### func Int64ToString(value int64) string > int64 转换为字符串 *** -#### func Uint32ToString(value uint32) string +#### func Uint32ToString(value uint32) string > uint32 转换为字符串 *** -#### func Int32ToString(value int32) string +#### func Int32ToString(value int32) string > int32 转换为字符串 *** -#### func Uint16ToString(value uint16) string +#### func Uint16ToString(value uint16) string > uint16 转换为字符串 *** -#### func Int16ToString(value int16) string +#### func Int16ToString(value int16) string > int16 转换为字符串 *** -#### func Uint8ToString(value uint8) string +#### func Uint8ToString(value uint8) string > uint8 转换为字符串 *** -#### func Int8ToString(value int8) string +#### func Int8ToString(value int8) string > int8 转换为字符串 *** -#### func ByteToString(value byte) string +#### func ByteToString(value byte) string > byte 转换为字符串 *** -#### func RuneToString(value rune) string +#### func RuneToString(value rune) string > rune 转换为字符串 *** -#### func StringToSlice(value string) []string +#### func StringToSlice(value string) []string > 字符串转换为切片 *** -#### func SliceToString(value []string) string +#### func SliceToString(value []string) string > 切片转换为字符串 *** -#### func NewPermission() *Permission[Code, EntityID] +#### func NewPermission() *Permission[Code, EntityID] > 创建权限 @@ -488,19 +488,19 @@ func TestNewPermission(t *testing.T) { *** -#### func Retry(count int, interval time.Duration, f func () error) error +#### func Retry(count int, interval time.Duration, f func () error) error > 根据提供的 count 次数尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功或者达到 count 次数 *** -#### func RetryByRule(f func () error, rule func (count int) time.Duration) error +#### func RetryByRule(f func () error, rule func (count int) time.Duration) error > 根据提供的规则尝试执行 f 函数,如果 f 函数返回错误,则根据 rule 的返回值进行重试 > - rule 将包含一个入参,表示第几次重试,返回值表示下一次重试的时间间隔,当返回值为 0 时,表示不再重试 > - rule 的 count 将在 f 首次失败后变为 1,因此 rule 的入参将从 1 开始 *** -#### func RetryByExponentialBackoff(f func () error, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error +#### func RetryByExponentialBackoff(f func () error, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error > 根据指数退避算法尝试执行 f 函数 > - maxRetries:最大重试次数 @@ -511,7 +511,7 @@ func TestNewPermission(t *testing.T) { > - ignoreErrors:忽略的错误,当 f 返回的错误在 ignoreErrors 中时,将不会进行重试 *** -#### func ConditionalRetryByExponentialBackoff(f func () error, cond func () bool, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error +#### func ConditionalRetryByExponentialBackoff(f func () error, cond func () bool, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error > 该函数与 RetryByExponentialBackoff 类似,但是可以被中断 > - cond 为中断条件,当 cond 返回 false 时,将会中断重试 @@ -532,37 +532,37 @@ func TestNewPermission(t *testing.T) { > 根据提供的 interval 时间间隔尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功 *** -#### func NewStackGo() *StackGo +#### func NewStackGo() *StackGo > 返回一个用于获取上一个协程调用的堆栈信息的收集器 *** -#### func LaunchTime() time.Time +#### func LaunchTime() time.Time > 获取程序启动时间 *** -#### func Hostname() string +#### func Hostname() string > 获取主机名 *** -#### func PID() int +#### func PID() int > 获取进程 PID *** -#### func StringToBytes(s string) []byte +#### func StringToBytes(s string) []byte > 以零拷贝的方式将字符串转换为字节切片 *** -#### func BytesToString(b []byte) string +#### func BytesToString(b []byte) string > 以零拷贝的方式将字节切片转换为字符串 *** -#### func Convert(src A) B +#### func Convert(src A) B > 以零拷贝的方式将一个对象转换为另一个对象 > - 两个对象字段必须完全一致 @@ -591,7 +591,7 @@ func TestConvert(t *testing.T) { *** -#### func Verify(handle func ( V)) *VerifyHandle[V] +#### func Verify(handle func ( V)) *VerifyHandle[V] > 对特定表达式进行校验,当表达式不成立时,将执行 handle @@ -618,7 +618,7 @@ func ExampleVerify() { ``` *** -#### func OldVersion(version1 string, version2 string) bool +#### func OldVersion(version1 string, version2 string) bool > 检查 version2 对于 version1 来说是不是旧版本 @@ -677,7 +677,7 @@ func BenchmarkOldVersion(b *testing.B) { *** -#### func CompareVersion(version1 string, version2 string) int +#### func CompareVersion(version1 string, version2 string) int > 返回一个整数,用于表示两个版本号的比较结果: > - 如果 version1 大于 version2,它将返回 1 @@ -901,7 +901,7 @@ func TestBitSet_Shrink(t *testing.T) { #### func (*BitSet) String() string > 返回当前 BitSet 的字符串表示 *** -#### func (*BitSet) MarshalJSON() []byte, error +#### func (*BitSet) MarshalJSON() ( []byte, error) > 实现 json.Marshaler 接口 *** #### func (*BitSet) UnmarshalJSON(data []byte) error diff --git a/utils/timer/README.md b/utils/timer/README.md index 63d2f226..9200c3ff 100644 --- a/utils/timer/README.md +++ b/utils/timer/README.md @@ -66,17 +66,17 @@ > 偏移时间新的一天事件 *** -#### func WithCaller(handle func (name string, caller func ())) Option +#### func WithCaller(handle func (name string, caller func ())) Option > 通过其他的 handle 执行 Caller *** -#### func WithMark(mark string) Option +#### func WithMark(mark string) Option > 通过特定的标记创建定时器 *** -#### func NewPool(tickerPoolSize int) *Pool +#### func NewPool(tickerPoolSize int) *Pool > 创建一个定时器池,当 tickerPoolSize 小于等于 0 时,将会引发 panic,可指定为 DefaultTickerPoolSize @@ -87,7 +87,7 @@ > - 默认值为 DefaultTickerPoolSize,当定时器池中的定时器不足时,会自动创建新的定时器,当定时器释放时,会将多余的定时器进行释放,否则将放入定时器池中 *** -#### func GetTicker(size int, options ...Option) *Ticker +#### func GetTicker(size int, options ...Option) *Ticker > 获取标准池中的一个定时器 diff --git a/utils/times/README.md b/utils/times/README.md index ff82c67e..66dbdc19 100644 --- a/utils/times/README.md +++ b/utils/times/README.md @@ -78,12 +78,12 @@ *** ## 详情信息 -#### func CalcNextSec(sec int) int +#### func CalcNextSec(sec int) int > 计算下一个N秒在多少秒之后 *** -#### func CalcNextSecWithTime(t time.Time, sec int) int +#### func CalcNextSecWithTime(t time.Time, sec int) int > 计算下一个N秒在多少秒之后 @@ -98,7 +98,7 @@ func ExampleCalcNextSecWithTime() { ``` *** -#### func CalcNextTimeWithRefer(now time.Time, refer time.Duration) time.Time +#### func CalcNextTimeWithRefer(now time.Time, refer time.Duration) time.Time > 根据参考时间计算下一个整点时间 > - 假设当 now 为 14:15:16 , 参考时间为 10 分钟, 则返回 14:20:00 @@ -112,69 +112,69 @@ func ExampleCalcNextSecWithTime() { > 针对 IntervalFormat 函数设置格式化内容 *** -#### func IntervalFormat(time1 time.Time, time2 time.Time) string +#### func IntervalFormat(time1 time.Time, time2 time.Time) string > 返回指定时间戳之间的间隔 > - 使用传入的时间进行计算换算,将结果体现为几年前、几天前、几小时前、几分钟前、几秒前。 *** -#### func GetMonthDays(t time.Time) int +#### func GetMonthDays(t time.Time) int > 获取一个时间当月共有多少天 *** -#### func WeekDay(t time.Time) int +#### func WeekDay(t time.Time) int > 获取一个时间是星期几 > - 1 ~ 7 *** -#### func GetNextDayInterval(t time.Time) time.Duration +#### func GetNextDayInterval(t time.Time) time.Duration > 获取一个时间到下一天间隔多少秒 *** -#### func GetToday(t time.Time) time.Time +#### func GetToday(t time.Time) time.Time > 获取一个时间的今天 *** -#### func GetSecond(d time.Duration) int +#### func GetSecond(d time.Duration) int > 获取共有多少秒 *** -#### func IsSameDay(t1 time.Time, t2 time.Time) bool +#### func IsSameDay(t1 time.Time, t2 time.Time) bool > 两个时间是否是同一天 *** -#### func IsSameHour(t1 time.Time, t2 time.Time) bool +#### func IsSameHour(t1 time.Time, t2 time.Time) bool > 两个时间是否是同一小时 *** -#### func GetMondayZero(t time.Time) time.Time +#### func GetMondayZero(t time.Time) time.Time > 获取本周一零点 *** -#### func Date(year int, month time.Month, day int) time.Time +#### func Date(year int, month time.Month, day int) time.Time > 返回一个特定日期的时间 *** -#### func DateWithHMS(year int, month time.Month, day int, hour int, min int, sec int) time.Time +#### func DateWithHMS(year int, month time.Month, day int, hour int, min int, sec int) time.Time > 返回一个精确到秒的时间 *** -#### func GetDeltaDay(t1 time.Time, t2 time.Time) int +#### func GetDeltaDay(t1 time.Time, t2 time.Time) int > 获取两个时间需要加减的天数 *** -#### func GetDeltaWeek(t1 time.Time, t2 time.Time) int +#### func GetDeltaWeek(t1 time.Time, t2 time.Time) int > 获取两个时间需要加减的周数 @@ -184,73 +184,73 @@ func ExampleCalcNextSecWithTime() { > 从时间字符串中获取时分秒 *** -#### func GetTimeFromString(timeStr string, layout string) time.Time +#### func GetTimeFromString(timeStr string, layout string) time.Time > 将时间字符串转化为时间 *** -#### func GetDayZero(t time.Time, day int) time.Time +#### func GetDayZero(t time.Time, day int) time.Time > 获取 t 增加 day 天后的零点时间 *** -#### func GetYesterday(t time.Time) time.Time +#### func GetYesterday(t time.Time) time.Time > 获取昨天 *** -#### func GetDayLast(t time.Time) time.Time +#### func GetDayLast(t time.Time) time.Time > 获取某天的最后一刻 > - 最后一刻即 23:59:59 *** -#### func GetYesterdayLast(t time.Time) time.Time +#### func GetYesterdayLast(t time.Time) time.Time > 获取昨天最后一刻 *** -#### func GetMinuteStart(t time.Time) time.Time +#### func GetMinuteStart(t time.Time) time.Time > 获取一个时间的 0 秒 *** -#### func GetMinuteEnd(t time.Time) time.Time +#### func GetMinuteEnd(t time.Time) time.Time > 获取一个时间的 59 秒 *** -#### func GetHourStart(t time.Time) time.Time +#### func GetHourStart(t time.Time) time.Time > 获取一个时间的 0 分 0 秒 *** -#### func GetHourEnd(t time.Time) time.Time +#### func GetHourEnd(t time.Time) time.Time > 获取一个时间的 59 分 59 秒 *** -#### func GetMonthStart(t time.Time) time.Time +#### func GetMonthStart(t time.Time) time.Time > 获取一个时间的月初 *** -#### func GetMonthEnd(t time.Time) time.Time +#### func GetMonthEnd(t time.Time) time.Time > 获取一个时间的月末 *** -#### func GetYearStart(t time.Time) time.Time +#### func GetYearStart(t time.Time) time.Time > 获取一个时间的年初 *** -#### func GetYearEnd(t time.Time) time.Time +#### func GetYearEnd(t time.Time) time.Time > 获取一个时间的年末 *** -#### func NewStateLine(zero State) *StateLine[State] +#### func NewStateLine(zero State) *StateLine[State] > 创建一个从左向右由早到晚的状态时间线 @@ -304,12 +304,12 @@ func TestSetGlobalTimeOffset(t *testing.T) { *** -#### func NowByNotOffset() time.Time +#### func NowByNotOffset() time.Time > 获取未偏移的当前时间 *** -#### func GetGlobalTimeOffset() time.Duration +#### func GetGlobalTimeOffset() time.Duration > 获取全局时间偏移量 @@ -319,13 +319,13 @@ func TestSetGlobalTimeOffset(t *testing.T) { > 重置全局时间偏移量 *** -#### func NewPeriod(start time.Time, end time.Time) Period +#### func NewPeriod(start time.Time, end time.Time) Period > 创建一个时间段 > - 如果 start 比 end 晚,则会自动交换两个时间 *** -#### func NewPeriodWindow(t time.Time, size time.Duration) Period +#### func NewPeriodWindow(t time.Time, size time.Duration) Period > 创建一个特定长度的时间窗口 @@ -349,52 +349,52 @@ func TestNewPeriodWindow(t *testing.T) { *** -#### func NewPeriodWindowWeek(t time.Time) Period +#### func NewPeriodWindowWeek(t time.Time) Period > 创建一周长度的时间窗口,从周一零点开始至周日 23:59:59 结束 *** -#### func NewPeriodWithTimeArray(times [2]time.Time) Period +#### func NewPeriodWithTimeArray(times [2]time.Time) Period > 创建一个时间段 *** -#### func NewPeriodWithDayZero(t time.Time, day int) Period +#### func NewPeriodWithDayZero(t time.Time, day int) Period > 创建一个时间段,从 t 开始,持续到 day 天后的 0 点 *** -#### func NewPeriodWithDay(t time.Time, day int) Period +#### func NewPeriodWithDay(t time.Time, day int) Period > 创建一个时间段,从 t 开始,持续 day 天 *** -#### func NewPeriodWithHour(t time.Time, hour int) Period +#### func NewPeriodWithHour(t time.Time, hour int) Period > 创建一个时间段,从 t 开始,持续 hour 小时 *** -#### func NewPeriodWithMinute(t time.Time, minute int) Period +#### func NewPeriodWithMinute(t time.Time, minute int) Period > 创建一个时间段,从 t 开始,持续 minute 分钟 *** -#### func NewPeriodWithSecond(t time.Time, second int) Period +#### func NewPeriodWithSecond(t time.Time, second int) Period > 创建一个时间段,从 t 开始,持续 second 秒 *** -#### func NewPeriodWithMillisecond(t time.Time, millisecond int) Period +#### func NewPeriodWithMillisecond(t time.Time, millisecond int) Period > 创建一个时间段,从 t 开始,持续 millisecond 毫秒 *** -#### func NewPeriodWithMicrosecond(t time.Time, microsecond int) Period +#### func NewPeriodWithMicrosecond(t time.Time, microsecond int) Period > 创建一个时间段,从 t 开始,持续 microsecond 微秒 *** -#### func NewPeriodWithNanosecond(t time.Time, nanosecond int) Period +#### func NewPeriodWithNanosecond(t time.Time, nanosecond int) Period > 创建一个时间段,从 t 开始,持续 nanosecond 纳秒 diff --git a/utils/xlsxtool/README.md b/utils/xlsxtool/README.md index 2fefedba..1dbcee73 100644 --- a/utils/xlsxtool/README.md +++ b/utils/xlsxtool/README.md @@ -25,7 +25,7 @@ *** ## 详情信息 -#### func GetSheetMatrix(sheet *xlsx.Sheet) *matrix.Matrix[*xlsx.Cell] +#### func GetSheetMatrix(sheet *xlsx.Sheet) *matrix.Matrix[*xlsx.Cell] > 将sheet转换为二维矩阵 From e7e679ea8662d84aa9014dd26e643cc57d3374d7 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 10:41:10 +0800 Subject: [PATCH 02/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E4=B8=AD=E6=97=A0=E6=B3=95=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BD=93=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configuration/README.md | 6 +- game/activity/README.md | 33 ++++-- .../activity/internal/example/types/README.md | 3 +- game/fight/README.md | 15 ++- game/space/README.md | 12 +- game/task/README.md | 15 ++- notify/README.md | 9 +- notify/notifies/README.md | 12 +- notify/senders/README.md | 3 +- planner/pce/README.md | 111 ++++++++++++------ planner/pce/cs/README.md | 6 +- planner/pce/tmpls/README.md | 6 +- server/README.md | 60 ++++++---- server/client/README.md | 21 ++-- server/gateway/README.md | 21 ++-- server/internal/dispatcher/README.md | 18 ++- server/internal/logger/README.md | 6 +- server/lockstep/README.md | 12 +- server/router/README.md | 9 +- server/writeloop/README.md | 9 +- utils/aoi/README.md | 9 +- utils/arrangement/README.md | 30 +++-- utils/buffer/README.md | 9 +- utils/collection/README.md | 6 +- utils/collection/listings/README.md | 12 +- utils/collection/mappings/README.md | 3 +- utils/combination/README.md | 21 ++-- utils/deck/README.md | 9 +- utils/fsm/README.md | 6 +- utils/generator/astgo/README.md | 21 ++-- utils/generator/genreadme/README.md | 3 +- utils/generator/genreadme/builder.go | 3 +- utils/generic/README.md | 45 ++++--- utils/geometry/README.md | 27 +++-- utils/geometry/astar/README.md | 3 +- utils/geometry/dp/README.md | 6 +- utils/geometry/matrix/README.md | 3 +- utils/geometry/navmesh/README.md | 3 +- utils/hub/README.md | 3 +- utils/huge/README.md | 6 +- utils/leaderboard/README.md | 9 +- utils/log/README.md | 12 +- utils/log/survey/README.md | 21 ++-- utils/maths/README.md | 3 +- utils/memory/README.md | 3 +- utils/moving/README.md | 12 +- utils/offset/README.md | 3 +- utils/sole/README.md | 3 +- utils/super/README.md | 18 ++- utils/timer/README.md | 15 ++- utils/times/README.md | 6 +- 51 files changed, 480 insertions(+), 240 deletions(-) diff --git a/configuration/README.md b/configuration/README.md index d3c23ccd..a913c410 100644 --- a/configuration/README.md +++ b/configuration/README.md @@ -29,8 +29,8 @@ configuration 基于配置导表功能实现的配置加载及刷新功能 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[RefreshEventHandle](#refresheventhandle)|配置刷新事件处理函数 -|`INTERFACE`|[Loader](#loader)|配置加载器 +|`STRUCT`|[RefreshEventHandle](#struct_RefreshEventHandle)|配置刷新事件处理函数 +|`INTERFACE`|[Loader](#struct_Loader)|配置加载器
@@ -75,11 +75,13 @@ configuration 基于配置导表功能实现的配置加载及刷新功能 *** + ### RefreshEventHandle `STRUCT` 配置刷新事件处理函数 ```go type RefreshEventHandle func() ``` + ### Loader `INTERFACE` 配置加载器 ```go diff --git a/game/activity/README.md b/game/activity/README.md index 77dd01e3..85c93600 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -43,17 +43,17 @@ activity 活动状态管理 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Activity](#activity)|活动描述 -|`STRUCT`|[Controller](#controller)|活动控制器 -|`INTERFACE`|[BasicActivityController](#basicactivitycontroller)|暂无描述... -|`INTERFACE`|[NoneDataActivityController](#nonedataactivitycontroller)|无数据活动控制器 -|`INTERFACE`|[GlobalDataActivityController](#globaldataactivitycontroller)|全局数据活动控制器 -|`INTERFACE`|[EntityDataActivityController](#entitydataactivitycontroller)|实体数据活动控制器 -|`INTERFACE`|[GlobalAndEntityDataActivityController](#globalandentitydataactivitycontroller)|全局数据和实体数据活动控制器 -|`STRUCT`|[DataMeta](#datameta)|全局活动数据 -|`STRUCT`|[EntityDataMeta](#entitydatameta)|活动实体数据 -|`STRUCT`|[UpcomingEventHandler](#upcomingeventhandler)|暂无描述... -|`STRUCT`|[Options](#options)|活动选项 +|`STRUCT`|[Activity](#struct_Activity)|活动描述 +|`STRUCT`|[Controller](#struct_Controller)|活动控制器 +|`INTERFACE`|[BasicActivityController](#struct_BasicActivityController)|暂无描述... +|`INTERFACE`|[NoneDataActivityController](#struct_NoneDataActivityController)|无数据活动控制器 +|`INTERFACE`|[GlobalDataActivityController](#struct_GlobalDataActivityController)|全局数据活动控制器 +|`INTERFACE`|[EntityDataActivityController](#struct_EntityDataActivityController)|实体数据活动控制器 +|`INTERFACE`|[GlobalAndEntityDataActivityController](#struct_GlobalAndEntityDataActivityController)|全局数据和实体数据活动控制器 +|`STRUCT`|[DataMeta](#struct_DataMeta)|全局活动数据 +|`STRUCT`|[EntityDataMeta](#struct_EntityDataMeta)|活动实体数据 +|`STRUCT`|[UpcomingEventHandler](#struct_UpcomingEventHandler)|暂无描述... +|`STRUCT`|[Options](#struct_Options)|活动选项
@@ -166,6 +166,7 @@ activity 活动状态管理 > 创建活动选项 *** + ### Activity `STRUCT` 活动描述 ```go @@ -185,6 +186,7 @@ type Activity[Type generic.Basic, ID generic.Basic] struct { initializeData func() } ``` + ### Controller `STRUCT` 活动控制器 ```go @@ -199,6 +201,7 @@ type Controller[Type generic.Basic, ID generic.Basic, Data any, EntityID generic mutex sync.RWMutex } ``` + ### BasicActivityController `INTERFACE` ```go @@ -209,6 +212,7 @@ type BasicActivityController[Type generic.Basic, ID generic.Basic, Data any, Ent Refresh(activityId ID) } ``` + ### NoneDataActivityController `INTERFACE` 无数据活动控制器 ```go @@ -217,6 +221,7 @@ type NoneDataActivityController[Type generic.Basic, ID generic.Basic, Data any, InitializeNoneData(handler func(activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData] } ``` + ### GlobalDataActivityController `INTERFACE` 全局数据活动控制器 ```go @@ -226,6 +231,7 @@ type GlobalDataActivityController[Type generic.Basic, ID generic.Basic, Data any InitializeGlobalData(handler func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData] } ``` + ### EntityDataActivityController `INTERFACE` 实体数据活动控制器 ```go @@ -235,6 +241,7 @@ type EntityDataActivityController[Type generic.Basic, ID generic.Basic, Data any InitializeEntityData(handler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData] } ``` + ### GlobalAndEntityDataActivityController `INTERFACE` 全局数据和实体数据活动控制器 ```go @@ -245,6 +252,7 @@ type GlobalAndEntityDataActivityController[Type generic.Basic, ID generic.Basic, InitializeGlobalAndEntityData(handler func(activityId ID, data *DataMeta[Data]), entityHandler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] } ``` + ### DataMeta `STRUCT` 全局活动数据 ```go @@ -254,6 +262,7 @@ type DataMeta[Data any] struct { LastNewDay time.Time } ``` + ### EntityDataMeta `STRUCT` 活动实体数据 ```go @@ -263,11 +272,13 @@ type EntityDataMeta[Data any] struct { LastNewDay time.Time } ``` + ### UpcomingEventHandler `STRUCT` ```go type UpcomingEventHandler[ID generic.Basic] func(activityId ID) ``` + ### Options `STRUCT` 活动选项 ```go diff --git a/game/activity/internal/example/types/README.md b/game/activity/internal/example/types/README.md index a7d0d51d..28267bf1 100644 --- a/game/activity/internal/example/types/README.md +++ b/game/activity/internal/example/types/README.md @@ -16,13 +16,14 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[DemoActivityData](#demoactivitydata)|暂无描述... +|`STRUCT`|[DemoActivityData](#struct_DemoActivityData)|暂无描述...
*** ## 详情信息 + ### DemoActivityData `STRUCT` ```go diff --git a/game/fight/README.md b/game/fight/README.md index 144feeb9..8f4bb3ef 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -23,11 +23,11 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[TurnBased](#turnbased)|回合制 -|`INTERFACE`|[TurnBasedControllerInfo](#turnbasedcontrollerinfo)|暂无描述... -|`INTERFACE`|[TurnBasedControllerAction](#turnbasedcontrolleraction)|暂无描述... -|`STRUCT`|[TurnBasedController](#turnbasedcontroller)|回合制控制器 -|`STRUCT`|[TurnBasedEntitySwitchEventHandler](#turnbasedentityswitcheventhandler)|暂无描述... +|`STRUCT`|[TurnBased](#struct_TurnBased)|回合制 +|`INTERFACE`|[TurnBasedControllerInfo](#struct_TurnBasedControllerInfo)|暂无描述... +|`INTERFACE`|[TurnBasedControllerAction](#struct_TurnBasedControllerAction)|暂无描述... +|`STRUCT`|[TurnBasedController](#struct_TurnBasedController)|回合制控制器 +|`STRUCT`|[TurnBasedEntitySwitchEventHandler](#struct_TurnBasedEntitySwitchEventHandler)|暂无描述... @@ -40,6 +40,7 @@ > - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间 *** + ### TurnBased `STRUCT` 回合制 ```go @@ -64,6 +65,7 @@ type TurnBased[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], closed bool } ``` + ### TurnBasedControllerInfo `INTERFACE` ```go @@ -77,6 +79,7 @@ type TurnBasedControllerInfo[CampID comparable, EntityID comparable, Camp generi Stop() } ``` + ### TurnBasedControllerAction `INTERFACE` ```go @@ -86,6 +89,7 @@ type TurnBasedControllerAction[CampID comparable, EntityID comparable, Camp gene Refresh(duration time.Duration) time.Time } ``` + ### TurnBasedController `STRUCT` 回合制控制器 ```go @@ -93,6 +97,7 @@ type TurnBasedController[CampID comparable, EntityID comparable, Camp generic.Id tb *TurnBased[CampID, EntityID, Camp, Entity] } ``` + ### TurnBasedEntitySwitchEventHandler `STRUCT` ```go diff --git a/game/space/README.md b/game/space/README.md index 55dc53c7..1e3542f2 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -24,10 +24,10 @@ space 游戏中常见的空间设计,例如房间、地图等 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[RoomController](#roomcontroller)|对房间进行操作的控制器,由 RoomManager 接管后返回 -|`STRUCT`|[RoomManager](#roommanager)|房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 -|`STRUCT`|[RoomAssumeControlEventHandle](#roomassumecontroleventhandle)|暂无描述... -|`STRUCT`|[RoomControllerOptions](#roomcontrolleroptions)|暂无描述... +|`STRUCT`|[RoomController](#struct_RoomController)|对房间进行操作的控制器,由 RoomManager 接管后返回 +|`STRUCT`|[RoomManager](#struct_RoomManager)|房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 +|`STRUCT`|[RoomAssumeControlEventHandle](#struct_RoomAssumeControlEventHandle)|暂无描述... +|`STRUCT`|[RoomControllerOptions](#struct_RoomControllerOptions)|暂无描述... @@ -54,6 +54,7 @@ func ExampleNewRoomManager() { > 创建房间控制器选项 *** + ### RoomController `STRUCT` 对房间进行操作的控制器,由 RoomManager 接管后返回 ```go @@ -68,6 +69,7 @@ type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[E owner *EntityID } ``` + ### RoomManager `STRUCT` 房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 - 该实例是线程安全的 @@ -78,11 +80,13 @@ type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[Enti rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] } ``` + ### RoomAssumeControlEventHandle `STRUCT` ```go type RoomAssumeControlEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room]) ``` + ### RoomControllerOptions `STRUCT` ```go diff --git a/game/task/README.md b/game/task/README.md index 9c116b9e..a86db7e0 100644 --- a/game/task/README.md +++ b/game/task/README.md @@ -34,11 +34,11 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Condition](#condition)|任务条件 -|`STRUCT`|[RefreshTaskCounterEventHandler](#refreshtaskcountereventhandler)|暂无描述... -|`STRUCT`|[Option](#option)|任务选项 -|`STRUCT`|[Status](#status)|暂无描述... -|`STRUCT`|[Task](#task)|是对任务信息进行描述和处理的结构体 +|`STRUCT`|[Condition](#struct_Condition)|任务条件 +|`STRUCT`|[RefreshTaskCounterEventHandler](#struct_RefreshTaskCounterEventHandler)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|任务选项 +|`STRUCT`|[Status](#struct_Status)|暂无描述... +|`STRUCT`|[Task](#struct_Task)|是对任务信息进行描述和处理的结构体 @@ -150,6 +150,7 @@ func TestCond(t *testing.T) { > 生成任务 *** + ### Condition `STRUCT` 任务条件 ```go @@ -221,16 +222,19 @@ type Condition map[any]any #### func (Condition) GetAny(key any) any > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 *** + ### RefreshTaskCounterEventHandler `STRUCT` ```go type RefreshTaskCounterEventHandler[Trigger any] func(taskType string, trigger Trigger, count int64) ``` + ### Option `STRUCT` 任务选项 ```go type Option func(task *Task) ``` + ### Status `STRUCT` ```go @@ -238,6 +242,7 @@ type Status byte ``` #### func (Status) String() string *** + ### Task `STRUCT` 是对任务信息进行描述和处理的结构体 ```go diff --git a/notify/README.md b/notify/README.md index 4d1c98e6..ea293bb4 100644 --- a/notify/README.md +++ b/notify/README.md @@ -23,9 +23,9 @@ notify 包含了对外部第三方通知的实现,如机器人消息等 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Manager](#manager)|通知管理器,可用于将通知同时发送至多个渠道 -|`INTERFACE`|[Notify](#notify)|通用通知接口定义 -|`INTERFACE`|[Sender](#sender)|通知发送器接口声明 +|`STRUCT`|[Manager](#struct_Manager)|通知管理器,可用于将通知同时发送至多个渠道 +|`INTERFACE`|[Notify](#struct_Notify)|通用通知接口定义 +|`INTERFACE`|[Sender](#struct_Sender)|通知发送器接口声明 @@ -37,6 +37,7 @@ notify 包含了对外部第三方通知的实现,如机器人消息等 > 通过指定的 Sender 创建一个通知管理器, senders 包中提供了一些内置的 Sender *** + ### Manager `STRUCT` 通知管理器,可用于将通知同时发送至多个渠道 ```go @@ -52,6 +53,7 @@ type Manager struct { #### func (*Manager) Release() > 释放通知管理器 *** + ### Notify `INTERFACE` 通用通知接口定义 ```go @@ -59,6 +61,7 @@ type Notify interface { Format() (string, error) } ``` + ### Sender `INTERFACE` 通知发送器接口声明 ```go diff --git a/notify/notifies/README.md b/notify/notifies/README.md index 21dfa210..17c6322e 100644 --- a/notify/notifies/README.md +++ b/notify/notifies/README.md @@ -35,10 +35,10 @@ notifies 包含了内置通知内容的实现 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[FeiShu](#feishu)|飞书通知消息 -|`STRUCT`|[FeiShuMessage](#feishumessage)|暂无描述... -|`STRUCT`|[FeiShuRichText](#feishurichtext)|飞书富文本结构 -|`STRUCT`|[FeiShuRichTextContent](#feishurichtextcontent)|飞书富文本内容体 +|`STRUCT`|[FeiShu](#struct_FeiShu)|飞书通知消息 +|`STRUCT`|[FeiShuMessage](#struct_FeiShuMessage)|暂无描述... +|`STRUCT`|[FeiShuRichText](#struct_FeiShuRichText)|飞书富文本结构 +|`STRUCT`|[FeiShuRichTextContent](#struct_FeiShuRichTextContent)|飞书富文本内容体 @@ -147,6 +147,7 @@ notifies 包含了内置通知内容的实现 > 创建一个飞书富文本 *** + ### FeiShu `STRUCT` 飞书通知消息 ```go @@ -158,11 +159,13 @@ type FeiShu struct { #### func (*FeiShu) Format() ( string, error) > 格式化通知内容 *** + ### FeiShuMessage `STRUCT` ```go type FeiShuMessage func(feishu *FeiShu) ``` + ### FeiShuRichText `STRUCT` 飞书富文本结构 ```go @@ -173,6 +176,7 @@ type FeiShuRichText struct { #### func (*FeiShuRichText) Create(lang string, title string) *FeiShuRichTextContent > 创建一个特定语言和标题的富文本内容 *** + ### FeiShuRichTextContent `STRUCT` 飞书富文本内容体 ```go diff --git a/notify/senders/README.md b/notify/senders/README.md index 5038d8bd..593f5979 100644 --- a/notify/senders/README.md +++ b/notify/senders/README.md @@ -23,7 +23,7 @@ senders Package 包含了内置通知发送器的实现 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[FeiShu](#feishu)|飞书发送器 +|`STRUCT`|[FeiShu](#struct_FeiShu)|飞书发送器 @@ -35,6 +35,7 @@ senders Package 包含了内置通知发送器的实现 > 根据特定的 webhook 地址创建飞书发送器 *** + ### FeiShu `STRUCT` 飞书发送器 ```go diff --git a/planner/pce/README.md b/planner/pce/README.md index 2d07abb8..485242c7 100644 --- a/planner/pce/README.md +++ b/planner/pce/README.md @@ -26,43 +26,43 @@ |类型|名称|描述 |:--|:--|:-- -|`INTERFACE`|[Config](#config)|配置解析接口 -|`INTERFACE`|[DataTmpl](#datatmpl)|数据导出模板 -|`STRUCT`|[Exporter](#exporter)|导出器 -|`INTERFACE`|[Field](#field)|基本字段类型接口 -|`STRUCT`|[Int](#int)|暂无描述... -|`STRUCT`|[Int8](#int8)|暂无描述... -|`STRUCT`|[Int16](#int16)|暂无描述... -|`STRUCT`|[Int32](#int32)|暂无描述... -|`STRUCT`|[Int64](#int64)|暂无描述... -|`STRUCT`|[Uint](#uint)|暂无描述... -|`STRUCT`|[Uint8](#uint8)|暂无描述... -|`STRUCT`|[Uint16](#uint16)|暂无描述... -|`STRUCT`|[Uint32](#uint32)|暂无描述... -|`STRUCT`|[Uint64](#uint64)|暂无描述... -|`STRUCT`|[Float32](#float32)|暂无描述... -|`STRUCT`|[Float64](#float64)|暂无描述... -|`STRUCT`|[String](#string)|暂无描述... -|`STRUCT`|[Bool](#bool)|暂无描述... -|`STRUCT`|[Byte](#byte)|暂无描述... -|`STRUCT`|[Rune](#rune)|暂无描述... -|`STRUCT`|[Complex64](#complex64)|暂无描述... -|`STRUCT`|[Complex128](#complex128)|暂无描述... -|`STRUCT`|[Uintptr](#uintptr)|暂无描述... -|`STRUCT`|[Double](#double)|暂无描述... -|`STRUCT`|[Float](#float)|暂无描述... -|`STRUCT`|[Long](#long)|暂无描述... -|`STRUCT`|[Short](#short)|暂无描述... -|`STRUCT`|[Char](#char)|暂无描述... -|`STRUCT`|[Number](#number)|暂无描述... -|`STRUCT`|[Integer](#integer)|暂无描述... -|`STRUCT`|[Boolean](#boolean)|暂无描述... -|`STRUCT`|[Loader](#loader)|配置加载器 -|`STRUCT`|[DataInfo](#datainfo)|配置数据 -|`STRUCT`|[DataField](#datafield)|配置数据字段 -|`INTERFACE`|[Tmpl](#tmpl)|配置结构模板接口 -|`STRUCT`|[TmplField](#tmplfield)|模板字段 -|`STRUCT`|[TmplStruct](#tmplstruct)|模板结构 +|`INTERFACE`|[Config](#struct_Config)|配置解析接口 +|`INTERFACE`|[DataTmpl](#struct_DataTmpl)|数据导出模板 +|`STRUCT`|[Exporter](#struct_Exporter)|导出器 +|`INTERFACE`|[Field](#struct_Field)|基本字段类型接口 +|`STRUCT`|[Int](#struct_Int)|暂无描述... +|`STRUCT`|[Int8](#struct_Int8)|暂无描述... +|`STRUCT`|[Int16](#struct_Int16)|暂无描述... +|`STRUCT`|[Int32](#struct_Int32)|暂无描述... +|`STRUCT`|[Int64](#struct_Int64)|暂无描述... +|`STRUCT`|[Uint](#struct_Uint)|暂无描述... +|`STRUCT`|[Uint8](#struct_Uint8)|暂无描述... +|`STRUCT`|[Uint16](#struct_Uint16)|暂无描述... +|`STRUCT`|[Uint32](#struct_Uint32)|暂无描述... +|`STRUCT`|[Uint64](#struct_Uint64)|暂无描述... +|`STRUCT`|[Float32](#struct_Float32)|暂无描述... +|`STRUCT`|[Float64](#struct_Float64)|暂无描述... +|`STRUCT`|[String](#struct_String)|暂无描述... +|`STRUCT`|[Bool](#struct_Bool)|暂无描述... +|`STRUCT`|[Byte](#struct_Byte)|暂无描述... +|`STRUCT`|[Rune](#struct_Rune)|暂无描述... +|`STRUCT`|[Complex64](#struct_Complex64)|暂无描述... +|`STRUCT`|[Complex128](#struct_Complex128)|暂无描述... +|`STRUCT`|[Uintptr](#struct_Uintptr)|暂无描述... +|`STRUCT`|[Double](#struct_Double)|暂无描述... +|`STRUCT`|[Float](#struct_Float)|暂无描述... +|`STRUCT`|[Long](#struct_Long)|暂无描述... +|`STRUCT`|[Short](#struct_Short)|暂无描述... +|`STRUCT`|[Char](#struct_Char)|暂无描述... +|`STRUCT`|[Number](#struct_Number)|暂无描述... +|`STRUCT`|[Integer](#struct_Integer)|暂无描述... +|`STRUCT`|[Boolean](#struct_Boolean)|暂无描述... +|`STRUCT`|[Loader](#struct_Loader)|配置加载器 +|`STRUCT`|[DataInfo](#struct_DataInfo)|配置数据 +|`STRUCT`|[DataField](#struct_DataField)|配置数据字段 +|`INTERFACE`|[Tmpl](#struct_Tmpl)|配置结构模板接口 +|`STRUCT`|[TmplField](#struct_TmplField)|模板字段 +|`STRUCT`|[TmplStruct](#struct_TmplStruct)|模板结构 @@ -106,6 +106,7 @@ func TestGetFieldGolangType(t *testing.T) { > - 加载器被用于加载配置表的数据和结构信息 *** + ### Config `INTERFACE` 配置解析接口 - 用于将配置文件解析为可供分析的数据结构 @@ -120,6 +121,7 @@ type Config interface { GetData() [][]DataInfo } ``` + ### DataTmpl `INTERFACE` 数据导出模板 ```go @@ -127,6 +129,7 @@ type DataTmpl interface { Render(data map[any]any) (string, error) } ``` + ### Exporter `STRUCT` 导出器 ```go @@ -138,6 +141,7 @@ type Exporter struct{} #### func (*Exporter) ExportData(tmpl DataTmpl, data map[any]any) ( []byte, error) > 导出数据 *** + ### Field `INTERFACE` 基本字段类型接口 ```go @@ -147,6 +151,7 @@ type Field interface { Parse(value string) any } ``` + ### Int `STRUCT` ```go @@ -158,6 +163,7 @@ type Int int *** #### func (Int) Parse(value string) any *** + ### Int8 `STRUCT` ```go @@ -169,6 +175,7 @@ type Int8 int8 *** #### func (Int8) Parse(value string) any *** + ### Int16 `STRUCT` ```go @@ -180,6 +187,7 @@ type Int16 int16 *** #### func (Int16) Parse(value string) any *** + ### Int32 `STRUCT` ```go @@ -191,6 +199,7 @@ type Int32 int32 *** #### func (Int32) Parse(value string) any *** + ### Int64 `STRUCT` ```go @@ -202,6 +211,7 @@ type Int64 int64 *** #### func (Int64) Parse(value string) any *** + ### Uint `STRUCT` ```go @@ -213,6 +223,7 @@ type Uint uint *** #### func (Uint) Parse(value string) any *** + ### Uint8 `STRUCT` ```go @@ -224,6 +235,7 @@ type Uint8 uint8 *** #### func (Uint8) Parse(value string) any *** + ### Uint16 `STRUCT` ```go @@ -235,6 +247,7 @@ type Uint16 uint16 *** #### func (Uint16) Parse(value string) any *** + ### Uint32 `STRUCT` ```go @@ -246,6 +259,7 @@ type Uint32 uint32 *** #### func (Uint32) Parse(value string) any *** + ### Uint64 `STRUCT` ```go @@ -257,6 +271,7 @@ type Uint64 uint64 *** #### func (Uint64) Parse(value string) any *** + ### Float32 `STRUCT` ```go @@ -268,6 +283,7 @@ type Float32 float32 *** #### func (Float32) Parse(value string) any *** + ### Float64 `STRUCT` ```go @@ -279,6 +295,7 @@ type Float64 float64 *** #### func (Float64) Parse(value string) any *** + ### String `STRUCT` ```go @@ -290,6 +307,7 @@ type String string *** #### func (String) Parse(value string) any *** + ### Bool `STRUCT` ```go @@ -301,6 +319,7 @@ type Bool bool *** #### func (Bool) Parse(value string) any *** + ### Byte `STRUCT` ```go @@ -312,6 +331,7 @@ type Byte byte *** #### func (Byte) Parse(value string) any *** + ### Rune `STRUCT` ```go @@ -323,6 +343,7 @@ type Rune rune *** #### func (Rune) Parse(value string) any *** + ### Complex64 `STRUCT` ```go @@ -334,6 +355,7 @@ type Complex64 complex64 *** #### func (Complex64) Parse(value string) any *** + ### Complex128 `STRUCT` ```go @@ -345,6 +367,7 @@ type Complex128 complex128 *** #### func (Complex128) Parse(value string) any *** + ### Uintptr `STRUCT` ```go @@ -356,6 +379,7 @@ type Uintptr uintptr *** #### func (Uintptr) Parse(value string) any *** + ### Double `STRUCT` ```go @@ -367,6 +391,7 @@ type Double float64 *** #### func (Double) Parse(value string) any *** + ### Float `STRUCT` ```go @@ -378,6 +403,7 @@ type Float float32 *** #### func (Float) Parse(value string) any *** + ### Long `STRUCT` ```go @@ -389,6 +415,7 @@ type Long int64 *** #### func (Long) Parse(value string) any *** + ### Short `STRUCT` ```go @@ -400,6 +427,7 @@ type Short int16 *** #### func (Short) Parse(value string) any *** + ### Char `STRUCT` ```go @@ -411,6 +439,7 @@ type Char int8 *** #### func (Char) Parse(value string) any *** + ### Number `STRUCT` ```go @@ -422,6 +451,7 @@ type Number float64 *** #### func (Number) Parse(value string) any *** + ### Integer `STRUCT` ```go @@ -433,6 +463,7 @@ type Integer int64 *** #### func (Integer) Parse(value string) any *** + ### Boolean `STRUCT` ```go @@ -444,6 +475,7 @@ type Boolean bool *** #### func (Boolean) Parse(value string) any *** + ### Loader `STRUCT` 配置加载器 ```go @@ -457,6 +489,7 @@ type Loader struct { #### func (*Loader) LoadData(config Config) map[any]any > 加载配置并得到配置数据 *** + ### DataInfo `STRUCT` 配置数据 ```go @@ -465,6 +498,7 @@ type DataInfo struct { Value string } ``` + ### DataField `STRUCT` 配置数据字段 ```go @@ -476,6 +510,7 @@ type DataField struct { ExportType string } ``` + ### Tmpl `INTERFACE` 配置结构模板接口 ```go @@ -483,6 +518,7 @@ type Tmpl interface { Render(templates ...*TmplStruct) (string, error) } ``` + ### TmplField `STRUCT` 模板字段 ```go @@ -505,6 +541,7 @@ type TmplField struct { #### func (*TmplField) IsSlice() bool > 是否是切片类型 *** + ### TmplStruct `STRUCT` 模板结构 ```go diff --git a/planner/pce/cs/README.md b/planner/pce/cs/README.md index 85c23328..4c9d3475 100644 --- a/planner/pce/cs/README.md +++ b/planner/pce/cs/README.md @@ -23,8 +23,8 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[XlsxExportType](#xlsxexporttype)|暂无描述... -|`STRUCT`|[Xlsx](#xlsx)|内置的 Xlsx 配置 +|`STRUCT`|[XlsxExportType](#struct_XlsxExportType)|暂无描述... +|`STRUCT`|[Xlsx](#struct_Xlsx)|内置的 Xlsx 配置 @@ -35,11 +35,13 @@ *** + ### XlsxExportType `STRUCT` ```go type XlsxExportType int ``` + ### Xlsx `STRUCT` 内置的 Xlsx 配置 ```go diff --git a/planner/pce/tmpls/README.md b/planner/pce/tmpls/README.md index dab54517..ac56c864 100644 --- a/planner/pce/tmpls/README.md +++ b/planner/pce/tmpls/README.md @@ -24,8 +24,8 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Golang](#golang)|配置导出模板 -|`STRUCT`|[JSON](#json)|暂无描述... +|`STRUCT`|[Golang](#struct_Golang)|配置导出模板 +|`STRUCT`|[JSON](#struct_JSON)|暂无描述... @@ -41,6 +41,7 @@ *** + ### Golang `STRUCT` 配置导出模板 ```go @@ -57,6 +58,7 @@ type Golang struct { *** #### func (*Golang) HasIndex(config *pce.TmplStruct) bool *** + ### JSON `STRUCT` ```go diff --git a/server/README.md b/server/README.md index 02ca621b..d048f66b 100644 --- a/server/README.md +++ b/server/README.md @@ -54,26 +54,26 @@ server 提供了包含多种网络类型的服务器实现 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Bot](#bot)|暂无描述... -|`STRUCT`|[BotOption](#botoption)|暂无描述... -|`STRUCT`|[Conn](#conn)|服务器连接单次消息的包装 -|`STRUCT`|[ConsoleParams](#consoleparams)|控制台参数 -|`STRUCT`|[MessageReadyEventHandler](#messagereadyeventhandler)|暂无描述... -|`STRUCT`|[Http](#http)|基于 gin.Engine 包装的 http 服务器 -|`STRUCT`|[HttpContext](#httpcontext)|基于 gin.Context 的 http 请求上下文 -|`STRUCT`|[HandlerFunc](#handlerfunc)|暂无描述... -|`STRUCT`|[ContextPacker](#contextpacker)|暂无描述... -|`STRUCT`|[HttpRouter](#httprouter)|暂无描述... -|`STRUCT`|[HttpWrapperHandleFunc](#httpwrapperhandlefunc)|暂无描述... -|`STRUCT`|[HttpWrapper](#httpwrapper)|http 包装器 -|`STRUCT`|[HttpWrapperGroup](#httpwrappergroup)|http 包装器 -|`STRUCT`|[MessageType](#messagetype)|暂无描述... -|`STRUCT`|[Message](#message)|服务器消息 -|`STRUCT`|[MultipleServer](#multipleserver)|暂无描述... -|`STRUCT`|[Network](#network)|暂无描述... -|`STRUCT`|[Option](#option)|暂无描述... -|`STRUCT`|[Server](#server)|网络服务器 -|`INTERFACE`|[Service](#service)|兼容传统 service 设计模式的接口 +|`STRUCT`|[Bot](#struct_Bot)|暂无描述... +|`STRUCT`|[BotOption](#struct_BotOption)|暂无描述... +|`STRUCT`|[Conn](#struct_Conn)|服务器连接单次消息的包装 +|`STRUCT`|[ConsoleParams](#struct_ConsoleParams)|控制台参数 +|`STRUCT`|[MessageReadyEventHandler](#struct_MessageReadyEventHandler)|暂无描述... +|`STRUCT`|[Http](#struct_Http)|基于 gin.Engine 包装的 http 服务器 +|`STRUCT`|[HttpContext](#struct_HttpContext)|基于 gin.Context 的 http 请求上下文 +|`STRUCT`|[HandlerFunc](#struct_HandlerFunc)|暂无描述... +|`STRUCT`|[ContextPacker](#struct_ContextPacker)|暂无描述... +|`STRUCT`|[HttpRouter](#struct_HttpRouter)|暂无描述... +|`STRUCT`|[HttpWrapperHandleFunc](#struct_HttpWrapperHandleFunc)|暂无描述... +|`STRUCT`|[HttpWrapper](#struct_HttpWrapper)|http 包装器 +|`STRUCT`|[HttpWrapperGroup](#struct_HttpWrapperGroup)|http 包装器 +|`STRUCT`|[MessageType](#struct_MessageType)|暂无描述... +|`STRUCT`|[Message](#struct_Message)|服务器消息 +|`STRUCT`|[MultipleServer](#struct_MultipleServer)|暂无描述... +|`STRUCT`|[Network](#struct_Network)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|暂无描述... +|`STRUCT`|[Server](#struct_Server)|网络服务器 +|`INTERFACE`|[Service](#struct_Service)|兼容传统 service 设计模式的接口 @@ -385,6 +385,7 @@ func TestBindService(t *testing.T) { *** + ### Bot `STRUCT` ```go @@ -413,11 +414,13 @@ type Bot struct { #### func (*Bot) SendWSPacket(wst int, packet []byte) > 发送 WebSocket 数据包到服务器 *** + ### BotOption `STRUCT` ```go type BotOption func(bot *Bot) ``` + ### Conn `STRUCT` 服务器连接单次消息的包装 ```go @@ -501,6 +504,7 @@ type Conn struct { #### func (*Conn) Close(err ...error) > 关闭连接 *** + ### ConsoleParams `STRUCT` 控制台参数 ```go @@ -527,11 +531,13 @@ type ConsoleParams map[string][]string #### func (ConsoleParams) Clear() > 清空参数 *** + ### MessageReadyEventHandler `STRUCT` ```go type MessageReadyEventHandler func(srv *Server) ``` + ### Http `STRUCT` 基于 gin.Engine 包装的 http 服务器 ```go @@ -543,6 +549,7 @@ type Http[Context any] struct { ``` #### func (*Http) Gin() *gin.Engine *** + ### HttpContext `STRUCT` 基于 gin.Context 的 http 请求上下文 ```go @@ -556,16 +563,19 @@ type HttpContext struct { #### func (*HttpContext) ReadTo(dest any) error > 读取请求数据到指定结构体,如果失败则返回错误 *** + ### HandlerFunc `STRUCT` ```go type HandlerFunc[Context any] func(ctx Context) ``` + ### ContextPacker `STRUCT` ```go type ContextPacker[Context any] func(ctx *gin.Context) Context ``` + ### HttpRouter `STRUCT` ```go @@ -639,11 +649,13 @@ type HttpRouter[Context any] struct { #### func (*HttpRouter) Use(middleware ...HandlerFunc[Context]) *HttpRouter[Context] > 将中间件附加到路由组。 *** + ### HttpWrapperHandleFunc `STRUCT` ```go type HttpWrapperHandleFunc[CTX any] func(ctx CTX) ``` + ### HttpWrapper `STRUCT` http 包装器 ```go @@ -703,6 +715,7 @@ type HttpWrapper[CTX any] struct { #### func (*HttpWrapper) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 *** + ### HttpWrapperGroup `STRUCT` http 包装器 ```go @@ -738,6 +751,7 @@ type HttpWrapperGroup[CTX any] struct { #### func (*HttpWrapperGroup) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 创建分组 *** + ### MessageType `STRUCT` ```go @@ -746,6 +760,7 @@ type MessageType byte #### func (MessageType) String() string > 返回消息类型的字符串表示 *** + ### Message `STRUCT` 服务器消息 ```go @@ -771,6 +786,7 @@ type Message struct { #### func (*Message) String() string > 返回消息的字符串表示 *** + ### MultipleServer `STRUCT` ```go @@ -787,6 +803,7 @@ type MultipleServer struct { *** #### func (*MultipleServer) OnExitEvent() *** + ### Network `STRUCT` ```go @@ -795,11 +812,13 @@ type Network string #### func (Network) IsSocket() bool > 返回当前服务器的网络模式是否为 Socket 模式 *** + ### Option `STRUCT` ```go type Option func(srv *Server) ``` + ### Server `STRUCT` 网络服务器 ```go @@ -966,6 +985,7 @@ func ExampleServer_Run() { #### func (*Server) HasMessageStatistics() bool > 是否了开启消息统计 *** + ### Service `INTERFACE` 兼容传统 service 设计模式的接口 ```go diff --git a/server/client/README.md b/server/client/README.md index ee5bc4cd..1d7658e3 100644 --- a/server/client/README.md +++ b/server/client/README.md @@ -27,13 +27,13 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Client](#client)|客户端 -|`INTERFACE`|[Core](#core)|暂无描述... -|`STRUCT`|[ConnectionClosedEventHandle](#connectionclosedeventhandle)|暂无描述... -|`STRUCT`|[Packet](#packet)|暂无描述... -|`STRUCT`|[TCP](#tcp)|暂无描述... -|`STRUCT`|[UnixDomainSocket](#unixdomainsocket)|暂无描述... -|`STRUCT`|[Websocket](#websocket)|websocket 客户端 +|`STRUCT`|[Client](#struct_Client)|客户端 +|`INTERFACE`|[Core](#struct_Core)|暂无描述... +|`STRUCT`|[ConnectionClosedEventHandle](#struct_ConnectionClosedEventHandle)|暂无描述... +|`STRUCT`|[Packet](#struct_Packet)|暂无描述... +|`STRUCT`|[TCP](#struct_TCP)|暂无描述... +|`STRUCT`|[UnixDomainSocket](#struct_UnixDomainSocket)|暂无描述... +|`STRUCT`|[Websocket](#struct_Websocket)|websocket 客户端 @@ -63,6 +63,7 @@ > 创建 websocket 客户端 *** + ### Client `STRUCT` 客户端 ```go @@ -138,6 +139,7 @@ func TestClient_WriteWS(t *testing.T) { #### func (*Client) GetServerAddr() string > 获取服务器地址 *** + ### Core `INTERFACE` ```go @@ -149,11 +151,13 @@ type Core interface { Clone() Core } ``` + ### ConnectionClosedEventHandle `STRUCT` ```go type ConnectionClosedEventHandle func(conn *Client, err any) ``` + ### Packet `STRUCT` ```go @@ -163,6 +167,7 @@ type Packet struct { callback func(err error) } ``` + ### TCP `STRUCT` ```go @@ -182,6 +187,7 @@ type TCP struct { *** #### func (*TCP) Clone() Core *** + ### UnixDomainSocket `STRUCT` ```go @@ -243,6 +249,7 @@ func TestUnixDomainSocket_Write(t *testing.T) { *** #### func (*UnixDomainSocket) Clone() Core *** + ### Websocket `STRUCT` websocket 客户端 ```go diff --git a/server/gateway/README.md b/server/gateway/README.md index 224b2bad..e647ee3d 100644 --- a/server/gateway/README.md +++ b/server/gateway/README.md @@ -32,13 +32,13 @@ gateway 是用于处理服务器消息的网关模块,适用于对客户端消 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Endpoint](#endpoint)|网关端点 -|`STRUCT`|[EndpointOption](#endpointoption)|网关端点选项 -|`STRUCT`|[ConnectionOpenedEventHandle](#connectionopenedeventhandle)|暂无描述... -|`STRUCT`|[EndpointSelector](#endpointselector)|暂无描述... -|`STRUCT`|[Gateway](#gateway)|基于 server.Server 实现的网关服务器 -|`STRUCT`|[Option](#option)|网关选项 -|`INTERFACE`|[Scanner](#scanner)|端点扫描器 +|`STRUCT`|[Endpoint](#struct_Endpoint)|网关端点 +|`STRUCT`|[EndpointOption](#struct_EndpointOption)|网关端点选项 +|`STRUCT`|[ConnectionOpenedEventHandle](#struct_ConnectionOpenedEventHandle)|暂无描述... +|`STRUCT`|[EndpointSelector](#struct_EndpointSelector)|暂无描述... +|`STRUCT`|[Gateway](#struct_Gateway)|基于 server.Server 实现的网关服务器 +|`STRUCT`|[Option](#struct_Option)|网关选项 +|`INTERFACE`|[Scanner](#struct_Scanner)|端点扫描器 @@ -105,6 +105,7 @@ gateway 是用于处理服务器消息的网关模块,适用于对客户端消 > - | ipv4(4) | port(2) | cost(4) | packet | *** + ### Endpoint `STRUCT` 网关端点 - 每一个端点均表示了一个目标服务,网关会将数据包转发到该端点,由该端点负责将数据包转发到目标服务。 @@ -141,21 +142,25 @@ type Endpoint struct { > 转发数据包到该端点 > - 端点在处理数据包时,应区分数据包为普通直连数据包还是网关数据包。可通过 UnmarshalGatewayOutPacket 进行数据包解析,当解析失败且无其他数据包协议时,可认为该数据包为普通直连数据包。 *** + ### EndpointOption `STRUCT` 网关端点选项 ```go type EndpointOption func(endpoint *Endpoint) ``` + ### ConnectionOpenedEventHandle `STRUCT` ```go type ConnectionOpenedEventHandle func(gateway *Gateway, conn *server.Conn) ``` + ### EndpointSelector `STRUCT` ```go type EndpointSelector func(endpoints []*Endpoint) *Endpoint ``` + ### Gateway `STRUCT` 基于 server.Server 实现的网关服务器 - 网关服务器是一个特殊的服务器,它会通过扫描器扫描端点列表,然后连接到端点列表中的所有端点,当端点连接成功后,网关服务器会将客户端的连接数据转发到端点服务器 @@ -230,11 +235,13 @@ func TestGateway_Run(t *testing.T) { #### func (*Gateway) SwitchEndpoint(source *Endpoint, dest *Endpoint) > 将端点端点的所有连接切换到另一个端点 *** + ### Option `STRUCT` 网关选项 ```go type Option func(gateway *Gateway) ``` + ### Scanner `INTERFACE` 端点扫描器 ```go diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index 1d3bd6e5..1c6e6a5b 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -24,12 +24,12 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Action](#action)|消息分发器操作器,用于暴露外部可操作的消息分发器函数 -|`STRUCT`|[Handler](#handler)|消息处理器 -|`STRUCT`|[Dispatcher](#dispatcher)|用于服务器消息处理的消息分发器 -|`STRUCT`|[Manager](#manager)|消息分发器管理器 -|`INTERFACE`|[Message](#message)|暂无描述... -|`INTERFACE`|[Producer](#producer)|暂无描述... +|`STRUCT`|[Action](#struct_Action)|消息分发器操作器,用于暴露外部可操作的消息分发器函数 +|`STRUCT`|[Handler](#struct_Handler)|消息处理器 +|`STRUCT`|[Dispatcher](#struct_Dispatcher)|用于服务器消息处理的消息分发器 +|`STRUCT`|[Manager](#struct_Manager)|消息分发器管理器 +|`INTERFACE`|[Message](#struct_Message)|暂无描述... +|`INTERFACE`|[Producer](#struct_Producer)|暂无描述... @@ -165,6 +165,7 @@ func TestNewManager(t *testing.T) { *** + ### Action `STRUCT` 消息分发器操作器,用于暴露外部可操作的消息分发器函数 ```go @@ -173,11 +174,13 @@ type Action[P Producer, M Message[P]] struct { d *Dispatcher[P, M] } ``` + ### Handler `STRUCT` 消息处理器 ```go type Handler[P Producer, M Message[P]] func(dispatcher *Dispatcher[P, M], message M) ``` + ### Dispatcher `STRUCT` 用于服务器消息处理的消息分发器 @@ -207,6 +210,7 @@ type Dispatcher[P Producer, M Message[P]] struct { abort chan struct{} } ``` + ### Manager `STRUCT` 消息分发器管理器 ```go @@ -223,6 +227,7 @@ type Manager[P Producer, M Message[P]] struct { createdHandler func(name string) } ``` + ### Message `INTERFACE` ```go @@ -230,6 +235,7 @@ type Message[P comparable] interface { GetProducer() P } ``` + ### Producer `INTERFACE` ```go diff --git a/server/internal/logger/README.md b/server/internal/logger/README.md index a4225b91..ca79363d 100644 --- a/server/internal/logger/README.md +++ b/server/internal/logger/README.md @@ -16,14 +16,15 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Ants](#ants)|暂无描述... -|`STRUCT`|[GNet](#gnet)|暂无描述... +|`STRUCT`|[Ants](#struct_Ants)|暂无描述... +|`STRUCT`|[GNet](#struct_GNet)|暂无描述... *** ## 详情信息 + ### Ants `STRUCT` ```go @@ -31,6 +32,7 @@ type Ants struct{} ``` #### func (*Ants) Printf(format string, args ...interface {}) *** + ### GNet `STRUCT` ```go diff --git a/server/lockstep/README.md b/server/lockstep/README.md index f7a5a725..4f9f1479 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -27,10 +27,10 @@ |类型|名称|描述 |:--|:--|:-- -|`INTERFACE`|[Client](#client)|帧同步客户端接口定义 -|`STRUCT`|[StoppedEventHandle](#stoppedeventhandle)|暂无描述... -|`STRUCT`|[Lockstep](#lockstep)|锁步(帧)同步默认实现 -|`STRUCT`|[Option](#option)|暂无描述... +|`INTERFACE`|[Client](#struct_Client)|帧同步客户端接口定义 +|`STRUCT`|[StoppedEventHandle](#struct_StoppedEventHandle)|暂无描述... +|`STRUCT`|[Lockstep](#struct_Lockstep)|锁步(帧)同步默认实现 +|`STRUCT`|[Option](#struct_Option)|暂无描述... @@ -108,6 +108,7 @@ func TestNewLockstep(t *testing.T) { > - 默认情况下为 0,即第一帧索引为 0 *** + ### Client `INTERFACE` 帧同步客户端接口定义 - 客户端应该具备ID及写入数据包的实现 @@ -117,11 +118,13 @@ type Client[ID comparable] interface { Write(packet []byte, callback ...func(err error)) } ``` + ### StoppedEventHandle `STRUCT` ```go type StoppedEventHandle[ClientID comparable, Command any] func(lockstep *Lockstep[ClientID, Command]) ``` + ### Lockstep `STRUCT` 锁步(帧)同步默认实现 - 支持最大帧上限 WithFrameLimit @@ -149,6 +152,7 @@ type Lockstep[ClientID comparable, Command any] struct { lockstepStoppedEventHandles []StoppedEventHandle[ClientID, Command] } ``` + ### Option `STRUCT` ```go diff --git a/server/router/README.md b/server/router/README.md index 7a470bc2..aa0095f0 100644 --- a/server/router/README.md +++ b/server/router/README.md @@ -24,9 +24,9 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[MultistageBind](#multistagebind)|多级分类路由绑定函数 -|`STRUCT`|[Multistage](#multistage)|支持多级分类的路由器 -|`STRUCT`|[MultistageOption](#multistageoption)|路由器选项 +|`STRUCT`|[MultistageBind](#struct_MultistageBind)|多级分类路由绑定函数 +|`STRUCT`|[Multistage](#struct_Multistage)|支持多级分类的路由器 +|`STRUCT`|[MultistageOption](#struct_MultistageOption)|路由器选项 @@ -53,6 +53,7 @@ func ExampleNewMultistage() { > - 将在路由注册前对路由进行对应处理 *** + ### MultistageBind `STRUCT` 多级分类路由绑定函数 ```go @@ -61,6 +62,7 @@ type MultistageBind[HandleFunc any] func(HandleFunc) #### func (MultistageBind) Bind(handleFunc HandleFunc) > 将处理函数绑定到预设的路由中 *** + ### Multistage `STRUCT` 支持多级分类的路由器 ```go @@ -164,6 +166,7 @@ func ExampleMultistage_Sub() { ``` *** + ### MultistageOption `STRUCT` 路由器选项 ```go diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 173ca785..42f8feb2 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -24,9 +24,9 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Channel](#channel)|基于 chan 的写循环,与 Unbounded 相同,但是使用 Channel 实现 -|`STRUCT`|[Unbounded](#unbounded)|写循环 -|`INTERFACE`|[WriteLoop](#writeloop)|暂无描述... +|`STRUCT`|[Channel](#struct_Channel)|基于 chan 的写循环,与 Unbounded 相同,但是使用 Channel 实现 +|`STRUCT`|[Unbounded](#struct_Unbounded)|写循环 +|`INTERFACE`|[WriteLoop](#struct_WriteLoop)|暂无描述... @@ -106,6 +106,7 @@ func TestNewUnbounded(t *testing.T) { *** + ### Channel `STRUCT` 基于 chan 的写循环,与 Unbounded 相同,但是使用 Channel 实现 ```go @@ -119,6 +120,7 @@ type Channel[T any] struct { #### func (*Channel) Close() > 关闭写循环 *** + ### Unbounded `STRUCT` 写循环 - 用于将数据并发安全的写入到底层连接 @@ -217,6 +219,7 @@ func TestUnbounded_Close(t *testing.T) { *** + ### WriteLoop `INTERFACE` ```go diff --git a/utils/aoi/README.md b/utils/aoi/README.md index c03f1f6d..02bc9260 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -28,9 +28,9 @@ AOI 问题是在大规模多人在线游戏中常见的问题,它涉及到确 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[TwoDimensional](#twodimensional)|暂无描述... -|`INTERFACE`|[TwoDimensionalEntity](#twodimensionalentity)|基于2D定义的AOI对象功能接口 -|`STRUCT`|[EntityJoinVisionEventHandle](#entityjoinvisioneventhandle)|暂无描述... +|`STRUCT`|[TwoDimensional](#struct_TwoDimensional)|暂无描述... +|`INTERFACE`|[TwoDimensionalEntity](#struct_TwoDimensionalEntity)|基于2D定义的AOI对象功能接口 +|`STRUCT`|[EntityJoinVisionEventHandle](#struct_EntityJoinVisionEventHandle)|暂无描述... @@ -65,6 +65,7 @@ func TestNewTwoDimensional(t *testing.T) { *** + ### TwoDimensional `STRUCT` ```go @@ -82,6 +83,7 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimens repartitionQueue []func() } ``` + ### TwoDimensionalEntity `INTERFACE` 基于2D定义的AOI对象功能接口 - AOI 对象提供了 AOI 系统中常用的属性,诸如位置坐标和视野范围等 @@ -92,6 +94,7 @@ type TwoDimensionalEntity[EID generic.Basic, PosType generic.SignedNumber] inter GetPosition() geometry.Point[PosType] } ``` + ### EntityJoinVisionEventHandle `STRUCT` ```go diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index 8caab46b..e06b7700 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -34,16 +34,16 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Area](#area)|编排区域 -|`STRUCT`|[AreaOption](#areaoption)|编排区域选项 -|`STRUCT`|[AreaConstraintHandle](#areaconstrainthandle)|暂无描述... -|`STRUCT`|[Arrangement](#arrangement)|用于针对多条数据进行合理编排的数据结构 -|`STRUCT`|[Editor](#editor)|提供了大量辅助函数的编辑器 -|`INTERFACE`|[Item](#item)|编排成员 -|`STRUCT`|[ItemOption](#itemoption)|编排成员选项 -|`STRUCT`|[ItemFixedAreaHandle](#itemfixedareahandle)|暂无描述... -|`STRUCT`|[Option](#option)|编排选项 -|`STRUCT`|[ConstraintHandle](#constrainthandle)|暂无描述... +|`STRUCT`|[Area](#struct_Area)|编排区域 +|`STRUCT`|[AreaOption](#struct_AreaOption)|编排区域选项 +|`STRUCT`|[AreaConstraintHandle](#struct_AreaConstraintHandle)|暂无描述... +|`STRUCT`|[Arrangement](#struct_Arrangement)|用于针对多条数据进行合理编排的数据结构 +|`STRUCT`|[Editor](#struct_Editor)|提供了大量辅助函数的编辑器 +|`INTERFACE`|[Item](#struct_Item)|编排成员 +|`STRUCT`|[ItemOption](#struct_ItemOption)|编排成员选项 +|`STRUCT`|[ItemFixedAreaHandle](#struct_ItemFixedAreaHandle)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|编排选项 +|`STRUCT`|[ConstraintHandle](#struct_ConstraintHandle)|暂无描述... @@ -115,6 +115,7 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 > - 当所有的冲突处理函数都无法处理冲突时,将会进入下一个编排区域的尝试,如果均无法完成,将会将该成员加入到编排队列的末端,等待下一次编排 *** + ### Area `STRUCT` 编排区域 ```go @@ -126,16 +127,19 @@ type Area[ID comparable, AreaInfo any] struct { evaluate AreaEvaluateHandle[ID, AreaInfo] } ``` + ### AreaOption `STRUCT` 编排区域选项 ```go type AreaOption[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo]) ``` + ### AreaConstraintHandle `STRUCT` ```go type AreaConstraintHandle[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo], item Item[ID]) error ``` + ### Arrangement `STRUCT` 用于针对多条数据进行合理编排的数据结构 - 我不知道这个数据结构的具体用途,但是我觉得这个数据结构应该是有用的 @@ -153,6 +157,7 @@ type Arrangement[ID comparable, AreaInfo any] struct { conflictHandles []ConflictHandle[ID, AreaInfo] } ``` + ### Editor `STRUCT` 提供了大量辅助函数的编辑器 ```go @@ -164,6 +169,7 @@ type Editor[ID comparable, AreaInfo any] struct { retryCount int } ``` + ### Item `INTERFACE` 编排成员 ```go @@ -172,21 +178,25 @@ type Item[ID comparable] interface { Equal(item Item[ID]) bool } ``` + ### ItemOption `STRUCT` 编排成员选项 ```go type ItemOption[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo], item Item[ID]) ``` + ### ItemFixedAreaHandle `STRUCT` ```go type ItemFixedAreaHandle[AreaInfo any] func(areaInfo AreaInfo) bool ``` + ### Option `STRUCT` 编排选项 ```go type Option[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo]) ``` + ### ConstraintHandle `STRUCT` ```go diff --git a/utils/buffer/README.md b/utils/buffer/README.md index b0f0d88b..b96e2b34 100644 --- a/utils/buffer/README.md +++ b/utils/buffer/README.md @@ -31,9 +31,9 @@ buffer 提供了缓冲区相关的实用程序。 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Ring](#ring)|环形缓冲区 -|`STRUCT`|[RingUnbounded](#ringunbounded)|基于环形缓冲区实现的无界缓冲区 -|`STRUCT`|[Unbounded](#unbounded)|是无界缓冲区的实现 +|`STRUCT`|[Ring](#struct_Ring)|环形缓冲区 +|`STRUCT`|[RingUnbounded](#struct_RingUnbounded)|基于环形缓冲区实现的无界缓冲区 +|`STRUCT`|[Unbounded](#struct_Unbounded)|是无界缓冲区的实现 @@ -82,6 +82,7 @@ func TestNewRing(t *testing.T) { > - 该缓冲区的所有方法都是线程安全的,除了用于同步的互斥锁外,不会阻塞任何东西 *** + ### Ring `STRUCT` 环形缓冲区 ```go @@ -160,6 +161,7 @@ func BenchmarkRing_Write(b *testing.B) { #### func (*Ring) Reset() > 重置缓冲区 *** + ### RingUnbounded `STRUCT` 基于环形缓冲区实现的无界缓冲区 ```go @@ -255,6 +257,7 @@ func TestRingUnbounded_Close(t *testing.T) { *** + ### Unbounded `STRUCT` 是无界缓冲区的实现 ```go diff --git a/utils/collection/README.md b/utils/collection/README.md index b9663e92..48502463 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -133,8 +133,8 @@ collection 用于对 input 和 map 操作的工具函数 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[ComparisonHandler](#comparisonhandler)|暂无描述... -|`STRUCT`|[OrderedValueGetter](#orderedvaluegetter)|暂无描述... +|`STRUCT`|[ComparisonHandler](#struct_ComparisonHandler)|暂无描述... +|`STRUCT`|[OrderedValueGetter](#struct_OrderedValueGetter)|暂无描述... @@ -5411,11 +5411,13 @@ func TestShuffleByClone(t *testing.T) { *** + ### ComparisonHandler `STRUCT` ```go type ComparisonHandler[V any] func(source V) bool ``` + ### OrderedValueGetter `STRUCT` ```go diff --git a/utils/collection/listings/README.md b/utils/collection/listings/README.md index 0ea0d9f7..9179267b 100644 --- a/utils/collection/listings/README.md +++ b/utils/collection/listings/README.md @@ -26,10 +26,10 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Matrix](#matrix)|暂无描述... -|`STRUCT`|[PagedSlice](#pagedslice)|是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。 -|`STRUCT`|[PrioritySlice](#priorityslice)|是一个优先级切片 -|`STRUCT`|[SyncSlice](#syncslice)|是基于 sync.RWMutex 实现的线程安全的 slice +|`STRUCT`|[Matrix](#struct_Matrix)|暂无描述... +|`STRUCT`|[PagedSlice](#struct_PagedSlice)|是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。 +|`STRUCT`|[PrioritySlice](#struct_PrioritySlice)|是一个优先级切片 +|`STRUCT`|[SyncSlice](#struct_SyncSlice)|是基于 sync.RWMutex 实现的线程安全的 slice @@ -56,6 +56,7 @@ > 创建一个 SyncSlice *** + ### Matrix `STRUCT` ```go @@ -76,6 +77,7 @@ type Matrix[V any] struct { #### func (*Matrix) Clear() > 清空矩阵。 *** + ### PagedSlice `STRUCT` 是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。 ```go @@ -104,6 +106,7 @@ type PagedSlice[T any] struct { #### func (*PagedSlice) Range(f func (index int, value T) bool) > 迭代 PagedSlice 中的所有元素。 *** + ### PrioritySlice `STRUCT` 是一个优先级切片 ```go @@ -181,6 +184,7 @@ func TestPrioritySlice_Append(t *testing.T) { #### func (*PrioritySlice) String() string > 返回切片字符串 *** + ### SyncSlice `STRUCT` 是基于 sync.RWMutex 实现的线程安全的 slice ```go diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md index f4910eb0..270393b4 100644 --- a/utils/collection/mappings/README.md +++ b/utils/collection/mappings/README.md @@ -23,7 +23,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[SyncMap](#syncmap)|是基于 sync.RWMutex 实现的线程安全的 map +|`STRUCT`|[SyncMap](#struct_SyncMap)|是基于 sync.RWMutex 实现的线程安全的 map @@ -35,6 +35,7 @@ > 创建一个 SyncMap *** + ### SyncMap `STRUCT` 是基于 sync.RWMutex 实现的线程安全的 map - 适用于要考虑并发读写但是并发读写的频率不高的情况 diff --git a/utils/combination/README.md b/utils/combination/README.md index 2826bbe1..bbe88371 100644 --- a/utils/combination/README.md +++ b/utils/combination/README.md @@ -52,13 +52,13 @@ combination 包提供了一些实用的组合函数。 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Combination](#combination)|用于从多个匹配器内提取组合的数据结构 -|`STRUCT`|[Option](#option)|组合器选项 -|`INTERFACE`|[Item](#item)|暂无描述... -|`STRUCT`|[Matcher](#matcher)|用于从一组数据内提取组合的数据结构 -|`STRUCT`|[MatcherOption](#matcheroption)|匹配器选项 -|`STRUCT`|[Validator](#validator)|用于对组合进行验证的校验器 -|`STRUCT`|[ValidatorOption](#validatoroption)|暂无描述... +|`STRUCT`|[Combination](#struct_Combination)|用于从多个匹配器内提取组合的数据结构 +|`STRUCT`|[Option](#struct_Option)|组合器选项 +|`INTERFACE`|[Item](#struct_Item)|暂无描述... +|`STRUCT`|[Matcher](#struct_Matcher)|用于从一组数据内提取组合的数据结构 +|`STRUCT`|[MatcherOption](#struct_MatcherOption)|匹配器选项 +|`STRUCT`|[Validator](#struct_Validator)|用于对组合进行验证的校验器 +|`STRUCT`|[ValidatorOption](#struct_ValidatorOption)|暂无描述... @@ -241,6 +241,7 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** + ### Combination `STRUCT` 用于从多个匹配器内提取组合的数据结构 ```go @@ -304,16 +305,19 @@ func TestCombination_Best(t *testing.T) { #### func (*Combination) Worst(items []T) (name string, result []T) > 从一组数据中提取符合匹配器规则的最差组合 *** + ### Option `STRUCT` 组合器选项 ```go type Option[T Item] func(*Combination[T]) ``` + ### Item `INTERFACE` ```go type Item interface{} ``` + ### Matcher `STRUCT` 用于从一组数据内提取组合的数据结构 ```go @@ -335,11 +339,13 @@ type Matcher[T Item] struct { #### func (*Matcher) Worst(items []T) []T > 从一组数据中提取符筛选器规则的最差组合 *** + ### MatcherOption `STRUCT` 匹配器选项 ```go type MatcherOption[T Item] func(matcher *Matcher[T]) ``` + ### Validator `STRUCT` 用于对组合进行验证的校验器 ```go @@ -382,6 +388,7 @@ func TestValidator_Validate(t *testing.T) { *** + ### ValidatorOption `STRUCT` ```go diff --git a/utils/deck/README.md b/utils/deck/README.md index 3c95f23b..3bfbca83 100644 --- a/utils/deck/README.md +++ b/utils/deck/README.md @@ -24,9 +24,9 @@ deck 包中的内容用于针对一堆内容的管理,适用但不限于牌堆 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Deck](#deck)|甲板,用于针对一堆 Group 进行管理的数据结构 -|`STRUCT`|[Group](#group)|甲板中的组,用于针对一堆内容进行管理的数据结构 -|`INTERFACE`|[Item](#item)|甲板成员 +|`STRUCT`|[Deck](#struct_Deck)|甲板,用于针对一堆 Group 进行管理的数据结构 +|`STRUCT`|[Group](#struct_Group)|甲板中的组,用于针对一堆内容进行管理的数据结构 +|`INTERFACE`|[Item](#struct_Item)|甲板成员 @@ -43,6 +43,7 @@ deck 包中的内容用于针对一堆内容的管理,适用但不限于牌堆 > 创建一个新的组 *** + ### Deck `STRUCT` 甲板,用于针对一堆 Group 进行管理的数据结构 ```go @@ -72,6 +73,7 @@ type Deck[I Item] struct { #### func (*Deck) GetPrev(guid int64) *Group[I] > 获取特定组的上一个组 *** + ### Group `STRUCT` 甲板中的组,用于针对一堆内容进行管理的数据结构 ```go @@ -126,6 +128,7 @@ type Group[I Item] struct { #### func (*Group) GetItem(index int) I > 获取组中的指定内容 *** + ### Item `INTERFACE` 甲板成员 ```go diff --git a/utils/fsm/README.md b/utils/fsm/README.md index 0661ad23..330019ff 100644 --- a/utils/fsm/README.md +++ b/utils/fsm/README.md @@ -28,8 +28,8 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[FSM](#fsm)|状态机 -|`STRUCT`|[Option](#option)|暂无描述... +|`STRUCT`|[FSM](#struct_FSM)|状态机 +|`STRUCT`|[Option](#struct_Option)|暂无描述... @@ -69,6 +69,7 @@ > - 该阶段状态机的状态为新的状态,而非退出前的状态 *** + ### FSM `STRUCT` 状态机 ```go @@ -84,6 +85,7 @@ type FSM[State comparable, Data any] struct { exitAfterEventHandles map[State][]func(state *FSM[State, Data]) } ``` + ### Option `STRUCT` ```go diff --git a/utils/generator/astgo/README.md b/utils/generator/astgo/README.md index a8edb188..3d775cb2 100644 --- a/utils/generator/astgo/README.md +++ b/utils/generator/astgo/README.md @@ -23,13 +23,13 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Comment](#comment)|暂无描述... -|`STRUCT`|[Field](#field)|暂无描述... -|`STRUCT`|[File](#file)|暂无描述... -|`STRUCT`|[Function](#function)|暂无描述... -|`STRUCT`|[Package](#package)|暂无描述... -|`STRUCT`|[Struct](#struct)|暂无描述... -|`STRUCT`|[Type](#type)|暂无描述... +|`STRUCT`|[Comment](#struct_Comment)|暂无描述... +|`STRUCT`|[Field](#struct_Field)|暂无描述... +|`STRUCT`|[File](#struct_File)|暂无描述... +|`STRUCT`|[Function](#struct_Function)|暂无描述... +|`STRUCT`|[Package](#struct_Package)|暂无描述... +|`STRUCT`|[Struct](#struct_Struct)|暂无描述... +|`STRUCT`|[Type](#struct_Type)|暂无描述... @@ -60,6 +60,7 @@ func TestNewPackage(t *testing.T) { *** + ### Comment `STRUCT` ```go @@ -68,6 +69,7 @@ type Comment struct { Clear []string } ``` + ### Field `STRUCT` ```go @@ -78,6 +80,7 @@ type Field struct { Comments *Comment } ``` + ### File `STRUCT` ```go @@ -92,6 +95,7 @@ type File struct { ``` #### func (*File) Package() string *** + ### Function `STRUCT` ```go @@ -112,6 +116,7 @@ type Function struct { ``` #### func (*Function) Code() string *** + ### Package `STRUCT` ```go @@ -137,6 +142,7 @@ type Package struct { *** #### func (*Package) GetBenchmarkTest(f *Function) *Function *** + ### Struct `STRUCT` ```go @@ -151,6 +157,7 @@ type Struct struct { Test bool } ``` + ### Type `STRUCT` ```go diff --git a/utils/generator/genreadme/README.md b/utils/generator/genreadme/README.md index 6ead52ec..04c96d04 100644 --- a/utils/generator/genreadme/README.md +++ b/utils/generator/genreadme/README.md @@ -23,7 +23,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Builder](#builder)|暂无描述... +|`STRUCT`|[Builder](#struct_Builder)|暂无描述... @@ -34,6 +34,7 @@ *** + ### Builder `STRUCT` ```go diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index 343656b1..e4ea42bf 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -115,7 +115,7 @@ func (b *Builder) genMenus() { structGen() b.tableRow( super.If(structInfo.Interface, "`INTERFACE`", "`STRUCT`"), - fmt.Sprintf("[%s](#%s)", structInfo.Name, strings.ToLower(structInfo.Name)), + fmt.Sprintf("[%s](#struct_%s)", structInfo.Name, structInfo.Name), collection.FindFirstOrDefaultInSlice(structInfo.Comments.Clear, "暂无描述..."), ) } @@ -199,6 +199,7 @@ func (b *Builder) genStructs() { continue } titleBuild() + b.newLine(fmt.Sprintf(``, structInfo.Name)) b.title(3, fmt.Sprintf("%s `%s`", structInfo.Name, super.If(structInfo.Interface, "INTERFACE", "STRUCT"))) b.newLine(structInfo.Comments.Clear...) b.newLine("```go") diff --git a/utils/generic/README.md b/utils/generic/README.md index 90c71d60..b4deb864 100644 --- a/utils/generic/README.md +++ b/utils/generic/README.md @@ -27,21 +27,21 @@ generic 目的在于提供一组基于泛型的用于处理通用功能的函数 |类型|名称|描述 |:--|:--|:-- -|`INTERFACE`|[IdR](#idr)|暂无描述... -|`INTERFACE`|[IDR](#idr)|暂无描述... -|`INTERFACE`|[IdW](#idw)|暂无描述... -|`INTERFACE`|[IDW](#idw)|暂无描述... -|`INTERFACE`|[IdR2W](#idr2w)|暂无描述... -|`INTERFACE`|[IDR2W](#idr2w)|暂无描述... -|`INTERFACE`|[Ordered](#ordered)|可排序类型 -|`INTERFACE`|[Number](#number)|数字类型 -|`INTERFACE`|[SignedNumber](#signednumber)|有符号数字类型 -|`INTERFACE`|[Integer](#integer)|整数类型 -|`INTERFACE`|[Signed](#signed)|有符号整数类型 -|`INTERFACE`|[Unsigned](#unsigned)|无符号整数类型 -|`INTERFACE`|[UnsignedNumber](#unsignednumber)|无符号数字类型 -|`INTERFACE`|[Float](#float)|浮点类型 -|`INTERFACE`|[Basic](#basic)|基本类型 +|`INTERFACE`|[IdR](#struct_IdR)|暂无描述... +|`INTERFACE`|[IDR](#struct_IDR)|暂无描述... +|`INTERFACE`|[IdW](#struct_IdW)|暂无描述... +|`INTERFACE`|[IDW](#struct_IDW)|暂无描述... +|`INTERFACE`|[IdR2W](#struct_IdR2W)|暂无描述... +|`INTERFACE`|[IDR2W](#struct_IDR2W)|暂无描述... +|`INTERFACE`|[Ordered](#struct_Ordered)|可排序类型 +|`INTERFACE`|[Number](#struct_Number)|数字类型 +|`INTERFACE`|[SignedNumber](#struct_SignedNumber)|有符号数字类型 +|`INTERFACE`|[Integer](#struct_Integer)|整数类型 +|`INTERFACE`|[Signed](#struct_Signed)|有符号整数类型 +|`INTERFACE`|[Unsigned](#struct_Unsigned)|无符号整数类型 +|`INTERFACE`|[UnsignedNumber](#struct_UnsignedNumber)|无符号数字类型 +|`INTERFACE`|[Float](#struct_Float)|浮点类型 +|`INTERFACE`|[Basic](#struct_Basic)|基本类型 @@ -63,6 +63,7 @@ generic 目的在于提供一组基于泛型的用于处理通用功能的函数 > 检查指定的值是否存在 nil *** + ### IdR `INTERFACE` ```go @@ -70,6 +71,7 @@ type IdR[ID comparable] interface { GetId() ID } ``` + ### IDR `INTERFACE` ```go @@ -77,6 +79,7 @@ type IDR[ID comparable] interface { GetID() ID } ``` + ### IdW `INTERFACE` ```go @@ -84,6 +87,7 @@ type IdW[ID comparable] interface { SetId(id ID) } ``` + ### IDW `INTERFACE` ```go @@ -91,6 +95,7 @@ type IDW[ID comparable] interface { SetID(id ID) } ``` + ### IdR2W `INTERFACE` ```go @@ -99,6 +104,7 @@ type IdR2W[ID comparable] interface { IdW[ID] } ``` + ### IDR2W `INTERFACE` ```go @@ -107,6 +113,7 @@ type IDR2W[ID comparable] interface { IDW[ID] } ``` + ### Ordered `INTERFACE` 可排序类型 ```go @@ -114,6 +121,7 @@ type Ordered interface { Integer | Float | ~string } ``` + ### Number `INTERFACE` 数字类型 ```go @@ -121,6 +129,7 @@ type Number interface { Integer | Float } ``` + ### SignedNumber `INTERFACE` 有符号数字类型 ```go @@ -128,6 +137,7 @@ type SignedNumber interface { Signed | Float } ``` + ### Integer `INTERFACE` 整数类型 ```go @@ -135,6 +145,7 @@ type Integer interface { Signed | Unsigned } ``` + ### Signed `INTERFACE` 有符号整数类型 ```go @@ -142,6 +153,7 @@ type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } ``` + ### Unsigned `INTERFACE` 无符号整数类型 ```go @@ -149,6 +161,7 @@ type Unsigned interface { UnsignedNumber | ~uintptr } ``` + ### UnsignedNumber `INTERFACE` 无符号数字类型 ```go @@ -156,6 +169,7 @@ type UnsignedNumber interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 } ``` + ### Float `INTERFACE` 浮点类型 ```go @@ -163,6 +177,7 @@ type Float interface { ~float32 | ~float64 } ``` + ### Basic `INTERFACE` 基本类型 ```go diff --git a/utils/geometry/README.md b/utils/geometry/README.md index 1d3055ea..9bed1fcd 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -117,15 +117,15 @@ geometry 旨在提供一组用于处理几何形状和计算几何属性的函 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Circle](#circle)|圆形 -|`STRUCT`|[FloorPlan](#floorplan)|平面图 -|`STRUCT`|[Direction](#direction)|方向 -|`STRUCT`|[LineSegment](#linesegment)|通过两个点表示一根线段 -|`STRUCT`|[LineSegmentCap](#linesegmentcap)|可以包含一份额外数据的线段 -|`STRUCT`|[Point](#point)|表示了一个由 x、y 坐标组成的点 -|`STRUCT`|[PointCap](#pointcap)|表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 -|`STRUCT`|[Shape](#shape)|通过多个点表示了一个形状 -|`STRUCT`|[ShapeSearchOption](#shapesearchoption)|图形搜索可选项,用于 Shape.ShapeSearch 搜索支持 +|`STRUCT`|[Circle](#struct_Circle)|圆形 +|`STRUCT`|[FloorPlan](#struct_FloorPlan)|平面图 +|`STRUCT`|[Direction](#struct_Direction)|方向 +|`STRUCT`|[LineSegment](#struct_LineSegment)|通过两个点表示一根线段 +|`STRUCT`|[LineSegmentCap](#struct_LineSegmentCap)|可以包含一份额外数据的线段 +|`STRUCT`|[Point](#struct_Point)|表示了一个由 x、y 坐标组成的点 +|`STRUCT`|[PointCap](#struct_PointCap)|表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 +|`STRUCT`|[Shape](#struct_Shape)|通过多个点表示了一个形状 +|`STRUCT`|[ShapeSearchOption](#struct_ShapeSearchOption)|图形搜索可选项,用于 Shape.ShapeSearch 搜索支持 @@ -864,6 +864,7 @@ func TestNewShapeWithString(t *testing.T) { > 通过降序的方式进行搜索 *** + ### Circle `STRUCT` 圆形 ```go @@ -889,6 +890,7 @@ type Circle[V generic.SignedNumber] struct { #### func (Circle) CentroidDistance(circle Circle[V]) V > 计算与另一个圆的质心距离 *** + ### FloorPlan `STRUCT` 平面图 ```go @@ -906,11 +908,13 @@ type FloorPlan []string #### func (FloorPlan) String() string > 获取平面图结果 *** + ### Direction `STRUCT` 方向 ```go type Direction uint8 ``` + ### LineSegment `STRUCT` 通过两个点表示一根线段 ```go @@ -928,6 +932,7 @@ type LineSegment[V generic.SignedNumber] [2]Point[V] #### func (LineSegment) GetLength() V > 获取该线段的长度 *** + ### LineSegmentCap `STRUCT` 可以包含一份额外数据的线段 ```go @@ -936,6 +941,7 @@ type LineSegmentCap[V generic.SignedNumber, Data any] struct { Data Data } ``` + ### Point `STRUCT` 表示了一个由 x、y 坐标组成的点 ```go @@ -989,6 +995,7 @@ type Point[V generic.SignedNumber] [2]V #### func (Point) Min(point Point[V]) Point[V] > 返回两个位置中每个维度的最小值组成的新的位置 *** + ### PointCap `STRUCT` 表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 ```go @@ -997,6 +1004,7 @@ type PointCap[V generic.SignedNumber, D any] struct { Data D } ``` + ### Shape `STRUCT` 通过多个点表示了一个形状 ```go @@ -1139,6 +1147,7 @@ func ExampleShape_ShapeSearch() { #### func (Shape) IsPointOnEdge(point Point[V]) bool > 检查点是否在该形状的一条边上 *** + ### ShapeSearchOption `STRUCT` 图形搜索可选项,用于 Shape.ShapeSearch 搜索支持 ```go diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md index c8e85403..938465c9 100644 --- a/utils/geometry/astar/README.md +++ b/utils/geometry/astar/README.md @@ -28,7 +28,7 @@ astar 提供用于实现 A* 算法的函数和数据结构。A* 算法是一种 |类型|名称|描述 |:--|:--|:-- -|`INTERFACE`|[Graph](#graph)|适用于 A* 算法的图数据结构接口定义,表示导航网格,其中包含了节点和连接节点的边。 +|`INTERFACE`|[Graph](#struct_Graph)|适用于 A* 算法的图数据结构接口定义,表示导航网格,其中包含了节点和连接节点的边。 @@ -77,6 +77,7 @@ func ExampleFind() { ``` *** + ### Graph `INTERFACE` 适用于 A* 算法的图数据结构接口定义,表示导航网格,其中包含了节点和连接节点的边。 ```go diff --git a/utils/geometry/dp/README.md b/utils/geometry/dp/README.md index 3b22ddc2..0250b7df 100644 --- a/utils/geometry/dp/README.md +++ b/utils/geometry/dp/README.md @@ -27,8 +27,8 @@ dp (DistributionPattern) 提供用于在二维数组中根据不同的特征标 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[DistributionPattern](#distributionpattern)|分布图 -|`STRUCT`|[Link](#link)|暂无描述... +|`STRUCT`|[DistributionPattern](#struct_DistributionPattern)|分布图 +|`STRUCT`|[Link](#struct_Link)|暂无描述... @@ -69,6 +69,7 @@ func TestNewDistributionPattern(t *testing.T) { *** + ### DistributionPattern `STRUCT` 分布图 ```go @@ -104,6 +105,7 @@ type DistributionPattern[Item any] struct { > 通过特定的成员刷新特定位置的分布关系 > - 如果矩阵通过 LoadMatrixWithPos 加载,将会重定向至 Refresh *** + ### Link `STRUCT` ```go diff --git a/utils/geometry/matrix/README.md b/utils/geometry/matrix/README.md index 691e139e..9d45d0ed 100644 --- a/utils/geometry/matrix/README.md +++ b/utils/geometry/matrix/README.md @@ -23,7 +23,7 @@ matrix 提供了一个简单的二维数组的实现 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Matrix](#matrix)|二维矩阵 +|`STRUCT`|[Matrix](#struct_Matrix)|二维矩阵 @@ -37,6 +37,7 @@ matrix 提供了一个简单的二维数组的实现 > - 该矩阵为XY,而非YX *** + ### Matrix `STRUCT` 二维矩阵 ```go diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md index 9bdab61e..657620b4 100644 --- a/utils/geometry/navmesh/README.md +++ b/utils/geometry/navmesh/README.md @@ -26,7 +26,7 @@ navmesh 提供了用于导航网格处理的函数和数据结构。导航网格 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[NavMesh](#navmesh)|暂无描述... +|`STRUCT`|[NavMesh](#struct_NavMesh)|暂无描述... @@ -54,6 +54,7 @@ navmesh 提供了用于导航网格处理的函数和数据结构。导航网格 > - 确保 NavMesh 计算精度的情况下,V 建议使用 float64 类型 *** + ### NavMesh `STRUCT` ```go diff --git a/utils/hub/README.md b/utils/hub/README.md index d350d930..d3bdaaaa 100644 --- a/utils/hub/README.md +++ b/utils/hub/README.md @@ -23,7 +23,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[ObjectPool](#objectpool)|基于 sync.Pool 实现的线程安全的对象池 +|`STRUCT`|[ObjectPool](#struct_ObjectPool)|基于 sync.Pool 实现的线程安全的对象池 @@ -89,6 +89,7 @@ func TestNewObjectPool(t *testing.T) { *** + ### ObjectPool `STRUCT` 基于 sync.Pool 实现的线程安全的对象池 - 一些高频临时生成使用的对象可以通过 ObjectPool 进行管理,例如属性计算等 diff --git a/utils/huge/README.md b/utils/huge/README.md index 0a762ceb..83c9e5b4 100644 --- a/utils/huge/README.md +++ b/utils/huge/README.md @@ -26,8 +26,8 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Float](#float)|暂无描述... -|`STRUCT`|[Int](#int)|暂无描述... +|`STRUCT`|[Float](#struct_Float)|暂无描述... +|`STRUCT`|[Int](#struct_Int)|暂无描述... @@ -56,6 +56,7 @@ > - 如果字符串不是一个合法的数字,则返回 0 *** + ### Float `STRUCT` ```go @@ -117,6 +118,7 @@ type Float big.Float #### func (*Float) IsNegative() bool > 是否为负数 *** + ### Int `STRUCT` ```go diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index 15bcb622..9080f557 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -25,9 +25,9 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[BinarySearch](#binarysearch)|暂无描述... -|`STRUCT`|[BinarySearchRankChangeEventHandle](#binarysearchrankchangeeventhandle)|暂无描述... -|`STRUCT`|[BinarySearchOption](#binarysearchoption)|暂无描述... +|`STRUCT`|[BinarySearch](#struct_BinarySearch)|暂无描述... +|`STRUCT`|[BinarySearchRankChangeEventHandle](#struct_BinarySearchRankChangeEventHandle)|暂无描述... +|`STRUCT`|[BinarySearchOption](#struct_BinarySearchOption)|暂无描述... @@ -61,6 +61,7 @@ func ExampleNewBinarySearch() { > - 默认情况下为降序 *** + ### BinarySearch `STRUCT` ```go @@ -74,11 +75,13 @@ type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct { rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] } ``` + ### BinarySearchRankChangeEventHandle `STRUCT` ```go type BinarySearchRankChangeEventHandle[CompetitorID comparable, Score generic.Ordered] func(leaderboard *BinarySearch[CompetitorID, Score], competitorId CompetitorID, oldRank int, oldScore Score) ``` + ### BinarySearchOption `STRUCT` ```go diff --git a/utils/log/README.md b/utils/log/README.md index 37be9bb3..7beab827 100644 --- a/utils/log/README.md +++ b/utils/log/README.md @@ -77,10 +77,10 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Field](#field)|暂无描述... -|`STRUCT`|[Logger](#logger)|暂无描述... -|`STRUCT`|[MultiHandler](#multihandler)|暂无描述... -|`STRUCT`|[Option](#option)|暂无描述... +|`STRUCT`|[Field](#struct_Field)|暂无描述... +|`STRUCT`|[Logger](#struct_Logger)|暂无描述... +|`STRUCT`|[MultiHandler](#struct_MultiHandler)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|暂无描述... @@ -393,11 +393,13 @@ func TestStack(t *testing.T) { > 创建一个新的日志选项 *** + ### Field `STRUCT` ```go type Field slog.Attr ``` + ### Logger `STRUCT` ```go @@ -405,6 +407,7 @@ type Logger struct { *slog.Logger } ``` + ### MultiHandler `STRUCT` ```go @@ -420,6 +423,7 @@ type MultiHandler struct { *** #### func (MultiHandler) WithGroup(name string) slog.Handler *** + ### Option `STRUCT` ```go diff --git a/utils/log/survey/README.md b/utils/log/survey/README.md index 5e05d6fa..f8529958 100644 --- a/utils/log/survey/README.md +++ b/utils/log/survey/README.md @@ -32,13 +32,13 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Analyzer](#analyzer)|分析器 -|`INTERFACE`|[Flusher](#flusher)|用于刷新缓冲区的接口 -|`STRUCT`|[FileFlusher](#fileflusher)|暂无描述... -|`STRUCT`|[Option](#option)|选项 -|`STRUCT`|[Result](#result)|暂无描述... -|`STRUCT`|[R](#r)|记录器所记录的一条数据 -|`STRUCT`|[Report](#report)|分析报告 +|`STRUCT`|[Analyzer](#struct_Analyzer)|分析器 +|`INTERFACE`|[Flusher](#struct_Flusher)|用于刷新缓冲区的接口 +|`STRUCT`|[FileFlusher](#struct_FileFlusher)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|选项 +|`STRUCT`|[Result](#struct_Result)|暂无描述... +|`STRUCT`|[R](#struct_R)|记录器所记录的一条数据 +|`STRUCT`|[Report](#struct_Report)|分析报告 @@ -153,6 +153,7 @@ func TestIncrementAnalyze(t *testing.T) { *** + ### Analyzer `STRUCT` 分析器 ```go @@ -214,6 +215,7 @@ type Analyzer struct { #### func (*Analyzer) GetValueString(key string) string > 获取当前记录的值 *** + ### Flusher `INTERFACE` 用于刷新缓冲区的接口 ```go @@ -222,6 +224,7 @@ type Flusher interface { Info() string } ``` + ### FileFlusher `STRUCT` ```go @@ -237,16 +240,19 @@ type FileFlusher struct { *** #### func (*FileFlusher) Info() string *** + ### Option `STRUCT` 选项 ```go type Option func(logger *logger) ``` + ### Result `STRUCT` ```go type Result gjson.Result ``` + ### R `STRUCT` 记录器所记录的一条数据 ```go @@ -280,6 +286,7 @@ type R string *** #### func (R) String() string *** + ### Report `STRUCT` 分析报告 ```go diff --git a/utils/maths/README.md b/utils/maths/README.md index 5d9170ce..8bde01c1 100644 --- a/utils/maths/README.md +++ b/utils/maths/README.md @@ -45,7 +45,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[CompareExpression](#compareexpression)|比较表达式 +|`STRUCT`|[CompareExpression](#struct_CompareExpression)|比较表达式 @@ -213,6 +213,7 @@ func TestMakeLastDigitsZero(t *testing.T) { *** + ### CompareExpression `STRUCT` 比较表达式 ```go diff --git a/utils/memory/README.md b/utils/memory/README.md index 25256182..3c232353 100644 --- a/utils/memory/README.md +++ b/utils/memory/README.md @@ -26,7 +26,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Option](#option)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|暂无描述... @@ -96,6 +96,7 @@ func TestBindAction(t *testing.T) { *** + ### Option `STRUCT` ```go diff --git a/utils/moving/README.md b/utils/moving/README.md index 8cefd3df..164b2350 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -27,10 +27,10 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[TwoDimensional](#twodimensional)|用于2D对象移动的数据结构 -|`INTERFACE`|[TwoDimensionalEntity](#twodimensionalentity)|2D移动对象接口定义 -|`STRUCT`|[Position2DChangeEventHandle](#position2dchangeeventhandle)|暂无描述... -|`STRUCT`|[TwoDimensionalOption](#twodimensionaloption)|暂无描述... +|`STRUCT`|[TwoDimensional](#struct_TwoDimensional)|用于2D对象移动的数据结构 +|`INTERFACE`|[TwoDimensionalEntity](#struct_TwoDimensionalEntity)|2D移动对象接口定义 +|`STRUCT`|[Position2DChangeEventHandle](#struct_Position2DChangeEventHandle)|暂无描述... +|`STRUCT`|[TwoDimensionalOption](#struct_TwoDimensionalOption)|暂无描述... @@ -95,6 +95,7 @@ func TestNewTwoDimensional(t *testing.T) { *** + ### TwoDimensional `STRUCT` 用于2D对象移动的数据结构 - 通过对象调用 MoveTo 方法后将开始执行该对象的移动 @@ -113,6 +114,7 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber] struct { position2DStopMoveEventHandles []Position2DStopMoveEventHandle[EID, PosType] } ``` + ### TwoDimensionalEntity `INTERFACE` 2D移动对象接口定义 ```go @@ -123,11 +125,13 @@ type TwoDimensionalEntity[EID generic.Basic, PosType generic.SignedNumber] inter SetPosition(geometry.Point[PosType]) } ``` + ### Position2DChangeEventHandle `STRUCT` ```go type Position2DChangeEventHandle[EID generic.Basic, PosType generic.SignedNumber] func(moving *TwoDimensional[EID, PosType], entity TwoDimensionalEntity[EID, PosType], oldX PosType) ``` + ### TwoDimensionalOption `STRUCT` ```go diff --git a/utils/offset/README.md b/utils/offset/README.md index 4dd1b328..4c11c007 100644 --- a/utils/offset/README.md +++ b/utils/offset/README.md @@ -27,7 +27,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Time](#time)|带有偏移量的时间 +|`STRUCT`|[Time](#struct_Time)|带有偏移量的时间 @@ -59,6 +59,7 @@ > 获取当前时间偏移后的时间自从 t 以来经过的时间 *** + ### Time `STRUCT` 带有偏移量的时间 ```go diff --git a/utils/sole/README.md b/utils/sole/README.md index 786c068f..5c238b08 100644 --- a/utils/sole/README.md +++ b/utils/sole/README.md @@ -38,7 +38,7 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[Once](#once)|用于数据取值去重的结构体 +|`STRUCT`|[Once](#struct_Once)|用于数据取值去重的结构体 @@ -125,6 +125,7 @@ > 获取一个自增的字符串 *** + ### Once `STRUCT` 用于数据取值去重的结构体 ```go diff --git a/utils/super/README.md b/utils/super/README.md index a3feb3f5..700d5ad3 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -92,12 +92,12 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[BitSet](#bitset)|是一个可以动态增长的比特位集合 -|`STRUCT`|[LossCounter](#losscounter)|暂无描述... -|`STRUCT`|[Matcher](#matcher)|匹配器 -|`STRUCT`|[Permission](#permission)|暂无描述... -|`STRUCT`|[StackGo](#stackgo)|用于获取上一个协程调用的堆栈信息 -|`STRUCT`|[VerifyHandle](#verifyhandle)|校验句柄 +|`STRUCT`|[BitSet](#struct_BitSet)|是一个可以动态增长的比特位集合 +|`STRUCT`|[LossCounter](#struct_LossCounter)|暂无描述... +|`STRUCT`|[Matcher](#struct_Matcher)|匹配器 +|`STRUCT`|[Permission](#struct_Permission)|暂无描述... +|`STRUCT`|[StackGo](#struct_StackGo)|用于获取上一个协程调用的堆栈信息 +|`STRUCT`|[VerifyHandle](#struct_VerifyHandle)|校验句柄 @@ -739,6 +739,7 @@ func BenchmarkCompareVersion(b *testing.B) { *** + ### BitSet `STRUCT` 是一个可以动态增长的比特位集合 - 默认情况下将使用 64 位无符号整数来表示比特位,当需要表示的比特位超过 64 位时,将自动增长 @@ -907,6 +908,7 @@ func TestBitSet_Shrink(t *testing.T) { #### func (*BitSet) UnmarshalJSON(data []byte) error > 实现 json.Unmarshaler 接口 *** + ### LossCounter `STRUCT` ```go @@ -924,6 +926,7 @@ type LossCounter struct { *** #### func (*LossCounter) String() string *** + ### Matcher `STRUCT` 匹配器 ```go @@ -933,6 +936,7 @@ type Matcher[Value any, Result any] struct { d bool } ``` + ### Permission `STRUCT` ```go @@ -941,6 +945,7 @@ type Permission[Code generic.Integer, EntityID comparable] struct { l sync.RWMutex } ``` + ### StackGo `STRUCT` 用于获取上一个协程调用的堆栈信息 - 应当最先运行 Wait 函数,然后在其他协程中调用 Stack 函数或者 GiveUp 函数 @@ -965,6 +970,7 @@ type StackGo struct { > - 在调用 Wait 函数后调用该函数,将会放弃收集消息堆栈并且释放资源 > - 在调用 GiveUp 函数后调用 Stack 函数,将会 panic *** + ### VerifyHandle `STRUCT` 校验句柄 ```go diff --git a/utils/timer/README.md b/utils/timer/README.md index 9200c3ff..ab9b2f2e 100644 --- a/utils/timer/README.md +++ b/utils/timer/README.md @@ -31,11 +31,11 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[SystemNewDayEventHandle](#systemnewdayeventhandle)|暂无描述... -|`STRUCT`|[Option](#option)|暂无描述... -|`STRUCT`|[Pool](#pool)|定时器池 -|`STRUCT`|[Scheduler](#scheduler)|调度器 -|`STRUCT`|[Ticker](#ticker)|定时器 +|`STRUCT`|[SystemNewDayEventHandle](#struct_SystemNewDayEventHandle)|暂无描述... +|`STRUCT`|[Option](#struct_Option)|暂无描述... +|`STRUCT`|[Pool](#struct_Pool)|定时器池 +|`STRUCT`|[Scheduler](#struct_Scheduler)|调度器 +|`STRUCT`|[Ticker](#struct_Ticker)|定时器 @@ -92,16 +92,19 @@ > 获取标准池中的一个定时器 *** + ### SystemNewDayEventHandle `STRUCT` ```go type SystemNewDayEventHandle func() ``` + ### Option `STRUCT` ```go type Option func(ticker *Ticker) ``` + ### Pool `STRUCT` 定时器池 ```go @@ -123,6 +126,7 @@ type Pool struct { > 释放定时器池的资源,释放后由其产生的 Ticker 在 Ticker.Release 后将不再回到池中,而是直接释放 > - 虽然定时器池已被释放,但是依旧可以产出 Ticker *** + ### Scheduler `STRUCT` 调度器 ```go @@ -150,6 +154,7 @@ type Scheduler struct { #### func (*Scheduler) Caller() > 可由外部发起调用的执行函数 *** + ### Ticker `STRUCT` 定时器 ```go diff --git a/utils/times/README.md b/utils/times/README.md index 66dbdc19..dc2a35ec 100644 --- a/utils/times/README.md +++ b/utils/times/README.md @@ -70,8 +70,8 @@ |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[StateLine](#stateline)|状态时间线 -|`STRUCT`|[Period](#period)|表示一个时间段 +|`STRUCT`|[StateLine](#struct_StateLine)|状态时间线 +|`STRUCT`|[Period](#struct_Period)|表示一个时间段 @@ -399,6 +399,7 @@ func TestNewPeriodWindow(t *testing.T) { > 创建一个时间段,从 t 开始,持续 nanosecond 纳秒 *** + ### StateLine `STRUCT` 状态时间线 ```go @@ -486,6 +487,7 @@ type StateLine[State generic.Basic] struct { #### func (*StateLine) RangeReverse(handler func (index int, state State, t time.Time) bool) > 按照时间逆序遍历时间线 *** + ### Period `STRUCT` 表示一个时间段 ```go From a026e4cf965ffa2390ac86a29478f609ceab23a3 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 10:54:27 +0800 Subject: [PATCH 03/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E5=8C=85=E7=BA=A7=E5=87=BD=E6=95=B0=E4=B8=8D=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E7=AD=BE=E5=90=8D=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/README.md | 34 ++-- game/fight/README.md | 2 +- game/space/README.md | 4 +- game/task/README.md | 4 +- server/README.md | 4 +- server/internal/dispatcher/README.md | 4 +- server/lockstep/README.md | 10 +- server/router/README.md | 4 +- server/writeloop/README.md | 4 +- utils/aoi/README.md | 2 +- utils/arrangement/README.md | 20 +-- utils/buffer/README.md | 6 +- utils/collection/README.md | 233 ++++++++++++++------------- utils/collection/clone.go | 5 +- utils/collection/doc.go | 2 +- utils/collection/listings/README.md | 8 +- utils/collection/mappings/README.md | 2 +- utils/combination/README.md | 60 +++---- utils/deck/README.md | 4 +- utils/fsm/README.md | 12 +- utils/generator/genreadme/builder.go | 18 ++- utils/generic/README.md | 6 +- utils/geometry/README.md | 150 ++++++++--------- utils/geometry/astar/README.md | 2 +- utils/geometry/dp/README.md | 2 +- utils/geometry/matrix/README.md | 2 +- utils/geometry/navmesh/README.md | 2 +- utils/hub/README.md | 2 +- utils/huge/README.md | 4 +- utils/leaderboard/README.md | 6 +- utils/log/README.md | 52 +++--- utils/maths/README.md | 38 ++--- utils/memory/README.md | 4 +- utils/moving/README.md | 8 +- utils/random/README.md | 12 +- utils/reflects/README.md | 10 +- utils/sole/README.md | 2 +- utils/sorts/README.md | 2 +- utils/super/README.md | 18 +-- utils/times/README.md | 2 +- 40 files changed, 389 insertions(+), 377 deletions(-) diff --git a/game/activity/README.md b/game/activity/README.md index 85c93600..0147d77b 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -75,88 +75,88 @@ activity 活动状态管理 > 加载所有活动实体数据 *** -#### func LoadOrRefreshActivity(activityType Type, activityId ID, options ...*Options) error +#### func LoadOrRefreshActivity\[Type generic.Basic, ID generic.Basic\](activityType Type, activityId ID, options ...*Options) error > 加载或刷新活动 > - 通常在活动配置刷新时候将活动通过该方法注册或刷新 *** -#### func DefineNoneDataActivity(activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] +#### func DefineNoneDataActivity\[Type generic.Basic, ID generic.Basic\](activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] > 声明无数据的活动类型 *** -#### func DefineGlobalDataActivity(activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] +#### func DefineGlobalDataActivity\[Type generic.Basic, ID generic.Basic, Data any\](activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] > 声明拥有全局数据的活动类型 *** -#### func DefineEntityDataActivity(activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] +#### func DefineEntityDataActivity\[Type generic.Basic, ID generic.Basic, EntityID generic.Basic, EntityData any\](activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] > 声明拥有实体数据的活动类型 *** -#### func DefineGlobalAndEntityDataActivity(activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] +#### func DefineGlobalAndEntityDataActivity\[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any\](activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] > 声明拥有全局数据和实体数据的活动类型 *** -#### func RegUpcomingEvent(activityType Type, handler UpcomingEventHandler[ID], priority ...int) +#### func RegUpcomingEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler UpcomingEventHandler[ID], priority ...int) > 注册即将开始的活动事件处理器 *** -#### func OnUpcomingEvent(activity *Activity[Type, ID]) +#### func OnUpcomingEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 即将开始的活动事件 *** -#### func RegStartedEvent(activityType Type, handler StartedEventHandler[ID], priority ...int) +#### func RegStartedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler StartedEventHandler[ID], priority ...int) > 注册活动开始事件处理器 *** -#### func OnStartedEvent(activity *Activity[Type, ID]) +#### func OnStartedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 活动开始事件 *** -#### func RegEndedEvent(activityType Type, handler EndedEventHandler[ID], priority ...int) +#### func RegEndedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler EndedEventHandler[ID], priority ...int) > 注册活动结束事件处理器 *** -#### func OnEndedEvent(activity *Activity[Type, ID]) +#### func OnEndedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 活动结束事件 *** -#### func RegExtendedShowStartedEvent(activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) +#### func RegExtendedShowStartedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) > 注册活动结束后延长展示开始事件处理器 *** -#### func OnExtendedShowStartedEvent(activity *Activity[Type, ID]) +#### func OnExtendedShowStartedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 活动结束后延长展示开始事件 *** -#### func RegExtendedShowEndedEvent(activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) +#### func RegExtendedShowEndedEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) > 注册活动结束后延长展示结束事件处理器 *** -#### func OnExtendedShowEndedEvent(activity *Activity[Type, ID]) +#### func OnExtendedShowEndedEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 活动结束后延长展示结束事件 *** -#### func RegNewDayEvent(activityType Type, handler NewDayEventHandler[ID], priority ...int) +#### func RegNewDayEvent\[Type generic.Basic, ID generic.Basic\](activityType Type, handler NewDayEventHandler[ID], priority ...int) > 注册新的一天事件处理器 *** -#### func OnNewDayEvent(activity *Activity[Type, ID]) +#### func OnNewDayEvent\[Type generic.Basic, ID generic.Basic\](activity *Activity[Type, ID]) > 新的一天事件 diff --git a/game/fight/README.md b/game/fight/README.md index 8f4bb3ef..14ceab14 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewTurnBased(calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] +#### func NewTurnBased\[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]\](calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] > 创建一个新的回合制 > - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间 diff --git a/game/space/README.md b/game/space/README.md index 1e3542f2..4b239fbf 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -34,7 +34,7 @@ space 游戏中常见的空间设计,例如房间、地图等 *** ## 详情信息 -#### func NewRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] +#### func NewRoomManager\[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]\]() *RoomManager[EntityID, RoomID, Entity, Room] > 创建房间管理器 RoomManager 的实例 @@ -49,7 +49,7 @@ func ExampleNewRoomManager() { ``` *** -#### func NewRoomControllerOptions() *RoomControllerOptions[EntityID, RoomID, Entity, Room] +#### func NewRoomControllerOptions\[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]\]() *RoomControllerOptions[EntityID, RoomID, Entity, Room] > 创建房间控制器选项 diff --git a/game/task/README.md b/game/task/README.md index a86db7e0..58902dec 100644 --- a/game/task/README.md +++ b/game/task/README.md @@ -88,7 +88,7 @@ func TestCond(t *testing.T) { *** -#### func RegisterRefreshTaskCounterEvent(taskType string, handler RefreshTaskCounterEventHandler[Trigger]) +#### func RegisterRefreshTaskCounterEvent\[Trigger any\](taskType string, handler RefreshTaskCounterEventHandler[Trigger]) > 注册特定任务类型的刷新任务计数器事件处理函数 @@ -98,7 +98,7 @@ func TestCond(t *testing.T) { > 触发特定任务类型的刷新任务计数器事件 *** -#### func RegisterRefreshTaskConditionEvent(taskType string, handler RefreshTaskConditionEventHandler[Trigger]) +#### func RegisterRefreshTaskConditionEvent\[Trigger any\](taskType string, handler RefreshTaskConditionEventHandler[Trigger]) > 注册特定任务类型的刷新任务条件事件处理函数 diff --git a/server/README.md b/server/README.md index d048f66b..80226cc9 100644 --- a/server/README.md +++ b/server/README.md @@ -138,7 +138,7 @@ func TestNewBot(t *testing.T) { *** -#### func NewHttpHandleWrapper(srv *Server, packer ContextPacker[Context]) *Http[Context] +#### func NewHttpHandleWrapper\[Context any\](srv *Server, packer ContextPacker[Context]) *Http[Context] > 创建一个新的 http 处理程序包装器 > - 默认使用 server.HttpContext 作为上下文,如果需要依赖其作为新的上下文,可以通过 NewHttpContext 创建 @@ -149,7 +149,7 @@ func TestNewBot(t *testing.T) { > 基于 gin.Context 创建一个新的 HttpContext *** -#### func NewGinWrapper(server *gin.Engine, pack func (ctx *gin.Context) CTX) *HttpWrapper[CTX] +#### func NewGinWrapper\[CTX any\](server *gin.Engine, pack func (ctx *gin.Context) CTX) *HttpWrapper[CTX] > 创建 gin 包装器,用于对 NewHttpWrapper 函数的替代 diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index 1c6e6a5b..3948e8b4 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -36,7 +36,7 @@ *** ## 详情信息 -#### func NewDispatcher(bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] +#### func NewDispatcher\[P Producer, M Message[P]\](bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] > 创建一个新的消息分发器 Dispatcher 实例 @@ -107,7 +107,7 @@ func TestNewDispatcher(t *testing.T) { *** -#### func NewManager(bufferSize int, handler Handler[P, M]) *Manager[P, M] +#### func NewManager\[P Producer, M Message[P]\](bufferSize int, handler Handler[P, M]) *Manager[P, M] > 生成消息分发器管理器 diff --git a/server/lockstep/README.md b/server/lockstep/README.md index 4f9f1479..e0a6a482 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -37,7 +37,7 @@ *** ## 详情信息 -#### func NewLockstep(options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] +#### func NewLockstep\[ClientID comparable, Command any\](options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] > 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回 @@ -78,19 +78,19 @@ func TestNewLockstep(t *testing.T) { *** -#### func WithFrameLimit(frameLimit int64) Option[ClientID, Command] +#### func WithFrameLimit\[ClientID comparable, Command any\](frameLimit int64) Option[ClientID, Command] > 通过特定逻辑帧上限创建锁步(帧)同步组件 > - 当达到上限时将停止广播 *** -#### func WithFrameRate(frameRate int64) Option[ClientID, Command] +#### func WithFrameRate\[ClientID comparable, Command any\](frameRate int64) Option[ClientID, Command] > 通过特定逻辑帧率创建锁步(帧)同步组件 > - 默认情况下为 15/s *** -#### func WithSerialization(handle func (frame int64, commands []Command) []byte) Option[ClientID, Command] +#### func WithSerialization\[ClientID comparable, Command any\](handle func (frame int64, commands []Command) []byte) Option[ClientID, Command] > 通过特定的序列化方式将每一帧的数据进行序列化 > @@ -102,7 +102,7 @@ func TestNewLockstep(t *testing.T) { > } *** -#### func WithInitFrame(initFrame int64) Option[ClientID, Command] +#### func WithInitFrame\[ClientID comparable, Command any\](initFrame int64) Option[ClientID, Command] > 通过特定的初始帧创建锁步(帧)同步组件 > - 默认情况下为 0,即第一帧索引为 0 diff --git a/server/router/README.md b/server/router/README.md index aa0095f0..56e4248f 100644 --- a/server/router/README.md +++ b/server/router/README.md @@ -33,7 +33,7 @@ *** ## 详情信息 -#### func NewMultistage(options ...MultistageOption[HandleFunc]) *Multistage[HandleFunc] +#### func NewMultistage\[HandleFunc any\](options ...MultistageOption[HandleFunc]) *Multistage[HandleFunc] > 创建一个支持多级分类的路由器 @@ -47,7 +47,7 @@ func ExampleNewMultistage() { ``` *** -#### func WithRouteTrim(handle func (route any) any) MultistageOption[HandleFunc] +#### func WithRouteTrim\[HandleFunc any\](handle func (route any) any) MultistageOption[HandleFunc] > 路由修剪选项 > - 将在路由注册前对路由进行对应处理 diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 42f8feb2..3b4d67bd 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -33,7 +33,7 @@ *** ## 详情信息 -#### func NewChannel(pool *hub.ObjectPool[Message], channelSize int, writeHandler func (message Message) error, errorHandler func (err any)) *Channel[Message] +#### func NewChannel\[Message any\](pool *hub.ObjectPool[Message], channelSize int, writeHandler func (message Message) error, errorHandler func (err any)) *Channel[Message] > 创建基于 Channel 的写循环 > - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Channel 会在写入完成后将 Message 对象放回缓冲池 @@ -44,7 +44,7 @@ > 传入 writeHandler 的消息对象是从 Channel 中获取的,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 *** -#### func NewUnbounded(pool *hub.ObjectPool[Message], writeHandler func (message Message) error, errorHandler func (err any)) *Unbounded[Message] +#### func NewUnbounded\[Message any\](pool *hub.ObjectPool[Message], writeHandler func (message Message) error, errorHandler func (err any)) *Unbounded[Message] > 创建写循环 > - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Unbounded 会在写入完成后将 Message 对象放回缓冲池 diff --git a/utils/aoi/README.md b/utils/aoi/README.md index 02bc9260..ba1f75c1 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -37,7 +37,7 @@ AOI 问题是在大规模多人在线游戏中常见的问题,它涉及到确 *** ## 详情信息 -#### func NewTwoDimensional(width int, height int, areaWidth int, areaHeight int) *TwoDimensional[EID, PosType, E] +#### func NewTwoDimensional\[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]\](width int, height int, areaWidth int, areaHeight int) *TwoDimensional[EID, PosType, E]
diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index e06b7700..e5feb021 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -50,54 +50,54 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 *** ## 详情信息 -#### func WithAreaConstraint(constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaConstraint\[ID comparable, AreaInfo any\](constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的约束条件 > - 该约束用于判断一个成员是否可以被添加到该编排区域中 > - 与 WithAreaConflict 不同的是,约束通常用于非成员关系导致的硬性约束,例如:成员的等级过滤、成员的性别等 *** -#### func WithAreaConflict(conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaConflict\[ID comparable, AreaInfo any\](conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的冲突条件,冲突处理函数需要返回造成冲突的成员列表 > - 该冲突用于判断一个成员是否可以被添加到该编排区域中 > - 与 WithAreaConstraint 不同的是,冲突通常用于成员关系导致的软性约束,例如:成员的职业唯一性、成员的种族唯一性等 *** -#### func WithAreaEvaluate(evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] +#### func WithAreaEvaluate\[ID comparable, AreaInfo any\](evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] > 设置编排区域的评估函数 > - 该评估函数将影响成员被编入区域的优先级 *** -#### func NewArrangement(options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] +#### func NewArrangement\[ID comparable, AreaInfo any\](options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] > 创建一个新的编排 *** -#### func WithItemFixed(matcher ItemFixedAreaHandle[AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemFixed\[ID comparable, AreaInfo any\](matcher ItemFixedAreaHandle[AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员的固定编排区域 *** -#### func WithItemPriority(priority ItemPriorityHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemPriority\[ID comparable, AreaInfo any\](priority ItemPriorityHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员的优先级 *** -#### func WithItemNotAllow(verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] +#### func WithItemNotAllow\[ID comparable, AreaInfo any\](verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] > 设置成员不允许的编排区域 *** -#### func WithRetryThreshold(threshold int) Option[ID, AreaInfo] +#### func WithRetryThreshold\[ID comparable, AreaInfo any\](threshold int) Option[ID, AreaInfo] > 设置编排时的重试阈值 > - 当每一轮编排结束任有成员未被编排时,将会进行下一轮编排,直到编排次数达到该阈值 > - 默认的阈值为 10 次 *** -#### func WithConstraintHandle(handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] +#### func WithConstraintHandle\[ID comparable, AreaInfo any\](handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] > 设置编排时触发约束时的处理函数 > - 当约束条件触发时,将会调用该函数。如果无法在该函数中处理约束,应该继续返回 err,尝试进行下一层的约束处理 @@ -107,7 +107,7 @@ arrangement 包提供了一些有用的函数来处理数组的排列。 > 有意思的是,硬性约束应该永远是无解的,而当需要进行一些打破规则的操作时,则可以透过该函数传入的 editor 进行操作 *** -#### func WithConflictHandle(handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] +#### func WithConflictHandle\[ID comparable, AreaInfo any\](handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] > 设置编排时触发冲突时的处理函数 > - 当冲突条件触发时,将会调用该函数。如果无法在该函数中处理冲突,应该继续返回这一批成员,尝试进行下一层的冲突处理 diff --git a/utils/buffer/README.md b/utils/buffer/README.md index b96e2b34..14701785 100644 --- a/utils/buffer/README.md +++ b/utils/buffer/README.md @@ -40,7 +40,7 @@ buffer 提供了缓冲区相关的实用程序。 *** ## 详情信息 -#### func NewRing(initSize ...int) *Ring[T] +#### func NewRing\[T any\](initSize ...int) *Ring[T] > 创建一个并发不安全的环形缓冲区 > - initSize: 初始容量 @@ -68,12 +68,12 @@ func TestNewRing(t *testing.T) { *** -#### func NewRingUnbounded(bufferSize int) *RingUnbounded[T] +#### func NewRingUnbounded\[T any\](bufferSize int) *RingUnbounded[T] > 创建一个并发安全的基于环形缓冲区实现的无界缓冲区 *** -#### func NewUnbounded() *Unbounded[V] +#### func NewUnbounded\[V any\]() *Unbounded[V] > 创建一个无界缓冲区 > - generateNil: 生成空值的函数,该函数仅需始终返回 nil 即可 diff --git a/utils/collection/README.md b/utils/collection/README.md index 48502463..5c3a13c2 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -3,7 +3,7 @@ [![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) -collection 用于对 input 和 map 操作的工具函数 +collection 定义了各种对于集合操作有用的各种函数 ## 目录导航 @@ -16,8 +16,8 @@ collection 用于对 input 和 map 操作的工具函数 |函数名称|描述 |:--|:-- -|[CloneSlice](#CloneSlice)|克隆切片,该函数是 slices.Clone 的快捷方式 -|[CloneMap](#CloneMap)|克隆 map +|[CloneSlice](#CloneSlice)|通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 +|[CloneMap](#CloneMap)|通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map |[CloneSliceN](#CloneSliceN)|克隆 slice 为 n 个切片进行返回 |[CloneMapN](#CloneMapN)|克隆 map 为 n 个 map 进行返回 |[CloneSlices](#CloneSlices)|克隆多个切片 @@ -141,9 +141,9 @@ collection 用于对 input 和 map 操作的工具函数 *** ## 详情信息 -#### func CloneSlice(slice S) S +#### func CloneSlice\[S ~[]V, V any\](slice S) S -> 克隆切片,该函数是 slices.Clone 的快捷方式 +> 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 示例代码: ```go @@ -190,9 +190,10 @@ func TestCloneSlice(t *testing.T) { *** -#### func CloneMap(m M) M +#### func CloneMap\[M ~map[K]V, K comparable, V any\](m M) M -> 克隆 map +> 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map +> - 当 m 为空时,将会返回 nil 示例代码: ```go @@ -239,7 +240,7 @@ func TestCloneMap(t *testing.T) { *** -#### func CloneSliceN(slice S, n int) []S +#### func CloneSliceN\[S ~[]V, V any\](slice S, n int) []S > 克隆 slice 为 n 个切片进行返回 @@ -294,7 +295,7 @@ func TestCloneSliceN(t *testing.T) { *** -#### func CloneMapN(m M, n int) []M +#### func CloneMapN\[M ~map[K]V, K comparable, V any\](m M, n int) []M > 克隆 map 为 n 个 map 进行返回 @@ -349,7 +350,7 @@ func TestCloneMapN(t *testing.T) { *** -#### func CloneSlices(slices ...S) []S +#### func CloneSlices\[S ~[]V, V any\](slices ...S) []S > 克隆多个切片 @@ -401,7 +402,7 @@ func TestCloneSlices(t *testing.T) { *** -#### func CloneMaps(maps ...M) []M +#### func CloneMaps\[M ~map[K]V, K comparable, V any\](maps ...M) []M > 克隆多个 map @@ -453,7 +454,7 @@ func TestCloneMaps(t *testing.T) { *** -#### func InSlice(slice S, v V, handler ComparisonHandler[V]) bool +#### func InSlice\[S ~[]V, V any\](slice S, v V, handler ComparisonHandler[V]) bool > 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 @@ -501,7 +502,7 @@ func TestInSlice(t *testing.T) { *** -#### func InComparableSlice(slice S, v V) bool +#### func InComparableSlice\[S ~[]V, V comparable\](slice S, v V) bool > 检查 v 是否被包含在 slice 中 @@ -545,7 +546,7 @@ func TestInComparableSlice(t *testing.T) { *** -#### func AllInSlice(slice S, values []V, handler ComparisonHandler[V]) bool +#### func AllInSlice\[S ~[]V, V any\](slice S, values []V, handler ComparisonHandler[V]) bool > 检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 > - 在所有 values 中的元素都被包含在 slice 中时,返回 true @@ -595,7 +596,7 @@ func TestAllInSlice(t *testing.T) { *** -#### func AllInComparableSlice(slice S, values []V) bool +#### func AllInComparableSlice\[S ~[]V, V comparable\](slice S, values []V) bool > 检查 values 中的所有元素是否均被包含在 slice 中 > - 在所有 values 中的元素都被包含在 slice 中时,返回 true @@ -641,7 +642,7 @@ func TestAllInComparableSlice(t *testing.T) { *** -#### func AnyInSlice(slice S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInSlice\[S ~[]V, V any\](slice S, values []V, handler ComparisonHandler[V]) bool > 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true @@ -690,7 +691,7 @@ func TestAnyInSlice(t *testing.T) { *** -#### func AnyInComparableSlice(slice S, values []V) bool +#### func AnyInComparableSlice\[S ~[]V, V comparable\](slice S, values []V) bool > 检查 values 中的任意一个元素是否被包含在 slice 中 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true @@ -735,7 +736,7 @@ func TestAnyInComparableSlice(t *testing.T) { *** -#### func InSlices(slices []S, v V, handler ComparisonHandler[V]) bool +#### func InSlices\[S ~[]V, V any\](slices []S, v V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true @@ -784,7 +785,7 @@ func TestInSlices(t *testing.T) { *** -#### func InComparableSlices(slices []S, v V) bool +#### func InComparableSlices\[S ~[]V, V comparable\](slices []S, v V) bool > 通过将多个切片合并后检查 v 是否被包含在 slices 中 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true @@ -829,7 +830,7 @@ func TestInComparableSlices(t *testing.T) { *** -#### func AllInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AllInSlices\[S ~[]V, V any\](slices []S, values []V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true @@ -878,7 +879,7 @@ func TestAllInSlices(t *testing.T) { *** -#### func AllInComparableSlices(slices []S, values []V) bool +#### func AllInComparableSlices\[S ~[]V, V comparable\](slices []S, values []V) bool > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true @@ -923,7 +924,7 @@ func TestAllInComparableSlices(t *testing.T) { *** -#### func AnyInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInSlices\[S ~[]V, V any\](slices []S, values []V, handler ComparisonHandler[V]) bool > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true @@ -972,7 +973,7 @@ func TestAnyInSlices(t *testing.T) { *** -#### func AnyInComparableSlices(slices []S, values []V) bool +#### func AnyInComparableSlices\[S ~[]V, V comparable\](slices []S, values []V) bool > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true @@ -1017,7 +1018,7 @@ func TestAnyInComparableSlices(t *testing.T) { *** -#### func InAllSlices(slices []S, v V, handler ComparisonHandler[V]) bool +#### func InAllSlices\[S ~[]V, V any\](slices []S, v V, handler ComparisonHandler[V]) bool > 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当 v 被包含在 slices 的每一项元素中时,返回 true @@ -1066,7 +1067,7 @@ func TestInAllSlices(t *testing.T) { *** -#### func InAllComparableSlices(slices []S, v V) bool +#### func InAllComparableSlices\[S ~[]V, V comparable\](slices []S, v V) bool > 检查 v 是否被包含在 slices 的每一项元素中 > - 当 v 被包含在 slices 的每一项元素中时,返回 true @@ -1111,7 +1112,7 @@ func TestInAllComparableSlices(t *testing.T) { *** -#### func AnyInAllSlices(slices []S, values []V, handler ComparisonHandler[V]) bool +#### func AnyInAllSlices\[S ~[]V, V any\](slices []S, values []V, handler ComparisonHandler[V]) bool > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true @@ -1160,7 +1161,7 @@ func TestAnyInAllSlices(t *testing.T) { *** -#### func AnyInAllComparableSlices(slices []S, values []V) bool +#### func AnyInAllComparableSlices\[S ~[]V, V comparable\](slices []S, values []V) bool > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true @@ -1205,7 +1206,7 @@ func TestAnyInAllComparableSlices(t *testing.T) { *** -#### func KeyInMap(m M, key K) bool +#### func KeyInMap\[M ~map[K]V, K comparable, V any\](m M, key K) bool > 检查 m 中是否包含特定 key @@ -1249,7 +1250,7 @@ func TestKeyInMap(t *testing.T) { *** -#### func ValueInMap(m M, value V, handler ComparisonHandler[V]) bool +#### func ValueInMap\[M ~map[K]V, K comparable, V any\](m M, value V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配 @@ -1296,7 +1297,7 @@ func TestValueInMap(t *testing.T) { *** -#### func AllKeyInMap(m M, keys ...K) bool +#### func AllKeyInMap\[M ~map[K]V, K comparable, V any\](m M, keys ...K) bool > 检查 m 中是否包含 keys 中所有的元素 @@ -1340,7 +1341,7 @@ func TestAllKeyInMap(t *testing.T) { *** -#### func AllValueInMap(m M, values []V, handler ComparisonHandler[V]) bool +#### func AllValueInMap\[M ~map[K]V, K comparable, V any\](m M, values []V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 @@ -1386,7 +1387,7 @@ func TestAllValueInMap(t *testing.T) { *** -#### func AnyKeyInMap(m M, keys ...K) bool +#### func AnyKeyInMap\[M ~map[K]V, K comparable, V any\](m M, keys ...K) bool > 检查 m 中是否包含 keys 中任意一个元素 @@ -1430,7 +1431,7 @@ func TestAnyKeyInMap(t *testing.T) { *** -#### func AnyValueInMap(m M, values []V, handler ComparisonHandler[V]) bool +#### func AnyValueInMap\[M ~map[K]V, K comparable, V any\](m M, values []V, handler ComparisonHandler[V]) bool > 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 @@ -1476,7 +1477,7 @@ func TestAnyValueInMap(t *testing.T) { *** -#### func AllKeyInMaps(maps []M, keys ...K) bool +#### func AllKeyInMaps\[M ~map[K]V, K comparable, V any\](maps []M, keys ...K) bool > 检查 maps 中的每一个元素是否均包含 keys 中所有的元素 @@ -1520,7 +1521,7 @@ func TestAllKeyInMaps(t *testing.T) { *** -#### func AllValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool +#### func AllValueInMaps\[M ~map[K]V, K comparable, V any\](maps []M, values []V, handler ComparisonHandler[V]) bool > 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 @@ -1566,7 +1567,7 @@ func TestAllValueInMaps(t *testing.T) { *** -#### func AnyKeyInMaps(maps []M, keys ...K) bool +#### func AnyKeyInMaps\[M ~map[K]V, K comparable, V any\](maps []M, keys ...K) bool > 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中 > - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true @@ -1611,7 +1612,7 @@ func TestAnyKeyInMaps(t *testing.T) { *** -#### func AnyValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool +#### func AnyValueInMaps\[M ~map[K]V, K comparable, V any\](maps []M, values []V, handler ComparisonHandler[V]) bool > 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 > - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true @@ -1658,7 +1659,7 @@ func TestAnyValueInMaps(t *testing.T) { *** -#### func KeyInAllMaps(maps []M, key K) bool +#### func KeyInAllMaps\[M ~map[K]V, K comparable, V any\](maps []M, key K) bool > 检查 key 是否被包含在 maps 的每一个元素中 @@ -1702,7 +1703,7 @@ func TestKeyInAllMaps(t *testing.T) { *** -#### func AnyKeyInAllMaps(maps []M, keys []K) bool +#### func AnyKeyInAllMaps\[M ~map[K]V, K comparable, V any\](maps []M, keys []K) bool > 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 > - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true @@ -1747,7 +1748,7 @@ func TestAnyKeyInAllMaps(t *testing.T) { *** -#### func ConvertSliceToAny(s S) []any +#### func ConvertSliceToAny\[S ~[]V, V any\](s S) []any > 将切片转换为任意类型的切片 @@ -1799,7 +1800,7 @@ func TestConvertSliceToAny(t *testing.T) { *** -#### func ConvertSliceToIndexMap(s S) map[int]V +#### func ConvertSliceToIndexMap\[S ~[]V, V any\](s S) map[int]V > 将切片转换为索引为键的映射 @@ -1850,7 +1851,7 @@ func TestConvertSliceToIndexMap(t *testing.T) { *** -#### func ConvertSliceToIndexOnlyMap(s S) map[int]struct {} +#### func ConvertSliceToIndexOnlyMap\[S ~[]V, V any\](s S) map[int]struct {} > 将切片转换为索引为键的映射 @@ -1905,7 +1906,7 @@ func TestConvertSliceToIndexOnlyMap(t *testing.T) { *** -#### func ConvertSliceToMap(s S) map[V]struct {} +#### func ConvertSliceToMap\[S ~[]V, V comparable\](s S) map[V]struct {} > 将切片转换为值为键的映射 @@ -1957,7 +1958,7 @@ func TestConvertSliceToMap(t *testing.T) { *** -#### func ConvertSliceToBoolMap(s S) map[V]bool +#### func ConvertSliceToBoolMap\[S ~[]V, V comparable\](s S) map[V]bool > 将切片转换为值为键的映射 @@ -2008,7 +2009,7 @@ func TestConvertSliceToBoolMap(t *testing.T) { *** -#### func ConvertMapKeysToSlice(m M) []K +#### func ConvertMapKeysToSlice\[M ~map[K]V, K comparable, V any\](m M) []K > 将映射的键转换为切片 @@ -2064,7 +2065,7 @@ func TestConvertMapKeysToSlice(t *testing.T) { *** -#### func ConvertMapValuesToSlice(m M) []V +#### func ConvertMapValuesToSlice\[M ~map[K]V, K comparable, V any\](m M) []V > 将映射的值转换为切片 @@ -2121,7 +2122,7 @@ func TestConvertMapValuesToSlice(t *testing.T) { *** -#### func InvertMap(m M) N +#### func InvertMap\[M ~map[K]V, N map[V]K, K comparable, V comparable\](m M) N > 将映射的键和值互换 @@ -2169,7 +2170,7 @@ func TestInvertMap(t *testing.T) { *** -#### func ConvertMapValuesToBool(m M) N +#### func ConvertMapValuesToBool\[M ~map[K]V, N map[K]bool, K comparable, V any\](m M) N > 将映射的值转换为布尔值 @@ -2217,7 +2218,7 @@ func TestConvertMapValuesToBool(t *testing.T) { *** -#### func ReverseSlice(s *S) +#### func ReverseSlice\[S ~[]V, V any\](s *S) > 将切片反转 @@ -2270,7 +2271,7 @@ func TestReverseSlice(t *testing.T) { *** -#### func ClearSlice(slice *S) +#### func ClearSlice\[S ~[]V, V any\](slice *S) > 清空切片 @@ -2319,7 +2320,7 @@ func TestClearSlice(t *testing.T) { *** -#### func ClearMap(m M) +#### func ClearMap\[M ~map[K]V, K comparable, V any\](m M) > 清空 map @@ -2368,7 +2369,7 @@ func TestClearMap(t *testing.T) { *** -#### func DropSliceByIndices(slice *S, indices ...int) +#### func DropSliceByIndices\[S ~[]V, V any\](slice *S, indices ...int) > 删除切片中特定索引的元素 @@ -2418,7 +2419,7 @@ func TestDropSliceByIndices(t *testing.T) { *** -#### func DropSliceByCondition(slice *S, condition func (v V) bool) +#### func DropSliceByCondition\[S ~[]V, V any\](slice *S, condition func (v V) bool) > 删除切片中符合条件的元素 > - condition 的返回值为 true 时,将会删除该元素 @@ -2479,7 +2480,7 @@ func TestDropSliceByCondition(t *testing.T) { *** -#### func DropSliceOverlappingElements(slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) +#### func DropSliceOverlappingElements\[S ~[]V, V any\](slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) > 删除切片中与另一个切片重叠的元素 @@ -2542,7 +2543,7 @@ func TestDropSliceOverlappingElements(t *testing.T) { *** -#### func DeduplicateSliceInPlace(s *S) +#### func DeduplicateSliceInPlace\[S ~[]V, V comparable\](s *S) > 去除切片中的重复元素 @@ -2592,7 +2593,7 @@ func TestDeduplicateSliceInPlace(t *testing.T) { *** -#### func DeduplicateSlice(s S) S +#### func DeduplicateSlice\[S ~[]V, V comparable\](s S) S > 去除切片中的重复元素,返回新切片 @@ -2641,7 +2642,7 @@ func TestDeduplicateSlice(t *testing.T) { *** -#### func DeduplicateSliceInPlaceWithCompare(s *S, compare func (a V) bool) +#### func DeduplicateSliceInPlaceWithCompare\[S ~[]V, V any\](s *S, compare func (a V) bool) > 去除切片中的重复元素,使用自定义的比较函数 @@ -2695,7 +2696,7 @@ func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) { *** -#### func DeduplicateSliceWithCompare(s S, compare func (a V) bool) S +#### func DeduplicateSliceWithCompare\[S ~[]V, V any\](s S, compare func (a V) bool) S > 去除切片中的重复元素,使用自定义的比较函数,返回新的切片 @@ -2748,7 +2749,7 @@ func TestDeduplicateSliceWithCompare(t *testing.T) { *** -#### func FilterOutByIndices(slice S, indices ...int) S +#### func FilterOutByIndices\[S []V, V any\](slice S, indices ...int) S > 过滤切片中特定索引的元素,返回过滤后的切片 @@ -2798,7 +2799,7 @@ func TestFilterOutByIndices(t *testing.T) { *** -#### func FilterOutByCondition(slice S, condition func (v V) bool) S +#### func FilterOutByCondition\[S ~[]V, V any\](slice S, condition func (v V) bool) S > 过滤切片中符合条件的元素,返回过滤后的切片 > - condition 的返回值为 true 时,将会过滤掉该元素 @@ -2857,7 +2858,7 @@ func TestFilterOutByCondition(t *testing.T) { *** -#### func FilterOutByKey(m M, key K) M +#### func FilterOutByKey\[M ~map[K]V, K comparable, V any\](m M, key K) M > 过滤 map 中特定的 key,返回过滤后的 map @@ -2907,7 +2908,7 @@ func TestFilterOutByKey(t *testing.T) { *** -#### func FilterOutByValue(m M, value V, handler ComparisonHandler[V]) M +#### func FilterOutByValue\[M ~map[K]V, K comparable, V any\](m M, value V, handler ComparisonHandler[V]) M > 过滤 map 中特定的 value,返回过滤后的 map @@ -2961,7 +2962,7 @@ func TestFilterOutByValue(t *testing.T) { *** -#### func FilterOutByKeys(m M, keys ...K) M +#### func FilterOutByKeys\[M ~map[K]V, K comparable, V any\](m M, keys ...K) M > 过滤 map 中多个 key,返回过滤后的 map @@ -3011,7 +3012,7 @@ func TestFilterOutByKeys(t *testing.T) { *** -#### func FilterOutByValues(m M, values []V, handler ComparisonHandler[V]) M +#### func FilterOutByValues\[M ~map[K]V, K comparable, V any\](m M, values []V, handler ComparisonHandler[V]) M > 过滤 map 中多个 values,返回过滤后的 map @@ -3067,7 +3068,7 @@ func TestFilterOutByValues(t *testing.T) { *** -#### func FilterOutByMap(m M, condition func (k K, v V) bool) M +#### func FilterOutByMap\[M ~map[K]V, K comparable, V any\](m M, condition func (k K, v V) bool) M > 过滤 map 中符合条件的元素,返回过滤后的 map > - condition 的返回值为 true 时,将会过滤掉该元素 @@ -3122,7 +3123,7 @@ func TestFilterOutByMap(t *testing.T) { *** -#### func FindLoopedNextInSlice(slice S, i int) (next int, value V) +#### func FindLoopedNextInSlice\[S ~[]V, V any\](slice S, i int) (next int, value V) > 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始 > - 当 i 为负数时将返回第一个元素 @@ -3167,7 +3168,7 @@ func TestFindLoopedNextInSlice(t *testing.T) { *** -#### func FindLoopedPrevInSlice(slice S, i int) (prev int, value V) +#### func FindLoopedPrevInSlice\[S ~[]V, V any\](slice S, i int) (prev int, value V) > 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始 > - 当 i 为负数时将返回最后一个元素 @@ -3212,7 +3213,7 @@ func TestFindLoopedPrevInSlice(t *testing.T) { *** -#### func FindCombinationsInSliceByRange(s S, minSize int, maxSize int) []S +#### func FindCombinationsInSliceByRange\[S ~[]V, V any\](s S, minSize int, maxSize int) []S > 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 @@ -3257,7 +3258,7 @@ func TestFindCombinationsInSliceByRange(t *testing.T) { *** -#### func FindFirstOrDefaultInSlice(slice S, defaultValue V) V +#### func FindFirstOrDefaultInSlice\[S ~[]V, V any\](slice S, defaultValue V) V > 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值 @@ -3300,7 +3301,7 @@ func TestFindFirstOrDefaultInSlice(t *testing.T) { *** -#### func FindOrDefaultInSlice(slice S, defaultValue V, handler func (v V) bool) (t V) +#### func FindOrDefaultInSlice\[S ~[]V, V any\](slice S, defaultValue V, handler func (v V) bool) (t V) > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 @@ -3347,7 +3348,7 @@ func TestFindOrDefaultInSlice(t *testing.T) { *** -#### func FindOrDefaultInComparableSlice(slice S, v V, defaultValue V) (t V) +#### func FindOrDefaultInComparableSlice\[S ~[]V, V comparable\](slice S, v V, defaultValue V) (t V) > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 @@ -3390,7 +3391,7 @@ func TestFindOrDefaultInComparableSlice(t *testing.T) { *** -#### func FindInSlice(slice S, handler func (v V) bool) (i int, t V) +#### func FindInSlice\[S ~[]V, V any\](slice S, handler func (v V) bool) (i int, t V) > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 @@ -3437,7 +3438,7 @@ func TestFindInSlice(t *testing.T) { *** -#### func FindIndexInSlice(slice S, handler func (v V) bool) int +#### func FindIndexInSlice\[S ~[]V, V any\](slice S, handler func (v V) bool) int > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 @@ -3484,7 +3485,7 @@ func TestFindIndexInSlice(t *testing.T) { *** -#### func FindInComparableSlice(slice S, v V) (i int, t V) +#### func FindInComparableSlice\[S ~[]V, V comparable\](slice S, v V) (i int, t V) > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 @@ -3527,7 +3528,7 @@ func TestFindInComparableSlice(t *testing.T) { *** -#### func FindIndexInComparableSlice(slice S, v V) int +#### func FindIndexInComparableSlice\[S ~[]V, V comparable\](slice S, v V) int > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 @@ -3570,7 +3571,7 @@ func TestFindIndexInComparableSlice(t *testing.T) { *** -#### func FindMinimumInComparableSlice(slice S) (result V) +#### func FindMinimumInComparableSlice\[S ~[]V, V generic.Ordered\](slice S) (result V) > 获取切片中的最小值 @@ -3613,7 +3614,7 @@ func TestFindMinimumInComparableSlice(t *testing.T) { *** -#### func FindMinimumInSlice(slice S, handler OrderedValueGetter[V, N]) (result V) +#### func FindMinimumInSlice\[S ~[]V, V any, N generic.Ordered\](slice S, handler OrderedValueGetter[V, N]) (result V) > 获取切片中的最小值 @@ -3660,7 +3661,7 @@ func TestFindMinimumInSlice(t *testing.T) { *** -#### func FindMaximumInComparableSlice(slice S) (result V) +#### func FindMaximumInComparableSlice\[S ~[]V, V generic.Ordered\](slice S) (result V) > 获取切片中的最大值 @@ -3703,7 +3704,7 @@ func TestFindMaximumInComparableSlice(t *testing.T) { *** -#### func FindMaximumInSlice(slice S, handler OrderedValueGetter[V, N]) (result V) +#### func FindMaximumInSlice\[S ~[]V, V any, N generic.Ordered\](slice S, handler OrderedValueGetter[V, N]) (result V) > 获取切片中的最大值 @@ -3750,7 +3751,7 @@ func TestFindMaximumInSlice(t *testing.T) { *** -#### func FindMin2MaxInComparableSlice(slice S) (min V, max V) +#### func FindMin2MaxInComparableSlice\[S ~[]V, V generic.Ordered\](slice S) (min V, max V) > 获取切片中的最小值和最大值 @@ -3794,7 +3795,7 @@ func TestFindMin2MaxInComparableSlice(t *testing.T) { *** -#### func FindMin2MaxInSlice(slice S, handler OrderedValueGetter[V, N]) (min V, max V) +#### func FindMin2MaxInSlice\[S ~[]V, V any, N generic.Ordered\](slice S, handler OrderedValueGetter[V, N]) (min V, max V) > 获取切片中的最小值和最大值 @@ -3842,7 +3843,7 @@ func TestFindMin2MaxInSlice(t *testing.T) { *** -#### func FindMinFromComparableMap(m M) (result V) +#### func FindMinFromComparableMap\[M ~map[K]V, K comparable, V generic.Ordered\](m M) (result V) > 获取 map 中的最小值 @@ -3885,7 +3886,7 @@ func TestFindMinFromComparableMap(t *testing.T) { *** -#### func FindMinFromMap(m M, handler OrderedValueGetter[V, N]) (result V) +#### func FindMinFromMap\[M ~map[K]V, K comparable, V any, N generic.Ordered\](m M, handler OrderedValueGetter[V, N]) (result V) > 获取 map 中的最小值 @@ -3932,7 +3933,7 @@ func TestFindMinFromMap(t *testing.T) { *** -#### func FindMaxFromComparableMap(m M) (result V) +#### func FindMaxFromComparableMap\[M ~map[K]V, K comparable, V generic.Ordered\](m M) (result V) > 获取 map 中的最大值 @@ -3975,7 +3976,7 @@ func TestFindMaxFromComparableMap(t *testing.T) { *** -#### func FindMaxFromMap(m M, handler OrderedValueGetter[V, N]) (result V) +#### func FindMaxFromMap\[M ~map[K]V, K comparable, V any, N generic.Ordered\](m M, handler OrderedValueGetter[V, N]) (result V) > 获取 map 中的最大值 @@ -4022,7 +4023,7 @@ func TestFindMaxFromMap(t *testing.T) { *** -#### func FindMin2MaxFromComparableMap(m M) (min V, max V) +#### func FindMin2MaxFromComparableMap\[M ~map[K]V, K comparable, V generic.Ordered\](m M) (min V, max V) > 获取 map 中的最小值和最大值 @@ -4066,7 +4067,7 @@ func TestFindMin2MaxFromComparableMap(t *testing.T) { *** -#### func FindMin2MaxFromMap(m M) (min V, max V) +#### func FindMin2MaxFromMap\[M ~map[K]V, K comparable, V generic.Ordered\](m M) (min V, max V) > 获取 map 中的最小值和最大值 @@ -4110,7 +4111,7 @@ func TestFindMin2MaxFromMap(t *testing.T) { *** -#### func SwapSlice(slice *S, i int, j int) +#### func SwapSlice\[S ~[]V, V any\](slice *S, i int, j int) > 将切片中的两个元素进行交换 @@ -4158,7 +4159,7 @@ func TestSwapSlice(t *testing.T) { *** -#### func MappingFromSlice(slice S, handler func (value V) N) NS +#### func MappingFromSlice\[S ~[]V, NS []N, V any, N any\](slice S, handler func (value V) N) NS > 将切片中的元素进行转换 @@ -4210,7 +4211,7 @@ func TestMappingFromSlice(t *testing.T) { *** -#### func MappingFromMap(m M, handler func (value V) N) NM +#### func MappingFromMap\[M ~map[K]V, NM map[K]N, K comparable, V any, N any\](m M, handler func (value V) N) NM > 将 map 中的元素进行转换 @@ -4262,7 +4263,7 @@ func TestMappingFromMap(t *testing.T) { *** -#### func MergeSlices(slices ...S) (result S) +#### func MergeSlices\[S ~[]V, V any\](slices ...S) (result S) > 合并切片 @@ -4309,7 +4310,7 @@ func TestMergeSlices(t *testing.T) { *** -#### func MergeMaps(maps ...M) (result M) +#### func MergeMaps\[M ~map[K]V, K comparable, V any\](maps ...M) (result M) > 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key @@ -4352,7 +4353,7 @@ func TestMergeMaps(t *testing.T) { *** -#### func MergeMapsWithSkip(maps ...M) (result M) +#### func MergeMapsWithSkip\[M ~map[K]V, K comparable, V any\](maps ...M) (result M) > 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会被跳过 @@ -4395,7 +4396,7 @@ func TestMergeMapsWithSkip(t *testing.T) { *** -#### func ChooseRandomSliceElementRepeatN(slice S, n int) (result []V) +#### func ChooseRandomSliceElementRepeatN\[S ~[]V, V any\](slice S, n int) (result []V) > 返回 slice 中的 n 个可重复随机元素 > - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil @@ -4439,7 +4440,7 @@ func TestChooseRandomSliceElementRepeatN(t *testing.T) { *** -#### func ChooseRandomIndexRepeatN(slice S, n int) (result []int) +#### func ChooseRandomIndexRepeatN\[S ~[]V, V any\](slice S, n int) (result []int) > 返回 slice 中的 n 个可重复随机元素的索引 > - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil @@ -4483,7 +4484,7 @@ func TestChooseRandomIndexRepeatN(t *testing.T) { *** -#### func ChooseRandomSliceElement(slice S) (v V) +#### func ChooseRandomSliceElement\[S ~[]V, V any\](slice S) (v V) > 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值 @@ -4526,7 +4527,7 @@ func TestChooseRandomSliceElement(t *testing.T) { *** -#### func ChooseRandomIndex(slice S) (index int) +#### func ChooseRandomIndex\[S ~[]V, V any\](slice S) (index int) > 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1 @@ -4569,7 +4570,7 @@ func TestChooseRandomIndex(t *testing.T) { *** -#### func ChooseRandomSliceElementN(slice S, n int) (result []V) +#### func ChooseRandomSliceElementN\[S ~[]V, V any\](slice S, n int) (result []V) > 返回 slice 中的 n 个不可重复的随机元素 > - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic @@ -4612,7 +4613,7 @@ func TestChooseRandomSliceElementN(t *testing.T) { *** -#### func ChooseRandomIndexN(slice S, n int) (result []int) +#### func ChooseRandomIndexN\[S ~[]V, V any\](slice S, n int) (result []int) > 获取切片中的 n 个随机元素的索引 > - 如果 n 大于切片长度或小于 0 时将会发生 panic @@ -4656,7 +4657,7 @@ func TestChooseRandomIndexN(t *testing.T) { *** -#### func ChooseRandomMapKeyRepeatN(m M, n int) (result []K) +#### func ChooseRandomMapKeyRepeatN\[M ~map[K]V, K comparable, V any\](m M, n int) (result []K) > 获取 map 中的 n 个随机 key,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -4700,7 +4701,7 @@ func TestChooseRandomMapKeyRepeatN(t *testing.T) { *** -#### func ChooseRandomMapValueRepeatN(m M, n int) (result []V) +#### func ChooseRandomMapValueRepeatN\[M ~map[K]V, K comparable, V any\](m M, n int) (result []V) > 获取 map 中的 n 个随机 n,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -4744,7 +4745,7 @@ func TestChooseRandomMapValueRepeatN(t *testing.T) { *** -#### func ChooseRandomMapKeyAndValueRepeatN(m M, n int) M +#### func ChooseRandomMapKeyAndValueRepeatN\[M ~map[K]V, K comparable, V any\](m M, n int) M > 获取 map 中的 n 个随机 key 和 v,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -4788,7 +4789,7 @@ func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) { *** -#### func ChooseRandomMapKey(m M) (k K) +#### func ChooseRandomMapKey\[M ~map[K]V, K comparable, V any\](m M) (k K) > 获取 map 中的随机 key @@ -4831,7 +4832,7 @@ func TestChooseRandomMapKey(t *testing.T) { *** -#### func ChooseRandomMapValue(m M) (v V) +#### func ChooseRandomMapValue\[M ~map[K]V, K comparable, V any\](m M) (v V) > 获取 map 中的随机 value @@ -4874,7 +4875,7 @@ func TestChooseRandomMapValue(t *testing.T) { *** -#### func ChooseRandomMapKeyN(m M, n int) (result []K) +#### func ChooseRandomMapKeyN\[M ~map[K]V, K comparable, V any\](m M, n int) (result []K) > 获取 map 中的 inputN 个随机 key > - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic @@ -4890,7 +4891,7 @@ func ExampleChooseRandomMapKeyN() { ``` *** -#### func ChooseRandomMapValueN(m M, n int) (result []V) +#### func ChooseRandomMapValueN\[M ~map[K]V, K comparable, V any\](m M, n int) (result []V) > 获取 map 中的 n 个随机 value > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -4934,7 +4935,7 @@ func TestChooseRandomMapValueN(t *testing.T) { *** -#### func ChooseRandomMapKeyAndValue(m M) (k K, v V) +#### func ChooseRandomMapKeyAndValue\[M ~map[K]V, K comparable, V any\](m M) (k K, v V) > 获取 map 中的随机 key 和 v @@ -4977,7 +4978,7 @@ func TestChooseRandomMapKeyAndValue(t *testing.T) { *** -#### func ChooseRandomMapKeyAndValueN(m M, n int) M +#### func ChooseRandomMapKeyAndValueN\[M ~map[K]V, K comparable, V any\](m M, n int) M > 获取 map 中的 inputN 个随机 key 和 v > - 如果 n 大于 map 长度或小于 0 时将会发生 panic @@ -5023,7 +5024,7 @@ func TestChooseRandomMapKeyAndValueN(t *testing.T) { *** -#### func DescBy(a Sort, b Sort) bool +#### func DescBy\[Sort generic.Ordered\](a Sort, b Sort) bool > 返回降序比较结果 @@ -5073,7 +5074,7 @@ func TestDescBy(t *testing.T) { *** -#### func AscBy(a Sort, b Sort) bool +#### func AscBy\[Sort generic.Ordered\](a Sort, b Sort) bool > 返回升序比较结果 @@ -5123,7 +5124,7 @@ func TestAscBy(t *testing.T) { *** -#### func Desc(slice *S, getter func (index int) Sort) +#### func Desc\[S ~[]V, V any, Sort generic.Ordered\](slice *S, getter func (index int) Sort) > 对切片进行降序排序 @@ -5173,7 +5174,7 @@ func TestDesc(t *testing.T) { *** -#### func DescByClone(slice S, getter func (index int) Sort) S +#### func DescByClone\[S ~[]V, V any, Sort generic.Ordered\](slice S, getter func (index int) Sort) S > 对切片进行降序排序,返回排序后的切片 @@ -5223,7 +5224,7 @@ func TestDescByClone(t *testing.T) { *** -#### func Asc(slice *S, getter func (index int) Sort) +#### func Asc\[S ~[]V, V any, Sort generic.Ordered\](slice *S, getter func (index int) Sort) > 对切片进行升序排序 @@ -5273,7 +5274,7 @@ func TestAsc(t *testing.T) { *** -#### func AscByClone(slice S, getter func (index int) Sort) S +#### func AscByClone\[S ~[]V, V any, Sort generic.Ordered\](slice S, getter func (index int) Sort) S > 对切片进行升序排序,返回排序后的切片 @@ -5323,7 +5324,7 @@ func TestAscByClone(t *testing.T) { *** -#### func Shuffle(slice *S) +#### func Shuffle\[S ~[]V, V any\](slice *S) > 对切片进行随机排序 @@ -5367,7 +5368,7 @@ func TestShuffle(t *testing.T) { *** -#### func ShuffleByClone(slice S) S +#### func ShuffleByClone\[S ~[]V, V any\](slice S) S > 对切片进行随机排序,返回排序后的切片 diff --git a/utils/collection/clone.go b/utils/collection/clone.go index 69a7c0d6..44400ce3 100644 --- a/utils/collection/clone.go +++ b/utils/collection/clone.go @@ -4,12 +4,13 @@ import ( "slices" ) -// CloneSlice 克隆切片,该函数是 slices.Clone 的快捷方式 +// CloneSlice 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 func CloneSlice[S ~[]V, V any](slice S) S { return slices.Clone(slice) } -// CloneMap 克隆 map +// CloneMap 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map +// - 当 m 为空时,将会返回 nil func CloneMap[M ~map[K]V, K comparable, V any](m M) M { if m == nil { return nil diff --git a/utils/collection/doc.go b/utils/collection/doc.go index 50528d99..195bdbbf 100644 --- a/utils/collection/doc.go +++ b/utils/collection/doc.go @@ -1,2 +1,2 @@ -// Package collection 用于对 input 和 map 操作的工具函数 +// Package collection 定义了各种对于集合操作有用的各种函数 package collection diff --git a/utils/collection/listings/README.md b/utils/collection/listings/README.md index 9179267b..6d0a0ddd 100644 --- a/utils/collection/listings/README.md +++ b/utils/collection/listings/README.md @@ -36,22 +36,22 @@ *** ## 详情信息 -#### func NewMatrix(dimensions ...int) *Matrix[V] +#### func NewMatrix\[V any\](dimensions ...int) *Matrix[V] > 创建一个新的 Matrix 实例。 *** -#### func NewPagedSlice(pageSize int) *PagedSlice[T] +#### func NewPagedSlice\[T any\](pageSize int) *PagedSlice[T] > 创建一个新的 PagedSlice 实例。 *** -#### func NewPrioritySlice(lengthAndCap ...int) *PrioritySlice[V] +#### func NewPrioritySlice\[V any\](lengthAndCap ...int) *PrioritySlice[V] > 创建一个优先级切片 *** -#### func NewSyncSlice(length int, cap int) *SyncSlice[V] +#### func NewSyncSlice\[V any\](length int, cap int) *SyncSlice[V] > 创建一个 SyncSlice diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md index 270393b4..b91ebf6d 100644 --- a/utils/collection/mappings/README.md +++ b/utils/collection/mappings/README.md @@ -30,7 +30,7 @@ *** ## 详情信息 -#### func NewSyncMap(source ...map[K]V) *SyncMap[K, V] +#### func NewSyncMap\[K comparable, V any\](source ...map[K]V) *SyncMap[K, V] > 创建一个 SyncMap diff --git a/utils/combination/README.md b/utils/combination/README.md index bbe88371..089fb4dd 100644 --- a/utils/combination/README.md +++ b/utils/combination/README.md @@ -65,12 +65,12 @@ combination 包提供了一些实用的组合函数。 *** ## 详情信息 -#### func NewCombination(options ...Option[T]) *Combination[T] +#### func NewCombination\[T Item\](options ...Option[T]) *Combination[T] > 创建一个新的组合器 *** -#### func WithEvaluation(evaluate func (items []T) float64) Option[T] +#### func WithEvaluation\[T Item\](evaluate func (items []T) float64) Option[T] > 设置组合评估函数 > - 用于对组合进行评估,返回一个分值的评价函数 @@ -79,57 +79,57 @@ combination 包提供了一些实用的组合函数。 > - 默认的评估函数将返回一个随机数 *** -#### func NewMatcher(options ...MatcherOption[T]) *Matcher[T] +#### func NewMatcher\[T Item\](options ...MatcherOption[T]) *Matcher[T] > 创建一个新的匹配器 *** -#### func WithMatcherEvaluation(evaluate func (items []T) float64) MatcherOption[T] +#### func WithMatcherEvaluation\[T Item\](evaluate func (items []T) float64) MatcherOption[T] > 设置匹配器评估函数 > - 用于对组合进行评估,返回一个分值的评价函数 > - 通过该选项将覆盖匹配器的默认(WithEvaluation)评估函数 *** -#### func WithMatcherLeastLength(length int) MatcherOption[T] +#### func WithMatcherLeastLength\[T Item\](length int) MatcherOption[T] > 通过匹配最小长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合最小数量 *** -#### func WithMatcherLength(length int) MatcherOption[T] +#### func WithMatcherLength\[T Item\](length int) MatcherOption[T] > 通过匹配长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合数量 *** -#### func WithMatcherMostLength(length int) MatcherOption[T] +#### func WithMatcherMostLength\[T Item\](length int) MatcherOption[T] > 通过匹配最大长度的组合创建匹配器 > - length: 组合的长度,表示需要匹配的组合最大数量 *** -#### func WithMatcherIntervalLength(min int, max int) MatcherOption[T] +#### func WithMatcherIntervalLength\[T Item\](min int, max int) MatcherOption[T] > 通过匹配长度区间的组合创建匹配器 > - min: 组合的最小长度,表示需要匹配的组合最小数量 > - max: 组合的最大长度,表示需要匹配的组合最大数量 *** -#### func WithMatcherContinuity(getIndex func (item T) Index) MatcherOption[T] +#### func WithMatcherContinuity\[T Item, Index generic.Number\](getIndex func (item T) Index) MatcherOption[T] > 通过匹配连续的组合创建匹配器 > - index: 用于获取组合中元素的索引值,用于判断是否连续 *** -#### func WithMatcherSame(count int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherSame\[T Item, E generic.Ordered\](count int, getType func (item T) E) MatcherOption[T] > 通过匹配相同的组合创建匹配器 > - count: 组合中相同元素的数量,当 count <= 0 时,表示相同元素的数量不限 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithMatcherNCarryM(n int, m int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherNCarryM\[T Item, E generic.Ordered\](n int, m int, getType func (item T) E) MatcherOption[T] > 通过匹配 N 携带 M 的组合创建匹配器 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -137,7 +137,7 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithMatcherNCarryIndependentM(n int, m int, getType func (item T) E) MatcherOption[T] +#### func WithMatcherNCarryIndependentM\[T Item, E generic.Ordered\](n int, m int, getType func (item T) E) MatcherOption[T] > 通过匹配 N 携带独立 M 的组合创建匹配器 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -145,87 +145,87 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func NewValidator(options ...ValidatorOption[T]) *Validator[T] +#### func NewValidator\[T Item\](options ...ValidatorOption[T]) *Validator[T] > 创建一个新的校验器 *** -#### func WithValidatorHandle(handle func (items []T) bool) ValidatorOption[T] +#### func WithValidatorHandle\[T Item\](handle func (items []T) bool) ValidatorOption[T] > 通过特定的验证函数对组合进行验证 *** -#### func WithValidatorHandleLength(length int) ValidatorOption[T] +#### func WithValidatorHandleLength\[T Item\](length int) ValidatorOption[T] > 校验组合的长度是否符合要求 *** -#### func WithValidatorHandleLengthRange(min int, max int) ValidatorOption[T] +#### func WithValidatorHandleLengthRange\[T Item\](min int, max int) ValidatorOption[T] > 校验组合的长度是否在指定的范围内 *** -#### func WithValidatorHandleLengthMin(min int) ValidatorOption[T] +#### func WithValidatorHandleLengthMin\[T Item\](min int) ValidatorOption[T] > 校验组合的长度是否大于等于指定的最小值 *** -#### func WithValidatorHandleLengthMax(max int) ValidatorOption[T] +#### func WithValidatorHandleLengthMax\[T Item\](max int) ValidatorOption[T] > 校验组合的长度是否小于等于指定的最大值 *** -#### func WithValidatorHandleLengthNot(length int) ValidatorOption[T] +#### func WithValidatorHandleLengthNot\[T Item\](length int) ValidatorOption[T] > 校验组合的长度是否不等于指定的值 *** -#### func WithValidatorHandleTypeLength(length int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLength\[T Item, E generic.Ordered\](length int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否为指定的值 *** -#### func WithValidatorHandleTypeLengthRange(min int, max int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthRange\[T Item, E generic.Ordered\](min int, max int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否在指定的范围内 *** -#### func WithValidatorHandleTypeLengthMin(min int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthMin\[T Item, E generic.Ordered\](min int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否大于等于指定的最小值 *** -#### func WithValidatorHandleTypeLengthMax(max int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthMax\[T Item, E generic.Ordered\](max int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否小于等于指定的最大值 *** -#### func WithValidatorHandleTypeLengthNot(length int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleTypeLengthNot\[T Item, E generic.Ordered\](length int, getType func (item T) E) ValidatorOption[T] > 校验组合成员类型数量是否不等于指定的值 *** -#### func WithValidatorHandleContinuous(getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleContinuous\[T Item, Index generic.Integer\](getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否连续 *** -#### func WithValidatorHandleContinuousNot(getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleContinuousNot\[T Item, Index generic.Integer\](getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否不连续 *** -#### func WithValidatorHandleGroupContinuous(getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleGroupContinuous\[T Item, E generic.Ordered, Index generic.Integer\](getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否能够按类型分组并且连续 *** -#### func WithValidatorHandleGroupContinuousN(n int, getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] +#### func WithValidatorHandleGroupContinuousN\[T Item, E generic.Ordered, Index generic.Integer\](n int, getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] > 校验组合成员是否能够按分组为 n 组类型并且连续 *** -#### func WithValidatorHandleNCarryM(n int, m int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleNCarryM\[T Item, E generic.Ordered\](n int, m int, getType func (item T) E) ValidatorOption[T] > 校验组合成员是否匹配 N 携带相同的 M 的组合 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 @@ -233,7 +233,7 @@ combination 包提供了一些实用的组合函数。 > - getType: 用于获取组合中元素的类型,用于判断是否相同 *** -#### func WithValidatorHandleNCarryIndependentM(n int, m int, getType func (item T) E) ValidatorOption[T] +#### func WithValidatorHandleNCarryIndependentM\[T Item, E generic.Ordered\](n int, m int, getType func (item T) E) ValidatorOption[T] > 校验组合成员是否匹配 N 携带独立的 M 的组合 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 diff --git a/utils/deck/README.md b/utils/deck/README.md index 3bfbca83..4dd3712b 100644 --- a/utils/deck/README.md +++ b/utils/deck/README.md @@ -33,12 +33,12 @@ deck 包中的内容用于针对一堆内容的管理,适用但不限于牌堆 *** ## 详情信息 -#### func NewDeck() *Deck[I] +#### func NewDeck\[I Item\]() *Deck[I] > 创建一个新的甲板 *** -#### func NewGroup(guid int64, fillHandle func (guid int64) []I) *Group[I] +#### func NewGroup\[I Item\](guid int64, fillHandle func (guid int64) []I) *Group[I] > 创建一个新的组 diff --git a/utils/fsm/README.md b/utils/fsm/README.md index 330019ff..a8c0f02e 100644 --- a/utils/fsm/README.md +++ b/utils/fsm/README.md @@ -36,34 +36,34 @@ *** ## 详情信息 -#### func NewFSM(data Data) *FSM[State, Data] +#### func NewFSM\[State comparable, Data any\](data Data) *FSM[State, Data] > 创建一个新的状态机 *** -#### func WithEnterBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithEnterBeforeEvent\[State comparable, Data any\](fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态进入前的回调 > - 在首次设置状态时,状态机本身的当前状态为零值状态 *** -#### func WithEnterAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithEnterAfterEvent\[State comparable, Data any\](fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态进入后的回调 *** -#### func WithUpdateEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithUpdateEvent\[State comparable, Data any\](fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态内刷新的回调 *** -#### func WithExitBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithExitBeforeEvent\[State comparable, Data any\](fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态退出前的回调 > - 该阶段状态机的状态为退出前的状态,而非新的状态 *** -#### func WithExitAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] +#### func WithExitAfterEvent\[State comparable, Data any\](fn func (state *FSM[State, Data])) Option[State, Data] > 设置状态退出后的回调 > - 该阶段状态机的状态为新的状态,而非退出前的状态 diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index e4ea42bf..c2061c2f 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -154,8 +154,21 @@ func (b *Builder) genStructs() { continue } titleBuild() - b.title(4, strings.TrimSpace(fmt.Sprintf("func %s%s %s", + b.title(4, strings.TrimSpace(fmt.Sprintf("func %s%s%s %s", function.Name, + func() string { + var sb strings.Builder + if len(function.Generic) > 0 { + sb.WriteString("\\[") + var genericStr = make([]string, 0) + for _, field := range function.Generic { + genericStr = append(genericStr, fmt.Sprintf("%s %s", field.Name, field.Type.Sign)) + } + sb.WriteString(strings.Join(genericStr, ", ")) + sb.WriteString("\\]") + } + return sb.String() + }(), func() string { f := funcHandler(function.Params) if !strings.HasPrefix(f, "(") { @@ -279,9 +292,6 @@ func (b *Builder) genStructs() { for _, comment := range function.Comments.Clear { b.quote(comment) } - if function.Name == "Write" { - fmt.Println() - } if example := b.p.GetExampleTest(function); example != nil { b.newLine("示例代码:", "```go\n", example.Code(), "```\n") } diff --git a/utils/generic/README.md b/utils/generic/README.md index b4deb864..9894f692 100644 --- a/utils/generic/README.md +++ b/utils/generic/README.md @@ -48,17 +48,17 @@ generic 目的在于提供一组基于泛型的用于处理通用功能的函数 *** ## 详情信息 -#### func IsNil(v V) bool +#### func IsNil\[V any\](v V) bool > 检查指定的值是否为 nil *** -#### func IsAllNil(v ...V) bool +#### func IsAllNil\[V any\](v ...V) bool > 检查指定的值是否全部为 nil *** -#### func IsHasNil(v ...V) bool +#### func IsHasNil\[V any\](v ...V) bool > 检查指定的值是否存在 nil diff --git a/utils/geometry/README.md b/utils/geometry/README.md index 9bed1fcd..238a8bb7 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -132,7 +132,7 @@ geometry 旨在提供一组用于处理几何形状和计算几何属性的函 *** ## 详情信息 -#### func NewCircle(radius V, points int) Circle[V] +#### func NewCircle\[V generic.SignedNumber\](radius V, points int) Circle[V] > 通过传入圆的半径和需要的点数量,生成一个圆 @@ -146,7 +146,7 @@ func ExampleNewCircle() { ``` *** -#### func CalcCircleCentroidDistance(circle1 Circle[V], circle2 Circle[V]) V +#### func CalcCircleCentroidDistance\[V generic.SignedNumber\](circle1 Circle[V], circle2 Circle[V]) V > 计算两个圆质心距离 @@ -156,133 +156,133 @@ func ExampleNewCircle() { > 获取特定方向的对立方向 *** -#### func GetDirectionNextWithCoordinate(direction Direction, x V, y V) (nx V, ny V) +#### func GetDirectionNextWithCoordinate\[V generic.SignedNumber\](direction Direction, x V, y V) (nx V, ny V) > 获取特定方向上的下一个坐标 *** -#### func GetDirectionNextWithPoint(direction Direction, point Point[V]) Point[V] +#### func GetDirectionNextWithPoint\[V generic.SignedNumber\](direction Direction, point Point[V]) Point[V] > 获取特定方向上的下一个坐标 *** -#### func GetDirectionNextWithPos(direction Direction, width V, pos V) V +#### func GetDirectionNextWithPos\[V generic.SignedNumber\](direction Direction, width V, pos V) V > 获取位置在特定宽度和特定方向上的下一个位置 > - 需要注意的是,在左右方向时,当下一个位置不在矩形区域内时,将会返回上一行的末位置或下一行的首位置 *** -#### func CalcDirection(x1 V, y1 V, x2 V, y2 V) Direction +#### func CalcDirection\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V) Direction > 计算点2位于点1的方向 *** -#### func CalcDistanceWithCoordinate(x1 V, y1 V, x2 V, y2 V) V +#### func CalcDistanceWithCoordinate\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V) V > 计算两点之间的距离 *** -#### func CalcDistanceWithPoint(point1 Point[V], point2 Point[V]) V +#### func CalcDistanceWithPoint\[V generic.SignedNumber\](point1 Point[V], point2 Point[V]) V > 计算两点之间的距离 *** -#### func CalcDistanceSquared(x1 V, y1 V, x2 V, y2 V) V +#### func CalcDistanceSquared\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V) V > 计算两点之间的平方距离 > - 这个函数的主要用途是在需要计算两点之间距离的情况下,但不需要得到实际的距离值,而只需要比较距离大小。因为平方根运算相对较为耗时,所以在只需要比较大小的情况下,通常会使用平方距离。 *** -#### func CalcAngle(x1 V, y1 V, x2 V, y2 V) V +#### func CalcAngle\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V) V > 计算点2位于点1之间的角度 *** -#### func CalcNewCoordinate(x V, y V, angle V, distance V) (newX V, newY V) +#### func CalcNewCoordinate\[V generic.SignedNumber\](x V, y V, angle V, distance V) (newX V, newY V) > 根据给定的x、y坐标、角度和距离计算新的坐标 *** -#### func CalcRadianWithAngle(angle V) V +#### func CalcRadianWithAngle\[V generic.SignedNumber\](angle V) V > 根据角度 angle 计算弧度 *** -#### func CalcAngleDifference(angleA V, angleB V) V +#### func CalcAngleDifference\[V generic.SignedNumber\](angleA V, angleB V) V > 计算两个角度之间的最小角度差 *** -#### func CalcRayIsIntersect(x V, y V, angle V, shape Shape[V]) bool +#### func CalcRayIsIntersect\[V generic.SignedNumber\](x V, y V, angle V, shape Shape[V]) bool > 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞 *** -#### func NewLineSegment(start Point[V], end Point[V]) LineSegment[V] +#### func NewLineSegment\[V generic.SignedNumber\](start Point[V], end Point[V]) LineSegment[V] > 创建一根线段 *** -#### func NewLineSegmentCap(start Point[V], end Point[V], data Data) LineSegmentCap[V, Data] +#### func NewLineSegmentCap\[V generic.SignedNumber, Data any\](start Point[V], end Point[V], data Data) LineSegmentCap[V, Data] > 创建一根包含数据的线段 *** -#### func NewLineSegmentCapWithLine(line LineSegment[V], data Data) LineSegmentCap[V, Data] +#### func NewLineSegmentCapWithLine\[V generic.SignedNumber, Data any\](line LineSegment[V], data Data) LineSegmentCap[V, Data] > 通过已有线段创建一根包含数据的线段 *** -#### func ConvertLineSegmentGeneric(line LineSegment[V]) LineSegment[TO] +#### func ConvertLineSegmentGeneric\[V generic.SignedNumber, TO generic.SignedNumber\](line LineSegment[V]) LineSegment[TO] > 转换线段的泛型类型为特定类型 *** -#### func PointOnLineSegmentWithCoordinate(x1 V, y1 V, x2 V, y2 V, x V, y V) bool +#### func PointOnLineSegmentWithCoordinate\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V, x V, y V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithPos(width V, pos1 V, pos2 V, pos V) bool +#### func PointOnLineSegmentWithPos\[V generic.SignedNumber\](width V, pos1 V, pos2 V, pos V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithPoint(point1 Point[V], point2 Point[V], point Point[V]) bool +#### func PointOnLineSegmentWithPoint\[V generic.SignedNumber\](point1 Point[V], point2 Point[V], point Point[V]) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 *** -#### func PointOnLineSegmentWithCoordinateInBounds(x1 V, y1 V, x2 V, y2 V, x V, y V) bool +#### func PointOnLineSegmentWithCoordinateInBounds\[V generic.SignedNumber\](x1 V, y1 V, x2 V, y2 V, x V, y V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithCoordinate 不同的是, PointOnLineSegmentWithCoordinateInBounds 中会判断线段及点的位置是否正确 *** -#### func PointOnLineSegmentWithPosInBounds(width V, pos1 V, pos2 V, pos V) bool +#### func PointOnLineSegmentWithPosInBounds\[V generic.SignedNumber\](width V, pos1 V, pos2 V, pos V) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithPos 不同的是, PointOnLineSegmentWithPosInBounds 中会判断线段及点的位置是否正确 *** -#### func PointOnLineSegmentWithPointInBounds(point1 Point[V], point2 Point[V], point Point[V]) bool +#### func PointOnLineSegmentWithPointInBounds\[V generic.SignedNumber\](point1 Point[V], point2 Point[V], point Point[V]) bool > 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 > - 与 PointOnLineSegmentWithPoint 不同的是, PointOnLineSegmentWithPointInBounds 中会判断线段及点的位置是否正确 *** -#### func CalcLineSegmentIsCollinear(line1 LineSegment[V], line2 LineSegment[V], tolerance V) bool +#### func CalcLineSegmentIsCollinear\[V generic.SignedNumber\](line1 LineSegment[V], line2 LineSegment[V], tolerance V) bool > 检查两条线段在一个误差内是否共线 > - 共线是指两条线段在同一直线上,即它们的延长线可以重合 *** -#### func CalcLineSegmentIsOverlap(line1 LineSegment[V], line2 LineSegment[V]) (line LineSegment[V], overlap bool) +#### func CalcLineSegmentIsOverlap\[V generic.SignedNumber\](line1 LineSegment[V], line2 LineSegment[V]) (line LineSegment[V], overlap bool) > 通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段 *** -#### func CalcLineSegmentIsIntersect(line1 LineSegment[V], line2 LineSegment[V]) bool +#### func CalcLineSegmentIsIntersect\[V generic.SignedNumber\](line1 LineSegment[V], line2 LineSegment[V]) bool > 计算两条线段是否相交 @@ -305,17 +305,17 @@ func TestCalcLineSegmentIsIntersect(t *testing.T) { *** -#### func CalcLineSegmentSlope(line LineSegment[V]) V +#### func CalcLineSegmentSlope\[V generic.SignedNumber\](line LineSegment[V]) V > 计算线段的斜率 *** -#### func CalcLineSegmentIntercept(line LineSegment[V]) V +#### func CalcLineSegmentIntercept\[V generic.SignedNumber\](line LineSegment[V]) V > 计算线段的截距 *** -#### func NewPoint(x V, y V) Point[V] +#### func NewPoint\[V generic.SignedNumber\](x V, y V) Point[V] > 创建一个由 x、y 坐标组成的点 @@ -337,146 +337,146 @@ func TestNewPoint(t *testing.T) { *** -#### func NewPointCap(x V, y V) PointCap[V, D] +#### func NewPointCap\[V generic.SignedNumber, D any\](x V, y V) PointCap[V, D] > 创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 *** -#### func NewPointCapWithData(x V, y V, data D) PointCap[V, D] +#### func NewPointCapWithData\[V generic.SignedNumber, D any\](x V, y V, data D) PointCap[V, D] > 通过设置数据的方式创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 *** -#### func NewPointCapWithPoint(point Point[V], data D) PointCap[V, D] +#### func NewPointCapWithPoint\[V generic.SignedNumber, D any\](point Point[V], data D) PointCap[V, D] > 通过设置数据的方式创建一个由已有坐标组成的点,这个点具有一个数据容量 *** -#### func CoordinateToPoint(x V, y V) Point[V] +#### func CoordinateToPoint\[V generic.SignedNumber\](x V, y V) Point[V] > 将坐标转换为x、y的坐标数组 *** -#### func CoordinateToPos(width V, x V, y V) V +#### func CoordinateToPos\[V generic.SignedNumber\](width V, x V, y V) V > 将坐标转换为二维数组的顺序位置坐标 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 *** -#### func PointToCoordinate(position Point[V]) (x V, y V) +#### func PointToCoordinate\[V generic.SignedNumber\](position Point[V]) (x V, y V) > 将坐标数组转换为x和y坐标 *** -#### func PointToPos(width V, xy Point[V]) V +#### func PointToPos\[V generic.SignedNumber\](width V, xy Point[V]) V > 将坐标转换为二维数组的顺序位置 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 *** -#### func PosToCoordinate(width V, pos V) (x V, y V) +#### func PosToCoordinate\[V generic.SignedNumber\](width V, pos V) (x V, y V) > 通过宽度将一个二维数组的顺序位置转换为xy坐标 *** -#### func PosToPoint(width V, pos V) Point[V] +#### func PosToPoint\[V generic.SignedNumber\](width V, pos V) Point[V] > 通过宽度将一个二维数组的顺序位置转换为x、y的坐标数组 *** -#### func PosToCoordinateX(width V, pos V) V +#### func PosToCoordinateX\[V generic.SignedNumber\](width V, pos V) V > 通过宽度将一个二维数组的顺序位置转换为X坐标 *** -#### func PosToCoordinateY(width V, pos V) V +#### func PosToCoordinateY\[V generic.SignedNumber\](width V, pos V) V > 通过宽度将一个二维数组的顺序位置转换为Y坐标 *** -#### func PointCopy(point Point[V]) Point[V] +#### func PointCopy\[V generic.SignedNumber\](point Point[V]) Point[V] > 复制一个坐标数组 *** -#### func PointToPosWithMulti(width V, points ...Point[V]) []V +#### func PointToPosWithMulti\[V generic.SignedNumber\](width V, points ...Point[V]) []V > 将一组坐标转换为二维数组的顺序位置 > - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 *** -#### func PosToPointWithMulti(width V, positions ...V) []Point[V] +#### func PosToPointWithMulti\[V generic.SignedNumber\](width V, positions ...V) []Point[V] > 将一组二维数组的顺序位置转换为一组数组坐标 *** -#### func PosSameRow(width V, pos1 V, pos2 V) bool +#### func PosSameRow\[V generic.SignedNumber\](width V, pos1 V, pos2 V) bool > 返回两个顺序位置在同一宽度是否位于同一行 *** -#### func DoublePointToCoordinate(point1 Point[V], point2 Point[V]) (x1 V, y1 V, x2 V, y2 V) +#### func DoublePointToCoordinate\[V generic.SignedNumber\](point1 Point[V], point2 Point[V]) (x1 V, y1 V, x2 V, y2 V) > 将两个位置转换为 x1, y1, x2, y2 的坐标进行返回 *** -#### func CalcProjectionPoint(line LineSegment[V], point Point[V]) Point[V] +#### func CalcProjectionPoint\[V generic.SignedNumber\](line LineSegment[V], point Point[V]) Point[V] > 计算一个点到一条线段的最近点(即投影点)的。这个函数接收一个点和一条线段作为输入,线段由两个端点组成。 > - 该函数的主要用于需要计算一个点到一条线段的最近点的情况下 *** -#### func GetAdjacentTranslatePos(matrix []T, width P, pos P) (result []P) +#### func GetAdjacentTranslatePos\[T any, P generic.SignedNumber\](matrix []T, width P, pos P) (result []P) > 获取一个连续位置的矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 *** -#### func GetAdjacentTranslateCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentTranslateCoordinateXY\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 *** -#### func GetAdjacentTranslateCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentTranslateCoordinateYX\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 y、x 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 *** -#### func GetAdjacentDiagonalsPos(matrix []T, width P, pos P) (result []P) +#### func GetAdjacentDiagonalsPos\[T any, P generic.SignedNumber\](matrix []T, width P, pos P) (result []P) > 获取一个连续位置的矩阵中,特定位置相邻的对角线最多四个方向的位置 *** -#### func GetAdjacentDiagonalsCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentDiagonalsCoordinateXY\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 x、y 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 *** -#### func GetAdjacentDiagonalsCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentDiagonalsCoordinateYX\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 tx 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 *** -#### func GetAdjacentPos(matrix []T, width P, pos P) (result []P) +#### func GetAdjacentPos\[T any, P generic.SignedNumber\](matrix []T, width P, pos P) (result []P) > 获取一个连续位置的矩阵中,特定位置相邻的最多八个方向的位置 *** -#### func GetAdjacentCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentCoordinateXY\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多八个方向的位置 *** -#### func GetAdjacentCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) +#### func GetAdjacentCoordinateYX\[T any, P generic.SignedNumber\](matrix [][]T, x P, y P) (result []Point[P]) > 获取一个基于 yx 的二维矩阵中,特定位置相邻的最多八个方向的位置 *** -#### func CoordinateMatrixToPosMatrix(matrix [][]V) (width int, posMatrix []V) +#### func CoordinateMatrixToPosMatrix\[V any\](matrix [][]V) (width int, posMatrix []V) > 将二维矩阵转换为顺序的二维矩阵 *** -#### func GetShapeCoverageAreaWithPoint(points ...Point[V]) (left V, right V, top V, bottom V) +#### func GetShapeCoverageAreaWithPoint\[V generic.SignedNumber\](points ...Point[V]) (left V, right V, top V, bottom V) > 通过传入的一组坐标 points 计算一个图形覆盖的矩形范围 @@ -521,7 +521,7 @@ func TestGetShapeCoverageAreaWithPoint(t *testing.T) { *** -#### func GetShapeCoverageAreaWithPos(width V, positions ...V) (left V, right V, top V, bottom V) +#### func GetShapeCoverageAreaWithPos\[V generic.SignedNumber\](width V, positions ...V) (left V, right V, top V, bottom V) > 通过传入的一组坐标 positions 计算一个图形覆盖的矩形范围 @@ -558,7 +558,7 @@ func TestGetShapeCoverageAreaWithPos(t *testing.T) { *** -#### func CoverageAreaBoundless(l V, r V, t V, b V) (left V, right V, top V, bottom V) +#### func CoverageAreaBoundless\[V generic.SignedNumber\](l V, r V, t V, b V) (left V, right V, top V, bottom V) > 将一个图形覆盖矩形范围设置为无边的 > - 无边化表示会将多余的部分进行裁剪,例如图形左边从 2 开始的时候,那么左边将会被裁剪到从 0 开始 @@ -596,7 +596,7 @@ func TestCoverageAreaBoundless(t *testing.T) { *** -#### func GenerateShapeOnRectangle(points ...Point[V]) (result []PointCap[V, bool]) +#### func GenerateShapeOnRectangle\[V generic.SignedNumber\](points ...Point[V]) (result []PointCap[V, bool]) > 生成一组二维坐标的形状 > - 这个形状将被在一个刚好能容纳形状的矩形中表示 @@ -638,44 +638,44 @@ func TestGenerateShapeOnRectangle(t *testing.T) { *** -#### func GenerateShapeOnRectangleWithCoordinate(points ...Point[V]) (result [][]bool) +#### func GenerateShapeOnRectangleWithCoordinate\[V generic.SignedNumber\](points ...Point[V]) (result [][]bool) > 生成一组二维坐标的形状 > - 这个形状将被在一个刚好能容纳形状的矩形中表示 > - 为 true 的位置表示了形状的每一个点 *** -#### func GetExpressibleRectangleBySize(width V, height V, minWidth V, minHeight V) (result []Point[V]) +#### func GetExpressibleRectangleBySize\[V generic.SignedNumber\](width V, height V, minWidth V, minHeight V) (result []Point[V]) > 获取一个宽高可表达的所有特定尺寸以上的矩形形状 > - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) > - 矩形尺寸由大到小 *** -#### func GetExpressibleRectangle(width V, height V) (result []Point[V]) +#### func GetExpressibleRectangle\[V generic.SignedNumber\](width V, height V) (result []Point[V]) > 获取一个宽高可表达的所有矩形形状 > - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) > - 矩形尺寸由大到小 *** -#### func GetRectangleFullPointsByXY(startX V, startY V, endX V, endY V) (result []Point[V]) +#### func GetRectangleFullPointsByXY\[V generic.SignedNumber\](startX V, startY V, endX V, endY V) (result []Point[V]) > 通过开始结束坐标获取一个矩形包含的所有点 > - 例如 1,1 到 2,2 的矩形结果为 1,1 2,1 1,2 2,2 *** -#### func GetRectangleFullPoints(width V, height V) (result []Point[V]) +#### func GetRectangleFullPoints\[V generic.SignedNumber\](width V, height V) (result []Point[V]) > 获取一个矩形填充满后包含的所有点 *** -#### func GetRectangleFullPos(width V, height V) (result []V) +#### func GetRectangleFullPos\[V generic.SignedNumber\](width V, height V) (result []V) > 获取一个矩形填充满后包含的所有位置 *** -#### func CalcRectangleCentroid(shape Shape[V]) Point[V] +#### func CalcRectangleCentroid\[V generic.SignedNumber\](shape Shape[V]) Point[V] > 计算矩形质心 > - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心 @@ -691,7 +691,7 @@ func TestGenerateShapeOnRectangle(t *testing.T) { > 设置 Shape.String 是没有边界的 *** -#### func NewShape(points ...Point[V]) Shape[V] +#### func NewShape\[V generic.SignedNumber\](points ...Point[V]) Shape[V] > 通过多个点生成一个形状进行返回 @@ -733,7 +733,7 @@ func TestNewShape(t *testing.T) { *** -#### func NewShapeWithString(rows []string, point rune) (shape Shape[V]) +#### func NewShapeWithString\[V generic.SignedNumber\](rows []string, point rune) (shape Shape[V]) > 通过字符串将指定 rune 转换为点位置生成形状进行返回 > - 每个点的顺序从上到下,从左到右 @@ -775,27 +775,27 @@ func TestNewShapeWithString(t *testing.T) { *** -#### func CalcBoundingRadius(shape Shape[V]) V +#### func CalcBoundingRadius\[V generic.SignedNumber\](shape Shape[V]) V > 计算多边形转换为圆的半径 *** -#### func CalcBoundingRadiusWithCentroid(shape Shape[V], centroid Point[V]) V +#### func CalcBoundingRadiusWithCentroid\[V generic.SignedNumber\](shape Shape[V], centroid Point[V]) V > 计算多边形在特定质心下圆的半径 *** -#### func CalcTriangleTwiceArea(a Point[V], b Point[V], c Point[V]) V +#### func CalcTriangleTwiceArea\[V generic.SignedNumber\](a Point[V], b Point[V], c Point[V]) V > 计算由 a、b、c 三个点组成的三角形的面积的两倍 *** -#### func IsPointOnEdge(edges []LineSegment[V], point Point[V]) bool +#### func IsPointOnEdge\[V generic.SignedNumber\](edges []LineSegment[V], point Point[V]) bool > 检查点是否在 edges 的任意一条边上 *** -#### func ProjectionPointToShape(point Point[V], shape Shape[V]) (Point[V], V) +#### func ProjectionPointToShape\[V generic.SignedNumber\](point Point[V], shape Shape[V]) (Point[V], V) > 将一个点投影到一个多边形上,找到离该点最近的投影点,并返回投影点和距离 diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md index 938465c9..b44daf6e 100644 --- a/utils/geometry/astar/README.md +++ b/utils/geometry/astar/README.md @@ -35,7 +35,7 @@ astar 提供用于实现 A* 算法的函数和数据结构。A* 算法是一种 *** ## 详情信息 -#### func Find(graph Graph[Node], start Node, end Node, cost func (a Node) V, heuristic func (a Node) V) []Node +#### func Find\[Node comparable, V generic.SignedNumber\](graph Graph[Node], start Node, end Node, cost func (a Node) V, heuristic func (a Node) V) []Node > 使用 A* 算法在导航网格上查找从起点到终点的最短路径,并返回路径上的节点序列。 > diff --git a/utils/geometry/dp/README.md b/utils/geometry/dp/README.md index 0250b7df..1eaf3cec 100644 --- a/utils/geometry/dp/README.md +++ b/utils/geometry/dp/README.md @@ -35,7 +35,7 @@ dp (DistributionPattern) 提供用于在二维数组中根据不同的特征标 *** ## 详情信息 -#### func NewDistributionPattern(sameKindVerifyHandle func (itemA Item) bool) *DistributionPattern[Item] +#### func NewDistributionPattern\[Item any\](sameKindVerifyHandle func (itemA Item) bool) *DistributionPattern[Item] > 构建一个分布图实例 diff --git a/utils/geometry/matrix/README.md b/utils/geometry/matrix/README.md index 9d45d0ed..c8972d1d 100644 --- a/utils/geometry/matrix/README.md +++ b/utils/geometry/matrix/README.md @@ -30,7 +30,7 @@ matrix 提供了一个简单的二维数组的实现 *** ## 详情信息 -#### func NewMatrix(width int, height int) *Matrix[T] +#### func NewMatrix\[T any\](width int, height int) *Matrix[T] > 生成特定宽高的二维矩阵 > - 虽然提供了通过x、y坐标的操作函数,但是建议无论如何使用pos进行处理 diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md index 657620b4..04d006e5 100644 --- a/utils/geometry/navmesh/README.md +++ b/utils/geometry/navmesh/README.md @@ -33,7 +33,7 @@ navmesh 提供了用于导航网格处理的函数和数据结构。导航网格 *** ## 详情信息 -#### func NewNavMesh(shapes []geometry.Shape[V], meshShrinkAmount V) *NavMesh[V] +#### func NewNavMesh\[V generic.SignedNumber\](shapes []geometry.Shape[V], meshShrinkAmount V) *NavMesh[V] > 创建一个新的导航网格,并返回一个指向该导航网格的指针。 > diff --git a/utils/hub/README.md b/utils/hub/README.md index d3bdaaaa..63d2ed74 100644 --- a/utils/hub/README.md +++ b/utils/hub/README.md @@ -30,7 +30,7 @@ *** ## 详情信息 -#### func NewObjectPool(generator func () *T, releaser func (data *T)) *ObjectPool[*T] +#### func NewObjectPool\[T any\](generator func () *T, releaser func (data *T)) *ObjectPool[*T] > 创建一个 ObjectPool diff --git a/utils/huge/README.md b/utils/huge/README.md index 83c9e5b4..1f8e8a60 100644 --- a/utils/huge/README.md +++ b/utils/huge/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewFloat(x T) *Float +#### func NewFloat\[T generic.Number\](x T) *Float > 创建一个 Float @@ -45,7 +45,7 @@ > - 如果字符串不是一个合法的数字,则返回 0 *** -#### func NewInt(x T) *Int +#### func NewInt\[T generic.Number\](x T) *Int > 创建一个 Int diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index 9080f557..3cd06c16 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -34,7 +34,7 @@ *** ## 详情信息 -#### func NewBinarySearch(options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] +#### func NewBinarySearch\[CompetitorID comparable, Score generic.Ordered\](options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] > 创建一个基于内存的二分查找排行榜 @@ -49,13 +49,13 @@ func ExampleNewBinarySearch() { ``` *** -#### func WithBinarySearchCount(rankCount int) BinarySearchOption[CompetitorID, Score] +#### func WithBinarySearchCount\[CompetitorID comparable, Score generic.Ordered\](rankCount int) BinarySearchOption[CompetitorID, Score] > 通过限制排行榜竞争者数量来创建排行榜 > - 默认情况下允许100位竞争者 *** -#### func WithBinarySearchASC() BinarySearchOption[CompetitorID, Score] +#### func WithBinarySearchASC\[CompetitorID comparable, Score generic.Ordered\]() BinarySearchOption[CompetitorID, Score] > 通过升序的方式创建排行榜 > - 默认情况下为降序 diff --git a/utils/log/README.md b/utils/log/README.md index 7beab827..16682a54 100644 --- a/utils/log/README.md +++ b/utils/log/README.md @@ -186,132 +186,132 @@ > 构造一个带有字符串值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int(key string, val I) slog.Attr +#### func Int\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func IntP(key string, val *I) slog.Attr +#### func IntP\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int8(key string, val I) slog.Attr +#### func Int8\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int8P(key string, val *I) slog.Attr +#### func Int8P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int16(key string, val I) slog.Attr +#### func Int16\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int16P(key string, val *I) slog.Attr +#### func Int16P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int32(key string, val I) slog.Attr +#### func Int32\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int32P(key string, val *I) slog.Attr +#### func Int32P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Int64(key string, val I) slog.Attr +#### func Int64\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Int64P(key string, val *I) slog.Attr +#### func Int64P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint(key string, val I) slog.Attr +#### func Uint\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func UintP(key string, val *I) slog.Attr +#### func UintP\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint8(key string, val I) slog.Attr +#### func Uint8\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint8P(key string, val *I) slog.Attr +#### func Uint8P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint16(key string, val I) slog.Attr +#### func Uint16\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint16P(key string, val *I) slog.Attr +#### func Uint16P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint32(key string, val I) slog.Attr +#### func Uint32\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint32P(key string, val *I) slog.Attr +#### func Uint32P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Uint64(key string, val I) slog.Attr +#### func Uint64\[I generic.Integer\](key string, val I) slog.Attr > 构造一个带有整数值的字段 *** -#### func Uint64P(key string, val *I) slog.Attr +#### func Uint64P\[I generic.Integer\](key string, val *I) slog.Attr > 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float(key string, val F) slog.Attr +#### func Float\[F generic.Float\](key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func FloatP(key string, val *F) slog.Attr +#### func FloatP\[F generic.Float\](key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float32(key string, val F) slog.Attr +#### func Float32\[F generic.Float\](key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func Float32P(key string, val *F) slog.Attr +#### func Float32P\[F generic.Float\](key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" *** -#### func Float64(key string, val F) slog.Attr +#### func Float64\[F generic.Float\](key string, val F) slog.Attr > 构造一个带有浮点值的字段 *** -#### func Float64P(key string, val *F) slog.Attr +#### func Float64P\[F generic.Float\](key string, val *F) slog.Attr > 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" diff --git a/utils/maths/README.md b/utils/maths/README.md index 8bde01c1..0b498480 100644 --- a/utils/maths/README.md +++ b/utils/maths/README.md @@ -52,12 +52,12 @@ *** ## 详情信息 -#### func Compare(a V, expression CompareExpression, b V) bool +#### func Compare\[V generic.Ordered\](a V, expression CompareExpression, b V) bool > 根据特定表达式比较两个值 *** -#### func IsContinuity(values S) bool +#### func IsContinuity\[S ~[]V, V generic.Integer\](values S) bool > 检查一组值是否连续 @@ -72,7 +72,7 @@ func ExampleIsContinuity() { ``` *** -#### func IsContinuityWithSort(values S) bool +#### func IsContinuityWithSort\[S ~[]V, V generic.Integer\](values S) bool > 检查一组值排序后是否连续 @@ -92,57 +92,57 @@ func ExampleIsContinuity() { > 整数幂运算 *** -#### func Min(a V, b V) V +#### func Min\[V generic.Number\](a V, b V) V > 返回两个数之中较小的值 *** -#### func Max(a V, b V) V +#### func Max\[V generic.Number\](a V, b V) V > 返回两个数之中较大的值 *** -#### func MinMax(a V, b V) (min V, max V) +#### func MinMax\[V generic.Number\](a V, b V) (min V, max V) > 将两个数按照较小的和较大的顺序进行返回 *** -#### func MaxMin(a V, b V) (max V, min V) +#### func MaxMin\[V generic.Number\](a V, b V) (max V, min V) > 将两个数按照较大的和较小的顺序进行返回 *** -#### func Clamp(value V, min V, max V) V +#### func Clamp\[V generic.Number\](value V, min V, max V) V > 将给定值限制在最小值和最大值之间 *** -#### func Tolerance(value1 V, value2 V, tolerance V) bool +#### func Tolerance\[V generic.Number\](value1 V, value2 V, tolerance V) bool > 检查两个值是否在一个误差范围内 *** -#### func Merge(refer V, a V, b V) V +#### func Merge\[V generic.SignedNumber\](refer V, a V, b V) V > 通过一个参考值合并两个数字 *** -#### func UnMerge(refer V, num V) (a V, b V) +#### func UnMerge\[V generic.SignedNumber\](refer V, num V) (a V, b V) > 通过一个参考值取消合并的两个数字 *** -#### func MergeToInt64(v1 V, v2 V) int64 +#### func MergeToInt64\[V generic.SignedNumber\](v1 V, v2 V) int64 > 将两个数字合并为一个 int64 数字 *** -#### func UnMergeInt64(n int64) (V, V) +#### func UnMergeInt64\[V generic.SignedNumber\](n int64) (V, V) > 将一个 int64 数字拆分为两个数字 *** -#### func ToContinuous(nums S) map[V]V +#### func ToContinuous\[S ~[]V, V generic.Integer\](nums S) map[V]V > 将一组非连续的数字转换为从1开始的连续数字 > - 返回值是一个 map,key 是从 1 开始的连续数字,value 是原始数字 @@ -160,7 +160,7 @@ func ExampleToContinuous() { ``` *** -#### func CountDigits(num V) int +#### func CountDigits\[V generic.Number\](num V) int > 接收一个整数 num 作为输入,并返回该数字的位数 @@ -172,22 +172,22 @@ func ExampleToContinuous() { > 过除以10的操作将 num 移动到目标位数上。然后,通过取余运算得到位数上的数值 *** -#### func JoinNumbers(num1 V, n ...V) V +#### func JoinNumbers\[V generic.Number\](num1 V, n ...V) V > 将一组数字连接起来 *** -#### func IsOdd(n V) bool +#### func IsOdd\[V generic.Integer\](n V) bool > 返回 n 是否为奇数 *** -#### func IsEven(n V) bool +#### func IsEven\[V generic.Integer\](n V) bool > 返回 n 是否为偶数 *** -#### func MakeLastDigitsZero(num T, digits int) T +#### func MakeLastDigitsZero\[T generic.Number\](num T, digits int) T > 返回一个新的数,其中 num 的最后 digits 位数被设为零。 > - 函数首先创建一个 10 的 digits 次方的遮罩,然后通过整除和乘以这个遮罩来使 num 的最后 digits 位归零。 diff --git a/utils/memory/README.md b/utils/memory/README.md index 3c232353..81e1057e 100644 --- a/utils/memory/README.md +++ b/utils/memory/README.md @@ -38,7 +38,7 @@ > 运行持久化缓存程序 *** -#### func BindPersistCacheProgram(name string, handler OutputParamHandlerFunc, option ...*Option) func () +#### func BindPersistCacheProgram\[OutputParamHandlerFunc any\](name string, handler OutputParamHandlerFunc, option ...*Option) func () > 绑定持久化缓存程序 > - name 持久化缓存程序名称 @@ -52,7 +52,7 @@ > - 所有持久化程序绑定完成后,应该主动调用 Run 函数运行 *** -#### func BindAction(name string, handler Func) Func +#### func BindAction\[Func any\](name string, handler Func) Func > 绑定需要缓存的操作函数 > - name 缓存操作名称 diff --git a/utils/moving/README.md b/utils/moving/README.md index 164b2350..5f88634b 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -37,7 +37,7 @@ *** ## 详情信息 -#### func NewTwoDimensional(options ...TwoDimensionalOption[EID, PosType]) *TwoDimensional[EID, PosType] +#### func NewTwoDimensional\[EID generic.Basic, PosType generic.SignedNumber\](options ...TwoDimensionalOption[EID, PosType]) *TwoDimensional[EID, PosType] > 创建一个用于2D对象移动的实例(TwoDimensional) @@ -74,19 +74,19 @@ func TestNewTwoDimensional(t *testing.T) { *** -#### func WithTwoDimensionalTimeUnit(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalTimeUnit\[EID generic.Basic, PosType generic.SignedNumber\](duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定时间单位创建 > - 默认单位为1毫秒,最小单位也为1毫秒 *** -#### func WithTwoDimensionalIdleWaitTime(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalIdleWaitTime\[EID generic.Basic, PosType generic.SignedNumber\](duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定的空闲等待时间创建 > - 默认情况下在没有新的移动计划时将限制 100毫秒 + 移动间隔事件(默认100毫秒) *** -#### func WithTwoDimensionalInterval(duration time.Duration) TwoDimensionalOption[EID, PosType] +#### func WithTwoDimensionalInterval\[EID generic.Basic, PosType generic.SignedNumber\](duration time.Duration) TwoDimensionalOption[EID, PosType] > 通过特定的移动间隔时间创建 diff --git a/utils/random/README.md b/utils/random/README.md index 4747b7fc..ee9e3a9e 100644 --- a/utils/random/README.md +++ b/utils/random/README.md @@ -119,7 +119,7 @@ > 返回一个随机的布尔值 *** -#### func ProbabilitySlice(getProbabilityHandle func (data T) float64, data ...T) (hit T, miss bool) +#### func ProbabilitySlice\[T any\](getProbabilityHandle func (data T) float64, data ...T) (hit T, miss bool) > 按概率随机从切片中产生一个数据并返回命中的对象及是否未命中 > - 当总概率小于 1 将会发生未命中的情况 @@ -147,7 +147,7 @@ func TestProbabilitySlice(t *testing.T) { *** -#### func ProbabilitySliceIndex(getProbabilityHandle func (data T) float64, data ...T) (hit T, index int, miss bool) +#### func ProbabilitySliceIndex\[T any\](getProbabilityHandle func (data T) float64, data ...T) (hit T, index int, miss bool) > 按概率随机从切片中产生一个数据并返回命中的对象及对象索引以及是否未命中 > - 当总概率小于 1 将会发生未命中的情况 @@ -200,22 +200,22 @@ func TestProbabilitySlice(t *testing.T) { > 返回一个随机产生的hostname。 *** -#### func WeightSlice(getWeightHandle func (data T) int64, data ...T) T +#### func WeightSlice\[T any\](getWeightHandle func (data T) int64, data ...T) T > 按权重随机从切片中产生一个数据并返回 *** -#### func WeightSliceIndex(getWeightHandle func (data T) int64, data ...T) (item T, index int) +#### func WeightSliceIndex\[T any\](getWeightHandle func (data T) int64, data ...T) (item T, index int) > 按权重随机从切片中产生一个数据并返回数据和对应索引 *** -#### func WeightMap(getWeightHandle func (data T) int64, data map[K]T) T +#### func WeightMap\[K comparable, T any\](getWeightHandle func (data T) int64, data map[K]T) T > 按权重随机从map中产生一个数据并返回 *** -#### func WeightMapKey(getWeightHandle func (data T) int64, data map[K]T) (item T, key K) +#### func WeightMapKey\[K comparable, T any\](getWeightHandle func (data T) int64, data map[K]T) (item T, key K) > 按权重随机从map中产生一个数据并返回数据和对应 key diff --git a/utils/reflects/README.md b/utils/reflects/README.md index 950e693b..63f0ea6c 100644 --- a/utils/reflects/README.md +++ b/utils/reflects/README.md @@ -32,22 +32,22 @@ *** ## 详情信息 -#### func WrapperFunc(f any, wrapper func (call func ( []reflect.Value) []reflect.Value) func (args []reflect.Value) []reflect.Value) (wf Func, err error) +#### func WrapperFunc\[Func any\](f any, wrapper func (call func ( []reflect.Value) []reflect.Value) func (args []reflect.Value) []reflect.Value) (wf Func, err error) > 包装函数 *** -#### func WrapperFuncBefore2After(f Func, before func (), after func ()) (wf Func, err error) +#### func WrapperFuncBefore2After\[Func any\](f Func, before func (), after func ()) (wf Func, err error) > 包装函数,前置函数执行前,后置函数执行后 *** -#### func WrapperFuncBefore(f Func, before func ()) (wf Func, err error) +#### func WrapperFuncBefore\[Func any\](f Func, before func ()) (wf Func, err error) > 包装函数,前置函数执行前 *** -#### func WrapperFuncAfter(f Func, after func ()) (wf Func, err error) +#### func WrapperFuncAfter\[Func any\](f Func, after func ()) (wf Func, err error) > 包装函数,后置函数执行后 @@ -67,7 +67,7 @@ > 拷贝 *** -#### func GetPointer(src T) reflect.Value +#### func GetPointer\[T any\](src T) reflect.Value > 获取指针 diff --git a/utils/sole/README.md b/utils/sole/README.md index 5c238b08..040be62d 100644 --- a/utils/sole/README.md +++ b/utils/sole/README.md @@ -75,7 +75,7 @@ > 重置特定命名空间的唯一标识符 *** -#### func NewOnce() *Once[V] +#### func NewOnce\[V any\]() *Once[V] > 创建一个用于数据取值去重的结构实例 diff --git a/utils/sorts/README.md b/utils/sorts/README.md index bc1ef79d..29f84045 100644 --- a/utils/sorts/README.md +++ b/utils/sorts/README.md @@ -25,7 +25,7 @@ *** ## 详情信息 -#### func Topological(slice S, queryIndexHandler func (item V) Index, queryDependsHandler func (item V) []Index) (S, error) +#### func Topological\[S ~[]V, Index comparable, V any\](slice S, queryIndexHandler func (item V) Index, queryDependsHandler func (item V) []Index) (S, error) > 拓扑排序是一种对有向图进行排序的算法,它可以用来解决一些依赖关系的问题,比如计算字段的依赖关系。拓扑排序会将存在依赖关系的元素进行排序,使得依赖关系的元素总是排在被依赖的元素之前。 > - slice: 需要排序的切片 diff --git a/utils/super/README.md b/utils/super/README.md index 700d5ad3..8c803094 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -104,18 +104,18 @@ *** ## 详情信息 -#### func NewBitSet(bits ...Bit) *BitSet[Bit] +#### func NewBitSet\[Bit generic.Integer\](bits ...Bit) *BitSet[Bit] > 通过指定的 Bit 位创建一个 BitSet *** -#### func TryWriteChannel(ch chan T, data T) bool +#### func TryWriteChannel\[T any\](ch chan T, data T) bool > 尝试写入 channel,如果 channel 无法写入则忽略,返回是否写入成功 > - 无法写入的情况包括:channel 已满、channel 已关闭 *** -#### func TryWriteChannelByHandler(ch chan T, data T, handler func ()) +#### func TryWriteChannelByHandler\[T any\](ch chan T, data T, handler func ()) > 尝试写入 channel,如果 channel 无法写入则执行 handler > - 无法写入的情况包括:channel 已满、channel 已关闭 @@ -184,7 +184,7 @@ func ExampleRecoverTransform() { > 执行 f 函数,如果 f 为 nil,则不执行 *** -#### func HandleV(v V, f func (v V)) +#### func HandleV\[V any\](v V, f func (v V)) > 执行 f 函数,如果 f 为 nil,则不执行 @@ -194,7 +194,7 @@ func ExampleRecoverTransform() { > go 代码格式化 *** -#### func If(expression bool, t T, f T) T +#### func If\[T any\](expression bool, t T, f T) T *** @@ -230,7 +230,7 @@ func ExampleRecoverTransform() { > 开始损耗计数 *** -#### func Match(value Value) *Matcher[Value, Result] +#### func Match\[Value any, Result any\](value Value) *Matcher[Value, Result] > 匹配 @@ -450,7 +450,7 @@ func TestNumberToRome(t *testing.T) { > 切片转换为字符串 *** -#### func NewPermission() *Permission[Code, EntityID] +#### func NewPermission\[Code generic.Integer, EntityID comparable\]() *Permission[Code, EntityID] > 创建权限 @@ -562,7 +562,7 @@ func TestNewPermission(t *testing.T) { > 以零拷贝的方式将字节切片转换为字符串 *** -#### func Convert(src A) B +#### func Convert\[A any, B any\](src A) B > 以零拷贝的方式将一个对象转换为另一个对象 > - 两个对象字段必须完全一致 @@ -591,7 +591,7 @@ func TestConvert(t *testing.T) { *** -#### func Verify(handle func ( V)) *VerifyHandle[V] +#### func Verify\[V any\](handle func ( V)) *VerifyHandle[V] > 对特定表达式进行校验,当表达式不成立时,将执行 handle diff --git a/utils/times/README.md b/utils/times/README.md index dc2a35ec..033d92fa 100644 --- a/utils/times/README.md +++ b/utils/times/README.md @@ -250,7 +250,7 @@ func ExampleCalcNextSecWithTime() { > 获取一个时间的年末 *** -#### func NewStateLine(zero State) *StateLine[State] +#### func NewStateLine\[State generic.Basic\](zero State) *StateLine[State] > 创建一个从左向右由早到晚的状态时间线 From 580bab2dfc847096fdc380a692fa5fc5bbbd63ec Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 11:12:24 +0800 Subject: [PATCH 04/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/space/README.md | 3 +- server/README.md | 9 +- server/internal/dispatcher/README.md | 6 +- server/router/README.md | 15 +- server/writeloop/README.md | 3 +- utils/collection/README.md | 342 +++++++++++++++++-------- utils/collection/clone.go | 3 +- utils/collection/clone_example_test.go | 7 + utils/generator/astgo/function.go | 3 + utils/generator/genreadme/builder.go | 42 ++- utils/geometry/README.md | 30 ++- utils/geometry/astar/README.md | 3 +- utils/geometry/navmesh/README.md | 3 +- utils/hub/README.md | 3 +- utils/leaderboard/README.md | 3 +- utils/maths/README.md | 6 +- utils/moving/README.md | 3 +- utils/sorts/README.md | 3 +- utils/super/README.md | 12 +- utils/times/README.md | 3 +- 20 files changed, 351 insertions(+), 151 deletions(-) diff --git a/game/space/README.md b/game/space/README.md index 4b239fbf..cb4d1528 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -38,7 +38,8 @@ space 游戏中常见的空间设计,例如房间、地图等 > 创建房间管理器 RoomManager 的实例 -示例代码: +**示例代码:** + ```go func ExampleNewRoomManager() { diff --git a/server/README.md b/server/README.md index 80226cc9..fe320bcd 100644 --- a/server/README.md +++ b/server/README.md @@ -300,7 +300,8 @@ func TestNewBot(t *testing.T) { > 根据特定网络类型创建一个服务器 -示例代码: +**示例代码:** + ```go func ExampleNew() { @@ -351,7 +352,8 @@ func TestNew(t *testing.T) { > 绑定服务到特定 Server,被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法 -示例代码: +**示例代码:** + ```go func ExampleBindService() { @@ -860,7 +862,8 @@ type Server struct { > - server.NetworkWebsocket (addr:":8888/ws") > - server.NetworkKcp (addr:":8888") > - server.NetworkNone (addr:"") -示例代码: +**示例代码:** + ```go func ExampleServer_Run() { diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index 3948e8b4..ec7fb12a 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -40,7 +40,8 @@ > 创建一个新的消息分发器 Dispatcher 实例 -示例代码: +**示例代码:** + ```go func ExampleNewDispatcher() { @@ -111,7 +112,8 @@ func TestNewDispatcher(t *testing.T) { > 生成消息分发器管理器 -示例代码: +**示例代码:** + ```go func ExampleNewManager() { diff --git a/server/router/README.md b/server/router/README.md index 56e4248f..993cbc57 100644 --- a/server/router/README.md +++ b/server/router/README.md @@ -37,7 +37,8 @@ > 创建一个支持多级分类的路由器 -示例代码: +**示例代码:** + ```go func ExampleNewMultistage() { @@ -76,7 +77,8 @@ type Multistage[HandleFunc any] struct { #### func (*Multistage) Register(routes ...any) MultistageBind[HandleFunc] > 注册路由是结合 Sub 和 Route 的快捷方式,用于一次性注册多级路由 > - 该函数将返回一个注册函数,可通过调用其将路由绑定到特定处理函数,例如:router.Register("a", "b").Bind(onExec()) -示例代码: +**示例代码:** + ```go func ExampleMultistage_Register() { @@ -90,7 +92,8 @@ func ExampleMultistage_Register() { *** #### func (*Multistage) Route(route any, handleFunc HandleFunc) > 为特定路由绑定处理函数,被绑定的处理函数将可以通过 Match 函数进行匹配 -示例代码: +**示例代码:** + ```go func ExampleMultistage_Route() { @@ -105,7 +108,8 @@ func ExampleMultistage_Route() { #### func (*Multistage) Match(routes ...any) HandleFunc > 匹配已绑定处理函数的路由,返回处理函数 > - 如果未找到将会返回空指针 -示例代码: +**示例代码:** + ```go func ExampleMultistage_Match() { @@ -154,7 +158,8 @@ func TestMultistage_Match(t *testing.T) { *** #### func (*Multistage) Sub(route any) *Multistage[HandleFunc] > 获取子路由器 -示例代码: +**示例代码:** + ```go func ExampleMultistage_Sub() { diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 3b4d67bd..3395e2d2 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -53,7 +53,8 @@ > > 传入 writeHandler 的消息对象是从 pool 中获取的,并且在 writeHandler 执行完成后会被放回 pool 中,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 -示例代码: +**示例代码:** + ```go func ExampleNewUnbounded() { diff --git a/utils/collection/README.md b/utils/collection/README.md index 5c3a13c2..6b59b43b 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -18,7 +18,7 @@ collection 定义了各种对于集合操作有用的各种函数 |:--|:-- |[CloneSlice](#CloneSlice)|通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 |[CloneMap](#CloneMap)|通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map -|[CloneSliceN](#CloneSliceN)|克隆 slice 为 n 个切片进行返回 +|[CloneSliceN](#CloneSliceN)|通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片为 n 个切片 |[CloneMapN](#CloneMapN)|克隆 map 为 n 个 map 进行返回 |[CloneSlices](#CloneSlices)|克隆多个切片 |[CloneMaps](#CloneMaps)|克隆多个 map @@ -145,7 +145,12 @@ collection 定义了各种对于集合操作有用的各种函数 > 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 -示例代码: +**示例代码:** + +slice 克隆后将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 + - 示例中的结果将会输出 [1 2 3] + + ```go func ExampleCloneSlice() { @@ -195,7 +200,8 @@ func TestCloneSlice(t *testing.T) { > 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map > - 当 m 为空时,将会返回 nil -示例代码: +**示例代码:** + ```go func ExampleCloneMap() { @@ -242,9 +248,11 @@ func TestCloneMap(t *testing.T) { *** #### func CloneSliceN\[S ~[]V, V any\](slice S, n int) []S -> 克隆 slice 为 n 个切片进行返回 +> 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片为 n 个切片 +> - 当 slice 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片 + +**示例代码:** -示例代码: ```go func ExampleCloneSliceN() { @@ -299,7 +307,8 @@ func TestCloneSliceN(t *testing.T) { > 克隆 map 为 n 个 map 进行返回 -示例代码: +**示例代码:** + ```go func ExampleCloneMapN() { @@ -354,7 +363,8 @@ func TestCloneMapN(t *testing.T) { > 克隆多个切片 -示例代码: +**示例代码:** + ```go func ExampleCloneSlices() { @@ -406,7 +416,8 @@ func TestCloneSlices(t *testing.T) { > 克隆多个 map -示例代码: +**示例代码:** + ```go func ExampleCloneMaps() { @@ -458,7 +469,8 @@ func TestCloneMaps(t *testing.T) { > 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 -示例代码: +**示例代码:** + ```go func ExampleInSlice() { @@ -506,7 +518,8 @@ func TestInSlice(t *testing.T) { > 检查 v 是否被包含在 slice 中 -示例代码: +**示例代码:** + ```go func ExampleInComparableSlice() { @@ -552,7 +565,8 @@ func TestInComparableSlice(t *testing.T) { > - 在所有 values 中的元素都被包含在 slice 中时,返回 true > - 当 values 长度为 0 或为 nil 时,将返回 true -示例代码: +**示例代码:** + ```go func ExampleAllInSlice() { @@ -602,7 +616,8 @@ func TestAllInSlice(t *testing.T) { > - 在所有 values 中的元素都被包含在 slice 中时,返回 true > - 当 values 长度为 0 或为 nil 时,将返回 true -示例代码: +**示例代码:** + ```go func ExampleAllInComparableSlice() { @@ -647,7 +662,8 @@ func TestAllInComparableSlice(t *testing.T) { > 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInSlice() { @@ -696,7 +712,8 @@ func TestAnyInSlice(t *testing.T) { > 检查 values 中的任意一个元素是否被包含在 slice 中 > - 当 values 中的任意一个元素被包含在 slice 中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInComparableSlice() { @@ -741,7 +758,8 @@ func TestAnyInComparableSlice(t *testing.T) { > 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleInSlices() { @@ -790,7 +808,8 @@ func TestInSlices(t *testing.T) { > 通过将多个切片合并后检查 v 是否被包含在 slices 中 > - 当传入的 v 被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleInComparableSlices() { @@ -835,7 +854,8 @@ func TestInComparableSlices(t *testing.T) { > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAllInSlices() { @@ -884,7 +904,8 @@ func TestAllInSlices(t *testing.T) { > 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中 > - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAllInComparableSlices() { @@ -929,7 +950,8 @@ func TestAllInComparableSlices(t *testing.T) { > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInSlices() { @@ -978,7 +1000,8 @@ func TestAnyInSlices(t *testing.T) { > 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中 > - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInComparableSlices() { @@ -1023,7 +1046,8 @@ func TestAnyInComparableSlices(t *testing.T) { > 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 > - 当 v 被包含在 slices 的每一项元素中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleInAllSlices() { @@ -1072,7 +1096,8 @@ func TestInAllSlices(t *testing.T) { > 检查 v 是否被包含在 slices 的每一项元素中 > - 当 v 被包含在 slices 的每一项元素中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleInAllComparableSlices() { @@ -1117,7 +1142,8 @@ func TestInAllComparableSlices(t *testing.T) { > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInAllSlices() { @@ -1166,7 +1192,8 @@ func TestAnyInAllSlices(t *testing.T) { > 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素 > - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyInAllComparableSlices() { @@ -1210,7 +1237,8 @@ func TestAnyInAllComparableSlices(t *testing.T) { > 检查 m 中是否包含特定 key -示例代码: +**示例代码:** + ```go func ExampleKeyInMap() { @@ -1254,7 +1282,8 @@ func TestKeyInMap(t *testing.T) { > 检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配 -示例代码: +**示例代码:** + ```go func ExampleValueInMap() { @@ -1301,7 +1330,8 @@ func TestValueInMap(t *testing.T) { > 检查 m 中是否包含 keys 中所有的元素 -示例代码: +**示例代码:** + ```go func ExampleAllKeyInMap() { @@ -1345,7 +1375,8 @@ func TestAllKeyInMap(t *testing.T) { > 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 -示例代码: +**示例代码:** + ```go func ExampleAllValueInMap() { @@ -1391,7 +1422,8 @@ func TestAllValueInMap(t *testing.T) { > 检查 m 中是否包含 keys 中任意一个元素 -示例代码: +**示例代码:** + ```go func ExampleAnyKeyInMap() { @@ -1435,7 +1467,8 @@ func TestAnyKeyInMap(t *testing.T) { > 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 -示例代码: +**示例代码:** + ```go func ExampleAnyValueInMap() { @@ -1481,7 +1514,8 @@ func TestAnyValueInMap(t *testing.T) { > 检查 maps 中的每一个元素是否均包含 keys 中所有的元素 -示例代码: +**示例代码:** + ```go func ExampleAllKeyInMaps() { @@ -1525,7 +1559,8 @@ func TestAllKeyInMaps(t *testing.T) { > 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 -示例代码: +**示例代码:** + ```go func ExampleAllValueInMaps() { @@ -1572,7 +1607,8 @@ func TestAllValueInMaps(t *testing.T) { > 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中 > - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyKeyInMaps() { @@ -1617,7 +1653,8 @@ func TestAnyKeyInMaps(t *testing.T) { > 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 > - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyValueInMaps() { @@ -1663,7 +1700,8 @@ func TestAnyValueInMaps(t *testing.T) { > 检查 key 是否被包含在 maps 的每一个元素中 -示例代码: +**示例代码:** + ```go func ExampleKeyInAllMaps() { @@ -1708,7 +1746,8 @@ func TestKeyInAllMaps(t *testing.T) { > 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 > - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true -示例代码: +**示例代码:** + ```go func ExampleAnyKeyInAllMaps() { @@ -1752,7 +1791,8 @@ func TestAnyKeyInAllMaps(t *testing.T) { > 将切片转换为任意类型的切片 -示例代码: +**示例代码:** + ```go func ExampleConvertSliceToAny() { @@ -1804,7 +1844,8 @@ func TestConvertSliceToAny(t *testing.T) { > 将切片转换为索引为键的映射 -示例代码: +**示例代码:** + ```go func ExampleConvertSliceToIndexMap() { @@ -1855,7 +1896,8 @@ func TestConvertSliceToIndexMap(t *testing.T) { > 将切片转换为索引为键的映射 -示例代码: +**示例代码:** + ```go func ExampleConvertSliceToIndexOnlyMap() { @@ -1910,7 +1952,8 @@ func TestConvertSliceToIndexOnlyMap(t *testing.T) { > 将切片转换为值为键的映射 -示例代码: +**示例代码:** + ```go func ExampleConvertSliceToMap() { @@ -1962,7 +2005,8 @@ func TestConvertSliceToMap(t *testing.T) { > 将切片转换为值为键的映射 -示例代码: +**示例代码:** + ```go func ExampleConvertSliceToBoolMap() { @@ -2013,7 +2057,8 @@ func TestConvertSliceToBoolMap(t *testing.T) { > 将映射的键转换为切片 -示例代码: +**示例代码:** + ```go func ExampleConvertMapKeysToSlice() { @@ -2069,7 +2114,8 @@ func TestConvertMapKeysToSlice(t *testing.T) { > 将映射的值转换为切片 -示例代码: +**示例代码:** + ```go func ExampleConvertMapValuesToSlice() { @@ -2126,7 +2172,8 @@ func TestConvertMapValuesToSlice(t *testing.T) { > 将映射的键和值互换 -示例代码: +**示例代码:** + ```go func ExampleInvertMap() { @@ -2174,7 +2221,8 @@ func TestInvertMap(t *testing.T) { > 将映射的值转换为布尔值 -示例代码: +**示例代码:** + ```go func ExampleConvertMapValuesToBool() { @@ -2222,7 +2270,8 @@ func TestConvertMapValuesToBool(t *testing.T) { > 将切片反转 -示例代码: +**示例代码:** + ```go func ExampleReverseSlice() { @@ -2275,7 +2324,8 @@ func TestReverseSlice(t *testing.T) { > 清空切片 -示例代码: +**示例代码:** + ```go func ExampleClearSlice() { @@ -2324,7 +2374,8 @@ func TestClearSlice(t *testing.T) { > 清空 map -示例代码: +**示例代码:** + ```go func ExampleClearMap() { @@ -2373,7 +2424,8 @@ func TestClearMap(t *testing.T) { > 删除切片中特定索引的元素 -示例代码: +**示例代码:** + ```go func ExampleDropSliceByIndices() { @@ -2424,7 +2476,8 @@ func TestDropSliceByIndices(t *testing.T) { > 删除切片中符合条件的元素 > - condition 的返回值为 true 时,将会删除该元素 -示例代码: +**示例代码:** + ```go func ExampleDropSliceByCondition() { @@ -2484,7 +2537,8 @@ func TestDropSliceByCondition(t *testing.T) { > 删除切片中与另一个切片重叠的元素 -示例代码: +**示例代码:** + ```go func ExampleDropSliceOverlappingElements() { @@ -2547,7 +2601,8 @@ func TestDropSliceOverlappingElements(t *testing.T) { > 去除切片中的重复元素 -示例代码: +**示例代码:** + ```go func ExampleDeduplicateSliceInPlace() { @@ -2597,7 +2652,8 @@ func TestDeduplicateSliceInPlace(t *testing.T) { > 去除切片中的重复元素,返回新切片 -示例代码: +**示例代码:** + ```go func ExampleDeduplicateSlice() { @@ -2646,7 +2702,8 @@ func TestDeduplicateSlice(t *testing.T) { > 去除切片中的重复元素,使用自定义的比较函数 -示例代码: +**示例代码:** + ```go func ExampleDeduplicateSliceInPlaceWithCompare() { @@ -2700,7 +2757,8 @@ func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) { > 去除切片中的重复元素,使用自定义的比较函数,返回新的切片 -示例代码: +**示例代码:** + ```go func ExampleDeduplicateSliceWithCompare() { @@ -2753,7 +2811,8 @@ func TestDeduplicateSliceWithCompare(t *testing.T) { > 过滤切片中特定索引的元素,返回过滤后的切片 -示例代码: +**示例代码:** + ```go func ExampleFilterOutByIndices() { @@ -2804,7 +2863,8 @@ func TestFilterOutByIndices(t *testing.T) { > 过滤切片中符合条件的元素,返回过滤后的切片 > - condition 的返回值为 true 时,将会过滤掉该元素 -示例代码: +**示例代码:** + ```go func ExampleFilterOutByCondition() { @@ -2862,7 +2922,8 @@ func TestFilterOutByCondition(t *testing.T) { > 过滤 map 中特定的 key,返回过滤后的 map -示例代码: +**示例代码:** + ```go func ExampleFilterOutByKey() { @@ -2912,7 +2973,8 @@ func TestFilterOutByKey(t *testing.T) { > 过滤 map 中特定的 value,返回过滤后的 map -示例代码: +**示例代码:** + ```go func ExampleFilterOutByValue() { @@ -2966,7 +3028,8 @@ func TestFilterOutByValue(t *testing.T) { > 过滤 map 中多个 key,返回过滤后的 map -示例代码: +**示例代码:** + ```go func ExampleFilterOutByKeys() { @@ -3016,7 +3079,8 @@ func TestFilterOutByKeys(t *testing.T) { > 过滤 map 中多个 values,返回过滤后的 map -示例代码: +**示例代码:** + ```go func ExampleFilterOutByValues() { @@ -3073,7 +3137,8 @@ func TestFilterOutByValues(t *testing.T) { > 过滤 map 中符合条件的元素,返回过滤后的 map > - condition 的返回值为 true 时,将会过滤掉该元素 -示例代码: +**示例代码:** + ```go func ExampleFilterOutByMap() { @@ -3128,7 +3193,8 @@ func TestFilterOutByMap(t *testing.T) { > 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始 > - 当 i 为负数时将返回第一个元素 -示例代码: +**示例代码:** + ```go func ExampleFindLoopedNextInSlice() { @@ -3173,7 +3239,8 @@ func TestFindLoopedNextInSlice(t *testing.T) { > 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始 > - 当 i 为负数时将返回最后一个元素 -示例代码: +**示例代码:** + ```go func ExampleFindLoopedPrevInSlice() { @@ -3217,7 +3284,8 @@ func TestFindLoopedPrevInSlice(t *testing.T) { > 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 -示例代码: +**示例代码:** + ```go func ExampleFindCombinationsInSliceByRange() { @@ -3262,7 +3330,8 @@ func TestFindCombinationsInSliceByRange(t *testing.T) { > 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值 -示例代码: +**示例代码:** + ```go func ExampleFindFirstOrDefaultInSlice() { @@ -3305,7 +3374,8 @@ func TestFindFirstOrDefaultInSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 -示例代码: +**示例代码:** + ```go func ExampleFindOrDefaultInSlice() { @@ -3352,7 +3422,8 @@ func TestFindOrDefaultInSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 -示例代码: +**示例代码:** + ```go func ExampleFindOrDefaultInComparableSlice() { @@ -3395,7 +3466,8 @@ func TestFindOrDefaultInComparableSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 -示例代码: +**示例代码:** + ```go func ExampleFindInSlice() { @@ -3442,7 +3514,8 @@ func TestFindInSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 -示例代码: +**示例代码:** + ```go func ExampleFindIndexInSlice() { @@ -3489,7 +3562,8 @@ func TestFindIndexInSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 -示例代码: +**示例代码:** + ```go func ExampleFindInComparableSlice() { @@ -3532,7 +3606,8 @@ func TestFindInComparableSlice(t *testing.T) { > 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 -示例代码: +**示例代码:** + ```go func ExampleFindIndexInComparableSlice() { @@ -3575,7 +3650,8 @@ func TestFindIndexInComparableSlice(t *testing.T) { > 获取切片中的最小值 -示例代码: +**示例代码:** + ```go func ExampleFindMinimumInComparableSlice() { @@ -3618,7 +3694,8 @@ func TestFindMinimumInComparableSlice(t *testing.T) { > 获取切片中的最小值 -示例代码: +**示例代码:** + ```go func ExampleFindMinimumInSlice() { @@ -3665,7 +3742,8 @@ func TestFindMinimumInSlice(t *testing.T) { > 获取切片中的最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMaximumInComparableSlice() { @@ -3708,7 +3786,8 @@ func TestFindMaximumInComparableSlice(t *testing.T) { > 获取切片中的最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMaximumInSlice() { @@ -3755,7 +3834,8 @@ func TestFindMaximumInSlice(t *testing.T) { > 获取切片中的最小值和最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMin2MaxInComparableSlice() { @@ -3799,7 +3879,8 @@ func TestFindMin2MaxInComparableSlice(t *testing.T) { > 获取切片中的最小值和最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMin2MaxInSlice() { @@ -3847,7 +3928,8 @@ func TestFindMin2MaxInSlice(t *testing.T) { > 获取 map 中的最小值 -示例代码: +**示例代码:** + ```go func ExampleFindMinFromComparableMap() { @@ -3890,7 +3972,8 @@ func TestFindMinFromComparableMap(t *testing.T) { > 获取 map 中的最小值 -示例代码: +**示例代码:** + ```go func ExampleFindMinFromMap() { @@ -3937,7 +4020,8 @@ func TestFindMinFromMap(t *testing.T) { > 获取 map 中的最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMaxFromComparableMap() { @@ -3980,7 +4064,8 @@ func TestFindMaxFromComparableMap(t *testing.T) { > 获取 map 中的最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMaxFromMap() { @@ -4027,7 +4112,8 @@ func TestFindMaxFromMap(t *testing.T) { > 获取 map 中的最小值和最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMin2MaxFromComparableMap() { @@ -4071,7 +4157,8 @@ func TestFindMin2MaxFromComparableMap(t *testing.T) { > 获取 map 中的最小值和最大值 -示例代码: +**示例代码:** + ```go func ExampleFindMin2MaxFromMap() { @@ -4115,7 +4202,8 @@ func TestFindMin2MaxFromMap(t *testing.T) { > 将切片中的两个元素进行交换 -示例代码: +**示例代码:** + ```go func ExampleSwapSlice() { @@ -4163,7 +4251,8 @@ func TestSwapSlice(t *testing.T) { > 将切片中的元素进行转换 -示例代码: +**示例代码:** + ```go func ExampleMappingFromSlice() { @@ -4215,7 +4304,8 @@ func TestMappingFromSlice(t *testing.T) { > 将 map 中的元素进行转换 -示例代码: +**示例代码:** + ```go func ExampleMappingFromMap() { @@ -4267,7 +4357,8 @@ func TestMappingFromMap(t *testing.T) { > 合并切片 -示例代码: +**示例代码:** + ```go func ExampleMergeSlices() { @@ -4314,7 +4405,8 @@ func TestMergeSlices(t *testing.T) { > 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key -示例代码: +**示例代码:** + ```go func ExampleMergeMaps() { @@ -4357,7 +4449,8 @@ func TestMergeMaps(t *testing.T) { > 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会被跳过 -示例代码: +**示例代码:** + ```go func ExampleMergeMapsWithSkip() { @@ -4401,7 +4494,8 @@ func TestMergeMapsWithSkip(t *testing.T) { > 返回 slice 中的 n 个可重复随机元素 > - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil -示例代码: +**示例代码:** + ```go func ExampleChooseRandomSliceElementRepeatN() { @@ -4445,7 +4539,8 @@ func TestChooseRandomSliceElementRepeatN(t *testing.T) { > 返回 slice 中的 n 个可重复随机元素的索引 > - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil -示例代码: +**示例代码:** + ```go func ExampleChooseRandomIndexRepeatN() { @@ -4488,7 +4583,8 @@ func TestChooseRandomIndexRepeatN(t *testing.T) { > 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值 -示例代码: +**示例代码:** + ```go func ExampleChooseRandomSliceElement() { @@ -4531,7 +4627,8 @@ func TestChooseRandomSliceElement(t *testing.T) { > 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1 -示例代码: +**示例代码:** + ```go func ExampleChooseRandomIndex() { @@ -4575,7 +4672,8 @@ func TestChooseRandomIndex(t *testing.T) { > 返回 slice 中的 n 个不可重复的随机元素 > - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomSliceElementN() { @@ -4618,7 +4716,8 @@ func TestChooseRandomSliceElementN(t *testing.T) { > 获取切片中的 n 个随机元素的索引 > - 如果 n 大于切片长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomIndexN() { @@ -4662,7 +4761,8 @@ func TestChooseRandomIndexN(t *testing.T) { > 获取 map 中的 n 个随机 key,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKeyRepeatN() { @@ -4706,7 +4806,8 @@ func TestChooseRandomMapKeyRepeatN(t *testing.T) { > 获取 map 中的 n 个随机 n,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapValueRepeatN() { @@ -4750,7 +4851,8 @@ func TestChooseRandomMapValueRepeatN(t *testing.T) { > 获取 map 中的 n 个随机 key 和 v,允许重复 > - 如果 n 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKeyAndValueRepeatN() { @@ -4793,7 +4895,8 @@ func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) { > 获取 map 中的随机 key -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKey() { @@ -4836,7 +4939,8 @@ func TestChooseRandomMapKey(t *testing.T) { > 获取 map 中的随机 value -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapValue() { @@ -4880,7 +4984,8 @@ func TestChooseRandomMapValue(t *testing.T) { > 获取 map 中的 inputN 个随机 key > - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKeyN() { @@ -4896,7 +5001,8 @@ func ExampleChooseRandomMapKeyN() { > 获取 map 中的 n 个随机 value > - 如果 n 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapValueN() { @@ -4939,7 +5045,8 @@ func TestChooseRandomMapValueN(t *testing.T) { > 获取 map 中的随机 key 和 v -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKeyAndValue() { @@ -4983,7 +5090,8 @@ func TestChooseRandomMapKeyAndValue(t *testing.T) { > 获取 map 中的 inputN 个随机 key 和 v > - 如果 n 大于 map 长度或小于 0 时将会发生 panic -示例代码: +**示例代码:** + ```go func ExampleChooseRandomMapKeyAndValueN() { @@ -5028,7 +5136,8 @@ func TestChooseRandomMapKeyAndValueN(t *testing.T) { > 返回降序比较结果 -示例代码: +**示例代码:** + ```go func ExampleDescBy() { @@ -5078,7 +5187,8 @@ func TestDescBy(t *testing.T) { > 返回升序比较结果 -示例代码: +**示例代码:** + ```go func ExampleAscBy() { @@ -5128,7 +5238,8 @@ func TestAscBy(t *testing.T) { > 对切片进行降序排序 -示例代码: +**示例代码:** + ```go func ExampleDesc() { @@ -5178,7 +5289,8 @@ func TestDesc(t *testing.T) { > 对切片进行降序排序,返回排序后的切片 -示例代码: +**示例代码:** + ```go func ExampleDescByClone() { @@ -5228,7 +5340,8 @@ func TestDescByClone(t *testing.T) { > 对切片进行升序排序 -示例代码: +**示例代码:** + ```go func ExampleAsc() { @@ -5278,7 +5391,8 @@ func TestAsc(t *testing.T) { > 对切片进行升序排序,返回排序后的切片 -示例代码: +**示例代码:** + ```go func ExampleAscByClone() { @@ -5328,7 +5442,8 @@ func TestAscByClone(t *testing.T) { > 对切片进行随机排序 -示例代码: +**示例代码:** + ```go func ExampleShuffle() { @@ -5372,7 +5487,8 @@ func TestShuffle(t *testing.T) { > 对切片进行随机排序,返回排序后的切片 -示例代码: +**示例代码:** + ```go func ExampleShuffleByClone() { diff --git a/utils/collection/clone.go b/utils/collection/clone.go index 44400ce3..d648b810 100644 --- a/utils/collection/clone.go +++ b/utils/collection/clone.go @@ -23,7 +23,8 @@ func CloneMap[M ~map[K]V, K comparable, V any](m M) M { return result } -// CloneSliceN 克隆 slice 为 n 个切片进行返回 +// CloneSliceN 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片为 n 个切片 +// - 当 slice 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片 func CloneSliceN[S ~[]V, V any](slice S, n int) []S { if slice == nil { return nil diff --git a/utils/collection/clone_example_test.go b/utils/collection/clone_example_test.go index e5e00ed9..9760ac01 100644 --- a/utils/collection/clone_example_test.go +++ b/utils/collection/clone_example_test.go @@ -5,6 +5,8 @@ import ( "github.com/kercylan98/minotaur/utils/collection" ) +// 在该示例中,将 slice 克隆后将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +// - 示例中的结果将会输出 [1 2 3] func ExampleCloneSlice() { var slice = []int{1, 2, 3} var result = collection.CloneSlice(slice) @@ -13,6 +15,8 @@ func ExampleCloneSlice() { // [1 2 3] } +// 在该示例中,将 map 克隆后将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +// - 示例中的结果将会输出 3 func ExampleCloneMap() { var m = map[int]int{1: 1, 2: 2, 3: 3} var result = collection.CloneMap(m) @@ -21,6 +25,9 @@ func ExampleCloneMap() { // 3 } +// 通过将 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +// - result 的结果为 [[1 2 3] [1 2 3]] +// - 示例中的结果将会输出 2 func ExampleCloneSliceN() { var slice = []int{1, 2, 3} var result = collection.CloneSliceN(slice, 2) diff --git a/utils/generator/astgo/function.go b/utils/generator/astgo/function.go index feb97998..40bf9e7b 100644 --- a/utils/generator/astgo/function.go +++ b/utils/generator/astgo/function.go @@ -56,6 +56,9 @@ type Function struct { } func (f *Function) Code() string { + if f.Test { + f.decl.Doc = nil + } var bb bytes.Buffer if err := format.Node(&bb, token.NewFileSet(), f.decl); err != nil { panic(err) diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index c2061c2f..435c5adf 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -191,15 +191,34 @@ func (b *Builder) genStructs() { } b.newLine() if example := b.p.GetExampleTest(function); example != nil { - b.newLine("示例代码:", "```go\n", example.Code(), "```\n") + b.newLine("**示例代码:**").newLine() + if len(example.Comments.Clear) > 0 { + for _, s := range example.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } + b.newLine("```go\n", example.Code(), "```\n") } if unitTest := b.p.GetUnitTest(function); unitTest != nil { b.detailsStart("查看 / 收起单元测试") + if len(unitTest.Comments.Clear) > 0 { + for _, s := range unitTest.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } b.newLine("```go\n", unitTest.Code(), "```\n") b.detailsEnd() } if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil { b.detailsStart("查看 / 收起基准测试") + if len(benchmarkTest.Comments.Clear) > 0 { + for _, s := range benchmarkTest.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } b.newLine("```go\n", benchmarkTest.Code(), "```\n") b.detailsEnd() } @@ -293,15 +312,34 @@ func (b *Builder) genStructs() { b.quote(comment) } if example := b.p.GetExampleTest(function); example != nil { - b.newLine("示例代码:", "```go\n", example.Code(), "```\n") + b.newLine("**示例代码:**").newLine() + if len(example.Comments.Clear) > 0 { + for _, s := range example.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } + b.newLine("```go\n", example.Code(), "```\n") } if unitTest := b.p.GetUnitTest(function); unitTest != nil { b.detailsStart("查看 / 收起单元测试") + if len(unitTest.Comments.Clear) > 0 { + for _, s := range unitTest.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } b.newLine("```go\n", unitTest.Code(), "```\n") b.detailsEnd() } if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil { b.detailsStart("查看 / 收起基准测试") + if len(benchmarkTest.Comments.Clear) > 0 { + for _, s := range benchmarkTest.Comments.Clear { + b.newLine(fmt.Sprintf("%s", s)) + } + b.newLine().newLine() + } b.newLine("```go\n", benchmarkTest.Code(), "```\n") b.detailsEnd() } diff --git a/utils/geometry/README.md b/utils/geometry/README.md index 238a8bb7..9dc4d81c 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -136,7 +136,8 @@ geometry 旨在提供一组用于处理几何形状和计算几何属性的函 > 通过传入圆的半径和需要的点数量,生成一个圆 -示例代码: +**示例代码:** + ```go func ExampleNewCircle() { @@ -480,7 +481,8 @@ func TestNewPoint(t *testing.T) { > 通过传入的一组坐标 points 计算一个图形覆盖的矩形范围 -示例代码: +**示例代码:** + ```go func ExampleGetShapeCoverageAreaWithPoint() { @@ -525,7 +527,8 @@ func TestGetShapeCoverageAreaWithPoint(t *testing.T) { > 通过传入的一组坐标 positions 计算一个图形覆盖的矩形范围 -示例代码: +**示例代码:** + ```go func ExampleGetShapeCoverageAreaWithPos() { @@ -563,7 +566,8 @@ func TestGetShapeCoverageAreaWithPos(t *testing.T) { > 将一个图形覆盖矩形范围设置为无边的 > - 无边化表示会将多余的部分进行裁剪,例如图形左边从 2 开始的时候,那么左边将会被裁剪到从 0 开始 -示例代码: +**示例代码:** + ```go func ExampleCoverageAreaBoundless() { @@ -695,7 +699,8 @@ func TestGenerateShapeOnRectangle(t *testing.T) { > 通过多个点生成一个形状进行返回 -示例代码: +**示例代码:** + ```go func ExampleNewShape() { @@ -738,7 +743,8 @@ func TestNewShape(t *testing.T) { > 通过字符串将指定 rune 转换为点位置生成形状进行返回 > - 每个点的顺序从上到下,从左到右 -示例代码: +**示例代码:** + ```go func ExampleNewShapeWithString() { @@ -1012,7 +1018,8 @@ type Shape[V generic.SignedNumber] []Point[V] ``` #### func (Shape) Points() []Point[V] > 获取这个形状的所有点 -示例代码: +**示例代码:** + ```go func ExampleShape_Points() { @@ -1049,7 +1056,8 @@ func TestShape_Points(t *testing.T) { *** #### func (Shape) PointCount() int > 获取这个形状的点数量 -示例代码: +**示例代码:** + ```go func ExampleShape_PointCount() { @@ -1088,7 +1096,8 @@ func TestShape_PointCount(t *testing.T) { *** #### func (Shape) String() string > 将该形状转换为可视化的字符串进行返回 -示例代码: +**示例代码:** + ```go func ExampleShape_String() { @@ -1126,7 +1135,8 @@ func TestShape_String(t *testing.T) { > - 返回的坐标为原始形状的坐标 > > 可通过可选项对搜索结果进行过滤 -示例代码: +**示例代码:** + ```go func ExampleShape_ShapeSearch() { diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md index b44daf6e..63809a3b 100644 --- a/utils/geometry/astar/README.md +++ b/utils/geometry/astar/README.md @@ -58,7 +58,8 @@ astar 提供用于实现 A* 算法的函数和数据结构。A* 算法是一种 > - 函数内部使用了堆数据结构来管理待处理的节点。 > - 函数返回一个节点序列,表示从起点到终点的最短路径。如果找不到路径,则返回空序列。 -示例代码: +**示例代码:** + ```go func ExampleFind() { diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md index 04d006e5..af138da1 100644 --- a/utils/geometry/navmesh/README.md +++ b/utils/geometry/navmesh/README.md @@ -99,7 +99,8 @@ type NavMesh[V generic.SignedNumber] struct { > - 如果起点或终点不在任何形状内部,且 NavMesh 的 meshShrinkAmount 大于0,则会考虑缩小的形状。 > - 使用 A* 算法在 NavMesh 上搜索从起点形状到终点形状的最短路径。 > - 使用漏斗算法对路径进行优化,以得到最终的路径点序列。 -示例代码: +**示例代码:** + ```go func ExampleNavMesh_FindPath() { diff --git a/utils/hub/README.md b/utils/hub/README.md index 63d2ed74..e5182027 100644 --- a/utils/hub/README.md +++ b/utils/hub/README.md @@ -34,7 +34,8 @@ > 创建一个 ObjectPool -示例代码: +**示例代码:** + ```go func ExampleNewObjectPool() { diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index 3cd06c16..d42bd9af 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -38,7 +38,8 @@ > 创建一个基于内存的二分查找排行榜 -示例代码: +**示例代码:** + ```go func ExampleNewBinarySearch() { diff --git a/utils/maths/README.md b/utils/maths/README.md index 0b498480..db1cf2ee 100644 --- a/utils/maths/README.md +++ b/utils/maths/README.md @@ -61,7 +61,8 @@ > 检查一组值是否连续 -示例代码: +**示例代码:** + ```go func ExampleIsContinuity() { @@ -147,7 +148,8 @@ func ExampleIsContinuity() { > 将一组非连续的数字转换为从1开始的连续数字 > - 返回值是一个 map,key 是从 1 开始的连续数字,value 是原始数字 -示例代码: +**示例代码:** + ```go func ExampleToContinuous() { diff --git a/utils/moving/README.md b/utils/moving/README.md index 5f88634b..61111dfb 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -41,7 +41,8 @@ > 创建一个用于2D对象移动的实例(TwoDimensional) -示例代码: +**示例代码:** + ```go func ExampleNewTwoDimensional() { diff --git a/utils/sorts/README.md b/utils/sorts/README.md index 29f84045..57fc082b 100644 --- a/utils/sorts/README.md +++ b/utils/sorts/README.md @@ -34,7 +34,8 @@ > > 该函数在存在循环依赖的情况下将会返回 ErrCircularDependencyDetected 错误 -示例代码: +**示例代码:** + ```go func ExampleTopological() { diff --git a/utils/super/README.md b/utils/super/README.md index 8c803094..6797a26d 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -159,7 +159,8 @@ func TestGetError(t *testing.T) { > recover 错误转换 -示例代码: +**示例代码:** + ```go func ExampleRecoverTransform() { @@ -595,7 +596,8 @@ func TestConvert(t *testing.T) { > 对特定表达式进行校验,当表达式不成立时,将执行 handle -示例代码: +**示例代码:** + ```go func ExampleVerify() { @@ -622,7 +624,8 @@ func ExampleVerify() { > 检查 version2 对于 version1 来说是不是旧版本 -示例代码: +**示例代码:** + ```go func ExampleOldVersion() { @@ -684,7 +687,8 @@ func BenchmarkOldVersion(b *testing.B) { > - 如果 version1 小于 version2,它将返回 -1 > - 如果 version1 和 version2 相等,它将返回 0 -示例代码: +**示例代码:** + ```go func ExampleCompareVersion() { diff --git a/utils/times/README.md b/utils/times/README.md index 033d92fa..091682e4 100644 --- a/utils/times/README.md +++ b/utils/times/README.md @@ -87,7 +87,8 @@ > 计算下一个N秒在多少秒之后 -示例代码: +**示例代码:** + ```go func ExampleCalcNextSecWithTime() { From cb340da0e5c9b884b86d459e6379cc0a0b146a50 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 11:15:51 +0800 Subject: [PATCH 05/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/collection/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/utils/collection/README.md b/utils/collection/README.md index 6b59b43b..6927fe5c 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -202,6 +202,10 @@ func TestCloneSlice(t *testing.T) { **示例代码:** +map 克隆后将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 + - 示例中的结果将会输出 3 + + ```go func ExampleCloneMap() { @@ -253,6 +257,11 @@ func TestCloneMap(t *testing.T) { **示例代码:** +slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 + - result 的结果为 [[1 2 3] [1 2 3]] + - 示例中的结果将会输出 2 + + ```go func ExampleCloneSliceN() { From 5ea32027320e24b8d036a8d5529ab51f5aef2f4b Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 11:26:30 +0800 Subject: [PATCH 06/21] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=20collection?= =?UTF-8?q?=20=E5=8C=85=E9=83=A8=E5=88=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/collection/README.md | 39 +++++++++++++++++++------- utils/collection/clone.go | 11 ++++++-- utils/collection/clone_example_test.go | 7 +++++ utils/collection/collection.go | 4 +++ 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/utils/collection/README.md b/utils/collection/README.md index 6927fe5c..355a9b8b 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -19,9 +19,9 @@ collection 定义了各种对于集合操作有用的各种函数 |[CloneSlice](#CloneSlice)|通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片 |[CloneMap](#CloneMap)|通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map |[CloneSliceN](#CloneSliceN)|通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片为 n 个切片 -|[CloneMapN](#CloneMapN)|克隆 map 为 n 个 map 进行返回 -|[CloneSlices](#CloneSlices)|克隆多个切片 -|[CloneMaps](#CloneMaps)|克隆多个 map +|[CloneMapN](#CloneMapN)|通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map 为 n 个 map +|[CloneSlices](#CloneSlices)|对 slices 中的每一项元素进行克隆,最终返回一个新的二维切片 +|[CloneMaps](#CloneMaps)|对 maps 中的每一项元素进行克隆,最终返回一个新的 map 切片 |[InSlice](#InSlice)|检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 |[InComparableSlice](#InComparableSlice)|检查 v 是否被包含在 slice 中 |[AllInSlice](#AllInSlice)|检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 @@ -133,8 +133,8 @@ collection 定义了各种对于集合操作有用的各种函数 |类型|名称|描述 |:--|:--|:-- -|`STRUCT`|[ComparisonHandler](#struct_ComparisonHandler)|暂无描述... -|`STRUCT`|[OrderedValueGetter](#struct_OrderedValueGetter)|暂无描述... +|`STRUCT`|[ComparisonHandler](#struct_ComparisonHandler)|用于比较 `source` 和 `target` 两个值是否相同的比较函数 +|`STRUCT`|[OrderedValueGetter](#struct_OrderedValueGetter)|用于获取 v 的可排序字段值的函数
@@ -314,10 +314,16 @@ func TestCloneSliceN(t *testing.T) { *** #### func CloneMapN\[M ~map[K]V, K comparable, V any\](m M, n int) []M -> 克隆 map 为 n 个 map 进行返回 +> 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map 为 n 个 map +> - 当 m 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片 **示例代码:** +map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 + - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` + - 示例中的结果将会输出 2 + + ```go func ExampleCloneMapN() { @@ -370,10 +376,16 @@ func TestCloneMapN(t *testing.T) { *** #### func CloneSlices\[S ~[]V, V any\](slices ...S) []S -> 克隆多个切片 +> 对 slices 中的每一项元素进行克隆,最终返回一个新的二维切片 +> - 当 slices 为空时,将会返回 nil +> - 该函数相当于使用 CloneSlice 函数一次性对多个切片进行克隆 **示例代码:** +slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 + - result 的结果为 [[1 2 3] [1 2 3]] + + ```go func ExampleCloneSlices() { @@ -423,10 +435,16 @@ func TestCloneSlices(t *testing.T) { *** #### func CloneMaps\[M ~map[K]V, K comparable, V any\](maps ...M) []M -> 克隆多个 map +> 对 maps 中的每一项元素进行克隆,最终返回一个新的 map 切片 +> - 当 maps 为空时,将会返回 nil +> - 该函数相当于使用 CloneMap 函数一次性对多个 map 进行克隆 **示例代码:** +map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 + - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` + + ```go func ExampleCloneMaps() { @@ -5539,13 +5557,14 @@ func TestShuffleByClone(t *testing.T) { *** ### ComparisonHandler `STRUCT` - +用于比较 `source` 和 `target` 两个值是否相同的比较函数 + - 该函数接受两个参数,分别是源值和目标值,返回 true 的情况下即表示两者相同 ```go type ComparisonHandler[V any] func(source V) bool ``` ### OrderedValueGetter `STRUCT` - +用于获取 v 的可排序字段值的函数 ```go type OrderedValueGetter[V any, N generic.Ordered] func(v V) N ``` diff --git a/utils/collection/clone.go b/utils/collection/clone.go index d648b810..6f1e0ed7 100644 --- a/utils/collection/clone.go +++ b/utils/collection/clone.go @@ -40,7 +40,8 @@ func CloneSliceN[S ~[]V, V any](slice S, n int) []S { return result } -// CloneMapN 克隆 map 为 n 个 map 进行返回 +// CloneMapN 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map 为 n 个 map +// - 当 m 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片 func CloneMapN[M ~map[K]V, K comparable, V any](m M, n int) []M { if m == nil { return nil @@ -57,7 +58,9 @@ func CloneMapN[M ~map[K]V, K comparable, V any](m M, n int) []M { return result } -// CloneSlices 克隆多个切片 +// CloneSlices 对 slices 中的每一项元素进行克隆,最终返回一个新的二维切片 +// - 当 slices 为空时,将会返回 nil +// - 该函数相当于使用 CloneSlice 函数一次性对多个切片进行克隆 func CloneSlices[S ~[]V, V any](slices ...S) []S { if slices == nil { return nil @@ -70,7 +73,9 @@ func CloneSlices[S ~[]V, V any](slices ...S) []S { return result } -// CloneMaps 克隆多个 map +// CloneMaps 对 maps 中的每一项元素进行克隆,最终返回一个新的 map 切片 +// - 当 maps 为空时,将会返回 nil +// - 该函数相当于使用 CloneMap 函数一次性对多个 map 进行克隆 func CloneMaps[M ~map[K]V, K comparable, V any](maps ...M) []M { if maps == nil { return nil diff --git a/utils/collection/clone_example_test.go b/utils/collection/clone_example_test.go index 9760ac01..7fe9b836 100644 --- a/utils/collection/clone_example_test.go +++ b/utils/collection/clone_example_test.go @@ -36,6 +36,9 @@ func ExampleCloneSliceN() { // 2 } +// 通过将 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +// - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` +// - 示例中的结果将会输出 2 func ExampleCloneMapN() { var m = map[int]int{1: 1, 2: 2, 3: 3} var result = collection.CloneMapN(m, 2) @@ -44,6 +47,8 @@ func ExampleCloneMapN() { // 2 } +// 通过将多个 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +// - result 的结果为 [[1 2 3] [1 2 3]] func ExampleCloneSlices() { var slice1 = []int{1, 2, 3} var slice2 = []int{1, 2, 3} @@ -53,6 +58,8 @@ func ExampleCloneSlices() { // 2 } +// 通过将多个 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +// - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` func ExampleCloneMaps() { var m1 = map[int]int{1: 1, 2: 2, 3: 3} var m2 = map[int]int{1: 1, 2: 2, 3: 3} diff --git a/utils/collection/collection.go b/utils/collection/collection.go index 69ec6ed9..519b27d3 100644 --- a/utils/collection/collection.go +++ b/utils/collection/collection.go @@ -2,5 +2,9 @@ package collection import "github.com/kercylan98/minotaur/utils/generic" +// ComparisonHandler 用于比较 `source` 和 `target` 两个值是否相同的比较函数 +// - 该函数接受两个参数,分别是源值和目标值,返回 true 的情况下即表示两者相同 type ComparisonHandler[V any] func(source, target V) bool + +// OrderedValueGetter 用于获取 v 的可排序字段值的函数 type OrderedValueGetter[V any, N generic.Ordered] func(v V) N From 6e6f33899b791c585f70c1b8bcca5c8e619f0cea Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 11:46:08 +0800 Subject: [PATCH 07/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=E6=B3=9B?= =?UTF-8?q?=E5=9E=8B=E7=BB=93=E6=9E=84=E4=BD=93=E5=87=BD=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/README.md | 26 + game/fight/README.md | 85 +++ game/space/README.md | 184 +++++++ server/internal/dispatcher/README.md | 654 +++++++++++++++++++++++ server/internal/dispatcher/dispatcher.go | 4 +- server/lockstep/README.md | 53 ++ utils/aoi/README.md | 12 + utils/arrangement/README.md | 120 +++++ utils/collection/mappings/README.md | 52 ++ utils/fsm/README.md | 27 + utils/generator/astgo/name.go | 11 +- utils/geometry/README.md | 5 + utils/leaderboard/README.md | 101 ++++ utils/moving/README.md | 118 ++++ utils/super/README.md | 18 + 15 files changed, 1464 insertions(+), 6 deletions(-) diff --git a/game/activity/README.md b/game/activity/README.md index 0147d77b..17275230 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -201,6 +201,32 @@ type Controller[Type generic.Basic, ID generic.Basic, Data any, EntityID generic mutex sync.RWMutex } ``` +#### func (*Controller) GetGlobalData(activityId ID) Data +> 获取特定活动全局数据 +*** +#### func (*Controller) GetEntityData(activityId ID, entityId EntityID) EntityData +> 获取特定活动实体数据 +*** +#### func (*Controller) IsOpen(activityId ID) bool +> 活动是否开启 +*** +#### func (*Controller) IsShow(activityId ID) bool +> 活动是否展示 +*** +#### func (*Controller) IsOpenOrShow(activityId ID) bool +> 活动是否开启或展示 +*** +#### func (*Controller) Refresh(activityId ID) +> 刷新活动 +*** +#### func (*Controller) InitializeNoneData(handler func (activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData] +*** +#### func (*Controller) InitializeGlobalData(handler func (activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData] +*** +#### func (*Controller) InitializeEntityData(handler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData] +*** +#### func (*Controller) InitializeGlobalAndEntityData(handler func (activityId ID, data *DataMeta[Data]), entityHandler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] +*** ### BasicActivityController `INTERFACE` diff --git a/game/fight/README.md b/game/fight/README.md index 14ceab14..fa9683dd 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -65,6 +65,63 @@ type TurnBased[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], closed bool } ``` +#### func (*TurnBased) Close() +> 关闭回合制 +*** +#### func (*TurnBased) AddCamp(camp Camp, entity Entity, entities ...Entity) +> 添加阵营 +*** +#### func (*TurnBased) SetActionTimeout(actionTimeoutHandler func ( Camp, Entity) time.Duration) +> 设置行动超时时间处理函数 +> - 默认情况下行动超时时间函数将始终返回 0 +*** +#### func (*TurnBased) Run() +> 运行 +
+查看 / 收起单元测试 + + +```go + +func TestTurnBased_Run(t *testing.T) { + tbi := fight.NewTurnBased[string, string, *Camp, *Entity](func(camp *Camp, entity *Entity) time.Duration { + return time.Duration(float64(time.Second) / entity.speed) + }) + tbi.SetActionTimeout(func(camp *Camp, entity *Entity) time.Duration { + return time.Second * 5 + }) + tbi.RegTurnBasedEntityActionTimeoutEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) { + t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "超时") + }) + tbi.RegTurnBasedRoundChangeEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) { + t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "回合切换") + }) + tbi.RegTurnBasedEntitySwitchEvent(func(controller fight.TurnBasedControllerAction[string, string, *Camp, *Entity]) { + switch controller.GetEntity().GetId() { + case "1": + go func() { + time.Sleep(time.Second * 2) + controller.Finish() + }() + case "2": + controller.Refresh(time.Second) + case "4": + controller.Stop() + } + t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "开始行动") + }) + tbi.AddCamp(&Camp{id: "1"}, &Entity{id: "1", speed: 1}, &Entity{id: "2", speed: 1}) + tbi.AddCamp(&Camp{id: "2"}, &Entity{id: "3", speed: 1}, &Entity{id: "4", speed: 1}) + tbi.Run() +} + +``` + + +
+ + +*** ### TurnBasedControllerInfo `INTERFACE` @@ -97,6 +154,34 @@ type TurnBasedController[CampID comparable, EntityID comparable, Camp generic.Id tb *TurnBased[CampID, EntityID, Camp, Entity] } ``` +#### func (*TurnBasedController) GetRound() int +> 获取当前回合数 +*** +#### func (*TurnBasedController) GetCamp() Camp +> 获取当前操作阵营 +*** +#### func (*TurnBasedController) GetEntity() Entity +> 获取当前操作实体 +*** +#### func (*TurnBasedController) GetActionTimeoutDuration() time.Duration +> 获取当前行动超时时长 +*** +#### func (*TurnBasedController) GetActionStartTime() time.Time +> 获取当前行动开始时间 +*** +#### func (*TurnBasedController) GetActionEndTime() time.Time +> 获取当前行动结束时间 +*** +#### func (*TurnBasedController) Finish() +> 结束当前操作,将立即切换到下一个操作实体 +*** +#### func (*TurnBasedController) Stop() +> 在当前回合执行完毕后停止回合进程 +*** +#### func (*TurnBasedController) Refresh(duration time.Duration) time.Time +> 刷新当前操作实体的行动超时时间 +> - 当不在行动阶段时,将返回 time.Time 零值 +*** ### TurnBasedEntitySwitchEventHandler `STRUCT` diff --git a/game/space/README.md b/game/space/README.md index cb4d1528..e93b2f70 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -70,6 +70,132 @@ type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[E owner *EntityID } ``` +#### func (*RoomController) HasOwner() bool +> 判断是否有房主 +*** +#### func (*RoomController) IsOwner(entityId EntityID) bool +> 判断是否为房主 +*** +#### func (*RoomController) GetOwner() Entity +> 获取房主 +*** +#### func (*RoomController) GetOwnerID() EntityID +> 获取房主 ID +*** +#### func (*RoomController) GetOwnerExist() ( Entity, bool) +> 获取房间,并返回房主是否存在的状态 +*** +#### func (*RoomController) SetOwner(entityId EntityID) +> 设置房主 +*** +#### func (*RoomController) DelOwner() +> 删除房主,将房间设置为无主的状态 +*** +#### func (*RoomController) JoinSeat(entityId EntityID, seat ...int) error +> 设置特定对象加入座位,当具体的座位不存在的时候,将会自动分配座位 +> - 当目标座位存在玩家或未添加到房间中的时候,将会返回错误 +*** +#### func (*RoomController) LeaveSeat(entityId EntityID) +> 离开座位 +*** +#### func (*RoomController) GetSeat(entityId EntityID) int +> 获取座位 +*** +#### func (*RoomController) GetFirstNotEmptySeat() int +> 获取第一个非空座位号,如果没有非空座位,将返回 UnknownSeat +*** +#### func (*RoomController) GetFirstEmptySeatEntity() (entity Entity) +> 获取第一个空座位上的实体,如果没有空座位,将返回空实体 +*** +#### func (*RoomController) GetRandomEntity() (entity Entity) +> 获取随机实体,如果房间中没有实体,将返回空实体 +*** +#### func (*RoomController) GetNotEmptySeat() []int +> 获取非空座位 +*** +#### func (*RoomController) GetEmptySeat() []int +> 获取空座位 +> - 空座位需要在有对象离开座位后才可能出现 +*** +#### func (*RoomController) HasSeat(entityId EntityID) bool +> 判断是否有座位 +*** +#### func (*RoomController) GetSeatEntityCount() int +> 获取座位上的实体数量 +*** +#### func (*RoomController) GetSeatEntities() map[EntityID]Entity +> 获取座位上的实体 +*** +#### func (*RoomController) GetSeatEntitiesByOrdered() []Entity +> 有序的获取座位上的实体 +*** +#### func (*RoomController) GetSeatEntitiesByOrderedAndContainsEmpty() []Entity +> 获取有序的座位上的实体,包含空座位 +*** +#### func (*RoomController) GetSeatEntity(seat int) (entity Entity) +> 获取座位上的实体 +*** +#### func (*RoomController) ContainEntity(id EntityID) bool +> 房间内是否包含实体 +*** +#### func (*RoomController) GetRoom() Room +> 获取原始房间实例,该实例为被接管的房间的原始实例 +*** +#### func (*RoomController) GetEntities() map[EntityID]Entity +> 获取所有实体 +*** +#### func (*RoomController) HasEntity(id EntityID) bool +> 判断是否有实体 +*** +#### func (*RoomController) GetEntity(id EntityID) Entity +> 获取实体 +*** +#### func (*RoomController) GetEntityExist(id EntityID) ( Entity, bool) +> 获取实体,并返回实体是否存在的状态 +*** +#### func (*RoomController) GetEntityIDs() []EntityID +> 获取所有实体ID +*** +#### func (*RoomController) GetEntityCount() int +> 获取实体数量 +*** +#### func (*RoomController) ChangePassword(password *string) +> 修改房间密码 +> - 当房间密码为 nil 时,将会取消密码 +*** +#### func (*RoomController) AddEntity(entity Entity) error +> 添加实体,如果房间存在密码,应使用 AddEntityByPassword 函数进行添加,否则将始终返回 ErrRoomPasswordNotMatch 错误 +> - 当房间已满时,将会返回 ErrRoomFull 错误 +*** +#### func (*RoomController) AddEntityByPassword(entity Entity, password string) error +> 通过房间密码添加实体到该房间中 +> - 当未设置房间密码时,password 参数将会被忽略 +> - 当房间密码不匹配时,将会返回 ErrRoomPasswordNotMatch 错误 +> - 当房间已满时,将会返回 ErrRoomFull 错误 +*** +#### func (*RoomController) RemoveEntity(id EntityID) +> 移除实体 +> - 当实体被移除时如果实体在座位上,将会自动离开座位 +> - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承 +*** +#### func (*RoomController) RemoveAllEntities() +> 移除该房间中的所有实体 +> - 当实体被移除时如果实体在座位上,将会自动离开座位 +> - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承 +*** +#### func (*RoomController) Destroy() +> 销毁房间,房间会从 RoomManager 中移除,同时所有房间的实体、座位等数据都会被清空 +> - 该函数与 RoomManager.DestroyRoom 相同,RoomManager.DestroyRoom 函数为该函数的快捷方式 +*** +#### func (*RoomController) GetRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] +> 获取该房间控制器所属的房间管理器 +*** +#### func (*RoomController) GetRoomID() RoomID +> 获取房间 ID +*** +#### func (*RoomController) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool) +> 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理 +*** ### RoomManager `STRUCT` 房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 @@ -81,6 +207,53 @@ type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[Enti rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] } ``` +#### func (*RoomManager) AssumeControl(room Room, options ...*RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomController[EntityID, RoomID, Entity, Room] +> 将房间控制权交由 RoomManager 接管,返回 RoomController 实例 +> - 当任何房间需要被 RoomManager 管理时,都应该调用该方法获取到 RoomController 实例后进行操作 +> - 房间被接管后需要在释放房间控制权时调用 RoomController.Destroy 方法,否则将会导致 RoomManager 一直持有房间资源 +**示例代码:** + +```go + +func ExampleRoomManager_AssumeControl() { + var rm = space.NewRoomManager[string, int64, *Player, *Room]() + var room = &Room{Id: 1} + var controller = rm.AssumeControl(room) + if err := controller.AddEntity(&Player{Id: "1"}); err != nil { + panic(err) + } + fmt.Println(controller.GetEntityCount()) +} + +``` + +*** +#### func (*RoomManager) DestroyRoom(id RoomID) +> 销毁房间,该函数为 RoomController.Destroy 的快捷方式 +*** +#### func (*RoomManager) GetRoom(id RoomID) *RoomController[EntityID, RoomID, Entity, Room] +> 通过房间 ID 获取对应房间的控制器 RoomController,当房间不存在时将返回 nil +*** +#### func (*RoomManager) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] +> 获取包含所有房间 ID 到对应控制器 RoomController 的映射 +> - 返回值的 map 为拷贝对象,可安全的对其进行增删等操作 +*** +#### func (*RoomManager) GetRoomCount() int +> 获取房间管理器接管的房间数量 +*** +#### func (*RoomManager) GetRoomIDs() []RoomID +> 获取房间管理器接管的所有房间 ID +*** +#### func (*RoomManager) HasEntity(entityId EntityID) bool +> 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false +*** +#### func (*RoomManager) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] +> 获取特定对象所在的房间,返回值为房间 ID 到对应控制器 RoomController 的映射 +> - 由于一个对象可能在多个房间中,因此返回值为 map 类型 +*** +#### func (*RoomManager) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool) +> 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法 +*** ### RoomAssumeControlEventHandle `STRUCT` @@ -98,3 +271,14 @@ type RoomControllerOptions[EntityID comparable, RoomID comparable, Entity generi ownerInheritHandler func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID } ``` +#### func (*RoomControllerOptions) WithOwnerInherit(inherit bool, inheritHandler ...func (controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID) *RoomControllerOptions[EntityID, RoomID, Entity, Room] +> 设置房间所有者是否继承,默认为 false +> - inherit: 是否继承,当未设置 inheritHandler 且 inherit 为 true 时,将会按照随机或根据座位号顺序继承房间所有者 +> - inheritHandler: 继承处理函数,当 inherit 为 true 时,该函数将会被调用,传入当前房间中的所有实体,返回值为新的房间所有者 +*** +#### func (*RoomControllerOptions) WithMaxEntityCount(maxEntityCount int) *RoomControllerOptions[EntityID, RoomID, Entity, Room] +> 设置房间最大实体数量 +*** +#### func (*RoomControllerOptions) WithPassword(password string) *RoomControllerOptions[EntityID, RoomID, Entity, Room] +> 设置房间密码 +*** diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index ec7fb12a..4325cdf6 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -176,6 +176,15 @@ type Action[P Producer, M Message[P]] struct { d *Dispatcher[P, M] } ``` +#### func (*Action) Name() string +> 获取消息分发器名称 +*** +#### func (*Action) UnExpel() +> 取消特定生产者的驱逐计划 +*** +#### func (*Action) Expel() +> 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭 +*** ### Handler `STRUCT` 消息处理器 @@ -212,6 +221,362 @@ type Dispatcher[P Producer, M Message[P]] struct { abort chan struct{} } ``` +#### func (*Dispatcher) SetProducerDoneHandler(p P, handler func (p P, dispatcher *Action[P, M])) *Dispatcher[P, M] +> 设置特定生产者所有消息处理完成时的回调函数 +> - 如果 handler 为 nil,则会删除该生产者的回调函数 +> +> 需要注意的是,该 handler 中 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_SetProducerDoneHandler(t *testing.T) { + var cases = []struct { + name string + producer string + messageFinish *atomic.Bool + cancel bool + }{{name: "TestDispatcher_SetProducerDoneHandlerNotCancel", producer: "producer", cancel: false}, {name: "TestDispatcher_SetProducerDoneHandlerCancel", producer: "producer", cancel: true}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.messageFinish = &atomic.Bool{} + w := new(sync.WaitGroup) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + w.Done() + }) + d.Put(&TestMessage{producer: c.producer}) + d.SetProducerDoneHandler(c.producer, func(p string, dispatcher *dispatcher.Action[string, *TestMessage]) { + c.messageFinish.Store(true) + }) + if c.cancel { + d.SetProducerDoneHandler(c.producer, nil) + } + w.Add(1) + d.Start() + w.Wait() + if c.cancel && c.messageFinish.Load() { + t.Errorf("%s should cancel, but not", c.name) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) SetClosedHandler(handler func (dispatcher *Action[P, M])) *Dispatcher[P, M] +> 设置消息分发器关闭时的回调函数 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_SetClosedHandler(t *testing.T) { + var cases = []struct { + name string + handlerFinishMsgCount *atomic.Int64 + msgTime time.Duration + msgCount int + }{{name: "TestDispatcher_SetClosedHandler_Normal", msgTime: 0, msgCount: 1}, {name: "TestDispatcher_SetClosedHandler_MessageCount1024", msgTime: 0, msgCount: 1024}, {name: "TestDispatcher_SetClosedHandler_MessageTime1sMessageCount3", msgTime: 1 * time.Second, msgCount: 3}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.handlerFinishMsgCount = &atomic.Int64{} + w := new(sync.WaitGroup) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + time.Sleep(c.msgTime) + c.handlerFinishMsgCount.Add(1) + }) + d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { + w.Done() + }) + for i := 0; i < c.msgCount; i++ { + d.Put(&TestMessage{producer: "producer"}) + } + w.Add(1) + d.Start() + d.Expel() + w.Wait() + if c.handlerFinishMsgCount.Load() != int64(c.msgCount) { + t.Errorf("%s should finish %d messages, but finish %d", c.name, c.msgCount, c.handlerFinishMsgCount.Load()) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) Name() string +> 获取消息分发器名称 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_Name(t *testing.T) { + var cases = []struct{ name string }{{name: "TestDispatcher_Name_Normal"}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + if d.Name() != c.name { + t.Errorf("%s should equal %s, but not", c.name, c.name) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) Unique(name string) bool +> 设置唯一消息键,返回是否已存在 +*** +#### func (*Dispatcher) AntiUnique(name string) +> 取消唯一消息键 +*** +#### func (*Dispatcher) Expel() +> 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_Expel(t *testing.T) { + var cases = []struct { + name string + handlerFinishMsgCount *atomic.Int64 + msgTime time.Duration + msgCount int + }{{name: "TestDispatcher_Expel_Normal", msgTime: 0, msgCount: 1}, {name: "TestDispatcher_Expel_MessageCount1024", msgTime: 0, msgCount: 1024}, {name: "TestDispatcher_Expel_MessageTime1sMessageCount3", msgTime: 1 * time.Second, msgCount: 3}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.handlerFinishMsgCount = &atomic.Int64{} + w := new(sync.WaitGroup) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + time.Sleep(c.msgTime) + c.handlerFinishMsgCount.Add(1) + }) + d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { + w.Done() + }) + for i := 0; i < c.msgCount; i++ { + d.Put(&TestMessage{producer: "producer"}) + } + w.Add(1) + d.Start() + d.Expel() + w.Wait() + if c.handlerFinishMsgCount.Load() != int64(c.msgCount) { + t.Errorf("%s should finish %d messages, but finish %d", c.name, c.msgCount, c.handlerFinishMsgCount.Load()) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) UnExpel() +> 取消特定生产者的驱逐计划 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_UnExpel(t *testing.T) { + var cases = []struct { + name string + closed *atomic.Bool + isUnExpel bool + expect bool + }{{name: "TestDispatcher_UnExpel_Normal", isUnExpel: true, expect: false}, {name: "TestDispatcher_UnExpel_NotExpel", isUnExpel: false, expect: true}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.closed = &atomic.Bool{} + w := new(sync.WaitGroup) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + w.Done() + }) + d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { + c.closed.Store(true) + }) + d.Put(&TestMessage{producer: "producer"}) + w.Add(1) + if c.isUnExpel { + d.Expel() + d.UnExpel() + } else { + d.Expel() + } + d.Start() + w.Wait() + if c.closed.Load() != c.expect { + t.Errorf("%s should %v, but %v", c.name, c.expect, c.closed.Load()) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) IncrCount(producer P, i int64) +> 主动增量设置特定生产者的消息计数,这在等待异步消息完成后再关闭消息分发器时非常有用 +> - 如果 i 为负数,则会减少消息计数 +*** +#### func (*Dispatcher) Put(message M) +> 将消息放入分发器 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_Put(t *testing.T) { + var cases = []struct { + name string + producer string + messageDone *atomic.Bool + }{{name: "TestDispatcher_Put_Normal", producer: "producer"}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.messageDone = &atomic.Bool{} + w := new(sync.WaitGroup) + w.Add(1) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + c.messageDone.Store(true) + w.Done() + }) + d.Start() + d.Put(&TestMessage{producer: c.producer}) + d.Expel() + w.Wait() + if !c.messageDone.Load() { + t.Errorf("%s should done, but not", c.name) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) Start() *Dispatcher[P, M] +> 以非阻塞的方式开始进行消息分发,当消息分发器中没有任何消息并且处于驱逐计划 Expel 时,将会自动关闭 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_Start(t *testing.T) { + var cases = []struct { + name string + producer string + messageDone *atomic.Bool + }{{name: "TestDispatcher_Start_Normal", producer: "producer"}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + c.messageDone = &atomic.Bool{} + w := new(sync.WaitGroup) + w.Add(1) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + c.messageDone.Store(true) + w.Done() + }) + d.Start() + d.Put(&TestMessage{producer: c.producer}) + d.Expel() + w.Wait() + if !c.messageDone.Load() { + t.Errorf("%s should done, but not", c.name) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Dispatcher) Closed() bool +> 判断消息分发器是否已关闭 +
+查看 / 收起单元测试 + + +```go + +func TestDispatcher_Closed(t *testing.T) { + var cases = []struct{ name string }{{name: "TestDispatcher_Closed_Normal"}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + w := new(sync.WaitGroup) + w.Add(1) + d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { + w.Done() + }) + d.Start() + d.Expel() + w.Wait() + if !d.Closed() { + t.Errorf("%s should closed, but not", c.name) + } + }) + } +} + +``` + + +
+ + +*** ### Manager `STRUCT` 消息分发器管理器 @@ -229,6 +594,295 @@ type Manager[P Producer, M Message[P]] struct { createdHandler func(name string) } ``` +#### func (*Manager) Wait() +> 等待所有消息分发器关闭 +*** +#### func (*Manager) SetDispatcherClosedHandler(handler func (name string)) *Manager[P, M] +> 设置消息分发器关闭时的回调函数 +
+查看 / 收起单元测试 + + +```go + +func TestManager_SetDispatcherClosedHandler(t *testing.T) { + var cases = []struct { + name string + setCloseHandler bool + }{{name: "TestManager_SetDispatcherClosedHandler_Set", setCloseHandler: true}, {name: "TestManager_SetDispatcherClosedHandler_NotSet", setCloseHandler: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var closed atomic.Bool + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + if c.setCloseHandler { + m.SetDispatcherClosedHandler(func(name string) { + closed.Store(true) + }) + } + m.BindProducer(c.name, c.name) + m.UnBindProducer(c.name) + m.Wait() + if c.setCloseHandler && !closed.Load() { + t.Errorf("SetDispatcherClosedHandler() should be called") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) SetDispatcherCreatedHandler(handler func (name string)) *Manager[P, M] +> 设置消息分发器创建时的回调函数 +
+查看 / 收起单元测试 + + +```go + +func TestManager_SetDispatcherCreatedHandler(t *testing.T) { + var cases = []struct { + name string + setCreatedHandler bool + }{{name: "TestManager_SetDispatcherCreatedHandler_Set", setCreatedHandler: true}, {name: "TestManager_SetDispatcherCreatedHandler_NotSet", setCreatedHandler: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var created atomic.Bool + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + if c.setCreatedHandler { + m.SetDispatcherCreatedHandler(func(name string) { + created.Store(true) + }) + } + m.BindProducer(c.name, c.name) + m.UnBindProducer(c.name) + m.Wait() + if c.setCreatedHandler && !created.Load() { + t.Errorf("SetDispatcherCreatedHandler() should be called") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) HasDispatcher(name string) bool +> 检查是否存在指定名称的消息分发器 +
+查看 / 收起单元测试 + + +```go + +func TestManager_HasDispatcher(t *testing.T) { + var cases = []struct { + name string + bindName string + has bool + }{{name: "TestManager_HasDispatcher_Has", bindName: "TestManager_HasDispatcher_Has", has: true}, {name: "TestManager_HasDispatcher_NotHas", bindName: "TestManager_HasDispatcher_NotHas", has: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + m.BindProducer(c.bindName, c.bindName) + var cond string + if c.has { + cond = c.bindName + } + if m.HasDispatcher(cond) != c.has { + t.Errorf("HasDispatcher() should return %v", c.has) + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) GetDispatcherNum() int +> 获取当前正在工作的消息分发器数量 +
+查看 / 收起单元测试 + + +```go + +func TestManager_GetDispatcherNum(t *testing.T) { + var cases = []struct { + name string + num int + }{{name: "TestManager_GetDispatcherNum_N1", num: -1}, {name: "TestManager_GetDispatcherNum_0", num: 0}, {name: "TestManager_GetDispatcherNum_1", num: 1}, {name: "TestManager_GetDispatcherNum_2", num: 2}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + switch { + case c.num <= 0: + return + case c.num == 1: + if m.GetDispatcherNum() != 1 { + t.Errorf("GetDispatcherNum() should return 1") + } + return + default: + for i := 0; i < c.num-1; i++ { + m.BindProducer(fmt.Sprintf("%s_%d", c.name, i), fmt.Sprintf("%s_%d", c.name, i)) + } + if m.GetDispatcherNum() != c.num { + t.Errorf("GetDispatcherNum() should return %v", c.num) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) GetSystemDispatcher() *Dispatcher[P, M] +> 获取系统消息分发器 +
+查看 / 收起单元测试 + + +```go + +func TestManager_GetSystemDispatcher(t *testing.T) { + var cases = []struct{ name string }{{name: "TestManager_GetSystemDispatcher"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + if m.GetSystemDispatcher() == nil { + t.Errorf("GetSystemDispatcher() should not return nil") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) GetDispatcher(p P) *Dispatcher[P, M] +> 获取生产者正在使用的消息分发器,如果生产者没有绑定消息分发器,则会返回系统消息分发器 +
+查看 / 收起单元测试 + + +```go + +func TestManager_GetDispatcher(t *testing.T) { + var cases = []struct { + name string + bindName string + }{{name: "TestManager_GetDispatcher", bindName: "TestManager_GetDispatcher"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + m.BindProducer(c.bindName, c.bindName) + if m.GetDispatcher(c.bindName) == nil { + t.Errorf("GetDispatcher() should not return nil") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) BindProducer(p P, name string) +> 绑定生产者使用特定的消息分发器,如果生产者已经绑定了消息分发器,则会先解绑 +
+查看 / 收起单元测试 + + +```go + +func TestManager_BindProducer(t *testing.T) { + var cases = []struct { + name string + bindName string + }{{name: "TestManager_BindProducer", bindName: "TestManager_BindProducer"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + m.BindProducer(c.bindName, c.bindName) + if m.GetDispatcher(c.bindName) == nil { + t.Errorf("GetDispatcher() should not return nil") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*Manager) UnBindProducer(p P) +> 解绑生产者使用特定的消息分发器 +
+查看 / 收起单元测试 + + +```go + +func TestManager_UnBindProducer(t *testing.T) { + var cases = []struct { + name string + bindName string + }{{name: "TestManager_UnBindProducer", bindName: "TestManager_UnBindProducer"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + m.BindProducer(c.bindName, c.bindName) + m.UnBindProducer(c.bindName) + if m.GetDispatcher(c.bindName) != m.GetSystemDispatcher() { + t.Errorf("GetDispatcher() should return SystemDispatcher") + } + }) + } +} + +``` + + +
+ + +*** ### Message `INTERFACE` diff --git a/server/internal/dispatcher/dispatcher.go b/server/internal/dispatcher/dispatcher.go index 7d696cd4..b88dcd2f 100644 --- a/server/internal/dispatcher/dispatcher.go +++ b/server/internal/dispatcher/dispatcher.go @@ -59,10 +59,8 @@ type Dispatcher[P Producer, M Message[P]] struct { abort chan struct{} } -// SetProducerDoneHandler 设置特定生产者所有消息处理完成时的回调函数 +// SetProducerDoneHandler 设置特定生产者的所有消息处理完成时的回调函数 // - 如果 handler 为 nil,则会删除该生产者的回调函数 -// -// 需要注意的是,该 handler 中 func (d *Dispatcher[P, M]) SetProducerDoneHandler(p P, handler func(p P, dispatcher *Action[P, M])) *Dispatcher[P, M] { d.lock.Lock() if handler == nil { diff --git a/server/lockstep/README.md b/server/lockstep/README.md index e0a6a482..3452107e 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -152,6 +152,59 @@ type Lockstep[ClientID comparable, Command any] struct { lockstepStoppedEventHandles []StoppedEventHandle[ClientID, Command] } ``` +#### func (*Lockstep) JoinClient(client Client[ClientID]) +> 将客户端加入到广播队列中,通常在开始广播前使用 +> - 如果客户端在开始广播后加入,将丢失之前的帧数据,如要从特定帧开始追帧请使用 JoinClientWithFrame +*** +#### func (*Lockstep) JoinClientWithFrame(client Client[ClientID], frameIndex int64) +> 加入客户端到广播队列中,并从特定帧开始追帧 +> - 可用于重连及状态同步、帧同步混用的情况 +> - 混用:服务端记录指令时同时做一次状态计算,新客户端加入时直接同步当前状态,之后从特定帧开始广播 +*** +#### func (*Lockstep) GetClientCount() int +> 获取客户端数量 +*** +#### func (*Lockstep) DropCache(handler func (frame int64) bool) +> 丢弃特定帧的缓存,当 handler 返回 true 时将丢弃缓存 +*** +#### func (*Lockstep) LeaveClient(clientId ClientID) +> 将客户端从广播队列中移除 +*** +#### func (*Lockstep) StartBroadcast() +> 开始广播 +> - 在开始广播后将持续按照设定的帧率进行帧数推进,并在每一帧推进时向客户端进行同步,需提前将客户端加入广播队列 JoinClient +> - 广播过程中使用 AddCommand 将该帧数据追加到当前帧中 +*** +#### func (*Lockstep) StopBroadcast() +> 停止广播 +*** +#### func (*Lockstep) IsRunning() bool +> 是否正在广播 +*** +#### func (*Lockstep) AddCommand(command Command) +> 添加命令到当前帧 +*** +#### func (*Lockstep) AddCommands(commands []Command) +> 添加命令到当前帧 +*** +#### func (*Lockstep) GetCurrentFrame() int64 +> 获取当前帧 +*** +#### func (*Lockstep) GetClientCurrentFrame(clientId ClientID) int64 +> 获取客户端当前帧 +*** +#### func (*Lockstep) GetFrameLimit() int64 +> 获取帧上限 +> - 未设置时将返回0 +*** +#### func (*Lockstep) GetCurrentCommands() []Command +> 获取当前帧还未结束时的所有指令 +*** +#### func (*Lockstep) RegLockstepStoppedEvent(handle StoppedEventHandle[ClientID, Command]) +> 当广播停止时将触发被注册的事件处理函数 +*** +#### func (*Lockstep) OnLockstepStoppedEvent() +*** ### Option `STRUCT` diff --git a/utils/aoi/README.md b/utils/aoi/README.md index ba1f75c1..b710b34c 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -83,6 +83,18 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimens repartitionQueue []func() } ``` +#### func (*TwoDimensional) AddEntity(entity E) +*** +#### func (*TwoDimensional) DeleteEntity(entity E) +*** +#### func (*TwoDimensional) Refresh(entity E) +*** +#### func (*TwoDimensional) GetFocus(id EID) map[EID]E +*** +#### func (*TwoDimensional) SetSize(width int, height int) +*** +#### func (*TwoDimensional) SetAreaSize(width int, height int) +*** ### TwoDimensionalEntity `INTERFACE` 基于2D定义的AOI对象功能接口 diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index e5feb021..e6fb97d8 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -127,6 +127,25 @@ type Area[ID comparable, AreaInfo any] struct { evaluate AreaEvaluateHandle[ID, AreaInfo] } ``` +#### func (*Area) GetAreaInfo() AreaInfo +> 获取编排区域的信息 +*** +#### func (*Area) GetItems() map[ID]Item[ID] +> 获取编排区域中的所有成员 +*** +#### func (*Area) IsAllow(item Item[ID]) (constraintErr error, conflictItems map[ID]Item[ID], allow bool) +> 检测一个成员是否可以被添加到该编排区域中 +*** +#### func (*Area) IsConflict(item Item[ID]) bool +> 检测一个成员是否会造成冲突 +*** +#### func (*Area) GetConflictItems(item Item[ID]) map[ID]Item[ID] +> 获取与一个成员产生冲突的所有其他成员 +*** +#### func (*Area) GetScore(extra ...Item[ID]) float64 +> 获取该编排区域的评估分数 +> - 当 extra 不为空时,将会将 extra 中的内容添加到 items 中进行评估 +*** ### AreaOption `STRUCT` 编排区域选项 @@ -157,6 +176,65 @@ type Arrangement[ID comparable, AreaInfo any] struct { conflictHandles []ConflictHandle[ID, AreaInfo] } ``` +#### func (*Arrangement) AddArea(areaInfo AreaInfo, options ...AreaOption[ID, AreaInfo]) +> 添加一个编排区域 +*** +#### func (*Arrangement) AddItem(item Item[ID]) +> 添加一个成员 +*** +#### func (*Arrangement) Arrange() (areas []*Area[ID, AreaInfo], noSolution map[ID]Item[ID]) +> 编排 +
+查看 / 收起单元测试 + + +```go + +func TestArrangement_Arrange(t *testing.T) { + var a = arrangement.NewArrangement[int, *Team]() + a.AddArea(&Team{ID: 1}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error { + if len(area.GetItems()) >= 2 { + return errors.New("too many") + } + return nil + })) + a.AddArea(&Team{ID: 2}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error { + if len(area.GetItems()) >= 1 { + return errors.New("too many") + } + return nil + })) + a.AddArea(&Team{ID: 3}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error { + if len(area.GetItems()) >= 2 { + return errors.New("too many") + } + return nil + })) + for i := 0; i < 10; i++ { + a.AddItem(&Player{ID: i + 1}) + } + res, no := a.Arrange() + for _, area := range res { + var str = fmt.Sprintf("area %d: ", area.GetAreaInfo().ID) + for id := range area.GetItems() { + str += fmt.Sprintf("%d ", id) + } + fmt.Println(str) + } + var noStr = "no: " + for _, i := range no { + noStr += fmt.Sprintf("%d ", i.GetID()) + } + fmt.Println(noStr) +} + +``` + + +
+ + +*** ### Editor `STRUCT` 提供了大量辅助函数的编辑器 @@ -169,6 +247,48 @@ type Editor[ID comparable, AreaInfo any] struct { retryCount int } ``` +#### func (*Editor) GetPendingCount() int +> 获取待编排的成员数量 +*** +#### func (*Editor) RemoveAreaItem(area *Area[ID, AreaInfo], item Item[ID]) +> 从编排区域中移除一个成员到待编排队列中,如果该成员不存在于编排区域中,则不进行任何操作 +*** +#### func (*Editor) AddAreaItem(area *Area[ID, AreaInfo], item Item[ID]) +> 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作 +*** +#### func (*Editor) GetAreas() []*Area[ID, AreaInfo] +> 获取所有的编排区域 +*** +#### func (*Editor) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area[ID, AreaInfo] +> 获取所有的编排区域,并按照分数升序排序 +*** +#### func (*Editor) GetAreasWithScoreDesc(extra ...Item[ID]) []*Area[ID, AreaInfo] +> 获取所有的编排区域,并按照分数降序排序 +*** +#### func (*Editor) GetRetryCount() int +> 获取重试次数 +*** +#### func (*Editor) GetThresholdProgressRate() float64 +> 获取重试次数阈值进度 +*** +#### func (*Editor) GetAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] +> 获取允许的编排区域 +*** +#### func (*Editor) GetNoAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] +> 获取不允许的编排区域 +*** +#### func (*Editor) GetBestAllowArea(item Item[ID]) *Area[ID, AreaInfo] +> 获取最佳的允许的编排区域,如果不存在,则返回 nil +*** +#### func (*Editor) GetBestNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] +> 获取最佳的不允许的编排区域,如果不存在,则返回 nil +*** +#### func (*Editor) GetWorstAllowArea(item Item[ID]) *Area[ID, AreaInfo] +> 获取最差的允许的编排区域,如果不存在,则返回 nil +*** +#### func (*Editor) GetWorstNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] +> 获取最差的不允许的编排区域,如果不存在,则返回 nil +*** ### Item `INTERFACE` 编排成员 diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md index b91ebf6d..40840403 100644 --- a/utils/collection/mappings/README.md +++ b/utils/collection/mappings/README.md @@ -46,3 +46,55 @@ type SyncMap[K comparable, V any] struct { atom bool } ``` +#### func (*SyncMap) Set(key K, value V) +> 设置一个值 +*** +#### func (*SyncMap) Get(key K) V +> 获取一个值 +*** +#### func (*SyncMap) Atom(handle func (m map[K]V)) +> 原子操作 +*** +#### func (*SyncMap) Exist(key K) bool +> 判断是否存在 +*** +#### func (*SyncMap) GetExist(key K) ( V, bool) +> 获取一个值并判断是否存在 +*** +#### func (*SyncMap) Delete(key K) +> 删除一个值 +*** +#### func (*SyncMap) DeleteGet(key K) V +> 删除一个值并返回 +*** +#### func (*SyncMap) DeleteGetExist(key K) ( V, bool) +> 删除一个值并返回是否存在 +*** +#### func (*SyncMap) DeleteExist(key K) bool +> 删除一个值并返回是否存在 +*** +#### func (*SyncMap) Clear() +> 清空 +*** +#### func (*SyncMap) ClearHandle(handle func (key K, value V)) +> 清空并处理 +*** +#### func (*SyncMap) Range(handle func (key K, value V) bool) +> 遍历所有值,如果 handle 返回 true 则停止遍历 +*** +#### func (*SyncMap) Keys() []K +> 获取所有的键 +*** +#### func (*SyncMap) Slice() []V +> 获取所有的值 +*** +#### func (*SyncMap) Map() map[K]V +> 转换为普通 map +*** +#### func (*SyncMap) Size() int +> 获取数量 +*** +#### func (*SyncMap) MarshalJSON() ( []byte, error) +*** +#### func (*SyncMap) UnmarshalJSON(bytes []byte) error +*** diff --git a/utils/fsm/README.md b/utils/fsm/README.md index a8c0f02e..ab18674e 100644 --- a/utils/fsm/README.md +++ b/utils/fsm/README.md @@ -85,6 +85,33 @@ type FSM[State comparable, Data any] struct { exitAfterEventHandles map[State][]func(state *FSM[State, Data]) } ``` +#### func (*FSM) Update() +> 触发当前状态 +*** +#### func (*FSM) Register(state State, options ...Option[State, Data]) +> 注册状态 +*** +#### func (*FSM) Unregister(state State) +> 反注册状态 +*** +#### func (*FSM) HasState(state State) bool +> 检查状态机是否存在特定状态 +*** +#### func (*FSM) Change(state State) +> 改变状态机状态到新的状态 +*** +#### func (*FSM) Current() (state State) +> 获取当前状态 +*** +#### func (*FSM) GetData() Data +> 获取状态机数据 +*** +#### func (*FSM) IsZero() bool +> 检查状态机是否无状态 +*** +#### func (*FSM) PrevIsZero() bool +> 检查状态机上一个状态是否无状态 +*** ### Option `STRUCT` diff --git a/utils/generator/astgo/name.go b/utils/generator/astgo/name.go index 67abeae4..89b8d736 100644 --- a/utils/generator/astgo/name.go +++ b/utils/generator/astgo/name.go @@ -14,7 +14,8 @@ func newName(expr ast.Expr) string { //case *ast.FuncType: //case *ast.InterfaceType: //case *ast.MapType: - //case *ast.ChanType: + case *ast.ChanType: + str.WriteString(newName(e.Value)) case *ast.Ident: str.WriteString(e.Name) case *ast.Ellipsis: @@ -28,13 +29,17 @@ func newName(expr ast.Expr) string { case *ast.IndexExpr: str.WriteString(newName(e.X)) case *ast.IndexListExpr: + str.WriteString(newName(e.X)) case *ast.SliceExpr: - case *ast.TypeAssertExpr: - case *ast.CallExpr: + str.WriteString(newName(e.X)) + //case *ast.TypeAssertExpr: + //case *ast.CallExpr: case *ast.StarExpr: str.WriteString(newName(e.X)) case *ast.UnaryExpr: + str.WriteString(newName(e.X)) case *ast.BinaryExpr: + str.WriteString(newName(e.X)) } return str.String() } diff --git a/utils/geometry/README.md b/utils/geometry/README.md index 9dc4d81c..ad081944 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -947,6 +947,8 @@ type LineSegmentCap[V generic.SignedNumber, Data any] struct { Data Data } ``` +#### func (*LineSegmentCap) GetData() Data +*** ### Point `STRUCT` 表示了一个由 x、y 坐标组成的点 @@ -1010,6 +1012,9 @@ type PointCap[V generic.SignedNumber, D any] struct { Data D } ``` +#### func (PointCap) GetData() D +> 获取数据 +*** ### Shape `STRUCT` 通过多个点表示了一个形状 diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index d42bd9af..2d8b992e 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -76,6 +76,107 @@ type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct { rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] } ``` +#### func (*BinarySearch) Competitor(competitorId CompetitorID, score Score) +> 声明排行榜竞争者 +> - 如果竞争者存在的情况下,会更新已有成绩,否则新增竞争者 +**示例代码:** + +```go + +func ExampleBinarySearch_Competitor() { + bs := leaderboard2.NewBinarySearch[string, int](leaderboard2.WithBinarySearchCount[string, int](10)) + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + for rank, competitor := range bs.GetAllCompetitor() { + fmt.Println(rank, competitor) + } +} + +``` + +*** +#### func (*BinarySearch) RemoveCompetitor(competitorId CompetitorID) +> 删除特定竞争者 +**示例代码:** + +```go + +func ExampleBinarySearch_RemoveCompetitor() { + bs := leaderboard2.NewBinarySearch[string, int](leaderboard2.WithBinarySearchCount[string, int](10)) + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + bs.RemoveCompetitor("competitor_ 1") + for rank, competitor := range bs.GetAllCompetitor() { + fmt.Println(rank, competitor) + } +} + +``` + +*** +#### func (*BinarySearch) Size() int +> 获取竞争者数量 +*** +#### func (*BinarySearch) GetRankDefault(competitorId CompetitorID, defaultValue int) int +> 获取竞争者排名,如果竞争者不存在则返回默认值 +> - 排名从 0 开始 +*** +#### func (*BinarySearch) GetRank(competitorId CompetitorID) ( int, error) +> 获取竞争者排名 +> - 排名从 0 开始 +**示例代码:** + +```go + +func ExampleBinarySearch_GetRank() { + bs := leaderboard2.NewBinarySearch[string, int](leaderboard2.WithBinarySearchCount[string, int](10)) + scores := []int{6131, 132, 5133, 134, 135, 136, 137, 138, 139, 140, 222, 333, 444, 555, 666} + for i := 1; i <= 15; i++ { + bs.Competitor(fmt.Sprintf("competitor_%2d", i), scores[i-1]) + } + fmt.Println(bs.GetRank("competitor_ 1")) +} + +``` + +*** +#### func (*BinarySearch) GetCompetitor(rank int) (competitorId CompetitorID, err error) +> 获取特定排名的竞争者 +*** +#### func (*BinarySearch) GetCompetitorWithRange(start int, end int) ( []CompetitorID, error) +> 获取第start名到第end名竞争者 +*** +#### func (*BinarySearch) GetScore(competitorId CompetitorID) (score Score, err error) +> 获取竞争者成绩 +*** +#### func (*BinarySearch) GetScoreDefault(competitorId CompetitorID, defaultValue Score) Score +> 获取竞争者成绩,不存在时返回默认值 +*** +#### func (*BinarySearch) GetAllCompetitor() []CompetitorID +> 获取所有竞争者ID +> - 结果为名次有序的 +*** +#### func (*BinarySearch) Clear() +> 清空排行榜 +*** +#### func (*BinarySearch) Cmp(s1 Score, s2 Score) int +*** +#### func (*BinarySearch) UnmarshalJSON(bytes []byte) error +*** +#### func (*BinarySearch) MarshalJSON() ( []byte, error) +*** +#### func (*BinarySearch) RegRankChangeEvent(handle BinarySearchRankChangeEventHandle[CompetitorID, Score]) +*** +#### func (*BinarySearch) OnRankChangeEvent(competitorId CompetitorID, oldRank int, newRank int, oldScore Score, newScore Score) +*** +#### func (*BinarySearch) RegRankClearBeforeEvent(handle BinarySearchRankClearBeforeEventHandle[CompetitorID, Score]) +*** +#### func (*BinarySearch) OnRankClearBeforeEvent() +*** ### BinarySearchRankChangeEventHandle `STRUCT` diff --git a/utils/moving/README.md b/utils/moving/README.md index 61111dfb..6606faf0 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -115,6 +115,124 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber] struct { position2DStopMoveEventHandles []Position2DStopMoveEventHandle[EID, PosType] } ``` +#### func (*TwoDimensional) MoveTo(entity TwoDimensionalEntity[EID, PosType], x PosType, y PosType) +> 设置对象移动到特定位置 +**示例代码:** + +```go + +func ExampleTwoDimensional_MoveTo() { + m := moving2.NewTwoDimensional(moving2.WithTwoDimensionalTimeUnit[int64, float64](time.Second)) + defer func() { + m.Release() + }() + var wait sync.WaitGroup + m.RegPosition2DDestinationEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64]) { + fmt.Println("done") + wait.Done() + }) + wait.Add(1) + entity := NewEntity(1, 100) + m.MoveTo(entity, 50, 30) + wait.Wait() +} + +``` + +*** +#### func (*TwoDimensional) StopMove(id EID) +> 停止特定对象的移动 +**示例代码:** + +```go + +func ExampleTwoDimensional_StopMove() { + m := moving2.NewTwoDimensional(moving2.WithTwoDimensionalTimeUnit[int64, float64](time.Second)) + defer func() { + m.Release() + }() + var wait sync.WaitGroup + m.RegPosition2DChangeEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64], oldX, oldY float64) { + fmt.Println("move") + }) + m.RegPosition2DStopMoveEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64]) { + fmt.Println("stop") + wait.Done() + }) + m.RegPosition2DDestinationEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64]) { + fmt.Println("done") + wait.Done() + }) + wait.Add(1) + entity := NewEntity(1, 100) + m.MoveTo(entity, 50, 300) + m.StopMove(1) + wait.Wait() +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestTwoDimensional_StopMove(t *testing.T) { + var wait sync.WaitGroup + m := moving2.NewTwoDimensional(moving2.WithTwoDimensionalTimeUnit[int64, float64](time.Second)) + defer func() { + m.Release() + }() + m.RegPosition2DChangeEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64], oldX, oldY float64) { + x, y := entity.GetPosition().GetXY() + fmt.Println(fmt.Sprintf("%d : %d | %f, %f > %f, %f", entity.GetTwoDimensionalEntityID(), time.Now().UnixMilli(), oldX, oldY, x, y)) + }) + m.RegPosition2DDestinationEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64]) { + fmt.Println(fmt.Sprintf("%d : %d | destination", entity.GetTwoDimensionalEntityID(), time.Now().UnixMilli())) + wait.Done() + }) + m.RegPosition2DStopMoveEvent(func(moving *moving2.TwoDimensional[int64, float64], entity moving2.TwoDimensionalEntity[int64, float64]) { + fmt.Println(fmt.Sprintf("%d : %d | stop", entity.GetTwoDimensionalEntityID(), time.Now().UnixMilli())) + wait.Done() + }) + for i := 0; i < 10; i++ { + wait.Add(1) + entity := NewEntity(int64(i)+1, float64(10+i)) + m.MoveTo(entity, 50, 30) + } + time.Sleep(time.Second * 1) + for i := 0; i < 10; i++ { + m.StopMove(int64(i) + 1) + } + wait.Wait() +} + +``` + + +
+ + +*** +#### func (*TwoDimensional) RegPosition2DChangeEvent(handle Position2DChangeEventHandle[EID, PosType]) +> 在对象位置改变时将执行注册的事件处理函数 +*** +#### func (*TwoDimensional) OnPosition2DChangeEvent(entity TwoDimensionalEntity[EID, PosType], oldX PosType, oldY PosType) +*** +#### func (*TwoDimensional) RegPosition2DDestinationEvent(handle Position2DDestinationEventHandle[EID, PosType]) +> 在对象到达终点时将执行被注册的事件处理函数 +*** +#### func (*TwoDimensional) OnPosition2DDestinationEvent(entity TwoDimensionalEntity[EID, PosType]) +*** +#### func (*TwoDimensional) RegPosition2DStopMoveEvent(handle Position2DStopMoveEventHandle[EID, PosType]) +> 在对象停止移动时将执行被注册的事件处理函数 +*** +#### func (*TwoDimensional) OnPosition2DStopMoveEvent(entity TwoDimensionalEntity[EID, PosType]) +*** +#### func (*TwoDimensional) Release() +> 释放对象移动对象所占用的资源 +*** ### TwoDimensionalEntity `INTERFACE` 2D移动对象接口定义 diff --git a/utils/super/README.md b/utils/super/README.md index 6797a26d..90754cc9 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -940,6 +940,12 @@ type Matcher[Value any, Result any] struct { d bool } ``` +#### func (*Matcher) Case(value Value, result Result) *Matcher[Value, Result] +> 匹配 +*** +#### func (*Matcher) Default(value Result) Result +> 默认 +*** ### Permission `STRUCT` @@ -949,6 +955,18 @@ type Permission[Code generic.Integer, EntityID comparable] struct { l sync.RWMutex } ``` +#### func (*Permission) HasPermission(entityId EntityID, permission Code) bool +> 是否有权限 +*** +#### func (*Permission) AddPermission(entityId EntityID, permission ...Code) +> 添加权限 +*** +#### func (*Permission) RemovePermission(entityId EntityID, permission ...Code) +> 移除权限 +*** +#### func (*Permission) SetPermission(entityId EntityID, permission ...Code) +> 设置权限 +*** ### StackGo `STRUCT` 用于获取上一个协程调用的堆栈信息 From 2b464021b010b7c7a21c056f987de8a5d76c65ee Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 11:49:21 +0800 Subject: [PATCH 08/21] =?UTF-8?q?recessive:=20=E5=85=B6=E4=BB=96=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notify/senders/feishu_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notify/senders/feishu_test.go b/notify/senders/feishu_test.go index 7272c174..9d46120d 100644 --- a/notify/senders/feishu_test.go +++ b/notify/senders/feishu_test.go @@ -6,7 +6,7 @@ import ( ) func TestFeiShu_Push(t *testing.T) { - fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/d886f30f-814c-47b1-aeb0-b508da0f7f22") + fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/bid") rt := notifies.NewFeiShu(notifies.FeiShuMessageWithRichText(notifies.NewFeiShuRichText().Create("zh_cn", "标题咯").AddText("哈哈哈").Ok())) if err := fs.Push(rt); err != nil { From 1645ae47df879067ba286affec39e5bed168fa02 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 14:45:14 +0800 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20server=20?= =?UTF-8?q?=E5=8C=85=20None=20=E7=BD=91=E7=BB=9C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E9=98=BB=E5=A1=9E=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E3=80=82=E5=A2=9E=E5=8A=A0=E4=BC=A0=E5=85=A5=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=BD=91=E7=BB=9C=E7=B1=BB=E5=9E=8B=E5=B0=86=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=20panic=20=E7=9A=84=E7=89=B9=E6=80=A7=E3=80=82?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20WebSocket=20=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E5=B0=86=E4=B8=8D=E5=86=8D=E4=BD=BF=E7=94=A8=20http.DefaultMux?= =?UTF-8?q?Server=EF=BC=8C=E8=BD=AC=E8=80=8C=E4=BD=BF=E7=94=A8=20http.NewS?= =?UTF-8?q?erveMux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/network.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/network.go b/server/network.go index 3e5c1f0b..7fadec24 100644 --- a/server/network.go +++ b/server/network.go @@ -103,6 +103,7 @@ func (n Network) adaptation(srv *Server) <-chan error { switch n { case NetworkNone: srv.addr = "-" + state <- nil case NetworkTcp: n.gNetMode(state, srv) case NetworkTcp4: @@ -125,6 +126,8 @@ func (n Network) adaptation(srv *Server) <-chan error { n.kcpMode(state, srv) case NetworkGRPC: n.grpcMode(state, srv) + default: + state <- fmt.Errorf("unsupported network mode: %s", n) } return state } @@ -248,7 +251,8 @@ func (n Network) websocketMode(state chan<- error, srv *Server) { if srv.websocketUpgrader == nil { srv.websocketUpgrader = DefaultWebsocketUpgrader() } - http.HandleFunc(pattern, func(writer http.ResponseWriter, request *http.Request) { + mux := http.NewServeMux() + mux.HandleFunc(pattern, func(writer http.ResponseWriter, request *http.Request) { ip := request.Header.Get("X-Real-IP") ws, err := srv.websocketUpgrader.Upgrade(writer, request, nil) if err != nil { @@ -304,17 +308,17 @@ func (n Network) websocketMode(state chan<- error, srv *Server) { srv.PushPacketMessage(conn, messageType, packet) } }) - go func(lis *listener) { + go func(lis *listener, mux *http.ServeMux) { var err error if len(lis.srv.certFile)+len(lis.srv.keyFile) > 0 { - err = http.ServeTLS(lis, nil, lis.srv.certFile, lis.srv.keyFile) + err = http.ServeTLS(lis, mux, lis.srv.certFile, lis.srv.keyFile) } else { - err = http.Serve(lis, nil) + err = http.Serve(lis, mux) } if err != nil { super.TryWriteChannel(lis.state, err) } - }((&listener{srv: srv, Listener: l, state: state}).init()) + }((&listener{srv: srv, Listener: l, state: state}).init(), mux) } // IsSocket 返回当前服务器的网络模式是否为 Socket 模式 From 22449ff5c34d681d34c0b59d3fce1a8987278d5e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 17:27:02 +0800 Subject: [PATCH 10/21] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/README.md | 45 ++ game/fight/README.md | 39 ++ game/space/README.md | 150 ++++++ game/task/README.md | 96 ++++ notify/README.md | 6 + notify/notifies/README.md | 36 ++ notify/senders/README.md | 5 +- planner/pce/README.md | 267 ++++++++++ planner/pce/cs/README.md | 18 + planner/pce/tmpls/README.md | 15 + server/README.md | 720 +++++++++++++++++++++++++-- server/client/README.md | 66 +++ server/gateway/README.md | 36 ++ server/internal/dispatcher/README.md | 73 ++- server/internal/logger/README.md | 18 + server/lockstep/README.md | 48 ++ server/router/README.md | 15 + server/writeloop/README.md | 12 + utils/aoi/README.md | 18 + utils/arrangement/README.md | 69 +++ utils/buffer/README.md | 51 ++ utils/collection/README.md | 12 +- utils/collection/listings/README.md | 104 +++- utils/collection/mappings/README.md | 54 ++ utils/combination/README.md | 40 +- utils/deck/README.md | 66 +++ utils/fsm/README.md | 27 + utils/generator/astgo/README.md | 27 + utils/generator/genreadme/README.md | 3 + utils/geometry/README.md | 120 +++++ utils/geometry/astar/README.md | 3 + utils/geometry/dp/README.md | 18 + utils/geometry/matrix/README.md | 48 ++ utils/geometry/navmesh/README.md | 9 + utils/hub/README.md | 6 + utils/huge/README.md | 396 +++++++++++++++ utils/leaderboard/README.md | 54 ++ utils/log/README.md | 12 + utils/log/survey/README.md | 99 ++++ utils/memory/README.md | 3 + utils/moving/README.md | 27 + utils/offset/README.md | 9 + utils/random/README.md | 6 + utils/sole/README.md | 6 + utils/super/README.md | 141 ++++++ utils/timer/README.md | 45 ++ utils/times/README.md | 129 +++++ 47 files changed, 3220 insertions(+), 47 deletions(-) diff --git a/game/activity/README.md b/game/activity/README.md index 17275230..3156ecc0 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -201,31 +201,61 @@ type Controller[Type generic.Basic, ID generic.Basic, Data any, EntityID generic mutex sync.RWMutex } ``` + + #### func (*Controller) GetGlobalData(activityId ID) Data > 获取特定活动全局数据 + *** + + #### func (*Controller) GetEntityData(activityId ID, entityId EntityID) EntityData > 获取特定活动实体数据 + *** + + #### func (*Controller) IsOpen(activityId ID) bool > 活动是否开启 + *** + + #### func (*Controller) IsShow(activityId ID) bool > 活动是否展示 + *** + + #### func (*Controller) IsOpenOrShow(activityId ID) bool > 活动是否开启或展示 + *** + + #### func (*Controller) Refresh(activityId ID) > 刷新活动 + *** + + #### func (*Controller) InitializeNoneData(handler func (activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData] + *** + + #### func (*Controller) InitializeGlobalData(handler func (activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData] + *** + + #### func (*Controller) InitializeEntityData(handler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData] + *** + + #### func (*Controller) InitializeGlobalAndEntityData(handler func (activityId ID, data *DataMeta[Data]), entityHandler func (activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] + *** ### BasicActivityController `INTERFACE` @@ -313,19 +343,34 @@ type Options struct { Loop time.Duration } ``` + + #### func (*Options) WithUpcomingTime(t time.Time) *Options > 设置活动预告时间 + *** + + #### func (*Options) WithStartTime(t time.Time) *Options > 设置活动开始时间 + *** + + #### func (*Options) WithEndTime(t time.Time) *Options > 设置活动结束时间 + *** + + #### func (*Options) WithExtendedShowTime(t time.Time) *Options > 设置延长展示时间 + *** + + #### func (*Options) WithLoop(interval time.Duration) *Options > 设置活动循环,时间间隔小于等于 0 表示不循环 > - 当活动状态展示结束后,会根据该选项设置的时间间隔重新开始 + *** diff --git a/game/fight/README.md b/game/fight/README.md index fa9683dd..f0527306 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -65,18 +65,30 @@ type TurnBased[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], closed bool } ``` + + #### func (*TurnBased) Close() > 关闭回合制 + *** + + #### func (*TurnBased) AddCamp(camp Camp, entity Entity, entities ...Entity) > 添加阵营 + *** + + #### func (*TurnBased) SetActionTimeout(actionTimeoutHandler func ( Camp, Entity) time.Duration) > 设置行动超时时间处理函数 > - 默认情况下行动超时时间函数将始终返回 0 + *** + + #### func (*TurnBased) Run() > 运行 +
查看 / 收起单元测试 @@ -154,33 +166,60 @@ type TurnBasedController[CampID comparable, EntityID comparable, Camp generic.Id tb *TurnBased[CampID, EntityID, Camp, Entity] } ``` + + #### func (*TurnBasedController) GetRound() int > 获取当前回合数 + *** + + #### func (*TurnBasedController) GetCamp() Camp > 获取当前操作阵营 + *** + + #### func (*TurnBasedController) GetEntity() Entity > 获取当前操作实体 + *** + + #### func (*TurnBasedController) GetActionTimeoutDuration() time.Duration > 获取当前行动超时时长 + *** + + #### func (*TurnBasedController) GetActionStartTime() time.Time > 获取当前行动开始时间 + *** + + #### func (*TurnBasedController) GetActionEndTime() time.Time > 获取当前行动结束时间 + *** + + #### func (*TurnBasedController) Finish() > 结束当前操作,将立即切换到下一个操作实体 + *** + + #### func (*TurnBasedController) Stop() > 在当前回合执行完毕后停止回合进程 + *** + + #### func (*TurnBasedController) Refresh(duration time.Duration) time.Time > 刷新当前操作实体的行动超时时间 > - 当不在行动阶段时,将返回 time.Time 零值 + *** ### TurnBasedEntitySwitchEventHandler `STRUCT` diff --git a/game/space/README.md b/game/space/README.md index e93b2f70..84f0f651 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -70,131 +70,245 @@ type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[E owner *EntityID } ``` + + #### func (*RoomController) HasOwner() bool > 判断是否有房主 + *** + + #### func (*RoomController) IsOwner(entityId EntityID) bool > 判断是否为房主 + *** + + #### func (*RoomController) GetOwner() Entity > 获取房主 + *** + + #### func (*RoomController) GetOwnerID() EntityID > 获取房主 ID + *** + + #### func (*RoomController) GetOwnerExist() ( Entity, bool) > 获取房间,并返回房主是否存在的状态 + *** + + #### func (*RoomController) SetOwner(entityId EntityID) > 设置房主 + *** + + #### func (*RoomController) DelOwner() > 删除房主,将房间设置为无主的状态 + *** + + #### func (*RoomController) JoinSeat(entityId EntityID, seat ...int) error > 设置特定对象加入座位,当具体的座位不存在的时候,将会自动分配座位 > - 当目标座位存在玩家或未添加到房间中的时候,将会返回错误 + *** + + #### func (*RoomController) LeaveSeat(entityId EntityID) > 离开座位 + *** + + #### func (*RoomController) GetSeat(entityId EntityID) int > 获取座位 + *** + + #### func (*RoomController) GetFirstNotEmptySeat() int > 获取第一个非空座位号,如果没有非空座位,将返回 UnknownSeat + *** + + #### func (*RoomController) GetFirstEmptySeatEntity() (entity Entity) > 获取第一个空座位上的实体,如果没有空座位,将返回空实体 + *** + + #### func (*RoomController) GetRandomEntity() (entity Entity) > 获取随机实体,如果房间中没有实体,将返回空实体 + *** + + #### func (*RoomController) GetNotEmptySeat() []int > 获取非空座位 + *** + + #### func (*RoomController) GetEmptySeat() []int > 获取空座位 > - 空座位需要在有对象离开座位后才可能出现 + *** + + #### func (*RoomController) HasSeat(entityId EntityID) bool > 判断是否有座位 + *** + + #### func (*RoomController) GetSeatEntityCount() int > 获取座位上的实体数量 + *** + + #### func (*RoomController) GetSeatEntities() map[EntityID]Entity > 获取座位上的实体 + *** + + #### func (*RoomController) GetSeatEntitiesByOrdered() []Entity > 有序的获取座位上的实体 + *** + + #### func (*RoomController) GetSeatEntitiesByOrderedAndContainsEmpty() []Entity > 获取有序的座位上的实体,包含空座位 + *** + + #### func (*RoomController) GetSeatEntity(seat int) (entity Entity) > 获取座位上的实体 + *** + + #### func (*RoomController) ContainEntity(id EntityID) bool > 房间内是否包含实体 + *** + + #### func (*RoomController) GetRoom() Room > 获取原始房间实例,该实例为被接管的房间的原始实例 + *** + + #### func (*RoomController) GetEntities() map[EntityID]Entity > 获取所有实体 + *** + + #### func (*RoomController) HasEntity(id EntityID) bool > 判断是否有实体 + *** + + #### func (*RoomController) GetEntity(id EntityID) Entity > 获取实体 + *** + + #### func (*RoomController) GetEntityExist(id EntityID) ( Entity, bool) > 获取实体,并返回实体是否存在的状态 + *** + + #### func (*RoomController) GetEntityIDs() []EntityID > 获取所有实体ID + *** + + #### func (*RoomController) GetEntityCount() int > 获取实体数量 + *** + + #### func (*RoomController) ChangePassword(password *string) > 修改房间密码 > - 当房间密码为 nil 时,将会取消密码 + *** + + #### func (*RoomController) AddEntity(entity Entity) error > 添加实体,如果房间存在密码,应使用 AddEntityByPassword 函数进行添加,否则将始终返回 ErrRoomPasswordNotMatch 错误 > - 当房间已满时,将会返回 ErrRoomFull 错误 + *** + + #### func (*RoomController) AddEntityByPassword(entity Entity, password string) error > 通过房间密码添加实体到该房间中 > - 当未设置房间密码时,password 参数将会被忽略 > - 当房间密码不匹配时,将会返回 ErrRoomPasswordNotMatch 错误 > - 当房间已满时,将会返回 ErrRoomFull 错误 + *** + + #### func (*RoomController) RemoveEntity(id EntityID) > 移除实体 > - 当实体被移除时如果实体在座位上,将会自动离开座位 > - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承 + *** + + #### func (*RoomController) RemoveAllEntities() > 移除该房间中的所有实体 > - 当实体被移除时如果实体在座位上,将会自动离开座位 > - 如果实体为房主,将会根据 RoomControllerOptions.WithOwnerInherit 函数的设置进行继承 + *** + + #### func (*RoomController) Destroy() > 销毁房间,房间会从 RoomManager 中移除,同时所有房间的实体、座位等数据都会被清空 > - 该函数与 RoomManager.DestroyRoom 相同,RoomManager.DestroyRoom 函数为该函数的快捷方式 + *** + + #### func (*RoomController) GetRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] > 获取该房间控制器所属的房间管理器 + *** + + #### func (*RoomController) GetRoomID() RoomID > 获取房间 ID + *** + + #### func (*RoomController) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool) > 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理 + *** ### RoomManager `STRUCT` @@ -207,10 +321,13 @@ type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[Enti rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] } ``` + + #### func (*RoomManager) AssumeControl(room Room, options ...*RoomControllerOptions[EntityID, RoomID, Entity, Room]) *RoomController[EntityID, RoomID, Entity, Room] > 将房间控制权交由 RoomManager 接管,返回 RoomController 实例 > - 当任何房间需要被 RoomManager 管理时,都应该调用该方法获取到 RoomController 实例后进行操作 > - 房间被接管后需要在释放房间控制权时调用 RoomController.Destroy 方法,否则将会导致 RoomManager 一直持有房间资源 + **示例代码:** ```go @@ -228,31 +345,55 @@ func ExampleRoomManager_AssumeControl() { ``` *** + + #### func (*RoomManager) DestroyRoom(id RoomID) > 销毁房间,该函数为 RoomController.Destroy 的快捷方式 + *** + + #### func (*RoomManager) GetRoom(id RoomID) *RoomController[EntityID, RoomID, Entity, Room] > 通过房间 ID 获取对应房间的控制器 RoomController,当房间不存在时将返回 nil + *** + + #### func (*RoomManager) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] > 获取包含所有房间 ID 到对应控制器 RoomController 的映射 > - 返回值的 map 为拷贝对象,可安全的对其进行增删等操作 + *** + + #### func (*RoomManager) GetRoomCount() int > 获取房间管理器接管的房间数量 + *** + + #### func (*RoomManager) GetRoomIDs() []RoomID > 获取房间管理器接管的所有房间 ID + *** + + #### func (*RoomManager) HasEntity(entityId EntityID) bool > 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false + *** + + #### func (*RoomManager) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] > 获取特定对象所在的房间,返回值为房间 ID 到对应控制器 RoomController 的映射 > - 由于一个对象可能在多个房间中,因此返回值为 map 类型 + *** + + #### func (*RoomManager) Broadcast(handler func ( Entity), conditions ...func ( Entity) bool) > 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法 + *** ### RoomAssumeControlEventHandle `STRUCT` @@ -271,14 +412,23 @@ type RoomControllerOptions[EntityID comparable, RoomID comparable, Entity generi ownerInheritHandler func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID } ``` + + #### func (*RoomControllerOptions) WithOwnerInherit(inherit bool, inheritHandler ...func (controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID) *RoomControllerOptions[EntityID, RoomID, Entity, Room] > 设置房间所有者是否继承,默认为 false > - inherit: 是否继承,当未设置 inheritHandler 且 inherit 为 true 时,将会按照随机或根据座位号顺序继承房间所有者 > - inheritHandler: 继承处理函数,当 inherit 为 true 时,该函数将会被调用,传入当前房间中的所有实体,返回值为新的房间所有者 + *** + + #### func (*RoomControllerOptions) WithMaxEntityCount(maxEntityCount int) *RoomControllerOptions[EntityID, RoomID, Entity, Room] > 设置房间最大实体数量 + *** + + #### func (*RoomControllerOptions) WithPassword(password string) *RoomControllerOptions[EntityID, RoomID, Entity, Room] > 设置房间密码 + *** diff --git a/game/task/README.md b/game/task/README.md index 58902dec..fe0196ea 100644 --- a/game/task/README.md +++ b/game/task/README.md @@ -156,71 +156,137 @@ func TestCond(t *testing.T) { ```go type Condition map[any]any ``` + + #### func (Condition) Cond(k any, v any) Condition > 创建任务条件 + *** + + #### func (Condition) GetString(key any) string > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetInt(key any) int > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetInt8(key any) int8 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetInt16(key any) int16 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetInt32(key any) int32 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetInt64(key any) int64 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetUint(key any) uint > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetUint8(key any) uint8 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetUint16(key any) uint16 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetUint32(key any) uint32 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetUint64(key any) uint64 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetFloat32(key any) float32 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetFloat64(key any) float64 > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetBool(key any) bool > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetTime(key any) time.Time > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetDuration(key any) time.Duration > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetByte(key any) byte > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetBytes(key any) []byte > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetRune(key any) rune > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetRunes(key any) []rune > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** + + #### func (Condition) GetAny(key any) any > 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 + *** ### RefreshTaskCounterEventHandler `STRUCT` @@ -240,7 +306,10 @@ type Option func(task *Task) ```go type Status byte ``` + + #### func (Status) String() string + *** ### Task `STRUCT` @@ -259,33 +328,60 @@ type Task struct { LimitedDuration time.Duration } ``` + + #### func (*Task) IsComplete() bool > 判断任务是否已完成 + *** + + #### func (*Task) IsFailed() bool > 判断任务是否已失败 + *** + + #### func (*Task) IsReward() bool > 判断任务是否已领取奖励 + *** + + #### func (*Task) ReceiveReward() bool > 领取任务奖励,当任务状态为已完成时,才能领取奖励,此时返回 true,并且任务状态变更为已领取奖励 + *** + + #### func (*Task) IncrementCounter(incr int64) *Task > 增加计数器的值,当 incr 为负数时,计数器的值不会发生变化 > - 如果需要溢出计数器,可通过 WithOverflowCounter 设置可溢出的任务计数器 + *** + + #### func (*Task) DecrementCounter(decr int64) *Task > 减少计数器的值,当 decr 为负数时,计数器的值不会发生变化 + *** + + #### func (*Task) AssignConditionValueAndRefresh(key any, value any) *Task > 分配条件值并刷新任务状态 + *** + + #### func (*Task) AssignConditionValueAndRefreshByCondition(condition Condition) *Task > 分配条件值并刷新任务状态 + *** + + #### func (*Task) ResetStatus() *Task > 重置任务状态 > - 该函数会将任务状态重置为已接受状态后,再刷新任务状态 > - 当任务条件变更,例如任务计数要求为 10,已经完成的情况下,将任务计数要求变更为 5 或 20,此时任务状态由于是已完成或已领取状态,不会自动刷新,需要调用该函数刷新任务状态 + *** diff --git a/notify/README.md b/notify/README.md index ea293bb4..91b960f6 100644 --- a/notify/README.md +++ b/notify/README.md @@ -47,11 +47,17 @@ type Manager struct { closeChannel chan struct{} } ``` + + #### func (*Manager) PushNotify(notify Notify) > 推送通知 + *** + + #### func (*Manager) Release() > 释放通知管理器 + *** ### Notify `INTERFACE` diff --git a/notify/notifies/README.md b/notify/notifies/README.md index 17c6322e..eb15f92c 100644 --- a/notify/notifies/README.md +++ b/notify/notifies/README.md @@ -156,8 +156,11 @@ type FeiShu struct { MsgType string } ``` + + #### func (*FeiShu) Format() ( string, error) > 格式化通知内容 + *** ### FeiShuMessage `STRUCT` @@ -173,8 +176,11 @@ type FeiShuRichText struct { content map[string]*FeiShuRichTextContent } ``` + + #### func (*FeiShuRichText) Create(lang string, title string) *FeiShuRichTextContent > 创建一个特定语言和标题的富文本内容 + *** ### FeiShuRichTextContent `STRUCT` @@ -186,45 +192,75 @@ type FeiShuRichTextContent struct { Content [][]map[string]any } ``` + + #### func (*FeiShuRichTextContent) AddText(text string, styles ...string) *FeiShuRichTextContent > 添加文本 + *** + + #### func (*FeiShuRichTextContent) AddUnescapeText(text string, styles ...string) *FeiShuRichTextContent > 添加 unescape 解码的文本 + *** + + #### func (*FeiShuRichTextContent) AddLink(text string, href string, styles ...string) *FeiShuRichTextContent > 添加超链接文本 > - 请确保链接地址的合法性,否则消息会发送失败 + *** + + #### func (*FeiShuRichTextContent) AddAt(userId string, styles ...string) *FeiShuRichTextContent > 添加@的用户 > - @单个用户时,userId 字段必须是有效值 > - @所有人填"all"。 + *** + + #### func (*FeiShuRichTextContent) AddAtWithUsername(userId string, username string, styles ...string) *FeiShuRichTextContent > 添加包含用户名的@用户 > - @单个用户时,userId 字段必须是有效值 > - @所有人填"all"。 + *** + + #### func (*FeiShuRichTextContent) AddImg(imageKey string) *FeiShuRichTextContent > 添加图片 > - imageKey 表示图片的唯一标识,可通过上传图片接口获取 + *** + + #### func (*FeiShuRichTextContent) AddMedia(fileKey string) *FeiShuRichTextContent > 添加视频 > - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取 + *** + + #### func (*FeiShuRichTextContent) AddMediaWithCover(fileKey string, imageKey string) *FeiShuRichTextContent > 添加包含封面的视频 > - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取 > - imageKey 表示图片的唯一标识,可通过上传图片接口获取 + *** + + #### func (*FeiShuRichTextContent) AddEmotion(emojiType string) *FeiShuRichTextContent > 添加表情 > - emojiType 表示表情类型,部分可选值请参见表情文案。 > > 表情文案:https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce + *** + + #### func (*FeiShuRichTextContent) Ok() *FeiShuRichText > 确认完成,将返回 FeiShuRichText 可继续创建多语言富文本 + *** diff --git a/notify/senders/README.md b/notify/senders/README.md index 593f5979..3045153c 100644 --- a/notify/senders/README.md +++ b/notify/senders/README.md @@ -44,8 +44,11 @@ type FeiShu struct { webhook string } ``` + + #### func (*FeiShu) Push(notify notify.Notify) error > 推送通知 +
查看 / 收起单元测试 @@ -53,7 +56,7 @@ type FeiShu struct { ```go func TestFeiShu_Push(t *testing.T) { - fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/d886f30f-814c-47b1-aeb0-b508da0f7f22") + fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/bid") rt := notifies.NewFeiShu(notifies.FeiShuMessageWithRichText(notifies.NewFeiShuRichText().Create("zh_cn", "标题咯").AddText("哈哈哈").Ok())) if err := fs.Push(rt); err != nil { panic(err) diff --git a/planner/pce/README.md b/planner/pce/README.md index 485242c7..cf4d54b0 100644 --- a/planner/pce/README.md +++ b/planner/pce/README.md @@ -135,11 +135,17 @@ type DataTmpl interface { ```go type Exporter struct{} ``` + + #### func (*Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) ( []byte, error) > 导出结构 + *** + + #### func (*Exporter) ExportData(tmpl DataTmpl, data map[any]any) ( []byte, error) > 导出数据 + *** ### Field `INTERFACE` @@ -157,11 +163,20 @@ type Field interface { ```go type Int int ``` + + #### func (Int) TypeName() string + *** + + #### func (Int) Zero() any + *** + + #### func (Int) Parse(value string) any + *** ### Int8 `STRUCT` @@ -169,11 +184,20 @@ type Int int ```go type Int8 int8 ``` + + #### func (Int8) TypeName() string + *** + + #### func (Int8) Zero() any + *** + + #### func (Int8) Parse(value string) any + *** ### Int16 `STRUCT` @@ -181,11 +205,20 @@ type Int8 int8 ```go type Int16 int16 ``` + + #### func (Int16) TypeName() string + *** + + #### func (Int16) Zero() any + *** + + #### func (Int16) Parse(value string) any + *** ### Int32 `STRUCT` @@ -193,11 +226,20 @@ type Int16 int16 ```go type Int32 int32 ``` + + #### func (Int32) TypeName() string + *** + + #### func (Int32) Zero() any + *** + + #### func (Int32) Parse(value string) any + *** ### Int64 `STRUCT` @@ -205,11 +247,20 @@ type Int32 int32 ```go type Int64 int64 ``` + + #### func (Int64) TypeName() string + *** + + #### func (Int64) Zero() any + *** + + #### func (Int64) Parse(value string) any + *** ### Uint `STRUCT` @@ -217,11 +268,20 @@ type Int64 int64 ```go type Uint uint ``` + + #### func (Uint) TypeName() string + *** + + #### func (Uint) Zero() any + *** + + #### func (Uint) Parse(value string) any + *** ### Uint8 `STRUCT` @@ -229,11 +289,20 @@ type Uint uint ```go type Uint8 uint8 ``` + + #### func (Uint8) TypeName() string + *** + + #### func (Uint8) Zero() any + *** + + #### func (Uint8) Parse(value string) any + *** ### Uint16 `STRUCT` @@ -241,11 +310,20 @@ type Uint8 uint8 ```go type Uint16 uint16 ``` + + #### func (Uint16) TypeName() string + *** + + #### func (Uint16) Zero() any + *** + + #### func (Uint16) Parse(value string) any + *** ### Uint32 `STRUCT` @@ -253,11 +331,20 @@ type Uint16 uint16 ```go type Uint32 uint32 ``` + + #### func (Uint32) TypeName() string + *** + + #### func (Uint32) Zero() any + *** + + #### func (Uint32) Parse(value string) any + *** ### Uint64 `STRUCT` @@ -265,11 +352,20 @@ type Uint32 uint32 ```go type Uint64 uint64 ``` + + #### func (Uint64) TypeName() string + *** + + #### func (Uint64) Zero() any + *** + + #### func (Uint64) Parse(value string) any + *** ### Float32 `STRUCT` @@ -277,11 +373,20 @@ type Uint64 uint64 ```go type Float32 float32 ``` + + #### func (Float32) TypeName() string + *** + + #### func (Float32) Zero() any + *** + + #### func (Float32) Parse(value string) any + *** ### Float64 `STRUCT` @@ -289,11 +394,20 @@ type Float32 float32 ```go type Float64 float64 ``` + + #### func (Float64) TypeName() string + *** + + #### func (Float64) Zero() any + *** + + #### func (Float64) Parse(value string) any + *** ### String `STRUCT` @@ -301,11 +415,20 @@ type Float64 float64 ```go type String string ``` + + #### func (String) TypeName() string + *** + + #### func (String) Zero() any + *** + + #### func (String) Parse(value string) any + *** ### Bool `STRUCT` @@ -313,11 +436,20 @@ type String string ```go type Bool bool ``` + + #### func (Bool) TypeName() string + *** + + #### func (Bool) Zero() any + *** + + #### func (Bool) Parse(value string) any + *** ### Byte `STRUCT` @@ -325,11 +457,20 @@ type Bool bool ```go type Byte byte ``` + + #### func (Byte) TypeName() string + *** + + #### func (Byte) Zero() any + *** + + #### func (Byte) Parse(value string) any + *** ### Rune `STRUCT` @@ -337,11 +478,20 @@ type Byte byte ```go type Rune rune ``` + + #### func (Rune) TypeName() string + *** + + #### func (Rune) Zero() any + *** + + #### func (Rune) Parse(value string) any + *** ### Complex64 `STRUCT` @@ -349,11 +499,20 @@ type Rune rune ```go type Complex64 complex64 ``` + + #### func (Complex64) TypeName() string + *** + + #### func (Complex64) Zero() any + *** + + #### func (Complex64) Parse(value string) any + *** ### Complex128 `STRUCT` @@ -361,11 +520,20 @@ type Complex64 complex64 ```go type Complex128 complex128 ``` + + #### func (Complex128) TypeName() string + *** + + #### func (Complex128) Zero() any + *** + + #### func (Complex128) Parse(value string) any + *** ### Uintptr `STRUCT` @@ -373,11 +541,20 @@ type Complex128 complex128 ```go type Uintptr uintptr ``` + + #### func (Uintptr) TypeName() string + *** + + #### func (Uintptr) Zero() any + *** + + #### func (Uintptr) Parse(value string) any + *** ### Double `STRUCT` @@ -385,11 +562,20 @@ type Uintptr uintptr ```go type Double float64 ``` + + #### func (Double) TypeName() string + *** + + #### func (Double) Zero() any + *** + + #### func (Double) Parse(value string) any + *** ### Float `STRUCT` @@ -397,11 +583,20 @@ type Double float64 ```go type Float float32 ``` + + #### func (Float) TypeName() string + *** + + #### func (Float) Zero() any + *** + + #### func (Float) Parse(value string) any + *** ### Long `STRUCT` @@ -409,11 +604,20 @@ type Float float32 ```go type Long int64 ``` + + #### func (Long) TypeName() string + *** + + #### func (Long) Zero() any + *** + + #### func (Long) Parse(value string) any + *** ### Short `STRUCT` @@ -421,11 +625,20 @@ type Long int64 ```go type Short int16 ``` + + #### func (Short) TypeName() string + *** + + #### func (Short) Zero() any + *** + + #### func (Short) Parse(value string) any + *** ### Char `STRUCT` @@ -433,11 +646,20 @@ type Short int16 ```go type Char int8 ``` + + #### func (Char) TypeName() string + *** + + #### func (Char) Zero() any + *** + + #### func (Char) Parse(value string) any + *** ### Number `STRUCT` @@ -445,11 +667,20 @@ type Char int8 ```go type Number float64 ``` + + #### func (Number) TypeName() string + *** + + #### func (Number) Zero() any + *** + + #### func (Number) Parse(value string) any + *** ### Integer `STRUCT` @@ -457,11 +688,20 @@ type Number float64 ```go type Integer int64 ``` + + #### func (Integer) TypeName() string + *** + + #### func (Integer) Zero() any + *** + + #### func (Integer) Parse(value string) any + *** ### Boolean `STRUCT` @@ -469,11 +709,20 @@ type Integer int64 ```go type Boolean bool ``` + + #### func (Boolean) TypeName() string + *** + + #### func (Boolean) Zero() any + *** + + #### func (Boolean) Parse(value string) any + *** ### Loader `STRUCT` @@ -483,11 +732,17 @@ type Loader struct { fields map[string]Field } ``` + + #### func (*Loader) LoadStruct(config Config) *TmplStruct > 加载结构 + *** + + #### func (*Loader) LoadData(config Config) map[any]any > 加载配置并得到配置数据 + *** ### DataInfo `STRUCT` @@ -532,14 +787,23 @@ type TmplField struct { isIndex bool } ``` + + #### func (*TmplField) IsIndex() bool > 是否是索引字段 + *** + + #### func (*TmplField) IsStruct() bool > 是否是结构类型 + *** + + #### func (*TmplField) IsSlice() bool > 是否是切片类型 + *** ### TmplStruct `STRUCT` @@ -552,6 +816,9 @@ type TmplStruct struct { IndexCount int } ``` + + #### func (*TmplStruct) AllChildren() []*TmplStruct > 获取所有子结构 + *** diff --git a/planner/pce/cs/README.md b/planner/pce/cs/README.md index 4c9d3475..1af49eb2 100644 --- a/planner/pce/cs/README.md +++ b/planner/pce/cs/README.md @@ -50,15 +50,33 @@ type Xlsx struct { exportType XlsxExportType } ``` + + #### func (*Xlsx) GetConfigName() string + *** + + #### func (*Xlsx) GetDisplayName() string + *** + + #### func (*Xlsx) GetDescription() string + *** + + #### func (*Xlsx) GetIndexCount() int + *** + + #### func (*Xlsx) GetFields() []pce.DataField + *** + + #### func (*Xlsx) GetData() [][]pce.DataInfo + *** diff --git a/planner/pce/tmpls/README.md b/planner/pce/tmpls/README.md index ac56c864..cd38f583 100644 --- a/planner/pce/tmpls/README.md +++ b/planner/pce/tmpls/README.md @@ -50,13 +50,25 @@ type Golang struct { Templates []*pce.TmplStruct } ``` + + #### func (*Golang) Render(templates ...*pce.TmplStruct) ( string, error) + *** + + #### func (*Golang) GetVariable(config *pce.TmplStruct) string + *** + + #### func (*Golang) GetConfigName(config *pce.TmplStruct) string + *** + + #### func (*Golang) HasIndex(config *pce.TmplStruct) bool + *** ### JSON `STRUCT` @@ -66,5 +78,8 @@ type JSON struct { jsonIter.API } ``` + + #### func (*JSON) Render(data map[any]any) ( string, error) + *** diff --git a/server/README.md b/server/README.md index fe320bcd..bb0d216f 100644 --- a/server/README.md +++ b/server/README.md @@ -70,10 +70,10 @@ server 提供了包含多种网络类型的服务器实现 |`STRUCT`|[MessageType](#struct_MessageType)|暂无描述... |`STRUCT`|[Message](#struct_Message)|服务器消息 |`STRUCT`|[MultipleServer](#struct_MultipleServer)|暂无描述... -|`STRUCT`|[Network](#struct_Network)|暂无描述... +|`STRUCT`|[Network](#struct_Network)|服务器运行的网络模式 |`STRUCT`|[Option](#struct_Option)|暂无描述... |`STRUCT`|[Server](#struct_Server)|网络服务器 -|`INTERFACE`|[Service](#struct_Service)|兼容传统 service 设计模式的接口 +|`INTERFACE`|[Service](#struct_Service)|兼容传统 service 设计模式的接口,通过该接口可以实现更简洁、更具有可读性的服务绑定
@@ -174,6 +174,84 @@ func TestNewBot(t *testing.T) { > - 默认值为 DefaultLowMessageDuration > - 当 duration <= 0 时,表示关闭慢消息检测 +**示例代码:** + +服务器在启动时将阻塞 1s,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 + - 该示例中,将在收到慢消息时关闭服务器 + + +```go + +func ExampleWithLowMessageDuration() { + srv := server.New(server.NetworkNone, server.WithLowMessageDuration(time.Second)) + srv.RegStartFinishEvent(func(srv *server.Server) { + time.Sleep(time.Second) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestWithLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{{name: "TestWithLowMessageDuration", duration: server.DefaultLowMessageDuration}, {name: "TestWithLowMessageDuration_Zero", duration: 0}, {name: "TestWithLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, server.WithLowMessageDuration(c.duration)) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + time.Sleep(server.DefaultLowMessageDuration) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = "addr" + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} + +``` + + +
+ + *** #### func WithAsyncLowMessageDuration(duration time.Duration) Option @@ -181,6 +259,89 @@ func TestNewBot(t *testing.T) { > - 默认值为 DefaultAsyncLowMessageDuration > - 当 duration <= 0 时,表示关闭慢消息检测 +**示例代码:** + +服务器在启动时将发布一条阻塞 1s 的异步消息,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 + - 该示例中,将在收到慢消息时关闭服务器 + + +```go + +func ExampleWithAsyncLowMessageDuration() { + srv := server.New(server.NetworkNone, server.WithAsyncLowMessageDuration(time.Second)) + srv.RegStartFinishEvent(func(srv *server.Server) { + srv.PushAsyncMessage(func() error { + time.Sleep(time.Second) + return nil + }, nil) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestWithAsyncLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{{name: "TestWithAsyncLowMessageDuration", duration: time.Millisecond * 100}, {name: "TestWithAsyncLowMessageDuration_Zero", duration: 0}, {name: "TestWithAsyncLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, server.WithAsyncLowMessageDuration(c.duration)) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + srv.PushAsyncMessage(func() error { + time.Sleep(c.duration) + return nil + }, nil) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = fmt.Sprintf("%s%d", "addr", random.Int(0, 9999)) + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} + +``` + + +
+ + *** #### func WithWebsocketConnInitializer(initializer func (writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error) Option @@ -302,16 +463,17 @@ func TestNewBot(t *testing.T) { **示例代码:** +该案例将创建一个简单的 WebSocket 服务器,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 + - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 + +该案例的输出结果为 true + + ```go func ExampleNew() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) - } + fmt.Println(srv != nil) } ``` @@ -320,24 +482,30 @@ func ExampleNew() { 查看 / 收起单元测试 +该单元测试用于测试以不同的基本参数创建服务器是否存在异常 + + ```go func TestNew(t *testing.T) { - srv := server.New(server.NetworkWebsocket, server.WithPProf()) - srv.RegStartBeforeEvent(func(srv *server.Server) { - fmt.Println("启动前") - }) - srv.RegStartFinishEvent(func(srv *server.Server) { - fmt.Println("启动完成") - }) - srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { - fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) - }) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) + var cases = []struct { + name string + network server.Network + addr string + shouldPanic bool + }{{name: "TestNew_Unknown", addr: "", network: "Unknown", shouldPanic: true}, {name: "TestNew_None", addr: "", network: server.NetworkNone, shouldPanic: false}, {name: "TestNew_None_Addr", addr: "addr", network: server.NetworkNone, shouldPanic: false}, {name: "TestNew_Tcp_AddrEmpty", addr: "", network: server.NetworkTcp, shouldPanic: true}, {name: "TestNew_Tcp_AddrIllegal", addr: "addr", network: server.NetworkTcp, shouldPanic: true}, {name: "TestNew_Tcp_Addr", addr: ":9999", network: server.NetworkTcp, shouldPanic: false}, {name: "TestNew_Tcp4_AddrEmpty", addr: "", network: server.NetworkTcp4, shouldPanic: true}, {name: "TestNew_Tcp4_AddrIllegal", addr: "addr", network: server.NetworkTcp4, shouldPanic: true}, {name: "TestNew_Tcp4_Addr", addr: ":9999", network: server.NetworkTcp4, shouldPanic: false}, {name: "TestNew_Tcp6_AddrEmpty", addr: "", network: server.NetworkTcp6, shouldPanic: true}, {name: "TestNew_Tcp6_AddrIllegal", addr: "addr", network: server.NetworkTcp6, shouldPanic: true}, {name: "TestNew_Tcp6_Addr", addr: ":9999", network: server.NetworkTcp6, shouldPanic: false}, {name: "TestNew_Udp_AddrEmpty", addr: "", network: server.NetworkUdp, shouldPanic: true}, {name: "TestNew_Udp_AddrIllegal", addr: "addr", network: server.NetworkUdp, shouldPanic: true}, {name: "TestNew_Udp_Addr", addr: ":9999", network: server.NetworkUdp, shouldPanic: false}, {name: "TestNew_Udp4_AddrEmpty", addr: "", network: server.NetworkUdp4, shouldPanic: true}, {name: "TestNew_Udp4_AddrIllegal", addr: "addr", network: server.NetworkUdp4, shouldPanic: true}, {name: "TestNew_Udp4_Addr", addr: ":9999", network: server.NetworkUdp4, shouldPanic: false}, {name: "TestNew_Udp6_AddrEmpty", addr: "", network: server.NetworkUdp6, shouldPanic: true}, {name: "TestNew_Udp6_AddrIllegal", addr: "addr", network: server.NetworkUdp6, shouldPanic: true}, {name: "TestNew_Udp6_Addr", addr: ":9999", network: server.NetworkUdp6, shouldPanic: false}, {name: "TestNew_Unix_AddrEmpty", addr: "", network: server.NetworkUnix, shouldPanic: true}, {name: "TestNew_Unix_AddrIllegal", addr: "addr", network: server.NetworkUnix, shouldPanic: true}, {name: "TestNew_Unix_Addr", addr: "addr", network: server.NetworkUnix, shouldPanic: false}, {name: "TestNew_Websocket_AddrEmpty", addr: "", network: server.NetworkWebsocket, shouldPanic: true}, {name: "TestNew_Websocket_AddrIllegal", addr: "addr", network: server.NetworkWebsocket, shouldPanic: true}, {name: "TestNew_Websocket_Addr", addr: ":9999/ws", network: server.NetworkWebsocket, shouldPanic: false}, {name: "TestNew_Http_AddrEmpty", addr: "", network: server.NetworkHttp, shouldPanic: true}, {name: "TestNew_Http_AddrIllegal", addr: "addr", network: server.NetworkHttp, shouldPanic: true}, {name: "TestNew_Http_Addr", addr: ":9999", network: server.NetworkHttp, shouldPanic: false}, {name: "TestNew_Kcp_AddrEmpty", addr: "", network: server.NetworkKcp, shouldPanic: true}, {name: "TestNew_Kcp_AddrIllegal", addr: "addr", network: server.NetworkKcp, shouldPanic: true}, {name: "TestNew_Kcp_Addr", addr: ":9999", network: server.NetworkKcp, shouldPanic: false}, {name: "TestNew_GRPC_AddrEmpty", addr: "", network: server.NetworkGRPC, shouldPanic: true}, {name: "TestNew_GRPC_AddrIllegal", addr: "addr", network: server.NetworkGRPC, shouldPanic: true}, {name: "TestNew_GRPC_Addr", addr: ":9999", network: server.NetworkGRPC, shouldPanic: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if err := super.RecoverTransform(recover()); err != nil && !c.shouldPanic { + debug.PrintStack() + t.Fatal("not should panic, err:", err) + } + }() + if err := server.New(c.network, server.WithLimitLife(time.Millisecond*10)).Run(""); err != nil { + panic(err) + } + }) } } @@ -354,6 +522,34 @@ func TestNew(t *testing.T) { **示例代码:** +这个案例中我们将 `TestService` 绑定到了 `srv` 服务器中,当服务器启动时,将会对 `TestService` 进行初始化 + +其中 `TestService` 的定义如下: +```go + + type TestService struct{} + + func (ts *TestService) OnInit(srv *server.Server) { + srv.RegStartFinishEvent(onStartFinish) + + srv.RegStopEvent(func(srv *server.Server) { + fmt.Println("server stop") + }) + } + + func (ts *TestService) onStartFinish(srv *server.Server) { + fmt.Println("server start finish") + } + +``` + +可以看出,在服务初始化时,该服务向服务器注册了启动完成事件及停止事件。这是我们推荐的编码方式,这样编码有以下好处: + - 具备可控制的初始化顺序,避免 init 产生的各种顺序导致的问题,如配置还未加载完成,即开始进行数据库连接等操作 + - 可以方便的将不同的服务拆分到不同的包中进行管理 + - 当不需要某个服务时,可以直接删除该服务的绑定,而不需要修改其他代码 + - ... + + ```go func ExampleBindService() { @@ -373,10 +569,15 @@ func ExampleBindService() { ```go func TestBindService(t *testing.T) { - srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) - server.BindService(srv, new(TestService)) - if err := srv.RunNone(); err != nil { - t.Fatal(err) + var cases = []struct{ name string }{{name: "TestBindService"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Millisecond)) + server.BindService(srv, new(TestService)) + if err := srv.RunNone(); err != nil { + t.Fatal(err) + } + }) } } @@ -396,25 +597,43 @@ type Bot struct { joined atomic.Bool } ``` + + #### func (*Bot) JoinServer() > 加入服务器 + *** + + #### func (*Bot) LeaveServer() > 离开服务器 + *** + + #### func (*Bot) SetNetworkDelay(delay time.Duration, fluctuation time.Duration) > 设置网络延迟和波动范围 > - delay 延迟 > - fluctuation 波动范围 + *** + + #### func (*Bot) SetWriter(writer io.Writer) > 设置写入器 + *** + + #### func (*Bot) SendPacket(packet []byte) > 发送数据包到服务器 + *** + + #### func (*Bot) SendWSPacket(wst int, packet []byte) > 发送 WebSocket 数据包到服务器 + *** ### BotOption `STRUCT` @@ -432,79 +651,148 @@ type Conn struct { ctx context.Context } ``` + + #### func (*Conn) Ticker() *timer.Ticker > 获取定时器 + *** + + #### func (*Conn) GetServer() *Server > 获取服务器 + *** + + #### func (*Conn) GetOpenTime() time.Time > 获取连接打开时间 + *** + + #### func (*Conn) GetOnlineTime() time.Duration > 获取连接在线时长 + *** + + #### func (*Conn) GetWebsocketRequest() *http.Request > 获取websocket请求 + *** + + #### func (*Conn) IsBot() bool > 是否是机器人连接 + *** + + #### func (*Conn) RemoteAddr() net.Addr > 获取远程地址 + *** + + #### func (*Conn) GetID() string > 获取连接ID > - 为远程地址的字符串形式 + *** + + #### func (*Conn) GetIP() string > 获取连接IP + *** + + #### func (*Conn) IsClosed() bool > 是否已经关闭 + *** + + #### func (*Conn) SetData(key any, value any) *Conn > 设置连接数据,该数据将在连接关闭前始终存在 + *** + + #### func (*Conn) GetData(key any) any > 获取连接数据 + *** + + #### func (*Conn) ViewData() map[any]any > 查看只读的连接数据 + *** + + #### func (*Conn) SetMessageData(key any, value any) *Conn > 设置消息数据,该数据将在消息处理完成后释放 + *** + + #### func (*Conn) GetMessageData(key any) any > 获取消息数据 + *** + + #### func (*Conn) ReleaseData() *Conn > 释放数据 + *** + + #### func (*Conn) IsWebsocket() bool > 是否是websocket连接 + *** + + #### func (*Conn) GetWST() int > 获取本次 websocket 消息类型 > - 默认将与发送类型相同 + *** + + #### func (*Conn) SetWST(wst int) *Conn > 设置本次 websocket 消息类型 + *** + + #### func (*Conn) PushAsyncMessage(caller func () error, callback func (err error), mark ...log.Field) > 推送异步消息,该消息将通过 Server.PushShuntAsyncMessage 函数推送 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Conn) PushUniqueAsyncMessage(name string, caller func () error, callback func (err error), mark ...log.Field) > 推送唯一异步消息,该消息将通过 Server.PushUniqueShuntAsyncMessage 函数推送 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 > - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 + *** + + #### func (*Conn) Write(packet []byte, callback ...func (err error)) > 向连接中写入数据 + *** + + #### func (*Conn) Close(err ...error) > 关闭连接 + *** ### ConsoleParams `STRUCT` @@ -512,26 +800,47 @@ type Conn struct { ```go type ConsoleParams map[string][]string ``` + + #### func (ConsoleParams) Get(key string) string > 获取参数值 + *** + + #### func (ConsoleParams) GetValues(key string) []string > 获取参数值 + *** + + #### func (ConsoleParams) GetValueNum(key string) int > 获取参数值数量 + *** + + #### func (ConsoleParams) Has(key string) bool > 是否存在参数 + *** + + #### func (ConsoleParams) Add(key string, value string) > 添加参数 + *** + + #### func (ConsoleParams) Del(key string) > 删除参数 + *** + + #### func (ConsoleParams) Clear() > 清空参数 + *** ### MessageReadyEventHandler `STRUCT` @@ -549,7 +858,10 @@ type Http[Context any] struct { *HttpRouter[Context] } ``` + + #### func (*Http) Gin() *gin.Engine + *** ### HttpContext `STRUCT` @@ -559,11 +871,17 @@ type HttpContext struct { *gin.Context } ``` + + #### func (*HttpContext) Gin() *gin.Context > 获取 gin.Context + *** + + #### func (*HttpContext) ReadTo(dest any) error > 读取请求数据到指定结构体,如果失败则返回错误 + *** ### HandlerFunc `STRUCT` @@ -587,69 +905,123 @@ type HttpRouter[Context any] struct { packer ContextPacker[Context] } ``` + + #### func (*HttpRouter) Handle(httpMethod string, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 使用给定的路径和方法注册新的请求句柄和中间件 > - 最后一个处理程序应该是真正的处理程序,其他处理程序应该是可以而且应该在不同路由之间共享的中间件。 + *** + + #### func (*HttpRouter) POST(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("POST", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) GET(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("GET", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) DELETE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("DELETE", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) PATCH(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("PATCH", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) PUT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("PUT", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) OPTIONS(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("OPTIONS", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) HEAD(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("HEAD", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) CONNECT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("CONNECT", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) TRACE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 是 Handle("TRACE", path, handlers) 的快捷方式 + *** + + #### func (*HttpRouter) Any(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 注册一个匹配所有 HTTP 方法的路由 > - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. + *** + + #### func (*HttpRouter) Match(methods []string, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 注册一个匹配指定 HTTP 方法的路由 > - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. + *** + + #### func (*HttpRouter) StaticFile(relativePath string, filepath string) *HttpRouter[Context] > 注册单个路由以便为本地文件系统的单个文件提供服务。 > - 例如: StaticFile("favicon.ico", "./resources/favicon.ico") + *** + + #### func (*HttpRouter) StaticFileFS(relativePath string, filepath string, fs http.FileSystem) *HttpRouter[Context] > 与 `StaticFile` 类似,但可以使用自定义的 `http.FileSystem` 代替。 > - 例如: StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) > - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir + *** + + #### func (*HttpRouter) Static(relativePath string, root string) *HttpRouter[Context] > 提供来自给定文件系统根目录的文件。 > - 例如: Static("/static", "/var/www") + *** + + #### func (*HttpRouter) StaticFS(relativePath string, fs http.FileSystem) *HttpRouter[Context] > 与 `Static` 类似,但可以使用自定义的 `http.FileSystem` 代替。 > - 例如: StaticFS("/static", Dir{"/var/www", false}) > - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir + *** + + #### func (*HttpRouter) Group(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] > 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 > - 例如: v1 := slf.Group("/v1") + *** + + #### func (*HttpRouter) Use(middleware ...HandlerFunc[Context]) *HttpRouter[Context] > 将中间件附加到路由组。 + *** ### HttpWrapperHandleFunc `STRUCT` @@ -666,56 +1038,107 @@ type HttpWrapper[CTX any] struct { packHandle func(ctx *gin.Context) CTX } ``` + + #### func (*HttpWrapper) Handle(httpMethod string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 处理请求 + *** + + #### func (*HttpWrapper) Use(middleware ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 使用中间件 + *** + + #### func (*HttpWrapper) GET(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 GET 请求 + *** + + #### func (*HttpWrapper) POST(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 POST 请求 + *** + + #### func (*HttpWrapper) DELETE(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 DELETE 请求 + *** + + #### func (*HttpWrapper) PATCH(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 PATCH 请求 + *** + + #### func (*HttpWrapper) PUT(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 PUT 请求 + *** + + #### func (*HttpWrapper) OPTIONS(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 OPTIONS 请求 + *** + + #### func (*HttpWrapper) HEAD(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 HEAD 请求 + *** + + #### func (*HttpWrapper) Trace(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 Trace 请求 + *** + + #### func (*HttpWrapper) Connect(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 Connect 请求 + *** + + #### func (*HttpWrapper) Any(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册 Any 请求 + *** + + #### func (*HttpWrapper) Match(methods []string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] > 注册与您声明的指定方法相匹配的路由。 + *** + + #### func (*HttpWrapper) StaticFile(relativePath string, filepath string) *HttpWrapper[CTX] > 注册 StaticFile 请求 + *** + + #### func (*HttpWrapper) Static(relativePath string, root string) *HttpWrapper[CTX] > 注册 Static 请求 + *** + + #### func (*HttpWrapper) StaticFS(relativePath string, fs http.FileSystem) *HttpWrapper[CTX] > 注册 StaticFS 请求 + *** + + #### func (*HttpWrapper) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 + *** ### HttpWrapperGroup `STRUCT` @@ -726,32 +1149,59 @@ type HttpWrapperGroup[CTX any] struct { group *gin.RouterGroup } ``` + + #### func (*HttpWrapperGroup) Handle(httpMethod string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 处理请求 + *** + + #### func (*HttpWrapperGroup) Use(middleware ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 使用中间件 + *** + + #### func (*HttpWrapperGroup) GET(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 GET 请求 + *** + + #### func (*HttpWrapperGroup) POST(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 POST 请求 + *** + + #### func (*HttpWrapperGroup) DELETE(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 DELETE 请求 + *** + + #### func (*HttpWrapperGroup) PATCH(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 PATCH 请求 + *** + + #### func (*HttpWrapperGroup) PUT(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 PUT 请求 + *** + + #### func (*HttpWrapperGroup) OPTIONS(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 注册 OPTIONS 请求 + *** + + #### func (*HttpWrapperGroup) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] > 创建分组 + *** ### MessageType `STRUCT` @@ -759,8 +1209,11 @@ type HttpWrapperGroup[CTX any] struct { ```go type MessageType byte ``` + + #### func (MessageType) String() string > 返回消息类型的字符串表示 + *** ### Message `STRUCT` @@ -780,13 +1233,22 @@ type Message struct { t MessageType } ``` + + #### func (*Message) GetProducer() string + *** + + #### func (*Message) MessageType() MessageType > 返回消息类型 + *** + + #### func (*Message) String() string > 返回消息的字符串表示 + *** ### MultipleServer `STRUCT` @@ -798,21 +1260,57 @@ type MultipleServer struct { exitEventHandles []func() } ``` + + #### func (*MultipleServer) Run() + *** + + #### func (*MultipleServer) RegExitEvent(handle func ()) > 注册退出事件 + *** + + #### func (*MultipleServer) OnExitEvent() + *** ### Network `STRUCT` - +服务器运行的网络模式 + - 根据不同的网络模式,服务器将会产生不同的行为,该类型将在服务器创建时候指定 + +服务器支持的网络模式如下: + - NetworkNone 该模式下不监听任何网络端口,仅开启消息队列,适用于纯粹的跨服服务器等情况 + - NetworkTcp 该模式下将会监听 TCP 协议的所有地址,包括 IPv4 和 IPv6 + - NetworkTcp4 该模式下将会监听 TCP 协议的 IPv4 地址 + - NetworkTcp6 该模式下将会监听 TCP 协议的 IPv6 地址 + - NetworkUdp 该模式下将会监听 UDP 协议的所有地址,包括 IPv4 和 IPv6 + - NetworkUdp4 该模式下将会监听 UDP 协议的 IPv4 地址 + - NetworkUdp6 该模式下将会监听 UDP 协议的 IPv6 地址 + - NetworkUnix 该模式下将会监听 Unix 协议的地址 + - NetworkHttp 该模式下将会监听 HTTP 协议的地址 + - NetworkWebsocket 该模式下将会监听 Websocket 协议的地址 + - NetworkKcp 该模式下将会监听 KCP 协议的地址 + - NetworkGRPC 该模式下将会监听 GRPC 协议的地址 ```go type Network string ``` + + #### func (Network) IsSocket() bool -> 返回当前服务器的网络模式是否为 Socket 模式 +> 返回当前服务器的网络模式是否为 Socket 模式,目前为止仅有如下几种模式为 Socket 模式: +> - NetworkTcp +> - NetworkTcp4 +> - NetworkTcp6 +> - NetworkUdp +> - NetworkUdp4 +> - NetworkUdp6 +> - NetworkUnix +> - NetworkKcp +> - NetworkWebsocket + *** ### Option `STRUCT` @@ -849,6 +1347,8 @@ type Server struct { services []func() } ``` + + #### func (*Server) Run(addr string) (err error) > 使用特定地址运行服务器 > - server.NetworkTcp (addr:":8888") @@ -862,15 +1362,18 @@ type Server struct { > - server.NetworkWebsocket (addr:":8888/ws") > - server.NetworkKcp (addr:":8888") > - server.NetworkNone (addr:"") + **示例代码:** +该案例将创建一个简单的 WebSocket 服务器并启动监听 `:9999/` 作为 WebSocket 监听地址,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 + - 当服务器启动失败后,将会返回错误信息并触发 panic + - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 + + ```go func ExampleServer_Run() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) if err := srv.Run(":9999"); err != nil { panic(err) } @@ -879,76 +1382,199 @@ func ExampleServer_Run() { ``` *** + + #### func (*Server) IsSocket() bool -> 是否是 Socket 模式 +> 通过执行 Network.IsSocket 函数检查该服务器是否是 Socket 模式 + +**示例代码:** + +该案例将创建两个不同类型的服务器,其中 WebSocket 是一个 Socket 服务器,而 Http 是一个非 Socket 服务器 + +可知案例输出结果为: + - true + - false + + +```go + +func ExampleServer_IsSocket() { + srv1 := server.New(server.NetworkWebsocket) + fmt.Println(srv1.IsSocket()) + srv2 := server.New(server.NetworkHttp) + fmt.Println(srv2.IsSocket()) +} + +``` + +
+查看 / 收起单元测试 + + +这个测试检查了各个类型的服务器是否为 Socket 模式。如需查看为 Socket 模式的网络类型,请参考 [` Network.IsSocket` ](#struct_Network_IsSocket) + + +```go + +func TestServer_IsSocket(t *testing.T) { + var cases = []struct { + name string + network server.Network + expect bool + }{{name: "TestServer_IsSocket_None", network: server.NetworkNone, expect: false}, {name: "TestServer_IsSocket_Tcp", network: server.NetworkTcp, expect: true}, {name: "TestServer_IsSocket_Tcp4", network: server.NetworkTcp4, expect: true}, {name: "TestServer_IsSocket_Tcp6", network: server.NetworkTcp6, expect: true}, {name: "TestServer_IsSocket_Udp", network: server.NetworkUdp, expect: true}, {name: "TestServer_IsSocket_Udp4", network: server.NetworkUdp4, expect: true}, {name: "TestServer_IsSocket_Udp6", network: server.NetworkUdp6, expect: true}, {name: "TestServer_IsSocket_Unix", network: server.NetworkUnix, expect: true}, {name: "TestServer_IsSocket_Http", network: server.NetworkHttp, expect: false}, {name: "TestServer_IsSocket_Websocket", network: server.NetworkWebsocket, expect: true}, {name: "TestServer_IsSocket_Kcp", network: server.NetworkKcp, expect: true}, {name: "TestServer_IsSocket_GRPC", network: server.NetworkGRPC, expect: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + s := server.New(c.network) + if s.IsSocket() != c.expect { + t.Fatalf("expect: %v, got: %v", c.expect, s.IsSocket()) + } + }) + } +} + +``` + + +
+ + *** + + #### func (*Server) RunNone() error > 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器 + +**示例代码:** + +RunNone 函数并没有特殊的意义,该函数内部调用了 `srv.Run("")` 函数,仅是一个语法糖,用来表示服务器不需要监听任何地址 + + +```go + +func ExampleServer_RunNone() { + srv := server.New(server.NetworkNone) + if err := srv.RunNone(); err != nil { + panic(err) + } +} + +``` + *** + + #### func (*Server) Context() context.Context > 获取服务器上下文 + *** + + #### func (*Server) TimeoutContext(timeout time.Duration) ( context.Context, context.CancelFunc) > 获取服务器超时上下文,context.WithTimeout 的简写 + *** + + #### func (*Server) Ticker() *timer.Ticker > 获取服务器定时器 + *** + + #### func (*Server) Shutdown() > 主动停止运行服务器 + *** + + #### func (*Server) GRPCServer() *grpc.Server > 当网络类型为 NetworkGRPC 时将被允许获取 grpc 服务器,否则将会发生 panic + *** + + #### func (*Server) HttpRouter() gin.IRouter > 当网络类型为 NetworkHttp 时将被允许获取路由器进行路由注册,否则将会发生 panic > - 通过该函数注册的路由将无法在服务器关闭时正常等待请求结束 > > Deprecated: 从 Minotaur 0.0.29 开始,由于设计原因已弃用,该函数将直接返回 *gin.Server 对象,导致无法正常的对请求结束时进行处理 + *** + + #### func (*Server) HttpServer() *Http[*HttpContext] > 替代 HttpRouter 的函数,返回一个 *Http[*HttpContext] 对象 > - 通过该函数注册的路由将在服务器关闭时正常等待请求结束 > - 如果需要自行包装 Context 对象,可以使用 NewHttpHandleWrapper 方法 + *** + + #### func (*Server) GetMessageCount() int64 > 获取当前服务器中消息的数量 + *** + + #### func (*Server) UseShunt(conn *Conn, name string) > 切换连接所使用的消息分流渠道,当分流渠道 name 不存在时将会创建一个新的分流渠道,否则将会加入已存在的分流渠道 > - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道且为分流消息类型时,将会使用指定的消息分流渠道进行消息分发 > - 分流渠道会在连接断开时标记为驱逐状态,当分流渠道中的所有消息处理完毕且没有新连接使用时,将会被清除 + *** + + #### func (*Server) HasShunt(name string) bool > 检查特定消息分流渠道是否存在 + *** + + #### func (*Server) GetConnCurrShunt(conn *Conn) string > 获取连接当前所使用的消息分流渠道 + *** + + #### func (*Server) GetShuntNum() int > 获取消息分流渠道数量 + *** + + #### func (*Server) PushSystemMessage(handler func (), mark ...log.Field) > 向服务器中推送 MessageTypeSystem 消息 > - 系统消息仅包含一个可执行函数,将在系统分发器中执行 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Server) PushAsyncMessage(caller func () error, callback func (err error), mark ...log.Field) > 向服务器中推送 MessageTypeAsync 消息 > - 异步消息将在服务器的异步消息队列中进行处理,处理完成 caller 的阻塞操作后,将会通过系统消息执行 callback 函数 > - callback 函数将在异步消息处理完成后进行调用,无论过程是否产生 err,都将被执行,允许为 nil > - 需要注意的是,为了避免并发问题,caller 函数请仅处理阻塞操作,其他操作应该在 callback 函数中进行 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Server) PushShuntAsyncMessage(conn *Conn, caller func () error, callback func (err error), mark ...log.Field) > 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 > - 需要注意的是,当未指定 UseShunt 时,将会通过 PushAsyncMessage 进行转发 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Server) PushPacketMessage(conn *Conn, wst int, packet []byte, mark ...log.Field) > 向服务器中推送 MessageTypePacket 消息 > - 当存在 UseShunt 的选项时,将会根据选项中的 shuntMatcher 进行分发,否则将在系统分发器中处理消息 + *** + + #### func (*Server) PushTickerMessage(name string, caller func (), mark ...log.Field) > 向服务器中推送 MessageTypeTicker 消息 > - 通过该函数推送定时消息,当消息触发时将在系统分发器中处理消息 @@ -957,40 +1583,66 @@ func ExampleServer_Run() { > > 定时消息执行不会有特殊的处理,仅标记为定时任务,也就是允许将各类函数通过该消息发送处理,但是并不建议 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Server) PushShuntTickerMessage(conn *Conn, name string, caller func (), mark ...log.Field) > 向特定分发器中推送 MessageTypeTicker 消息,消息执行与 MessageTypeTicker 一致 > - 需要注意的是,当未指定 UseShunt 时,将会通过 PushTickerMessage 进行转发 > - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 + *** + + #### func (*Server) PushUniqueAsyncMessage(unique string, caller func () error, callback func (err error), mark ...log.Field) > 向服务器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 > - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 + *** + + #### func (*Server) PushUniqueShuntAsyncMessage(conn *Conn, unique string, caller func () error, callback func (err error), mark ...log.Field) > 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 > - 需要注意的是,当未指定 UseShunt 时,将会通过系统分流渠道进行转发 > - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 + *** + + #### func (*Server) PushShuntMessage(conn *Conn, caller func (), mark ...log.Field) > 向特定分发器中推送 MessageTypeShunt 消息,消息执行与 MessageTypeSystem 一致,不同的是将会在特定分发器中执行 + *** + + #### func (*Server) GetDurationMessageCount() int64 > 获取当前 WithMessageStatistics 设置的 duration 期间的消息量 + *** + + #### func (*Server) GetDurationMessageCountByOffset(offset int) int64 > 获取特定偏移次数的 WithMessageStatistics 设置的 duration 期间的消息量 > - 该值小于 0 时,将与 GetDurationMessageCount 无异,否则将返回 +n 个期间的消息量,例如 duration 为 1 分钟,limit 为 10,那么 offset 为 1 的情况下,获取的则是上一分钟消息量 + *** + + #### func (*Server) GetAllDurationMessageCount() []int64 > 获取所有 WithMessageStatistics 设置的 duration 期间的消息量 + *** + + #### func (*Server) HasMessageStatistics() bool > 是否了开启消息统计 + *** ### Service `INTERFACE` -兼容传统 service 设计模式的接口 +兼容传统 service 设计模式的接口,通过该接口可以实现更简洁、更具有可读性的服务绑定 + - 在这之前,我们在实现功能上会将 Server 进行全局存储,之后通过 init 函数进行初始化,这样的顺序是不可控的。 ```go type Service interface { OnInit(srv *Server) diff --git a/server/client/README.md b/server/client/README.md index 1d7658e3..25293ce0 100644 --- a/server/client/README.md +++ b/server/client/README.md @@ -78,23 +78,38 @@ type Client struct { block chan struct{} } ``` + + #### func (*Client) Run(block ...bool) error > 运行客户端,当客户端已运行时,会先关闭客户端再重新运行 > - block 以阻塞方式运行 + *** + + #### func (*Client) RunByBufferSize(size int, block ...bool) error > 指定写入循环缓冲区大小运行客户端,当客户端已运行时,会先关闭客户端再重新运行 > - block 以阻塞方式运行 + *** + + #### func (*Client) IsConnected() bool > 是否已连接 + *** + + #### func (*Client) Close(err ...error) > 关闭 + *** + + #### func (*Client) WriteWS(wst int, packet []byte, callback ...func (err error)) > 向连接中写入指定 websocket 数据类型 > - wst: websocket模式中指定消息类型 +
查看 / 收起单元测试 @@ -133,11 +148,17 @@ func TestClient_WriteWS(t *testing.T) { *** + + #### func (*Client) Write(packet []byte, callback ...func (err error)) > 向连接中写入数据 + *** + + #### func (*Client) GetServerAddr() string > 获取服务器地址 + *** ### Core `INTERFACE` @@ -177,15 +198,30 @@ type TCP struct { closed bool } ``` + + #### func (*TCP) Run(runState chan error, receive func (wst int, packet []byte)) + *** + + #### func (*TCP) Write(packet *Packet) error + *** + + #### func (*TCP) Close() + *** + + #### func (*TCP) GetServerAddr() string + *** + + #### func (*TCP) Clone() Core + *** ### UnixDomainSocket `STRUCT` @@ -197,9 +233,15 @@ type UnixDomainSocket struct { closed bool } ``` + + #### func (*UnixDomainSocket) Run(runState chan error, receive func (wst int, packet []byte)) + *** + + #### func (*UnixDomainSocket) Write(packet *Packet) error +
查看 / 收起单元测试 @@ -243,11 +285,20 @@ func TestUnixDomainSocket_Write(t *testing.T) { *** + + #### func (*UnixDomainSocket) Close() + *** + + #### func (*UnixDomainSocket) GetServerAddr() string + *** + + #### func (*UnixDomainSocket) Clone() Core + *** ### Websocket `STRUCT` @@ -260,13 +311,28 @@ type Websocket struct { mu sync.Mutex } ``` + + #### func (*Websocket) Run(runState chan error, receive func (wst int, packet []byte)) + *** + + #### func (*Websocket) Write(packet *Packet) error + *** + + #### func (*Websocket) Close() + *** + + #### func (*Websocket) GetServerAddr() string + *** + + #### func (*Websocket) Clone() Core + *** diff --git a/server/gateway/README.md b/server/gateway/README.md index e647ee3d..a0b776ba 100644 --- a/server/gateway/README.md +++ b/server/gateway/README.md @@ -129,18 +129,30 @@ type Endpoint struct { cps int } ``` + + #### func (*Endpoint) GetName() string > 获取端点名称 + *** + + #### func (*Endpoint) GetAddress() string > 获取端点地址 + *** + + #### func (*Endpoint) GetState() float64 > 获取端点健康值 + *** + + #### func (*Endpoint) Forward(conn *server.Conn, packet []byte, callback ...func (err error)) > 转发数据包到该端点 > - 端点在处理数据包时,应区分数据包为普通直连数据包还是网关数据包。可通过 UnmarshalGatewayOutPacket 进行数据包解析,当解析失败且无其他数据包协议时,可认为该数据包为普通直连数据包。 + *** ### EndpointOption `STRUCT` @@ -186,8 +198,11 @@ type Gateway struct { cceLock sync.RWMutex } ``` + + #### func (*Gateway) Run(addr string) error > 运行网关 +
查看 / 收起单元测试 @@ -217,23 +232,38 @@ func TestGateway_Run(t *testing.T) { *** + + #### func (*Gateway) Shutdown() > 关闭网关 + *** + + #### func (*Gateway) Server() *server.Server > 获取网关服务器核心 + *** + + #### func (*Gateway) GetEndpoint(name string) ( *Endpoint, error) > 获取一个可用的端点 > - name: 端点名称 + *** + + #### func (*Gateway) GetConnEndpoint(name string, conn *server.Conn) ( *Endpoint, error) > 获取一个可用的端点,如果客户端已经连接到了某个端点,将优先返回该端点 > - 当连接到的端点不可用或没有连接记录时,效果同 GetEndpoint 相同 > - 当连接行为为有状态时,推荐使用该方法 + *** + + #### func (*Gateway) SwitchEndpoint(source *Endpoint, dest *Endpoint) > 将端点端点的所有连接切换到另一个端点 + *** ### Option `STRUCT` @@ -250,7 +280,13 @@ type Scanner interface { GetInterval() time.Duration } ``` + + #### func (*Scanner) GetEndpoints() ( []*gateway.Endpoint, error) + *** + + #### func (*Scanner) GetInterval() time.Duration + *** diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md index 4325cdf6..bda48529 100644 --- a/server/internal/dispatcher/README.md +++ b/server/internal/dispatcher/README.md @@ -176,14 +176,23 @@ type Action[P Producer, M Message[P]] struct { d *Dispatcher[P, M] } ``` + + #### func (*Action) Name() string > 获取消息分发器名称 + *** + + #### func (*Action) UnExpel() > 取消特定生产者的驱逐计划 + *** + + #### func (*Action) Expel() > 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭 + *** ### Handler `STRUCT` @@ -221,11 +230,12 @@ type Dispatcher[P Producer, M Message[P]] struct { abort chan struct{} } ``` + + #### func (*Dispatcher) SetProducerDoneHandler(p P, handler func (p P, dispatcher *Action[P, M])) *Dispatcher[P, M] -> 设置特定生产者所有消息处理完成时的回调函数 +> 设置特定生产者的所有消息处理完成时的回调函数 > - 如果 handler 为 nil,则会删除该生产者的回调函数 -> -> 需要注意的是,该 handler 中 +
查看 / 收起单元测试 @@ -271,8 +281,11 @@ func TestDispatcher_SetProducerDoneHandler(t *testing.T) { *** + + #### func (*Dispatcher) SetClosedHandler(handler func (dispatcher *Action[P, M])) *Dispatcher[P, M] > 设置消息分发器关闭时的回调函数 +
查看 / 收起单元测试 @@ -319,8 +332,11 @@ func TestDispatcher_SetClosedHandler(t *testing.T) { *** + + #### func (*Dispatcher) Name() string > 获取消息分发器名称 +
查看 / 收起单元测试 @@ -348,14 +364,23 @@ func TestDispatcher_Name(t *testing.T) { *** + + #### func (*Dispatcher) Unique(name string) bool > 设置唯一消息键,返回是否已存在 + *** + + #### func (*Dispatcher) AntiUnique(name string) > 取消唯一消息键 + *** + + #### func (*Dispatcher) Expel() > 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭 +
查看 / 收起单元测试 @@ -402,8 +427,11 @@ func TestDispatcher_Expel(t *testing.T) { *** + + #### func (*Dispatcher) UnExpel() > 取消特定生产者的驱逐计划 +
查看 / 收起单元测试 @@ -452,12 +480,18 @@ func TestDispatcher_UnExpel(t *testing.T) { *** + + #### func (*Dispatcher) IncrCount(producer P, i int64) > 主动增量设置特定生产者的消息计数,这在等待异步消息完成后再关闭消息分发器时非常有用 > - 如果 i 为负数,则会减少消息计数 + *** + + #### func (*Dispatcher) Put(message M) > 将消息放入分发器 +
查看 / 收起单元测试 @@ -498,8 +532,11 @@ func TestDispatcher_Put(t *testing.T) { *** + + #### func (*Dispatcher) Start() *Dispatcher[P, M] > 以非阻塞的方式开始进行消息分发,当消息分发器中没有任何消息并且处于驱逐计划 Expel 时,将会自动关闭 +
查看 / 收起单元测试 @@ -540,8 +577,11 @@ func TestDispatcher_Start(t *testing.T) { *** + + #### func (*Dispatcher) Closed() bool > 判断消息分发器是否已关闭 +
查看 / 收起单元测试 @@ -594,11 +634,17 @@ type Manager[P Producer, M Message[P]] struct { createdHandler func(name string) } ``` + + #### func (*Manager) Wait() > 等待所有消息分发器关闭 + *** + + #### func (*Manager) SetDispatcherClosedHandler(handler func (name string)) *Manager[P, M] > 设置消息分发器关闭时的回调函数 +
查看 / 收起单元测试 @@ -637,8 +683,11 @@ func TestManager_SetDispatcherClosedHandler(t *testing.T) { *** + + #### func (*Manager) SetDispatcherCreatedHandler(handler func (name string)) *Manager[P, M] > 设置消息分发器创建时的回调函数 +
查看 / 收起单元测试 @@ -677,8 +726,11 @@ func TestManager_SetDispatcherCreatedHandler(t *testing.T) { *** + + #### func (*Manager) HasDispatcher(name string) bool > 检查是否存在指定名称的消息分发器 +
查看 / 收起单元测试 @@ -714,8 +766,11 @@ func TestManager_HasDispatcher(t *testing.T) { *** + + #### func (*Manager) GetDispatcherNum() int > 获取当前正在工作的消息分发器数量 +
查看 / 收起单元测试 @@ -758,8 +813,11 @@ func TestManager_GetDispatcherNum(t *testing.T) { *** + + #### func (*Manager) GetSystemDispatcher() *Dispatcher[P, M] > 获取系统消息分发器 +
查看 / 收起单元测试 @@ -786,8 +844,11 @@ func TestManager_GetSystemDispatcher(t *testing.T) { *** + + #### func (*Manager) GetDispatcher(p P) *Dispatcher[P, M] > 获取生产者正在使用的消息分发器,如果生产者没有绑定消息分发器,则会返回系统消息分发器 +
查看 / 收起单元测试 @@ -818,8 +879,11 @@ func TestManager_GetDispatcher(t *testing.T) { *** + + #### func (*Manager) BindProducer(p P, name string) > 绑定生产者使用特定的消息分发器,如果生产者已经绑定了消息分发器,则会先解绑 +
查看 / 收起单元测试 @@ -850,8 +914,11 @@ func TestManager_BindProducer(t *testing.T) { *** + + #### func (*Manager) UnBindProducer(p P) > 解绑生产者使用特定的消息分发器 +
查看 / 收起单元测试 diff --git a/server/internal/logger/README.md b/server/internal/logger/README.md index ca79363d..4b644d8c 100644 --- a/server/internal/logger/README.md +++ b/server/internal/logger/README.md @@ -30,7 +30,10 @@ ```go type Ants struct{} ``` + + #### func (*Ants) Printf(format string, args ...interface {}) + *** ### GNet `STRUCT` @@ -38,13 +41,28 @@ type Ants struct{} ```go type GNet struct{} ``` + + #### func (*GNet) Debugf(format string, args ...interface {}) + *** + + #### func (*GNet) Infof(format string, args ...interface {}) + *** + + #### func (*GNet) Warnf(format string, args ...interface {}) + *** + + #### func (*GNet) Errorf(format string, args ...interface {}) + *** + + #### func (*GNet) Fatalf(format string, args ...interface {}) + *** diff --git a/server/lockstep/README.md b/server/lockstep/README.md index 3452107e..7c74cfc9 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -152,58 +152,106 @@ type Lockstep[ClientID comparable, Command any] struct { lockstepStoppedEventHandles []StoppedEventHandle[ClientID, Command] } ``` + + #### func (*Lockstep) JoinClient(client Client[ClientID]) > 将客户端加入到广播队列中,通常在开始广播前使用 > - 如果客户端在开始广播后加入,将丢失之前的帧数据,如要从特定帧开始追帧请使用 JoinClientWithFrame + *** + + #### func (*Lockstep) JoinClientWithFrame(client Client[ClientID], frameIndex int64) > 加入客户端到广播队列中,并从特定帧开始追帧 > - 可用于重连及状态同步、帧同步混用的情况 > - 混用:服务端记录指令时同时做一次状态计算,新客户端加入时直接同步当前状态,之后从特定帧开始广播 + *** + + #### func (*Lockstep) GetClientCount() int > 获取客户端数量 + *** + + #### func (*Lockstep) DropCache(handler func (frame int64) bool) > 丢弃特定帧的缓存,当 handler 返回 true 时将丢弃缓存 + *** + + #### func (*Lockstep) LeaveClient(clientId ClientID) > 将客户端从广播队列中移除 + *** + + #### func (*Lockstep) StartBroadcast() > 开始广播 > - 在开始广播后将持续按照设定的帧率进行帧数推进,并在每一帧推进时向客户端进行同步,需提前将客户端加入广播队列 JoinClient > - 广播过程中使用 AddCommand 将该帧数据追加到当前帧中 + *** + + #### func (*Lockstep) StopBroadcast() > 停止广播 + *** + + #### func (*Lockstep) IsRunning() bool > 是否正在广播 + *** + + #### func (*Lockstep) AddCommand(command Command) > 添加命令到当前帧 + *** + + #### func (*Lockstep) AddCommands(commands []Command) > 添加命令到当前帧 + *** + + #### func (*Lockstep) GetCurrentFrame() int64 > 获取当前帧 + *** + + #### func (*Lockstep) GetClientCurrentFrame(clientId ClientID) int64 > 获取客户端当前帧 + *** + + #### func (*Lockstep) GetFrameLimit() int64 > 获取帧上限 > - 未设置时将返回0 + *** + + #### func (*Lockstep) GetCurrentCommands() []Command > 获取当前帧还未结束时的所有指令 + *** + + #### func (*Lockstep) RegLockstepStoppedEvent(handle StoppedEventHandle[ClientID, Command]) > 当广播停止时将触发被注册的事件处理函数 + *** + + #### func (*Lockstep) OnLockstepStoppedEvent() + *** ### Option `STRUCT` diff --git a/server/router/README.md b/server/router/README.md index 993cbc57..f248399e 100644 --- a/server/router/README.md +++ b/server/router/README.md @@ -60,8 +60,11 @@ func ExampleNewMultistage() { ```go type MultistageBind[HandleFunc any] func(HandleFunc) ``` + + #### func (MultistageBind) Bind(handleFunc HandleFunc) > 将处理函数绑定到预设的路由中 + *** ### Multistage `STRUCT` @@ -74,9 +77,12 @@ type Multistage[HandleFunc any] struct { trim func(route any) any } ``` + + #### func (*Multistage) Register(routes ...any) MultistageBind[HandleFunc] > 注册路由是结合 Sub 和 Route 的快捷方式,用于一次性注册多级路由 > - 该函数将返回一个注册函数,可通过调用其将路由绑定到特定处理函数,例如:router.Register("a", "b").Bind(onExec()) + **示例代码:** ```go @@ -90,8 +96,11 @@ func ExampleMultistage_Register() { ``` *** + + #### func (*Multistage) Route(route any, handleFunc HandleFunc) > 为特定路由绑定处理函数,被绑定的处理函数将可以通过 Match 函数进行匹配 + **示例代码:** ```go @@ -105,9 +114,12 @@ func ExampleMultistage_Route() { ``` *** + + #### func (*Multistage) Match(routes ...any) HandleFunc > 匹配已绑定处理函数的路由,返回处理函数 > - 如果未找到将会返回空指针 + **示例代码:** ```go @@ -156,8 +168,11 @@ func TestMultistage_Match(t *testing.T) { *** + + #### func (*Multistage) Sub(route any) *Multistage[HandleFunc] > 获取子路由器 + **示例代码:** ```go diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 3395e2d2..649cb941 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -115,11 +115,17 @@ type Channel[T any] struct { c chan T } ``` + + #### func (*Channel) Put(message T) > 将数据放入写循环,message 应该来源于 hub.ObjectPool + *** + + #### func (*Channel) Close() > 关闭写循环 + *** ### Unbounded `STRUCT` @@ -130,8 +136,11 @@ type Unbounded[Message any] struct { buf *buffer.Unbounded[Message] } ``` + + #### func (*Unbounded) Put(message Message) > 将数据放入写循环,message 应该来源于 hub.ObjectPool +
查看 / 收起单元测试 @@ -189,8 +198,11 @@ func BenchmarkUnbounded_Put(b *testing.B) { *** + + #### func (*Unbounded) Close() > 关闭写循环 +
查看 / 收起单元测试 diff --git a/utils/aoi/README.md b/utils/aoi/README.md index b710b34c..eb256012 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -83,17 +83,35 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimens repartitionQueue []func() } ``` + + #### func (*TwoDimensional) AddEntity(entity E) + *** + + #### func (*TwoDimensional) DeleteEntity(entity E) + *** + + #### func (*TwoDimensional) Refresh(entity E) + *** + + #### func (*TwoDimensional) GetFocus(id EID) map[EID]E + *** + + #### func (*TwoDimensional) SetSize(width int, height int) + *** + + #### func (*TwoDimensional) SetAreaSize(width int, height int) + *** ### TwoDimensionalEntity `INTERFACE` diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index e6fb97d8..66946682 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -127,24 +127,42 @@ type Area[ID comparable, AreaInfo any] struct { evaluate AreaEvaluateHandle[ID, AreaInfo] } ``` + + #### func (*Area) GetAreaInfo() AreaInfo > 获取编排区域的信息 + *** + + #### func (*Area) GetItems() map[ID]Item[ID] > 获取编排区域中的所有成员 + *** + + #### func (*Area) IsAllow(item Item[ID]) (constraintErr error, conflictItems map[ID]Item[ID], allow bool) > 检测一个成员是否可以被添加到该编排区域中 + *** + + #### func (*Area) IsConflict(item Item[ID]) bool > 检测一个成员是否会造成冲突 + *** + + #### func (*Area) GetConflictItems(item Item[ID]) map[ID]Item[ID] > 获取与一个成员产生冲突的所有其他成员 + *** + + #### func (*Area) GetScore(extra ...Item[ID]) float64 > 获取该编排区域的评估分数 > - 当 extra 不为空时,将会将 extra 中的内容添加到 items 中进行评估 + *** ### AreaOption `STRUCT` @@ -176,14 +194,23 @@ type Arrangement[ID comparable, AreaInfo any] struct { conflictHandles []ConflictHandle[ID, AreaInfo] } ``` + + #### func (*Arrangement) AddArea(areaInfo AreaInfo, options ...AreaOption[ID, AreaInfo]) > 添加一个编排区域 + *** + + #### func (*Arrangement) AddItem(item Item[ID]) > 添加一个成员 + *** + + #### func (*Arrangement) Arrange() (areas []*Area[ID, AreaInfo], noSolution map[ID]Item[ID]) > 编排 +
查看 / 收起单元测试 @@ -247,47 +274,89 @@ type Editor[ID comparable, AreaInfo any] struct { retryCount int } ``` + + #### func (*Editor) GetPendingCount() int > 获取待编排的成员数量 + *** + + #### func (*Editor) RemoveAreaItem(area *Area[ID, AreaInfo], item Item[ID]) > 从编排区域中移除一个成员到待编排队列中,如果该成员不存在于编排区域中,则不进行任何操作 + *** + + #### func (*Editor) AddAreaItem(area *Area[ID, AreaInfo], item Item[ID]) > 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作 + *** + + #### func (*Editor) GetAreas() []*Area[ID, AreaInfo] > 获取所有的编排区域 + *** + + #### func (*Editor) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area[ID, AreaInfo] > 获取所有的编排区域,并按照分数升序排序 + *** + + #### func (*Editor) GetAreasWithScoreDesc(extra ...Item[ID]) []*Area[ID, AreaInfo] > 获取所有的编排区域,并按照分数降序排序 + *** + + #### func (*Editor) GetRetryCount() int > 获取重试次数 + *** + + #### func (*Editor) GetThresholdProgressRate() float64 > 获取重试次数阈值进度 + *** + + #### func (*Editor) GetAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] > 获取允许的编排区域 + *** + + #### func (*Editor) GetNoAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] > 获取不允许的编排区域 + *** + + #### func (*Editor) GetBestAllowArea(item Item[ID]) *Area[ID, AreaInfo] > 获取最佳的允许的编排区域,如果不存在,则返回 nil + *** + + #### func (*Editor) GetBestNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] > 获取最佳的不允许的编排区域,如果不存在,则返回 nil + *** + + #### func (*Editor) GetWorstAllowArea(item Item[ID]) *Area[ID, AreaInfo] > 获取最差的允许的编排区域,如果不存在,则返回 nil + *** + + #### func (*Editor) GetWorstNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] > 获取最差的不允许的编排区域,如果不存在,则返回 nil + *** ### Item `INTERFACE` diff --git a/utils/buffer/README.md b/utils/buffer/README.md index 14701785..81cd7974 100644 --- a/utils/buffer/README.md +++ b/utils/buffer/README.md @@ -94,8 +94,11 @@ type Ring[T any] struct { w int } ``` + + #### func (*Ring) Read() ( T, error) > 读取数据 +
查看 / 收起基准测试 @@ -120,14 +123,23 @@ func BenchmarkRing_Read(b *testing.B) { *** + + #### func (*Ring) ReadAll() []T > 读取所有数据 + *** + + #### func (*Ring) Peek() (t T, err error) > 查看数据 + *** + + #### func (*Ring) Write(v T) > 写入数据 +
查看 / 收起基准测试 @@ -149,17 +161,29 @@ func BenchmarkRing_Write(b *testing.B) { *** + + #### func (*Ring) IsEmpty() bool > 是否为空 + *** + + #### func (*Ring) Cap() int > 返回缓冲区容量 + *** + + #### func (*Ring) Len() int > 返回缓冲区长度 + *** + + #### func (*Ring) Reset() > 重置缓冲区 + *** ### RingUnbounded `STRUCT` @@ -175,8 +199,11 @@ type RingUnbounded[T any] struct { closedSignal chan struct{} } ``` + + #### func (*RingUnbounded) Write(v T) > 写入数据 +
查看 / 收起基准测试 @@ -198,8 +225,11 @@ func BenchmarkRingUnbounded_Write(b *testing.B) { *** + + #### func (*RingUnbounded) Read() chan T > 读取数据 +
查看 / 收起基准测试 @@ -224,11 +254,17 @@ func BenchmarkRingUnbounded_Read(b *testing.B) { *** + + #### func (*RingUnbounded) Closed() bool > 判断缓冲区是否已关闭 + *** + + #### func (*RingUnbounded) Close() chan struct {} > 关闭缓冲区,关闭后将不再接收新数据,但是已有数据仍然可以读取 +
查看 / 收起单元测试 @@ -268,15 +304,24 @@ type Unbounded[V any] struct { backlog []V } ``` + + #### func (*Unbounded) Put(t V) > 将数据放入缓冲区 + *** + + #### func (*Unbounded) Load() > 将缓冲区中的数据发送到读取通道中,如果缓冲区中没有数据,则不会发送 > - 在每次 Get 后都应该执行该函数 + *** + + #### func (*Unbounded) Get() chan V > 获取读取通道 +
查看 / 收起单元测试 @@ -299,9 +344,15 @@ func TestUnbounded_Get(t *testing.T) { *** + + #### func (*Unbounded) Close() > 关闭 + *** + + #### func (*Unbounded) IsClosed() bool > 是否已关闭 + *** diff --git a/utils/collection/README.md b/utils/collection/README.md index 355a9b8b..4c5f5667 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -147,7 +147,7 @@ collection 定义了各种对于集合操作有用的各种函数 **示例代码:** -slice 克隆后将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +在该示例中,将 slice 克隆后将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 - 示例中的结果将会输出 [1 2 3] @@ -202,7 +202,7 @@ func TestCloneSlice(t *testing.T) { **示例代码:** -map 克隆后将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +在该示例中,将 map 克隆后将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 - 示例中的结果将会输出 3 @@ -257,7 +257,7 @@ func TestCloneMap(t *testing.T) { **示例代码:** -slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +通过将 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 - result 的结果为 [[1 2 3] [1 2 3]] - 示例中的结果将会输出 2 @@ -319,7 +319,7 @@ func TestCloneSliceN(t *testing.T) { **示例代码:** -map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +通过将 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` - 示例中的结果将会输出 2 @@ -382,7 +382,7 @@ func TestCloneMapN(t *testing.T) { **示例代码:** -slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 +通过将多个 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址 - result 的结果为 [[1 2 3] [1 2 3]] @@ -441,7 +441,7 @@ func TestCloneSlices(t *testing.T) { **示例代码:** -map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 +通过将多个 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址 - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对` diff --git a/utils/collection/listings/README.md b/utils/collection/listings/README.md index 6d0a0ddd..6ff92765 100644 --- a/utils/collection/listings/README.md +++ b/utils/collection/listings/README.md @@ -65,17 +65,29 @@ type Matrix[V any] struct { data []V } ``` + + #### func (*Matrix) Get(index ...int) *V > 获取矩阵中给定索引的元素。 + *** + + #### func (*Matrix) Set(index []int, value V) > 设置矩阵中给定索引的元素。 + *** + + #### func (*Matrix) Dimensions() []int > 返回矩阵的维度大小。 + *** + + #### func (*Matrix) Clear() > 清空矩阵。 + *** ### PagedSlice `STRUCT` @@ -88,23 +100,41 @@ type PagedSlice[T any] struct { lenLast int } ``` + + #### func (*PagedSlice) Add(value T) > 添加一个元素到 PagedSlice 中。 + *** + + #### func (*PagedSlice) Get(index int) *T > 获取 PagedSlice 中给定索引的元素。 + *** + + #### func (*PagedSlice) Set(index int, value T) > 设置 PagedSlice 中给定索引的元素。 + *** + + #### func (*PagedSlice) Len() int > 返回 PagedSlice 中元素的数量。 + *** + + #### func (*PagedSlice) Clear() > 清空 PagedSlice。 + *** + + #### func (*PagedSlice) Range(f func (index int, value T) bool) > 迭代 PagedSlice 中的所有元素。 + *** ### PrioritySlice `STRUCT` @@ -114,17 +144,29 @@ type PrioritySlice[V any] struct { items []*priorityItem[V] } ``` + + #### func (*PrioritySlice) Len() int > 返回切片长度 + *** + + #### func (*PrioritySlice) Cap() int > 返回切片容量 + *** + + #### func (*PrioritySlice) Clear() > 清空切片 + *** + + #### func (*PrioritySlice) Append(v V, p int) > 添加元素 +
查看 / 收起单元测试 @@ -145,44 +187,83 @@ func TestPrioritySlice_Append(t *testing.T) { *** + + #### func (*PrioritySlice) Appends(priority int, vs ...V) > 添加元素 + *** + + #### func (*PrioritySlice) Get(index int) ( V, int) > 获取元素 + *** + + #### func (*PrioritySlice) GetValue(index int) V > 获取元素值 + *** + + #### func (*PrioritySlice) GetPriority(index int) int > 获取元素优先级 + *** + + #### func (*PrioritySlice) Set(index int, value V, priority int) > 设置元素 + *** + + #### func (*PrioritySlice) SetValue(index int, value V) > 设置元素值 + *** + + #### func (*PrioritySlice) SetPriority(index int, priority int) > 设置元素优先级 + *** + + #### func (*PrioritySlice) Action(action func (items []*priorityItem[V]) []*priorityItem[V]) > 直接操作切片,如果返回值不为 nil,则替换切片 + *** + + #### func (*PrioritySlice) Range(action func (index int, item *priorityItem[V]) bool) > 遍历切片,如果返回值为 false,则停止遍历 + *** + + #### func (*PrioritySlice) RangeValue(action func (index int, value V) bool) > 遍历切片值,如果返回值为 false,则停止遍历 + *** + + #### func (*PrioritySlice) RangePriority(action func (index int, priority int) bool) > 遍历切片优先级,如果返回值为 false,则停止遍历 + *** + + #### func (*PrioritySlice) Slice() []V -> 返回切片 +> SyncSlice 返回切片 + *** + + #### func (*PrioritySlice) String() string > 返回切片字符串 + *** ### SyncSlice `STRUCT` @@ -193,17 +274,38 @@ type SyncSlice[V any] struct { data []V } ``` + + #### func (*SyncSlice) Get(index int) V + *** + + #### func (*SyncSlice) GetWithRange(start int, end int) []V + *** + + #### func (*SyncSlice) Set(index int, value V) + *** + + #### func (*SyncSlice) Append(values ...V) + *** + + #### func (*SyncSlice) Release() + *** + + #### func (*SyncSlice) Clear() + *** + + #### func (*SyncSlice) GetData() []V + *** diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md index 40840403..17546e19 100644 --- a/utils/collection/mappings/README.md +++ b/utils/collection/mappings/README.md @@ -46,55 +46,109 @@ type SyncMap[K comparable, V any] struct { atom bool } ``` + + #### func (*SyncMap) Set(key K, value V) > 设置一个值 + *** + + #### func (*SyncMap) Get(key K) V > 获取一个值 + *** + + #### func (*SyncMap) Atom(handle func (m map[K]V)) > 原子操作 + *** + + #### func (*SyncMap) Exist(key K) bool > 判断是否存在 + *** + + #### func (*SyncMap) GetExist(key K) ( V, bool) > 获取一个值并判断是否存在 + *** + + #### func (*SyncMap) Delete(key K) > 删除一个值 + *** + + #### func (*SyncMap) DeleteGet(key K) V > 删除一个值并返回 + *** + + #### func (*SyncMap) DeleteGetExist(key K) ( V, bool) > 删除一个值并返回是否存在 + *** + + #### func (*SyncMap) DeleteExist(key K) bool > 删除一个值并返回是否存在 + *** + + #### func (*SyncMap) Clear() > 清空 + *** + + #### func (*SyncMap) ClearHandle(handle func (key K, value V)) > 清空并处理 + *** + + #### func (*SyncMap) Range(handle func (key K, value V) bool) > 遍历所有值,如果 handle 返回 true 则停止遍历 + *** + + #### func (*SyncMap) Keys() []K > 获取所有的键 + *** + + #### func (*SyncMap) Slice() []V > 获取所有的值 + *** + + #### func (*SyncMap) Map() map[K]V > 转换为普通 map + *** + + #### func (*SyncMap) Size() int > 获取数量 + *** + + #### func (*SyncMap) MarshalJSON() ( []byte, error) + *** + + #### func (*SyncMap) UnmarshalJSON(bytes []byte) error + *** diff --git a/utils/combination/README.md b/utils/combination/README.md index 089fb4dd..ec273030 100644 --- a/utils/combination/README.md +++ b/utils/combination/README.md @@ -44,7 +44,7 @@ combination 包提供了一些实用的组合函数。 |[WithValidatorHandleContinuousNot](#WithValidatorHandleContinuousNot)|校验组合成员是否不连续 |[WithValidatorHandleGroupContinuous](#WithValidatorHandleGroupContinuous)|校验组合成员是否能够按类型分组并且连续 |[WithValidatorHandleGroupContinuousN](#WithValidatorHandleGroupContinuousN)|校验组合成员是否能够按分组为 n 组类型并且连续 -|[WithValidatorHandleNCarryM](#WithValidatorHandleNCarryM)| 校验组合成员是否匹配 N 携带相同的 M 的组合 +|[WithValidatorHandleNCarryM](#WithValidatorHandleNCarryM)|校验组合成员是否匹配 N 携带相同的 M 的组合 |[WithValidatorHandleNCarryIndependentM](#WithValidatorHandleNCarryIndependentM)|校验组合成员是否匹配 N 携带独立的 M 的组合 @@ -227,7 +227,7 @@ combination 包提供了一些实用的组合函数。 *** #### func WithValidatorHandleNCarryM\[T Item, E generic.Ordered\](n int, m int, getType func (item T) E) ValidatorOption[T] -> 校验组合成员是否匹配 N 携带相同的 M 的组合 +> 校验组合成员是否匹配 N 携带相同的 M 的组合 > - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 > - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型需要全部相同 > - getType: 用于获取组合中元素的类型,用于判断是否相同 @@ -251,23 +251,41 @@ type Combination[T Item] struct { priority []string } ``` + + #### func (*Combination) NewMatcher(name string, options ...MatcherOption[T]) *Combination[T] > 添加一个新的匹配器 + *** + + #### func (*Combination) AddMatcher(name string, matcher *Matcher[T]) *Combination[T] > 添加一个匹配器 + *** + + #### func (*Combination) RemoveMatcher(name string) *Combination[T] > 移除一个匹配器 + *** + + #### func (*Combination) Combinations(items []T) (result [][]T) > 从一组数据中提取所有符合匹配器规则的组合 + *** + + #### func (*Combination) CombinationsToName(items []T) (result map[string][][]T) > 从一组数据中提取所有符合匹配器规则的组合,并返回匹配器名称 + *** + + #### func (*Combination) Best(items []T) (name string, result []T) > 从一组数据中提取符合匹配器规则的最佳组合 +
查看 / 收起单元测试 @@ -302,8 +320,11 @@ func TestCombination_Best(t *testing.T) { *** + + #### func (*Combination) Worst(items []T) (name string, result []T) > 从一组数据中提取符合匹配器规则的最差组合 + *** ### Option `STRUCT` @@ -326,18 +347,30 @@ type Matcher[T Item] struct { filter []func(items []T) [][]T } ``` + + #### func (*Matcher) AddFilter(filter func (items []T) [][]T) > 添加一个筛选器 > - 筛选器用于对组合进行筛选,返回一个二维数组,每个数组内的元素都是一个组合 + *** + + #### func (*Matcher) Combinations(items []T) [][]T > 从一组数据中提取所有符合筛选器规则的组合 + *** + + #### func (*Matcher) Best(items []T) []T > 从一组数据中提取符筛选器规则的最佳组合 + *** + + #### func (*Matcher) Worst(items []T) []T > 从一组数据中提取符筛选器规则的最差组合 + *** ### MatcherOption `STRUCT` @@ -353,8 +386,11 @@ type Validator[T Item] struct { vh []func(items []T) bool } ``` + + #### func (*Validator) Validate(items []T) bool > 校验组合是否符合要求 +
查看 / 收起单元测试 diff --git a/utils/deck/README.md b/utils/deck/README.md index 4dd3712b..b34c7f88 100644 --- a/utils/deck/README.md +++ b/utils/deck/README.md @@ -52,26 +52,47 @@ type Deck[I Item] struct { sort []int64 } ``` + + #### func (*Deck) AddGroup(group *Group[I]) > 将一个组添加到甲板中 + *** + + #### func (*Deck) RemoveGroup(guid int64) > 移除甲板中的一个组 + *** + + #### func (*Deck) GetCount() int > 获取甲板中的组数量 + *** + + #### func (*Deck) GetGroups() map[int64]*Group[I] > 获取所有组 + *** + + #### func (*Deck) GetGroupsSlice() []*Group[I] > 获取所有组 + *** + + #### func (*Deck) GetNext(guid int64) *Group[I] > 获取特定组的下一个组 + *** + + #### func (*Deck) GetPrev(guid int64) *Group[I] > 获取特定组的上一个组 + *** ### Group `STRUCT` @@ -83,50 +104,95 @@ type Group[I Item] struct { items []I } ``` + + #### func (*Group) GetGuid() int64 > 获取组的 guid + *** + + #### func (*Group) Fill() > 将该组的数据填充为 WithGroupFillHandle 中设置的内容 + *** + + #### func (*Group) Pop() (item I) > 从顶部获取一个内容 + *** + + #### func (*Group) PopN(n int) (items []I) > 从顶部获取指定数量的内容 + *** + + #### func (*Group) PressOut() (item I) > 从底部压出一个内容 + *** + + #### func (*Group) PressOutN(n int) (items []I) > 从底部压出指定数量的内容 + *** + + #### func (*Group) Push(item I) > 向顶部压入一个内容 + *** + + #### func (*Group) PushN(items []I) > 向顶部压入指定数量的内容 + *** + + #### func (*Group) Insert(item I) > 向底部插入一个内容 + *** + + #### func (*Group) InsertN(items []I) > 向底部插入指定数量的内容 + *** + + #### func (*Group) Pull(index int) (item I) > 从特定位置拔出一个内容 + *** + + #### func (*Group) Thrust(index int, item I) > 向特定位置插入一个内容 + *** + + #### func (*Group) IsFree() bool > 检查组是否为空 + *** + + #### func (*Group) GetCount() int > 获取组中剩余的内容数量 + *** + + #### func (*Group) GetItem(index int) I > 获取组中的指定内容 + *** ### Item `INTERFACE` diff --git a/utils/fsm/README.md b/utils/fsm/README.md index ab18674e..12625fbc 100644 --- a/utils/fsm/README.md +++ b/utils/fsm/README.md @@ -85,32 +85,59 @@ type FSM[State comparable, Data any] struct { exitAfterEventHandles map[State][]func(state *FSM[State, Data]) } ``` + + #### func (*FSM) Update() > 触发当前状态 + *** + + #### func (*FSM) Register(state State, options ...Option[State, Data]) > 注册状态 + *** + + #### func (*FSM) Unregister(state State) > 反注册状态 + *** + + #### func (*FSM) HasState(state State) bool > 检查状态机是否存在特定状态 + *** + + #### func (*FSM) Change(state State) > 改变状态机状态到新的状态 + *** + + #### func (*FSM) Current() (state State) > 获取当前状态 + *** + + #### func (*FSM) GetData() Data > 获取状态机数据 + *** + + #### func (*FSM) IsZero() bool > 检查状态机是否无状态 + *** + + #### func (*FSM) PrevIsZero() bool > 检查状态机上一个状态是否无状态 + *** ### Option `STRUCT` diff --git a/utils/generator/astgo/README.md b/utils/generator/astgo/README.md index 3d775cb2..3433548a 100644 --- a/utils/generator/astgo/README.md +++ b/utils/generator/astgo/README.md @@ -93,7 +93,10 @@ type File struct { Comment *Comment } ``` + + #### func (*File) Package() string + *** ### Function `STRUCT` @@ -114,7 +117,10 @@ type Function struct { Test bool } ``` + + #### func (*Function) Code() string + *** ### Package `STRUCT` @@ -128,19 +134,40 @@ type Package struct { Functions map[string]*Function } ``` + + #### func (*Package) StructFunc(name string) []*Function + *** + + #### func (*Package) PackageFunc() []*Function + *** + + #### func (*Package) Structs() []*Struct + *** + + #### func (*Package) FileComments() *Comment + *** + + #### func (*Package) GetUnitTest(f *Function) *Function + *** + + #### func (*Package) GetExampleTest(f *Function) *Function + *** + + #### func (*Package) GetBenchmarkTest(f *Function) *Function + *** ### Struct `STRUCT` diff --git a/utils/generator/genreadme/README.md b/utils/generator/genreadme/README.md index 04c96d04..cbfe512f 100644 --- a/utils/generator/genreadme/README.md +++ b/utils/generator/genreadme/README.md @@ -44,7 +44,10 @@ type Builder struct { o string } ``` + + #### func (*Builder) Generate() error +
查看 / 收起单元测试 diff --git a/utils/geometry/README.md b/utils/geometry/README.md index ad081944..1f5b0d00 100644 --- a/utils/geometry/README.md +++ b/utils/geometry/README.md @@ -878,23 +878,41 @@ type Circle[V generic.SignedNumber] struct { Shape[V] } ``` + + #### func (Circle) Radius() V > 获取圆形半径 + *** + + #### func (Circle) Centroid() Point[V] > 获取圆形质心位置 + *** + + #### func (Circle) Overlap(circle Circle[V]) bool > 与另一个圆是否发生重叠 + *** + + #### func (Circle) Area() V > 获取圆形面积 + *** + + #### func (Circle) Length() V > 获取圆的周长 + *** + + #### func (Circle) CentroidDistance(circle Circle[V]) V > 计算与另一个圆的质心距离 + *** ### FloorPlan `STRUCT` @@ -902,17 +920,29 @@ type Circle[V generic.SignedNumber] struct { ```go type FloorPlan []string ``` + + #### func (FloorPlan) IsFree(point Point[int]) bool > 检查位置是否为空格 + *** + + #### func (FloorPlan) IsInBounds(point Point[int]) bool > 检查位置是否在边界内 + *** + + #### func (FloorPlan) Put(point Point[int], c rune) > 设置平面图特定位置的字符 + *** + + #### func (FloorPlan) String() string > 获取平面图结果 + *** ### Direction `STRUCT` @@ -926,17 +956,29 @@ type Direction uint8 ```go type LineSegment[V generic.SignedNumber] [2]Point[V] ``` + + #### func (LineSegment) GetPoints() [2]Point[V] > 获取该线段的两个点 + *** + + #### func (LineSegment) GetStart() Point[V] > 获取该线段的开始位置 + *** + + #### func (LineSegment) GetEnd() Point[V] > 获取该线段的结束位置 + *** + + #### func (LineSegment) GetLength() V > 获取该线段的长度 + *** ### LineSegmentCap `STRUCT` @@ -947,7 +989,10 @@ type LineSegmentCap[V generic.SignedNumber, Data any] struct { Data Data } ``` + + #### func (*LineSegmentCap) GetData() Data + *** ### Point `STRUCT` @@ -955,53 +1000,101 @@ type LineSegmentCap[V generic.SignedNumber, Data any] struct { ```go type Point[V generic.SignedNumber] [2]V ``` + + #### func (Point) GetX() V > 返回该点的 x 坐标 + *** + + #### func (Point) GetY() V > 返回该点的 y 坐标 + *** + + #### func (Point) GetXY() (x V, y V) > 返回该点的 x、y 坐标 + *** + + #### func (Point) GetPos(width V) V > 返回该点位于特定宽度的二维数组的顺序位置 + *** + + #### func (Point) GetOffset(x V, y V) Point[V] > 获取偏移后的新坐标 + *** + + #### func (Point) Negative() bool > 返回该点是否是一个负数坐标 + *** + + #### func (Point) OutOf(minWidth V, minHeight V, maxWidth V, maxHeight V) bool > 返回该点在特定宽高下是否越界f + *** + + #### func (Point) Equal(point Point[V]) bool > 返回两个点是否相等 + *** + + #### func (Point) Copy() Point[V] > 复制一个点位置 + *** + + #### func (Point) Add(point Point[V]) Point[V] > 得到加上 point 后的点 + *** + + #### func (Point) Sub(point Point[V]) Point[V] > 得到减去 point 后的点 + *** + + #### func (Point) Mul(point Point[V]) Point[V] > 得到乘以 point 后的点 + *** + + #### func (Point) Div(point Point[V]) Point[V] > 得到除以 point 后的点 + *** + + #### func (Point) Abs() Point[V] > 返回位置的绝对值 + *** + + #### func (Point) Max(point Point[V]) Point[V] > 返回两个位置中每个维度的最大值组成的新的位置 + *** + + #### func (Point) Min(point Point[V]) Point[V] > 返回两个位置中每个维度的最小值组成的新的位置 + *** ### PointCap `STRUCT` @@ -1012,8 +1105,11 @@ type PointCap[V generic.SignedNumber, D any] struct { Data D } ``` + + #### func (PointCap) GetData() D > 获取数据 + *** ### Shape `STRUCT` @@ -1021,8 +1117,11 @@ type PointCap[V generic.SignedNumber, D any] struct { ```go type Shape[V generic.SignedNumber] []Point[V] ``` + + #### func (Shape) Points() []Point[V] > 获取这个形状的所有点 + **示例代码:** ```go @@ -1059,8 +1158,11 @@ func TestShape_Points(t *testing.T) { *** + + #### func (Shape) PointCount() int > 获取这个形状的点数量 + **示例代码:** ```go @@ -1092,15 +1194,24 @@ func TestShape_PointCount(t *testing.T) { *** + + #### func (Shape) Contains(point Point[V]) bool > 返回该形状中是否包含点 + *** + + #### func (Shape) ToCircle() Circle[V] > 将形状转换为圆形进行处理 > - 当形状非圆形时将会产生意外情况 + *** + + #### func (Shape) String() string > 将该形状转换为可视化的字符串进行返回 + **示例代码:** ```go @@ -1133,6 +1244,8 @@ func TestShape_String(t *testing.T) { *** + + #### func (Shape) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) > 获取该形状中包含的所有图形组合及其位置 > - 需要注意的是,即便图形最终表示为相同的,但是只要位置组合顺序不同,那么也将被认定为一种图形组合 @@ -1140,6 +1253,7 @@ func TestShape_String(t *testing.T) { > - 返回的坐标为原始形状的坐标 > > 可通过可选项对搜索结果进行过滤 + **示例代码:** ```go @@ -1155,12 +1269,18 @@ func ExampleShape_ShapeSearch() { ``` *** + + #### func (Shape) Edges() (edges []LineSegment[V]) > 获取该形状每一条边 > - 该形状需要最少由3个点组成,否则将不会返回任意一边 + *** + + #### func (Shape) IsPointOnEdge(point Point[V]) bool > 检查点是否在该形状的一条边上 + *** ### ShapeSearchOption `STRUCT` diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md index 63809a3b..b5e87530 100644 --- a/utils/geometry/astar/README.md +++ b/utils/geometry/astar/README.md @@ -86,5 +86,8 @@ type Graph[Node comparable] interface { Neighbours(node Node) []Node } ``` + + #### func (Graph) Neighbours(point geometry.Point[int]) []geometry.Point[int] + *** diff --git a/utils/geometry/dp/README.md b/utils/geometry/dp/README.md index 1eaf3cec..bbec081b 100644 --- a/utils/geometry/dp/README.md +++ b/utils/geometry/dp/README.md @@ -81,29 +81,47 @@ type DistributionPattern[Item any] struct { usePos bool } ``` + + #### func (*DistributionPattern) GetLinks(pos int) (result []Link[Item]) > 获取关联的成员 > - 其中包含传入的 pos 成员 + *** + + #### func (*DistributionPattern) HasLink(pos int) bool > 检查一个位置是否包含除它本身外的其他关联成员 + *** + + #### func (*DistributionPattern) LoadMatrix(matrix [][]Item) > 通过二维矩阵加载分布图 > - 通过该函数加载的分布图使用的矩阵是复制后的矩阵,因此无法直接通过刷新(Refresh)来更新分布关系 > - 需要通过直接刷新的方式请使用 LoadMatrixWithPos + *** + + #### func (*DistributionPattern) LoadMatrixWithPos(width int, matrix []Item) > 通过二维矩阵加载分布图 + *** + + #### func (*DistributionPattern) Refresh(pos int) > 刷新特定位置的分布关系 > - 由于 LoadMatrix 的矩阵是复制后的矩阵,所以任何外部的改动都不会影响到分布图的变化,在这种情况下,刷新将没有任何意义 > - 需要通过直接刷新的方式请使用 LoadMatrixWithPos 加载矩阵,或者通过 RefreshWithItem 函数进行刷新 + *** + + #### func (*DistributionPattern) RefreshWithItem(pos int, item Item) > 通过特定的成员刷新特定位置的分布关系 > - 如果矩阵通过 LoadMatrixWithPos 加载,将会重定向至 Refresh + *** ### Link `STRUCT` diff --git a/utils/geometry/matrix/README.md b/utils/geometry/matrix/README.md index c8972d1d..7ed208c6 100644 --- a/utils/geometry/matrix/README.md +++ b/utils/geometry/matrix/README.md @@ -47,52 +47,100 @@ type Matrix[T any] struct { m []T } ``` + + #### func (*Matrix) GetWidth() int > 获取二维矩阵宽度 + *** + + #### func (*Matrix) GetHeight() int > 获取二维矩阵高度 + *** + + #### func (*Matrix) GetWidth2Height() (width int, height int) > 获取二维矩阵的宽度和高度 + *** + + #### func (*Matrix) GetMatrix() [][]T > 获取二维矩阵 > - 通常建议使用 GetMatrixWithPos 进行处理这样将拥有更高的效率 + *** + + #### func (*Matrix) GetMatrixWithPos() []T > 获取顺序的矩阵 + *** + + #### func (*Matrix) Get(x int, y int) (value T) > 获取特定坐标的内容 + *** + + #### func (*Matrix) GetExist(x int, y int) (value T, exist bool) > 获取特定坐标的内容,如果不存在则返回 false + *** + + #### func (*Matrix) GetWithPos(pos int) (value T) > 获取特定坐标的内容 + *** + + #### func (*Matrix) Set(x int, y int, data T) > 设置特定坐标的内容 + *** + + #### func (*Matrix) SetWithPos(pos int, data T) > 设置特定坐标的内容 + *** + + #### func (*Matrix) Swap(x1 int, y1 int, x2 int, y2 int) > 交换两个位置的内容 + *** + + #### func (*Matrix) SwapWithPos(pos1 int, pos2 int) > 交换两个位置的内容 + *** + + #### func (*Matrix) TrySwap(x1 int, y1 int, x2 int, y2 int, expressionHandle func (matrix *Matrix[T]) bool) > 尝试交换两个位置的内容,交换后不满足表达式时进行撤销 + *** + + #### func (*Matrix) TrySwapWithPos(pos1 int, pos2 int, expressionHandle func (matrix *Matrix[T]) bool) > 尝试交换两个位置的内容,交换后不满足表达式时进行撤销 + *** + + #### func (*Matrix) FillFull(generateHandle func (x int) T) > 根据提供的生成器填充整个矩阵 + *** + + #### func (*Matrix) FillFullWithPos(generateHandle func (pos int) T) > 根据提供的生成器填充整个矩阵 + *** diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md index af138da1..301e2ede 100644 --- a/utils/geometry/navmesh/README.md +++ b/utils/geometry/navmesh/README.md @@ -63,9 +63,14 @@ type NavMesh[V generic.SignedNumber] struct { meshShrinkAmount V } ``` + + #### func (*NavMesh) Neighbours(node *shape[V]) []*shape[V] > 实现 astar.Graph 的接口,用于向 A* 算法提供相邻图形 + *** + + #### func (*NavMesh) Find(point geometry.Point[V], maxDistance V) (distance V, findPoint geometry.Point[V], findShape geometry.Shape[V]) > 用于在 NavMesh 中查找离给定点最近的形状,并返回距离、找到的点和找到的形状。 > @@ -83,7 +88,10 @@ type NavMesh[V generic.SignedNumber] struct { > - 如果给定点不在任何形状内部或者形状的边上,将计算给定点到每个形状的距离,并找到最近的形状和对应的点。 > - 距离的计算采用几何学中的投影点到形状的距离。 > - 函数返回离给定点最近的形状的距离、找到的点和找到的形状。 + *** + + #### func (*NavMesh) FindPath(start geometry.Point[V], end geometry.Point[V]) (result []geometry.Point[V]) > 函数用于在 NavMesh 中查找从起点到终点的路径,并返回路径上的点序列。 > @@ -99,6 +107,7 @@ type NavMesh[V generic.SignedNumber] struct { > - 如果起点或终点不在任何形状内部,且 NavMesh 的 meshShrinkAmount 大于0,则会考虑缩小的形状。 > - 使用 A* 算法在 NavMesh 上搜索从起点形状到终点形状的最短路径。 > - 使用漏斗算法对路径进行优化,以得到最终的路径点序列。 + **示例代码:** ```go diff --git a/utils/hub/README.md b/utils/hub/README.md index e5182027..a89558dd 100644 --- a/utils/hub/README.md +++ b/utils/hub/README.md @@ -100,8 +100,11 @@ type ObjectPool[T any] struct { releaser func(data T) } ``` + + #### func (*ObjectPool) Get() T > 获取一个对象 +
查看 / 收起单元测试 @@ -137,8 +140,11 @@ func TestObjectPool_Get(t *testing.T) { *** + + #### func (*ObjectPool) Release(data T) > 将使用完成的对象放回缓冲区 +
查看 / 收起单元测试 diff --git a/utils/huge/README.md b/utils/huge/README.md index 1f8e8a60..c79ece4c 100644 --- a/utils/huge/README.md +++ b/utils/huge/README.md @@ -62,61 +62,124 @@ ```go type Float big.Float ``` + + #### func (*Float) Copy() *Float + *** + + #### func (*Float) Set(i *Float) *Float + *** + + #### func (*Float) IsZero() bool + *** + + #### func (*Float) ToBigFloat() *big.Float + *** + + #### func (*Float) Cmp(i *Float) int > 比较,当 slf > i 时返回 1,当 slf < i 时返回 -1,当 slf == i 时返回 0 + *** + + #### func (*Float) GreaterThan(i *Float) bool > 大于 + *** + + #### func (*Float) GreaterThanOrEqualTo(i *Float) bool > 大于或等于 + *** + + #### func (*Float) LessThan(i *Float) bool > 小于 + *** + + #### func (*Float) LessThanOrEqualTo(i *Float) bool > 小于或等于 + *** + + #### func (*Float) EqualTo(i *Float) bool > 等于 + *** + + #### func (*Float) Float64() float64 + *** + + #### func (*Float) String() string + *** + + #### func (*Float) Add(i *Float) *Float + *** + + #### func (*Float) Sub(i *Float) *Float + *** + + #### func (*Float) Mul(i *Float) *Float + *** + + #### func (*Float) Div(i *Float) *Float + *** + + #### func (*Float) Sqrt() *Float > 平方根 + *** + + #### func (*Float) Abs() *Float > 返回数字的绝对值 + *** + + #### func (*Float) Sign() int > 返回数字的符号 > - 1:正数 > - 0:零 > - -1:负数 + *** + + #### func (*Float) IsPositive() bool > 是否为正数 + *** + + #### func (*Float) IsNegative() bool > 是否为负数 + *** ### Int `STRUCT` @@ -124,258 +187,591 @@ type Float big.Float ```go type Int big.Int ``` + + #### func (*Int) Copy() *Int + *** + + #### func (*Int) Set(i *Int) *Int + *** + + #### func (*Int) SetInt(i int) *Int + *** + + #### func (*Int) SetInt8(i int8) *Int + *** + + #### func (*Int) SetInt16(i int16) *Int + *** + + #### func (*Int) SetInt32(i int32) *Int + *** + + #### func (*Int) SetInt64(i int64) *Int + *** + + #### func (*Int) SetUint(i uint) *Int + *** + + #### func (*Int) SetUint8(i uint8) *Int + *** + + #### func (*Int) SetUint16(i uint16) *Int + *** + + #### func (*Int) SetUint32(i uint32) *Int + *** + + #### func (*Int) SetUint64(i uint64) *Int + *** + + #### func (*Int) IsZero() bool + *** + + #### func (*Int) ToBigint() *big.Int + *** + + #### func (*Int) Cmp(i *Int) int > 比较,当 slf > i 时返回 1,当 slf < i 时返回 -1,当 slf == i 时返回 0 + *** + + #### func (*Int) GreaterThan(i *Int) bool > 大于 + *** + + #### func (*Int) GreaterThanOrEqualTo(i *Int) bool > 大于或等于 + *** + + #### func (*Int) LessThan(i *Int) bool > 小于 + *** + + #### func (*Int) LessThanOrEqualTo(i *Int) bool > 小于或等于 + *** + + #### func (*Int) EqualTo(i *Int) bool > 等于 + *** + + #### func (*Int) Int64() int64 + *** + + #### func (*Int) String() string + *** + + #### func (*Int) Add(i *Int) *Int + *** + + #### func (*Int) AddInt(i int) *Int + *** + + #### func (*Int) AddInt8(i int8) *Int + *** + + #### func (*Int) AddInt16(i int16) *Int + *** + + #### func (*Int) AddInt32(i int32) *Int + *** + + #### func (*Int) AddInt64(i int64) *Int + *** + + #### func (*Int) AddUint(i uint) *Int + *** + + #### func (*Int) AddUint8(i uint8) *Int + *** + + #### func (*Int) AddUint16(i uint16) *Int + *** + + #### func (*Int) AddUint32(i uint32) *Int + *** + + #### func (*Int) AddUint64(i uint64) *Int + *** + + #### func (*Int) Mul(i *Int) *Int + *** + + #### func (*Int) MulInt(i int) *Int + *** + + #### func (*Int) MulInt8(i int8) *Int + *** + + #### func (*Int) MulInt16(i int16) *Int + *** + + #### func (*Int) MulInt32(i int32) *Int + *** + + #### func (*Int) MulInt64(i int64) *Int + *** + + #### func (*Int) MulUint(i uint) *Int + *** + + #### func (*Int) MulUint8(i uint8) *Int + *** + + #### func (*Int) MulUint16(i uint16) *Int + *** + + #### func (*Int) MulUint32(i uint32) *Int + *** + + #### func (*Int) MulUint64(i uint64) *Int + *** + + #### func (*Int) Sub(i *Int) *Int + *** + + #### func (*Int) SubInt(i int) *Int + *** + + #### func (*Int) SubInt8(i int8) *Int + *** + + #### func (*Int) SubInt16(i int16) *Int + *** + + #### func (*Int) SubInt32(i int32) *Int + *** + + #### func (*Int) SubInt64(i int64) *Int + *** + + #### func (*Int) SubUint(i uint) *Int + *** + + #### func (*Int) SubUint8(i uint8) *Int + *** + + #### func (*Int) SubUint16(i uint16) *Int + *** + + #### func (*Int) SubUint32(i uint32) *Int + *** + + #### func (*Int) SubUint64(i uint64) *Int + *** + + #### func (*Int) Div(i *Int) *Int + *** + + #### func (*Int) DivInt(i int) *Int + *** + + #### func (*Int) DivInt8(i int8) *Int + *** + + #### func (*Int) DivInt16(i int16) *Int + *** + + #### func (*Int) DivInt32(i int32) *Int + *** + + #### func (*Int) DivInt64(i int64) *Int + *** + + #### func (*Int) DivUint(i uint) *Int + *** + + #### func (*Int) DivUint8(i uint8) *Int + *** + + #### func (*Int) DivUint16(i uint16) *Int + *** + + #### func (*Int) DivUint32(i uint32) *Int + *** + + #### func (*Int) DivUint64(i uint64) *Int + *** + + #### func (*Int) Mod(i *Int) *Int + *** + + #### func (*Int) ModInt(i int) *Int + *** + + #### func (*Int) ModInt8(i int8) *Int + *** + + #### func (*Int) ModInt16(i int16) *Int + *** + + #### func (*Int) ModInt32(i int32) *Int + *** + + #### func (*Int) ModInt64(i int64) *Int + *** + + #### func (*Int) ModUint(i uint) *Int + *** + + #### func (*Int) ModUint8(i uint8) *Int + *** + + #### func (*Int) ModUint16(i uint16) *Int + *** + + #### func (*Int) ModUint32(i uint32) *Int + *** + + #### func (*Int) ModUint64(i uint64) *Int + *** + + #### func (*Int) Pow(i *Int) *Int + *** + + #### func (*Int) PowInt(i int) *Int + *** + + #### func (*Int) PowInt8(i int8) *Int + *** + + #### func (*Int) PowInt16(i int16) *Int + *** + + #### func (*Int) PowInt32(i int32) *Int + *** + + #### func (*Int) PowInt64(i int64) *Int + *** + + #### func (*Int) PowUint(i uint) *Int + *** + + #### func (*Int) PowUint8(i uint8) *Int + *** + + #### func (*Int) PowUint16(i uint16) *Int + *** + + #### func (*Int) PowUint32(i uint32) *Int + *** + + #### func (*Int) PowUint64(i uint64) *Int + *** + + #### func (*Int) Lsh(i int) *Int > 左移 + *** + + #### func (*Int) Rsh(i int) *Int > 右移 + *** + + #### func (*Int) And(i *Int) *Int > 与 + *** + + #### func (*Int) AndNot(i *Int) *Int > 与非 + *** + + #### func (*Int) Or(i *Int) *Int > 或 + *** + + #### func (*Int) Xor(i *Int) *Int > 异或 + *** + + #### func (*Int) Not() *Int > 非 + *** + + #### func (*Int) Sqrt() *Int > 平方根 + *** + + #### func (*Int) GCD(i *Int) *Int > 最大公约数 + *** + + #### func (*Int) LCM(i *Int) *Int > 最小公倍数 + *** + + #### func (*Int) ModInverse(i *Int) *Int > 模反元素 + *** + + #### func (*Int) ModSqrt(i *Int) *Int > 模平方根 + *** + + #### func (*Int) BitLen() int > 二进制长度 + *** + + #### func (*Int) Bit(i int) uint > 二进制位 + *** + + #### func (*Int) SetBit(i int, v uint) *Int > 设置二进制位 + *** + + #### func (*Int) Neg() *Int > 返回数字的相反数 + *** + + #### func (*Int) Abs() *Int > 返回数字的绝对值 + *** + + #### func (*Int) Sign() int > 返回数字的符号 > - 1:正数 > - 0:零 > - -1:负数 + *** + + #### func (*Int) IsPositive() bool > 是否为正数 + *** + + #### func (*Int) IsNegative() bool > 是否为负数 + *** + + #### func (*Int) IsEven() bool > 是否为偶数 + *** + + #### func (*Int) IsOdd() bool > 是否为奇数 + *** + + #### func (*Int) ProportionalCalc(proportional *Int, formula func (v *Int) *Int) *Int > 比例计算,该函数会再 formula 返回值的基础上除以 proportional > - formula 为计算公式,该公式的参数为调用该函数的 Int 的拷贝 + *** diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md index 2d8b992e..1576b6c4 100644 --- a/utils/leaderboard/README.md +++ b/utils/leaderboard/README.md @@ -76,9 +76,12 @@ type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct { rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] } ``` + + #### func (*BinarySearch) Competitor(competitorId CompetitorID, score Score) > 声明排行榜竞争者 > - 如果竞争者存在的情况下,会更新已有成绩,否则新增竞争者 + **示例代码:** ```go @@ -97,8 +100,11 @@ func ExampleBinarySearch_Competitor() { ``` *** + + #### func (*BinarySearch) RemoveCompetitor(competitorId CompetitorID) > 删除特定竞争者 + **示例代码:** ```go @@ -118,16 +124,25 @@ func ExampleBinarySearch_RemoveCompetitor() { ``` *** + + #### func (*BinarySearch) Size() int > 获取竞争者数量 + *** + + #### func (*BinarySearch) GetRankDefault(competitorId CompetitorID, defaultValue int) int > 获取竞争者排名,如果竞争者不存在则返回默认值 > - 排名从 0 开始 + *** + + #### func (*BinarySearch) GetRank(competitorId CompetitorID) ( int, error) > 获取竞争者排名 > - 排名从 0 开始 + **示例代码:** ```go @@ -144,38 +159,77 @@ func ExampleBinarySearch_GetRank() { ``` *** + + #### func (*BinarySearch) GetCompetitor(rank int) (competitorId CompetitorID, err error) > 获取特定排名的竞争者 + *** + + #### func (*BinarySearch) GetCompetitorWithRange(start int, end int) ( []CompetitorID, error) > 获取第start名到第end名竞争者 + *** + + #### func (*BinarySearch) GetScore(competitorId CompetitorID) (score Score, err error) > 获取竞争者成绩 + *** + + #### func (*BinarySearch) GetScoreDefault(competitorId CompetitorID, defaultValue Score) Score > 获取竞争者成绩,不存在时返回默认值 + *** + + #### func (*BinarySearch) GetAllCompetitor() []CompetitorID > 获取所有竞争者ID > - 结果为名次有序的 + *** + + #### func (*BinarySearch) Clear() > 清空排行榜 + *** + + #### func (*BinarySearch) Cmp(s1 Score, s2 Score) int + *** + + #### func (*BinarySearch) UnmarshalJSON(bytes []byte) error + *** + + #### func (*BinarySearch) MarshalJSON() ( []byte, error) + *** + + #### func (*BinarySearch) RegRankChangeEvent(handle BinarySearchRankChangeEventHandle[CompetitorID, Score]) + *** + + #### func (*BinarySearch) OnRankChangeEvent(competitorId CompetitorID, oldRank int, newRank int, oldScore Score, newScore Score) + *** + + #### func (*BinarySearch) RegRankClearBeforeEvent(handle BinarySearchRankClearBeforeEventHandle[CompetitorID, Score]) + *** + + #### func (*BinarySearch) OnRankClearBeforeEvent() + *** ### BinarySearchRankChangeEventHandle `STRUCT` diff --git a/utils/log/README.md b/utils/log/README.md index 16682a54..a743124d 100644 --- a/utils/log/README.md +++ b/utils/log/README.md @@ -415,13 +415,25 @@ type MultiHandler struct { handlers []slog.Handler } ``` + + #### func (MultiHandler) Enabled(ctx context.Context, level slog.Level) bool + *** + + #### func (MultiHandler) Handle(ctx context.Context, record slog.Record) (err error) + *** + + #### func (MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler + *** + + #### func (MultiHandler) WithGroup(name string) slog.Handler + *** ### Option `STRUCT` diff --git a/utils/log/survey/README.md b/utils/log/survey/README.md index f8529958..18084d75 100644 --- a/utils/log/survey/README.md +++ b/utils/log/survey/README.md @@ -166,32 +166,58 @@ type Analyzer struct { m sync.Mutex } ``` + + #### func (*Analyzer) Sub(key string) *Analyzer > 获取子分析器 + *** + + #### func (*Analyzer) SetFormat(key string, format func (v any) any) > 设置格式化函数 + *** + + #### func (*Analyzer) SetValueIfGreaterThan(key string, value float64) > 设置指定 key 的值,当新值大于旧值时 > - 当已有值不为 float64 时,将会被忽略 + *** + + #### func (*Analyzer) SetValueIfLessThan(key string, value float64) > 设置指定 key 的值,当新值小于旧值时 > - 当已有值不为 float64 时,将会被忽略 + *** + + #### func (*Analyzer) SetValueIf(key string, expression bool, value float64) > 当表达式满足的时候将设置指定 key 的值为 value + *** + + #### func (*Analyzer) SetValueStringIf(key string, expression bool, value string) > 当表达式满足的时候将设置指定 key 的值为 value + *** + + #### func (*Analyzer) SetValue(key string, value float64) > 设置指定 key 的值 + *** + + #### func (*Analyzer) SetValueString(key string, value string) > 设置指定 key 的值 + *** + + #### func (*Analyzer) Increase(key string, record R, recordKey string) > 在指定 key 现有值的基础上增加 recordKey 的值 > - 当分析器已经记录过相同 key 的值时,会根据已有的值类型进行不同处理 @@ -199,21 +225,37 @@ type Analyzer struct { > 处理方式: > - 当已有值类型为 string 时,将会使用新的值的 string 类型进行覆盖 > - 当已有值类型为 float64 时,当新的值类型不为 float64 时,将会被忽略 + *** + + #### func (*Analyzer) IncreaseValue(key string, value float64) > 在指定 key 现有值的基础上增加 value + *** + + #### func (*Analyzer) IncreaseNonRepeat(key string, record R, recordKey string, dimension ...string) > 在指定 key 现有值的基础上增加 recordKey 的值,但是当去重维度 dimension 相同时,不会增加 + *** + + #### func (*Analyzer) IncreaseValueNonRepeat(key string, record R, value float64, dimension ...string) > 在指定 key 现有值的基础上增加 value,但是当去重维度 dimension 相同时,不会增加 + *** + + #### func (*Analyzer) GetValue(key string) float64 > 获取当前记录的值 + *** + + #### func (*Analyzer) GetValueString(key string) string > 获取当前记录的值 + *** ### Flusher `INTERFACE` @@ -236,9 +278,15 @@ type FileFlusher struct { layoutLen int } ``` + + #### func (*FileFlusher) Flush(records []string) + *** + + #### func (*FileFlusher) Info() string + *** ### Option `STRUCT` @@ -258,33 +306,60 @@ type Result gjson.Result ```go type R string ``` + + #### func (R) GetTime(layout string) time.Time > 获取该记录的时间 + *** + + #### func (R) Get(key string) Result > 获取指定 key 的值 > - 当 key 为嵌套 key 时,使用 . 进行分割,例如:a.b.c > - 更多用法参考:https://github.com/tidwall/gjson + *** + + #### func (R) Exist(key string) bool > 判断指定 key 是否存在 + *** + + #### func (R) GetString(key string) string > 该函数为 Get(key).String() 的简写 + *** + + #### func (R) GetInt64(key string) int64 > 该函数为 Get(key).Int() 的简写 + *** + + #### func (R) GetInt(key string) int > 该函数为 Get(key).Int() 的简写,但是返回值为 int 类型 + *** + + #### func (R) GetFloat64(key string) float64 > 该函数为 Get(key).Float() 的简写 + *** + + #### func (R) GetBool(key string) bool > 该函数为 Get(key).Bool() 的简写 + *** + + #### func (R) String() string + *** ### Report `STRUCT` @@ -298,26 +373,50 @@ type Report struct { Subs []*Report } ``` + + #### func (*Report) Avg(key string) float64 > 计算平均值 + *** + + #### func (*Report) Count(key string) int64 > 获取特定 key 的计数次数 + *** + + #### func (*Report) Sum(keys ...string) float64 > 获取特定 key 的总和 + *** + + #### func (*Report) Sub(name string) *Report > 获取特定名称的子报告 + *** + + #### func (*Report) ReserveSubByPrefix(prefix string) *Report > 仅保留特定前缀的子报告 + *** + + #### func (*Report) ReserveSub(names ...string) *Report > 仅保留特定名称子报告 + *** + + #### func (*Report) FilterSub(names ...string) *Report > 将特定名称的子报告过滤掉 + *** + + #### func (*Report) String() string + *** diff --git a/utils/memory/README.md b/utils/memory/README.md index 81e1057e..4410a5e6 100644 --- a/utils/memory/README.md +++ b/utils/memory/README.md @@ -107,10 +107,13 @@ type Option struct { delay time.Duration } ``` + + #### func (*Option) WithPeriodicity(ticker *timer.Ticker, firstDelay time.Duration, interval time.Duration, delay time.Duration) *Option > 设置持久化周期 > - ticker 定时器,通常建议使用服务器的定时器,这样可以降低多线程的程序复杂性 > - firstDelay 首次持久化延迟,当首次持久化为 0 时,将会在下一个持久化周期开始时持久化 > - interval 持久化间隔 > - delay 每条数据持久化间隔,适当的设置该值可以使持久化期间尽量降低对用户体验的影响,如果为0,将会一次性持久化所有数据 + *** diff --git a/utils/moving/README.md b/utils/moving/README.md index 6606faf0..48538d6b 100644 --- a/utils/moving/README.md +++ b/utils/moving/README.md @@ -115,8 +115,11 @@ type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber] struct { position2DStopMoveEventHandles []Position2DStopMoveEventHandle[EID, PosType] } ``` + + #### func (*TwoDimensional) MoveTo(entity TwoDimensionalEntity[EID, PosType], x PosType, y PosType) > 设置对象移动到特定位置 + **示例代码:** ```go @@ -140,8 +143,11 @@ func ExampleTwoDimensional_MoveTo() { ``` *** + + #### func (*TwoDimensional) StopMove(id EID) > 停止特定对象的移动 + **示例代码:** ```go @@ -215,23 +221,44 @@ func TestTwoDimensional_StopMove(t *testing.T) { *** + + #### func (*TwoDimensional) RegPosition2DChangeEvent(handle Position2DChangeEventHandle[EID, PosType]) > 在对象位置改变时将执行注册的事件处理函数 + *** + + #### func (*TwoDimensional) OnPosition2DChangeEvent(entity TwoDimensionalEntity[EID, PosType], oldX PosType, oldY PosType) + *** + + #### func (*TwoDimensional) RegPosition2DDestinationEvent(handle Position2DDestinationEventHandle[EID, PosType]) > 在对象到达终点时将执行被注册的事件处理函数 + *** + + #### func (*TwoDimensional) OnPosition2DDestinationEvent(entity TwoDimensionalEntity[EID, PosType]) + *** + + #### func (*TwoDimensional) RegPosition2DStopMoveEvent(handle Position2DStopMoveEventHandle[EID, PosType]) > 在对象停止移动时将执行被注册的事件处理函数 + *** + + #### func (*TwoDimensional) OnPosition2DStopMoveEvent(entity TwoDimensionalEntity[EID, PosType]) + *** + + #### func (*TwoDimensional) Release() > 释放对象移动对象所占用的资源 + *** ### TwoDimensionalEntity `INTERFACE` diff --git a/utils/offset/README.md b/utils/offset/README.md index 4c11c007..a25117d3 100644 --- a/utils/offset/README.md +++ b/utils/offset/README.md @@ -67,12 +67,21 @@ type Time struct { offset time.Duration } ``` + + #### func (*Time) SetOffset(offset time.Duration) > 设置时间偏移 + *** + + #### func (*Time) Now() time.Time > 获取当前时间偏移后的时间 + *** + + #### func (*Time) Since(t time.Time) time.Duration > 获取当前时间偏移后的时间自从 t 以来经过的时间 + *** diff --git a/utils/random/README.md b/utils/random/README.md index ee9e3a9e..d4e92fcf 100644 --- a/utils/random/README.md +++ b/utils/random/README.md @@ -20,6 +20,7 @@ |[DiceN](#DiceN)|掷骰子 |[NetIP](#NetIP)|返回一个随机的IP地址 |[Port](#Port)|返回一个随机的端口号 +|[UsablePort](#UsablePort)|随机返回一个可用的端口号,如果没有可用端口号则返回 -1 |[IPv4](#IPv4)|返回一个随机产生的IPv4地址。 |[IPv4Port](#IPv4Port)|返回一个随机产生的IPv4地址和端口。 |[Int64](#Int64)|返回一个介于min和max之间的int64类型的随机数。 @@ -73,6 +74,11 @@ > 返回一个随机的端口号 +*** +#### func UsablePort() int + +> 随机返回一个可用的端口号,如果没有可用端口号则返回 -1 + *** #### func IPv4() string diff --git a/utils/sole/README.md b/utils/sole/README.md index 040be62d..0bfd023a 100644 --- a/utils/sole/README.md +++ b/utils/sole/README.md @@ -133,9 +133,15 @@ type Once[V any] struct { r map[any]struct{} } ``` + + #### func (*Once) Get(key any, value V, defaultValue V) V > 获取一个值,当该值已经被获取过的时候,返回 defaultValue,否则返回 value + *** + + #### func (*Once) Reset(key ...any) > 当 key 数量大于 0 时,将会重置对应 key 的记录,否则重置所有记录 + *** diff --git a/utils/super/README.md b/utils/super/README.md index 90754cc9..b0f1093f 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -752,8 +752,11 @@ type BitSet[Bit generic.Integer] struct { set []uint64 } ``` + + #### func (*BitSet) Set(bit Bit) *BitSet[Bit] > 将指定的位 bit 设置为 1 +
查看 / 收起单元测试 @@ -775,8 +778,11 @@ func TestBitSet_Set(t *testing.T) { *** + + #### func (*BitSet) Del(bit Bit) *BitSet[Bit] > 将指定的位 bit 设置为 0 +
查看 / 收起单元测试 @@ -799,9 +805,12 @@ func TestBitSet_Del(t *testing.T) { *** + + #### func (*BitSet) Shrink() *BitSet[Bit] > 将 BitSet 中的比特位集合缩小到最小 > - 正常情况下当 BitSet 中的比特位超出 64 位时,将自动增长,当 BitSet 中的比特位数量减少时,可以使用该方法将 BitSet 中的比特位集合缩小到最小 +
查看 / 收起单元测试 @@ -825,92 +834,179 @@ func TestBitSet_Shrink(t *testing.T) { *** + + #### func (*BitSet) Cap() int > 返回当前 BitSet 中可以表示的最大比特位数量 + *** + + #### func (*BitSet) Has(bit Bit) bool > 检查指定的位 bit 是否被设置为 1 + *** + + #### func (*BitSet) Clear() *BitSet[Bit] > 清空所有的比特位 + *** + + #### func (*BitSet) Len() int > 返回当前 BitSet 中被设置的比特位数量 + *** + + #### func (*BitSet) Bits() []Bit > 返回当前 BitSet 中被设置的比特位 + *** + + #### func (*BitSet) Reverse() *BitSet[Bit] > 反转当前 BitSet 中的所有比特位 + *** + + #### func (*BitSet) Not() *BitSet[Bit] > 返回当前 BitSet 中所有比特位的反转 + *** + + #### func (*BitSet) And(other *BitSet[Bit]) *BitSet[Bit] > 将当前 BitSet 与另一个 BitSet 进行按位与运算 + *** + + #### func (*BitSet) Or(other *BitSet[Bit]) *BitSet[Bit] > 将当前 BitSet 与另一个 BitSet 进行按位或运算 + *** + + #### func (*BitSet) Xor(other *BitSet[Bit]) *BitSet[Bit] > 将当前 BitSet 与另一个 BitSet 进行按位异或运算 + *** + + #### func (*BitSet) Sub(other *BitSet[Bit]) *BitSet[Bit] > 将当前 BitSet 与另一个 BitSet 进行按位减运算 + *** + + #### func (*BitSet) IsZero() bool > 检查当前 BitSet 是否为空 + *** + + #### func (*BitSet) Clone() *BitSet[Bit] > 返回当前 BitSet 的副本 + *** + + #### func (*BitSet) Equal(other *BitSet[Bit]) bool > 检查当前 BitSet 是否与另一个 BitSet 相等 + *** + + #### func (*BitSet) Contains(other *BitSet[Bit]) bool > 检查当前 BitSet 是否包含另一个 BitSet + *** + + #### func (*BitSet) ContainsAny(other *BitSet[Bit]) bool > 检查当前 BitSet 是否包含另一个 BitSet 中的任意比特位 + *** + + #### func (*BitSet) ContainsAll(other *BitSet[Bit]) bool > 检查当前 BitSet 是否包含另一个 BitSet 中的所有比特位 + *** + + #### func (*BitSet) Intersect(other *BitSet[Bit]) bool > 检查当前 BitSet 是否与另一个 BitSet 有交集 + *** + + #### func (*BitSet) Union(other *BitSet[Bit]) bool > 检查当前 BitSet 是否与另一个 BitSet 有并集 + *** + + #### func (*BitSet) Difference(other *BitSet[Bit]) bool > 检查当前 BitSet 是否与另一个 BitSet 有差集 + *** + + #### func (*BitSet) SymmetricDifference(other *BitSet[Bit]) bool > 检查当前 BitSet 是否与另一个 BitSet 有对称差集 + *** + + #### func (*BitSet) Subset(other *BitSet[Bit]) bool > 检查当前 BitSet 是否为另一个 BitSet 的子集 + *** + + #### func (*BitSet) Superset(other *BitSet[Bit]) bool > 检查当前 BitSet 是否为另一个 BitSet 的超集 + *** + + #### func (*BitSet) Complement(other *BitSet[Bit]) bool > 检查当前 BitSet 是否为另一个 BitSet 的补集 + *** + + #### func (*BitSet) Max() Bit > 返回当前 BitSet 中最大的比特位 + *** + + #### func (*BitSet) Min() Bit > 返回当前 BitSet 中最小的比特位 + *** + + #### func (*BitSet) String() string > 返回当前 BitSet 的字符串表示 + *** + + #### func (*BitSet) MarshalJSON() ( []byte, error) > 实现 json.Marshaler 接口 + *** + + #### func (*BitSet) UnmarshalJSON(data []byte) error > 实现 json.Unmarshaler 接口 + *** ### LossCounter `STRUCT` @@ -922,13 +1018,22 @@ type LossCounter struct { lossKey []string } ``` + + #### func (*LossCounter) Record(name string) > 记录一次损耗 + *** + + #### func (*LossCounter) GetLoss(handler func (step int, name string, loss time.Duration)) > 获取损耗 + *** + + #### func (*LossCounter) String() string + *** ### Matcher `STRUCT` @@ -940,11 +1045,17 @@ type Matcher[Value any, Result any] struct { d bool } ``` + + #### func (*Matcher) Case(value Value, result Result) *Matcher[Value, Result] > 匹配 + *** + + #### func (*Matcher) Default(value Result) Result > 默认 + *** ### Permission `STRUCT` @@ -955,17 +1066,29 @@ type Permission[Code generic.Integer, EntityID comparable] struct { l sync.RWMutex } ``` + + #### func (*Permission) HasPermission(entityId EntityID, permission Code) bool > 是否有权限 + *** + + #### func (*Permission) AddPermission(entityId EntityID, permission ...Code) > 添加权限 + *** + + #### func (*Permission) RemovePermission(entityId EntityID, permission ...Code) > 移除权限 + *** + + #### func (*Permission) SetPermission(entityId EntityID, permission ...Code) > 设置权限 + *** ### StackGo `STRUCT` @@ -978,19 +1101,28 @@ type StackGo struct { collect chan []byte } ``` + + #### func (*StackGo) Wait() > 等待收集消息堆栈 > - 在调用 Wait 函数后,当前协程将会被挂起,直到调用 Stack 或 GiveUp 函数 + *** + + #### func (*StackGo) Stack() []byte > 获取消息堆栈 > - 在调用 Wait 函数后调用该函数,将会返回上一个协程的堆栈信息 > - 在调用 GiveUp 函数后调用该函数,将会 panic + *** + + #### func (*StackGo) GiveUp() > 放弃收集消息堆栈 > - 在调用 Wait 函数后调用该函数,将会放弃收集消息堆栈并且释放资源 > - 在调用 GiveUp 函数后调用 Stack 函数,将会 panic + *** ### VerifyHandle `STRUCT` @@ -1002,13 +1134,22 @@ type VerifyHandle[V any] struct { hit bool } ``` + + #### func (*VerifyHandle) PreCase(expression func () bool, value V, caseHandle func (verify *VerifyHandle[V]) bool) bool > 先决校验用例,当 expression 成立时,将跳过 caseHandle 的执行,直接执行 handle 并返回 false > - 常用于对前置参数的空指针校验,例如当 a 为 nil 时,不执行 a.B(),而是直接返回 false + *** + + #### func (*VerifyHandle) Case(expression bool, value V) *VerifyHandle[V] > 校验用例,当 expression 成立时,将忽略后续 Case,并将在 Do 时执行 handle,返回 false + *** + + #### func (*VerifyHandle) Do() bool > 执行校验,当校验失败时,将执行 handle,并返回 false + *** diff --git a/utils/timer/README.md b/utils/timer/README.md index ab9b2f2e..38d3129d 100644 --- a/utils/timer/README.md +++ b/utils/timer/README.md @@ -115,16 +115,25 @@ type Pool struct { closed bool } ``` + + #### func (*Pool) ChangePoolSize(size int) error > 改变定时器池大小 > - 当传入的大小小于或等于 0 时,将会返回错误,并且不会发生任何改变 + *** + + #### func (*Pool) GetTicker(size int, options ...Option) *Ticker > 获取一个新的定时器 + *** + + #### func (*Pool) Release() > 释放定时器池的资源,释放后由其产生的 Ticker 在 Ticker.Release 后将不再回到池中,而是直接释放 > - 虽然定时器池已被释放,但是依旧可以产出 Ticker + *** ### Scheduler `STRUCT` @@ -145,14 +154,23 @@ type Scheduler struct { expr *cronexpr.Expression } ``` + + #### func (*Scheduler) Name() string > 获取调度器名称 + *** + + #### func (*Scheduler) Next(prev time.Time) time.Time > 获取下一次执行的时间 + *** + + #### func (*Scheduler) Caller() > 可由外部发起调用的执行函数 + *** ### Ticker `STRUCT` @@ -167,24 +185,42 @@ type Ticker struct { mark string } ``` + + #### func (*Ticker) Mark() string > 获取定时器的标记 > - 通常用于鉴别定时器来源 + *** + + #### func (*Ticker) Release() > 释放定时器,并将定时器重新放回 Pool 池中 + *** + + #### func (*Ticker) StopTimer(name string) > 停止特定名称的调度器 + *** + + #### func (*Ticker) IsStopped(name string) bool > 特定名称的调度器是否已停止 + *** + + #### func (*Ticker) GetSchedulers() []string > 获取所有调度器名称 + *** + + #### func (*Ticker) Cron(name string, expression string, handleFunc interface {}, args ...interface {}) > 通过 cron 表达式设置一个调度器,当 cron 表达式错误时,将会引发 panic +
查看 / 收起单元测试 @@ -212,12 +248,21 @@ func TestTicker_Cron(t *testing.T) { *** + + #### func (*Ticker) CronByInstantly(name string, expression string, handleFunc interface {}, args ...interface {}) > 与 Cron 相同,但是会立即执行一次 + *** + + #### func (*Ticker) After(name string, after time.Duration, handleFunc interface {}, args ...interface {}) > 设置一个在特定时间后运行一次的调度器 + *** + + #### func (*Ticker) Loop(name string, after time.Duration, interval time.Duration, times int, handleFunc interface {}, args ...interface {}) > 设置一个在特定时间后反复运行的调度器 + *** diff --git a/utils/times/README.md b/utils/times/README.md index 091682e4..90dc59c7 100644 --- a/utils/times/README.md +++ b/utils/times/README.md @@ -410,6 +410,8 @@ type StateLine[State generic.Basic] struct { trigger [][]func() } ``` + + #### func (*StateLine) Check(missingAllowed bool, states ...State) bool > 根据状态顺序检查时间线是否合法 > - missingAllowed: 是否允许状态缺失,如果为 true,则状态可以不连续,如果为 false,则状态必须连续 @@ -417,76 +419,146 @@ type StateLine[State generic.Basic] struct { > 状态不连续表示时间线中存在状态缺失,例如: > - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 true,则状态为 [1, 3, 5] 也是合法的 > - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 false,则状态为 [1, 3, 5] 是不合法的 + *** + + #### func (*StateLine) GetMissingStates(states ...State) []State > 获取缺失的状态 + *** + + #### func (*StateLine) HasState(state State) bool > 检查时间线中是否包含指定状态 + *** + + #### func (*StateLine) String() string > 获取时间线的字符串表示 + *** + + #### func (*StateLine) AddState(state State, t time.Time, onTrigger ...func ()) *StateLine[State] > 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略 > - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行 + *** + + #### func (*StateLine) GetTimeByState(state State) time.Time > 获取指定状态的时间点 + *** + + #### func (*StateLine) GetNextTimeByState(state State) time.Time > 获取指定状态的下一个时间点 + *** + + #### func (*StateLine) GetLastState() State > 获取最后一个状态 + *** + + #### func (*StateLine) GetPrevTimeByState(state State) time.Time > 获取指定状态的上一个时间点 + *** + + #### func (*StateLine) GetIndexByState(state State) int > 获取指定状态的索引 + *** + + #### func (*StateLine) GetStateByTime(t time.Time) State > 获取指定时间点的状态 + *** + + #### func (*StateLine) GetTimeByIndex(index int) time.Time > 获取指定索引的时间点 + *** + + #### func (*StateLine) Move(d time.Duration) *StateLine[State] > 时间线整体移动 + *** + + #### func (*StateLine) GetNextStateTimeByIndex(index int) time.Time > 获取指定索引的下一个时间点 + *** + + #### func (*StateLine) GetPrevStateTimeByIndex(index int) time.Time > 获取指定索引的上一个时间点 + *** + + #### func (*StateLine) GetStateIndexByTime(t time.Time) int > 获取指定时间点的索引 + *** + + #### func (*StateLine) GetStateCount() int > 获取状态数量 + *** + + #### func (*StateLine) GetStateByIndex(index int) State > 获取指定索引的状态 + *** + + #### func (*StateLine) GetTriggerByTime(t time.Time) []func () > 获取指定时间点的触发器 + *** + + #### func (*StateLine) GetTriggerByIndex(index int) []func () > 获取指定索引的触发器 + *** + + #### func (*StateLine) GetTriggerByState(state State) []func () > 获取指定状态的触发器 + *** + + #### func (*StateLine) AddTriggerToState(state State, onTrigger ...func ()) *StateLine[State] > 给指定状态添加触发器 + *** + + #### func (*StateLine) Range(handler func (index int, state State, t time.Time) bool) > 按照时间顺序遍历时间线 + *** + + #### func (*StateLine) RangeReverse(handler func (index int, state State, t time.Time) bool) > 按照时间逆序遍历时间线 + *** ### Period `STRUCT` @@ -494,61 +566,118 @@ type StateLine[State generic.Basic] struct { ```go type Period [2]time.Time ``` + + #### func (Period) Start() time.Time > 返回时间段的开始时间 + *** + + #### func (Period) End() time.Time > 返回时间段的结束时间 + *** + + #### func (Period) Duration() time.Duration > 返回时间段的持续时间 + *** + + #### func (Period) Day() int > 返回时间段的持续天数 + *** + + #### func (Period) Hour() int > 返回时间段的持续小时数 + *** + + #### func (Period) Minute() int > 返回时间段的持续分钟数 + *** + + #### func (Period) Seconds() int > 返回时间段的持续秒数 + *** + + #### func (Period) Milliseconds() int > 返回时间段的持续毫秒数 + *** + + #### func (Period) Microseconds() int > 返回时间段的持续微秒数 + *** + + #### func (Period) Nanoseconds() int > 返回时间段的持续纳秒数 + *** + + #### func (Period) IsZero() bool > 判断时间段是否为零值 + *** + + #### func (Period) IsInvalid() bool > 判断时间段是否无效 + *** + + #### func (Period) IsBefore(t time.Time) bool > 判断时间段是否在指定时间之前 + *** + + #### func (Period) IsAfter(t time.Time) bool > 判断时间段是否在指定时间之后 + *** + + #### func (Period) IsBetween(t time.Time) bool > 判断指定时间是否在时间段之间 + *** + + #### func (Period) IsOngoing(t time.Time) bool > 判断指定时间是否正在进行时 > - 如果时间段的开始时间在指定时间之前或者等于指定时间,且时间段的结束时间在指定时间之后,则返回 true + *** + + #### func (Period) IsBetweenOrEqual(t time.Time) bool > 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 + *** + + #### func (Period) IsBetweenOrEqualPeriod(t Period) bool > 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 + *** + + #### func (Period) IsOverlap(t Period) bool > 判断时间段是否与指定时间段重叠 + *** From bbf70fab02712ffb4a30a5f9b6400f16758d97c2 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 15 Jan 2024 17:27:29 +0800 Subject: [PATCH 11/21] =?UTF-8?q?test:=20server=20=E5=8C=85=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E9=83=A8=E5=88=86=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/network.go | 27 ++++- server/options_example_test.go | 51 ++++++++++ server/options_test.go | 109 ++++++++++++++++++++ server/server.go | 2 +- server/server_example_test.go | 41 ++++++-- server/server_test.go | 146 +++++++++++++++------------ server/service.go | 3 +- server/service_example_test.go | 44 ++++++++ server/service_test.go | 29 +++--- utils/generator/astgo/comment.go | 9 +- utils/generator/astgo/field.go | 4 +- utils/generator/astgo/file.go | 2 +- utils/generator/astgo/function.go | 2 +- utils/generator/astgo/package.go | 2 +- utils/generator/astgo/struct.go | 2 +- utils/generator/genreadme/builder.go | 2 + utils/random/ip.go | 14 +++ 17 files changed, 387 insertions(+), 102 deletions(-) create mode 100644 server/options_example_test.go create mode 100644 server/options_test.go create mode 100644 server/service_example_test.go diff --git a/server/network.go b/server/network.go index 7fadec24..6351ef1c 100644 --- a/server/network.go +++ b/server/network.go @@ -16,6 +16,22 @@ import ( "time" ) +// Network 服务器运行的网络模式 +// - 根据不同的网络模式,服务器将会产生不同的行为,该类型将在服务器创建时候指定 +// +// 服务器支持的网络模式如下: +// - NetworkNone 该模式下不监听任何网络端口,仅开启消息队列,适用于纯粹的跨服服务器等情况 +// - NetworkTcp 该模式下将会监听 TCP 协议的所有地址,包括 IPv4 和 IPv6 +// - NetworkTcp4 该模式下将会监听 TCP 协议的 IPv4 地址 +// - NetworkTcp6 该模式下将会监听 TCP 协议的 IPv6 地址 +// - NetworkUdp 该模式下将会监听 UDP 协议的所有地址,包括 IPv4 和 IPv6 +// - NetworkUdp4 该模式下将会监听 UDP 协议的 IPv4 地址 +// - NetworkUdp6 该模式下将会监听 UDP 协议的 IPv6 地址 +// - NetworkUnix 该模式下将会监听 Unix 协议的地址 +// - NetworkHttp 该模式下将会监听 HTTP 协议的地址 +// - NetworkWebsocket 该模式下将会监听 Websocket 协议的地址 +// - NetworkKcp 该模式下将会监听 KCP 协议的地址 +// - NetworkGRPC 该模式下将会监听 GRPC 协议的地址 type Network string const ( @@ -321,7 +337,16 @@ func (n Network) websocketMode(state chan<- error, srv *Server) { }((&listener{srv: srv, Listener: l, state: state}).init(), mux) } -// IsSocket 返回当前服务器的网络模式是否为 Socket 模式 +// IsSocket 返回当前服务器的网络模式是否为 Socket 模式,目前为止仅有如下几种模式为 Socket 模式: +// - NetworkTcp +// - NetworkTcp4 +// - NetworkTcp6 +// - NetworkUdp +// - NetworkUdp4 +// - NetworkUdp6 +// - NetworkUnix +// - NetworkKcp +// - NetworkWebsocket func (n Network) IsSocket() bool { return collection.KeyInMap(socketNetworks, n) } diff --git a/server/options_example_test.go b/server/options_example_test.go new file mode 100644 index 00000000..c9166033 --- /dev/null +++ b/server/options_example_test.go @@ -0,0 +1,51 @@ +package server_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/server" + "github.com/kercylan98/minotaur/utils/times" + "time" +) + +// 服务器在启动时将阻塞 1s,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 +// - 该示例中,将在收到慢消息时关闭服务器 +func ExampleWithLowMessageDuration() { + srv := server.New(server.NetworkNone, + server.WithLowMessageDuration(time.Second), + ) + srv.RegStartFinishEvent(func(srv *server.Server) { + time.Sleep(time.Second) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // 1 +} + +// 服务器在启动时将发布一条阻塞 1s 的异步消息,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 +// - 该示例中,将在收到慢消息时关闭服务器 +func ExampleWithAsyncLowMessageDuration() { + srv := server.New(server.NetworkNone, + server.WithAsyncLowMessageDuration(time.Second), + ) + srv.RegStartFinishEvent(func(srv *server.Server) { + srv.PushAsyncMessage(func() error { + time.Sleep(time.Second) + return nil + }, nil) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // 1 +} diff --git a/server/options_test.go b/server/options_test.go new file mode 100644 index 00000000..aea3e50f --- /dev/null +++ b/server/options_test.go @@ -0,0 +1,109 @@ +package server_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/server" + "github.com/kercylan98/minotaur/utils/random" + "testing" + "time" +) + +func TestWithLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{ + {name: "TestWithLowMessageDuration", duration: server.DefaultLowMessageDuration}, + {name: "TestWithLowMessageDuration_Zero", duration: 0}, + {name: "TestWithLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, + server.WithLowMessageDuration(c.duration), + ) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + time.Sleep(server.DefaultLowMessageDuration) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = "addr" + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} + +func TestWithAsyncLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{ + {name: "TestWithAsyncLowMessageDuration", duration: time.Millisecond * 100}, + {name: "TestWithAsyncLowMessageDuration_Zero", duration: 0}, + {name: "TestWithAsyncLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, + server.WithAsyncLowMessageDuration(c.duration), + ) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + srv.PushAsyncMessage(func() error { + time.Sleep(c.duration) + return nil + }, nil) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = fmt.Sprintf("%s%d", "addr", random.Int(0, 9999)) + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} diff --git a/server/server.go b/server/server.go index ea537204..19234862 100644 --- a/server/server.go +++ b/server/server.go @@ -149,7 +149,7 @@ func (srv *Server) Run(addr string) (err error) { return nil } -// IsSocket 是否是 Socket 模式 +// IsSocket 通过执行 Network.IsSocket 函数检查该服务器是否是 Socket 模式 func (srv *Server) IsSocket() bool { return srv.network.IsSocket() } diff --git a/server/server_example_test.go b/server/server_example_test.go index a7c2cb21..acc679c4 100644 --- a/server/server_example_test.go +++ b/server/server_example_test.go @@ -1,30 +1,53 @@ package server_test import ( + "fmt" "github.com/kercylan98/minotaur/server" "time" ) +// 该案例将创建一个简单的 WebSocket 服务器,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 +// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 +// +// 该案例的输出结果为 true func ExampleNew() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) - } + fmt.Println(srv != nil) + // Output: + // true +} +// 该案例将创建两个不同类型的服务器,其中 WebSocket 是一个 Socket 服务器,而 Http 是一个非 Socket 服务器 +// +// 可知案例输出结果为: +// - true +// - false +func ExampleServer_IsSocket() { + srv1 := server.New(server.NetworkWebsocket) + fmt.Println(srv1.IsSocket()) + srv2 := server.New(server.NetworkHttp) + fmt.Println(srv2.IsSocket()) // Output: + // true + // false } +// 该案例将创建一个简单的 WebSocket 服务器并启动监听 `:9999/` 作为 WebSocket 监听地址,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 +// - 当服务器启动失败后,将会返回错误信息并触发 panic +// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 func ExampleServer_Run() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) if err := srv.Run(":9999"); err != nil { panic(err) } + // Output: +} +// RunNone 函数并没有特殊的意义,该函数内部调用了 `srv.Run("")` 函数,仅是一个语法糖,用来表示服务器不需要监听任何地址 +func ExampleServer_RunNone() { + srv := server.New(server.NetworkNone) + if err := srv.RunNone(); err != nil { + panic(err) + } // Output: } diff --git a/server/server_test.go b/server/server_test.go index 7e8fe1f0..074f3d56 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,81 +1,101 @@ package server_test import ( - "fmt" "github.com/kercylan98/minotaur/server" - "github.com/kercylan98/minotaur/server/client" - "github.com/kercylan98/minotaur/utils/times" + "github.com/kercylan98/minotaur/utils/super" + "runtime/debug" "testing" "time" ) +// 该单元测试用于测试以不同的基本参数创建服务器是否存在异常 func TestNew(t *testing.T) { - srv := server.New(server.NetworkWebsocket, server.WithPProf()) - srv.RegStartBeforeEvent(func(srv *server.Server) { - fmt.Println("启动前") - }) - srv.RegStartFinishEvent(func(srv *server.Server) { - fmt.Println("启动完成") - }) - srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { - fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) - }) + var cases = []struct { + name string + network server.Network + addr string + shouldPanic bool + }{ + {name: "TestNew_Unknown", addr: "", network: "Unknown", shouldPanic: true}, + {name: "TestNew_None", addr: "", network: server.NetworkNone, shouldPanic: false}, + {name: "TestNew_None_Addr", addr: "addr", network: server.NetworkNone, shouldPanic: false}, + {name: "TestNew_Tcp_AddrEmpty", addr: "", network: server.NetworkTcp, shouldPanic: true}, + {name: "TestNew_Tcp_AddrIllegal", addr: "addr", network: server.NetworkTcp, shouldPanic: true}, + {name: "TestNew_Tcp_Addr", addr: ":9999", network: server.NetworkTcp, shouldPanic: false}, + {name: "TestNew_Tcp4_AddrEmpty", addr: "", network: server.NetworkTcp4, shouldPanic: true}, + {name: "TestNew_Tcp4_AddrIllegal", addr: "addr", network: server.NetworkTcp4, shouldPanic: true}, + {name: "TestNew_Tcp4_Addr", addr: ":9999", network: server.NetworkTcp4, shouldPanic: false}, + {name: "TestNew_Tcp6_AddrEmpty", addr: "", network: server.NetworkTcp6, shouldPanic: true}, + {name: "TestNew_Tcp6_AddrIllegal", addr: "addr", network: server.NetworkTcp6, shouldPanic: true}, + {name: "TestNew_Tcp6_Addr", addr: ":9999", network: server.NetworkTcp6, shouldPanic: false}, + {name: "TestNew_Udp_AddrEmpty", addr: "", network: server.NetworkUdp, shouldPanic: true}, + {name: "TestNew_Udp_AddrIllegal", addr: "addr", network: server.NetworkUdp, shouldPanic: true}, + {name: "TestNew_Udp_Addr", addr: ":9999", network: server.NetworkUdp, shouldPanic: false}, + {name: "TestNew_Udp4_AddrEmpty", addr: "", network: server.NetworkUdp4, shouldPanic: true}, + {name: "TestNew_Udp4_AddrIllegal", addr: "addr", network: server.NetworkUdp4, shouldPanic: true}, + {name: "TestNew_Udp4_Addr", addr: ":9999", network: server.NetworkUdp4, shouldPanic: false}, + {name: "TestNew_Udp6_AddrEmpty", addr: "", network: server.NetworkUdp6, shouldPanic: true}, + {name: "TestNew_Udp6_AddrIllegal", addr: "addr", network: server.NetworkUdp6, shouldPanic: true}, + {name: "TestNew_Udp6_Addr", addr: ":9999", network: server.NetworkUdp6, shouldPanic: false}, + {name: "TestNew_Unix_AddrEmpty", addr: "", network: server.NetworkUnix, shouldPanic: true}, + {name: "TestNew_Unix_AddrIllegal", addr: "addr", network: server.NetworkUnix, shouldPanic: true}, + {name: "TestNew_Unix_Addr", addr: "addr", network: server.NetworkUnix, shouldPanic: false}, + {name: "TestNew_Websocket_AddrEmpty", addr: "", network: server.NetworkWebsocket, shouldPanic: true}, + {name: "TestNew_Websocket_AddrIllegal", addr: "addr", network: server.NetworkWebsocket, shouldPanic: true}, + {name: "TestNew_Websocket_Addr", addr: ":9999/ws", network: server.NetworkWebsocket, shouldPanic: false}, + {name: "TestNew_Http_AddrEmpty", addr: "", network: server.NetworkHttp, shouldPanic: true}, + {name: "TestNew_Http_AddrIllegal", addr: "addr", network: server.NetworkHttp, shouldPanic: true}, + {name: "TestNew_Http_Addr", addr: ":9999", network: server.NetworkHttp, shouldPanic: false}, + {name: "TestNew_Kcp_AddrEmpty", addr: "", network: server.NetworkKcp, shouldPanic: true}, + {name: "TestNew_Kcp_AddrIllegal", addr: "addr", network: server.NetworkKcp, shouldPanic: true}, + {name: "TestNew_Kcp_Addr", addr: ":9999", network: server.NetworkKcp, shouldPanic: false}, + {name: "TestNew_GRPC_AddrEmpty", addr: "", network: server.NetworkGRPC, shouldPanic: true}, + {name: "TestNew_GRPC_AddrIllegal", addr: "addr", network: server.NetworkGRPC, shouldPanic: true}, + {name: "TestNew_GRPC_Addr", addr: ":9999", network: server.NetworkGRPC, shouldPanic: false}, + } - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if err := super.RecoverTransform(recover()); err != nil && !c.shouldPanic { + debug.PrintStack() + t.Fatal("not should panic, err:", err) + } + }() + if err := server.New(c.network, server.WithLimitLife(time.Millisecond*10)).Run(""); err != nil { + panic(err) + } + }) } } -func TestNew2(t *testing.T) { - srv := server.New(server.NetworkWebsocket, server.WithPProf()) - srv.RegStartBeforeEvent(func(srv *server.Server) { - fmt.Println("启动前") - }) - srv.RegStartFinishEvent(func(srv *server.Server) { - fmt.Println("启动完成") - }) - srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { - fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) - }) - - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) +// 这个测试检查了各个类型的服务器是否为 Socket 模式。如需查看为 Socket 模式的网络类型,请参考 [` Network.IsSocket` ](#struct_Network_IsSocket) +func TestServer_IsSocket(t *testing.T) { + var cases = []struct { + name string + network server.Network + expect bool + }{ + {name: "TestServer_IsSocket_None", network: server.NetworkNone, expect: false}, + {name: "TestServer_IsSocket_Tcp", network: server.NetworkTcp, expect: true}, + {name: "TestServer_IsSocket_Tcp4", network: server.NetworkTcp4, expect: true}, + {name: "TestServer_IsSocket_Tcp6", network: server.NetworkTcp6, expect: true}, + {name: "TestServer_IsSocket_Udp", network: server.NetworkUdp, expect: true}, + {name: "TestServer_IsSocket_Udp4", network: server.NetworkUdp4, expect: true}, + {name: "TestServer_IsSocket_Udp6", network: server.NetworkUdp6, expect: true}, + {name: "TestServer_IsSocket_Unix", network: server.NetworkUnix, expect: true}, + {name: "TestServer_IsSocket_Http", network: server.NetworkHttp, expect: false}, + {name: "TestServer_IsSocket_Websocket", network: server.NetworkWebsocket, expect: true}, + {name: "TestServer_IsSocket_Kcp", network: server.NetworkKcp, expect: true}, + {name: "TestServer_IsSocket_GRPC", network: server.NetworkGRPC, expect: false}, } -} -func TestNewClient(t *testing.T) { - count := 500 - for i := 0; i < count; i++ { - fmt.Println("启动", i+1) - cli := client.NewWebsocket("ws://172.29.5.138:9999") - cli.RegConnectionReceivePacketEvent(func(conn *client.Client, wst int, packet []byte) { - fmt.Println(time.Now().Unix(), "收到", string(packet)) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + s := server.New(c.network) + if s.IsSocket() != c.expect { + t.Fatalf("expect: %v, got: %v", c.expect, s.IsSocket()) + } }) - cli.RegConnectionClosedEvent(func(conn *client.Client, err any) { - fmt.Println("关闭", err) - }) - cli.RegConnectionOpenedEvent(func(conn *client.Client) { - go func() { - for i < count { - time.Sleep(time.Second) - } - for { - for i := 0; i < 10; i++ { - cli.WriteWS(2, []byte("hello")) - } - } - }() - }) - if err := cli.Run(); err != nil { - panic(err) - } } - - time.Sleep(times.Week) } diff --git a/server/service.go b/server/service.go index 54abe6ca..722f9233 100644 --- a/server/service.go +++ b/server/service.go @@ -5,7 +5,8 @@ import ( "reflect" ) -// Service 兼容传统 service 设计模式的接口 +// Service 兼容传统 service 设计模式的接口,通过该接口可以实现更简洁、更具有可读性的服务绑定 +// - 在这之前,我们在实现功能上会将 Server 进行全局存储,之后通过 init 函数进行初始化,这样的顺序是不可控的。 type Service interface { // OnInit 初始化服务,该方法将会在 Server 初始化时执行 // - 通常来说,该阶段发生任何错误都应该 panic 以阻止 Server 启动 diff --git a/server/service_example_test.go b/server/service_example_test.go new file mode 100644 index 00000000..88817115 --- /dev/null +++ b/server/service_example_test.go @@ -0,0 +1,44 @@ +package server_test + +import ( + "github.com/kercylan98/minotaur/server" + "time" +) + +// 这个案例中我们将 `TestService` 绑定到了 `srv` 服务器中,当服务器启动时,将会对 `TestService` 进行初始化 +// +// 其中 `TestService` 的定义如下: +// ```go +// +// type TestService struct{} +// +// func (ts *TestService) OnInit(srv *server.Server) { +// srv.RegStartFinishEvent(onStartFinish) +// +// srv.RegStopEvent(func(srv *server.Server) { +// fmt.Println("server stop") +// }) +// } +// +// func (ts *TestService) onStartFinish(srv *server.Server) { +// fmt.Println("server start finish") +// } +// +// ``` +// +// 可以看出,在服务初始化时,该服务向服务器注册了启动完成事件及停止事件。这是我们推荐的编码方式,这样编码有以下好处: +// - 具备可控制的初始化顺序,避免 init 产生的各种顺序导致的问题,如配置还未加载完成,即开始进行数据库连接等操作 +// - 可以方便的将不同的服务拆分到不同的包中进行管理 +// - 当不需要某个服务时,可以直接删除该服务的绑定,而不需要修改其他代码 +// - ... +func ExampleBindService() { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) + server.BindService(srv, new(TestService)) + + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // server start finish + // server stop +} diff --git a/server/service_test.go b/server/service_test.go index 1ffb7104..caafa7bc 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -20,24 +20,19 @@ func (ts *TestService) OnInit(srv *server.Server) { } func TestBindService(t *testing.T) { - srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) - - server.BindService(srv, new(TestService)) - - if err := srv.RunNone(); err != nil { - t.Fatal(err) + var cases = []struct { + name string + }{ + {name: "TestBindService"}, } -} -func ExampleBindService() { - srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) - server.BindService(srv, new(TestService)) - - if err := srv.RunNone(); err != nil { - panic(err) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Millisecond)) + server.BindService(srv, new(TestService)) + if err := srv.RunNone(); err != nil { + t.Fatal(err) + } + }) } - - // Output: - // server start finish - // server stop } diff --git a/utils/generator/astgo/comment.go b/utils/generator/astgo/comment.go index 68e7767f..9e7527c3 100644 --- a/utils/generator/astgo/comment.go +++ b/utils/generator/astgo/comment.go @@ -5,7 +5,7 @@ import ( "strings" ) -func newComment(cg *ast.CommentGroup) *Comment { +func newComment(name string, cg *ast.CommentGroup) *Comment { c := &Comment{} if cg == nil { return c @@ -14,9 +14,10 @@ func newComment(cg *ast.CommentGroup) *Comment { c.Comments = append(c.Comments, comment.Text) cc := strings.TrimPrefix(strings.Replace(comment.Text, "// ", "//", 1), "//") if i == 0 { - s := strings.SplitN(cc, " ", 2) - if len(s) == 2 { - cc = s[1] + tsc := strings.TrimSpace(cc) + if strings.HasPrefix(tsc, name) { + s := strings.TrimSpace(strings.TrimPrefix(tsc, name)) + cc = s } } c.Clear = append(c.Clear, cc) diff --git a/utils/generator/astgo/field.go b/utils/generator/astgo/field.go index 1c007896..da2a5e26 100644 --- a/utils/generator/astgo/field.go +++ b/utils/generator/astgo/field.go @@ -10,7 +10,7 @@ func newField(field *ast.Field) []*Field { return []*Field{{ Anonymous: true, Type: newType(field.Type), - Comments: newComment(field.Comment), + Comments: newComment("", field.Comment), }} } else { var fs []*Field @@ -22,7 +22,7 @@ func newField(field *ast.Field) []*Field { Anonymous: false, Name: name.String(), Type: newType(field.Type), - Comments: newComment(field.Comment), + Comments: newComment(name.String(), field.Comment), }) } return fs diff --git a/utils/generator/astgo/file.go b/utils/generator/astgo/file.go index 233b4a16..ff4c03ca 100644 --- a/utils/generator/astgo/file.go +++ b/utils/generator/astgo/file.go @@ -16,7 +16,7 @@ func newFile(owner *Package, filePath string) (*File, error) { af: af, owner: owner, FilePath: filePath, - Comment: newComment(af.Doc), + Comment: newComment("Package", af.Doc), } for _, decl := range af.Decls { switch typ := decl.(type) { diff --git a/utils/generator/astgo/function.go b/utils/generator/astgo/function.go index 40bf9e7b..7613476c 100644 --- a/utils/generator/astgo/function.go +++ b/utils/generator/astgo/function.go @@ -12,7 +12,7 @@ func newFunction(astFunc *ast.FuncDecl) *Function { f := &Function{ decl: astFunc, Name: astFunc.Name.String(), - Comments: newComment(astFunc.Doc), + Comments: newComment(astFunc.Name.String(), astFunc.Doc), } f.IsTest = strings.HasPrefix(f.Name, "Test") f.IsBenchmark = strings.HasPrefix(f.Name, "Benchmark") diff --git a/utils/generator/astgo/package.go b/utils/generator/astgo/package.go index 681564dd..1491e946 100644 --- a/utils/generator/astgo/package.go +++ b/utils/generator/astgo/package.go @@ -91,7 +91,7 @@ func (p *Package) Structs() []*Struct { } func (p *Package) FileComments() *Comment { - var comment = newComment(nil) + var comment = newComment("", nil) for _, file := range p.Files { for _, c := range file.Comment.Comments { comment.Comments = append(comment.Comments, c) diff --git a/utils/generator/astgo/struct.go b/utils/generator/astgo/struct.go index 263e9d11..5fba9dbc 100644 --- a/utils/generator/astgo/struct.go +++ b/utils/generator/astgo/struct.go @@ -8,7 +8,7 @@ func newStruct(astGen *ast.GenDecl) *Struct { astTypeSpec := astGen.Specs[0].(*ast.TypeSpec) s := &Struct{ Name: astTypeSpec.Name.String(), - Comments: newComment(astGen.Doc), + Comments: newComment(astTypeSpec.Name.String(), astGen.Doc), } s.Internal = s.Name[0] >= 97 && s.Name[0] <= 122 if astTypeSpec.TypeParams != nil { diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index 435c5adf..a15f662a 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -288,6 +288,7 @@ func (b *Builder) genStructs() { if function.Internal || function.Test { continue } + b.newLine(fmt.Sprintf(``, structInfo.Name, function.Name)).newLine() b.title(4, strings.TrimSpace(fmt.Sprintf("func (%s%s) %s%s %s", super.If(function.Struct.Type.IsPointer, "*", ""), structInfo.Name, @@ -311,6 +312,7 @@ func (b *Builder) genStructs() { for _, comment := range function.Comments.Clear { b.quote(comment) } + b.newLine() if example := b.p.GetExampleTest(function); example != nil { b.newLine("**示例代码:**").newLine() if len(example.Comments.Clear) > 0 { diff --git a/utils/random/ip.go b/utils/random/ip.go index 952552fd..862af974 100644 --- a/utils/random/ip.go +++ b/utils/random/ip.go @@ -15,6 +15,20 @@ func Port() int { return Int(1, 65535) } +// UsablePort 随机返回一个可用的端口号,如果没有可用端口号则返回 -1 +func UsablePort() int { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + return -1 + } + cli, err := net.ListenTCP("tcp", addr) + if err != nil { + return -1 + } + defer func() { _ = cli.Close() }() + return cli.Addr().(*net.TCPAddr).Port +} + // IPv4 返回一个随机产生的IPv4地址。 func IPv4() string { return fmt.Sprintf("%d.%d.%d.%d", Int(1, 255), Int(0, 255), Int(0, 255), Int(0, 255)) From 36de5934ce1591fb6347d8b34f6550e2fe4811fb Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 16 Jan 2024 16:15:51 +0800 Subject: [PATCH 12/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20super=20?= =?UTF-8?q?=E5=8C=85=20JSON=20=E8=A7=A3=E6=9E=90=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=9B=B6=E5=80=BC=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/json.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/utils/super/json.go b/utils/super/json.go index 70e375e5..61e88c29 100644 --- a/utils/super/json.go +++ b/utils/super/json.go @@ -12,12 +12,7 @@ var json = jsonIter.ConfigCompatibleWithStandardLibrary func MarshalJSON(v interface{}) []byte { b, err := json.Marshal(v) if err != nil { - switch reflect.TypeOf(v).Kind() { - case reflect.Array, reflect.Slice: - return StringToBytes("[]") - default: - return StringToBytes("{}") - } + return jsonZero(v) } return b } @@ -37,12 +32,7 @@ func UnmarshalJSON(data []byte, v interface{}) error { func MarshalIndentJSON(v interface{}, prefix, indent string) []byte { b, err := json.MarshalIndent(v, prefix, indent) if err != nil { - switch reflect.TypeOf(v).Kind() { - case reflect.Array, reflect.Slice: - return StringToBytes("[]") - default: - return StringToBytes("{}") - } + return jsonZero(v) } return b } @@ -51,3 +41,23 @@ func MarshalIndentJSON(v interface{}, prefix, indent string) []byte { func MarshalToTargetWithJSON(src, dest interface{}) error { return json.Unmarshal(MarshalJSON(src), dest) } + +// 获取 json 格式的空对象 +func jsonZero(v any) []byte { + vof := reflect.Indirect(reflect.ValueOf(v)) + switch vof.Kind() { + case reflect.Array, reflect.Slice: + return StringToBytes("[]") + case reflect.Bool: + return StringToBytes("false") // false 在 JSON 中是不合法的,但是 Golang 中能够正确解析 + case + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return StringToBytes("0") // 0 在 JSON 中是不合法的,但是 Golang 中能够正确解析 + case reflect.String: + return StringToBytes("\"\"") + default: + return StringToBytes("{}") + } +} From 6cc158d43aa144f3f076711b54dd38ef9641b9ec Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 16 Jan 2024 18:19:22 +0800 Subject: [PATCH 13/21] =?UTF-8?q?other:=20=E4=BE=9D=E8=B5=96=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 45 ++++++++++---------- go.sum | 129 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 97 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index c7855dfd..85334057 100644 --- a/go.mod +++ b/go.mod @@ -4,26 +4,26 @@ go 1.21 require ( github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 - github.com/alphadose/haxmap v1.3.0 + github.com/alphadose/haxmap v1.3.1 github.com/gin-contrib/pprof v1.4.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-resty/resty/v2 v2.7.0 + github.com/go-resty/resty/v2 v2.11.0 github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.1 github.com/json-iterator/go v1.1.12 - github.com/panjf2000/ants/v2 v2.8.1 + github.com/panjf2000/ants/v2 v2.9.0 github.com/panjf2000/gnet v1.6.7 github.com/pkg/errors v0.9.1 github.com/smartystreets/goconvey v1.8.1 github.com/sony/sonyflake v1.2.0 - github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.3 + github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.4 github.com/tealeg/xlsx v1.0.5 - github.com/tidwall/gjson v1.16.0 - github.com/xtaci/kcp-go/v5 v5.6.3 - go.uber.org/atomic v1.10.0 - golang.org/x/crypto v0.17.0 - google.golang.org/grpc v1.59.0 + github.com/tidwall/gjson v1.17.0 + github.com/xtaci/kcp-go/v5 v5.6.7 + go.uber.org/atomic v1.11.0 + golang.org/x/crypto v0.18.0 + google.golang.org/grpc v1.60.1 ) require ( @@ -40,11 +40,10 @@ require ( github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/klauspost/reedsolomon v1.11.8 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/reedsolomon v1.12.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect @@ -59,16 +58,16 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.19.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a26711f0..9260a51f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 h1:iPugyBI7oFtbDZXC4dnY093M1kZx6k/95sen92gafbY= github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:WAMLHwunr1hi3u7OjGV6/VWG9QbdMhGpEKjROiSFd10= -github.com/alphadose/haxmap v1.3.0 h1:C/2LboOnPCZP27GmmSXOcwx360st0P8N0fTJ3voefKc= -github.com/alphadose/haxmap v1.3.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM= +github.com/alphadose/haxmap v1.3.1 h1:KmZh75duO1tC8pt3LmUwoTYiZ9sh4K52FX8p7/yrlqU= +github.com/alphadose/haxmap v1.3.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -16,7 +16,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhD github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -45,8 +45,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -76,8 +76,8 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -85,15 +85,14 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= -github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno= +github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -102,16 +101,16 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= -github.com/panjf2000/ants/v2 v2.8.1 h1:C+n/f++aiW8kHCExKlpX6X+okmxKXP7DWLutxuAPuwQ= -github.com/panjf2000/ants/v2 v2.8.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= +github.com/panjf2000/ants/v2 v2.9.0 h1:SztCLkVxBRigbg+vt0S5QvF5vxAbxbKt09/YfAJ0tEo= +github.com/panjf2000/ants/v2 v2.9.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= github.com/panjf2000/gnet v1.6.7 h1:zv1k6kw80sG5ZQrLpbbFDheNCm50zm3z2e3ck5GwMOM= github.com/panjf2000/gnet v1.6.7/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= @@ -125,9 +124,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= @@ -135,8 +133,8 @@ github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sS github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ= github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -150,16 +148,17 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE= github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM= github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40= github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI= github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -174,25 +173,23 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/xtaci/kcp-go/v5 v5.6.3 h1:yd59SKXdJ0PBxeMBy3apalxFCEmBLGgQmL6nP46tU0g= -github.com/xtaci/kcp-go/v5 v5.6.3/go.mod h1:uIuw2KEg3FcmEdS4PeXHaGty9Ui7NYb1WKIrSDwpMg4= +github.com/xtaci/kcp-go/v5 v5.6.7 h1:7+rnxNFIsjEwTXQk4cSZpXM4pO0hqtpwE1UFFoJBffA= +github.com/xtaci/kcp-go/v5 v5.6.7/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -201,16 +198,20 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -220,41 +221,59 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -262,6 +281,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -270,14 +291,14 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -295,12 +316,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 9dba7ffe19f0b5502e06d3cafcd1602736e6648e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 18 Jan 2024 17:16:00 +0800 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20collection=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20ConvertSliceToBatches=E3=80=81ConvertMapKeysToBatch?= =?UTF-8?q?es=E3=80=81ConvertMapValuesToBatches=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=B0=86=E5=88=87=E7=89=87=E6=88=96?= =?UTF-8?q?=20map=20=E8=BD=AC=E6=8D=A2=E4=B8=BA=E6=8C=89=E7=89=B9=E5=AE=9A?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E5=88=86=E6=89=B9=E7=9A=84=E6=89=B9=E6=AC=A1?= =?UTF-8?q?=E5=88=87=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/collection/convert.go | 50 ++++++++++++++ utils/collection/convert_example_test.go | 26 ++++++++ utils/collection/convert_test.go | 84 ++++++++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/utils/collection/convert.go b/utils/collection/convert.go index 63df6509..e5e7f546 100644 --- a/utils/collection/convert.go +++ b/utils/collection/convert.go @@ -1,5 +1,55 @@ package collection +// ConvertSliceToBatches 将切片 s 转换为分批次的切片,当 batchSize 小于等于 0 或者 s 长度为 0 时,将会返回 nil +func ConvertSliceToBatches[S ~[]V, V any](s S, batchSize int) []S { + if len(s) == 0 || batchSize <= 0 { + return nil + } + var batches = make([]S, 0, len(s)/batchSize+1) + for i := 0; i < len(s); i += batchSize { + var end = i + batchSize + if end > len(s) { + end = len(s) + } + batches = append(batches, s[i:end]) + } + return batches +} + +// ConvertMapKeysToBatches 将映射的键转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil +func ConvertMapKeysToBatches[M ~map[K]V, K comparable, V any](m M, batchSize int) [][]K { + if len(m) == 0 || batchSize <= 0 { + return nil + } + var batches = make([][]K, 0, len(m)/batchSize+1) + var keys = ConvertMapKeysToSlice(m) + for i := 0; i < len(keys); i += batchSize { + var end = i + batchSize + if end > len(keys) { + end = len(keys) + } + batches = append(batches, keys[i:end]) + } + return batches +} + +// ConvertMapValuesToBatches 将映射的值转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil +func ConvertMapValuesToBatches[M ~map[K]V, K comparable, V any](m M, batchSize int) [][]V { + if len(m) == 0 || batchSize <= 0 { + return nil + } + var batches = make([][]V, 0, len(m)/batchSize+1) + var values = ConvertMapValuesToSlice(m) + for i := 0; i < len(values); i += batchSize { + var end = i + batchSize + if end > len(values) { + end = len(values) + } + batches = append(batches, values[i:end]) + } + return batches +} + // ConvertSliceToAny 将切片转换为任意类型的切片 func ConvertSliceToAny[S ~[]V, V any](s S) []any { if len(s) == 0 { diff --git a/utils/collection/convert_example_test.go b/utils/collection/convert_example_test.go index 36582248..468e6eac 100644 --- a/utils/collection/convert_example_test.go +++ b/utils/collection/convert_example_test.go @@ -4,8 +4,33 @@ import ( "fmt" "github.com/kercylan98/minotaur/utils/collection" "reflect" + "sort" ) +func ExampleConvertSliceToBatches() { + result := collection.ConvertSliceToBatches([]int{1, 2, 3}, 2) + for _, v := range result { + fmt.Println(v) + } + // Output: + // [1 2] + // [3] +} + +func ExampleConvertMapKeysToBatches() { + result := collection.ConvertMapKeysToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2) + fmt.Println(len(result)) + // Output: + // 2 +} + +func ExampleConvertMapValuesToBatches() { + result := collection.ConvertMapValuesToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2) + fmt.Println(len(result)) + // Output: + // 2 +} + func ExampleConvertSliceToAny() { result := collection.ConvertSliceToAny([]int{1, 2, 3}) fmt.Println(reflect.TypeOf(result).String(), len(result)) @@ -60,6 +85,7 @@ func ExampleConvertSliceToBoolMap() { func ExampleConvertMapKeysToSlice() { result := collection.ConvertMapKeysToSlice(map[int]int{1: 1, 2: 2, 3: 3}) + sort.Ints(result) for i, v := range result { fmt.Println(i, v) } diff --git a/utils/collection/convert_test.go b/utils/collection/convert_test.go index 8b875ebb..a117cc9d 100644 --- a/utils/collection/convert_test.go +++ b/utils/collection/convert_test.go @@ -6,6 +6,90 @@ import ( "testing" ) +func TestConvertSliceToBatches(t *testing.T) { + var cases = []struct { + name string + input []int + batch int + expected [][]int + }{ + {name: "TestConvertSliceToBatches_NonEmpty", input: []int{1, 2, 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, + {name: "TestConvertSliceToBatches_Empty", input: []int{}, batch: 2, expected: nil}, + {name: "TestConvertSliceToBatches_Nil", input: nil, batch: 2, expected: nil}, + {name: "TestConvertSliceToBatches_NonPositive", input: []int{1, 2, 3}, batch: 0, expected: nil}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertSliceToBatches(c.input, c.batch) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for i := 0; i < len(actual); i++ { + av, ev := actual[i], c.expected[i] + if len(av) != len(ev) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for j := 0; j < len(av); j++ { + aj, ej := av[j], ev[j] + if reflect.TypeOf(aj).Kind() != reflect.TypeOf(ej).Kind() { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + if aj != ej { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + } + } + }) + } +} + +func TestConvertMapKeysToBatches(t *testing.T) { + var cases = []struct { + name string + input map[int]int + batch int + expected [][]int + }{ + {name: "TestConvertMapKeysToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, + {name: "TestConvertMapKeysToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil}, + {name: "TestConvertMapKeysToBatches_Nil", input: nil, batch: 2, expected: nil}, + {name: "TestConvertMapKeysToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertMapKeysToBatches(c.input, c.batch) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + }) + } +} + +func TestConvertMapValuesToBatches(t *testing.T) { + var cases = []struct { + name string + input map[int]int + batch int + expected [][]int + }{ + {name: "TestConvertMapValuesToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, + {name: "TestConvertMapValuesToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil}, + {name: "TestConvertMapValuesToBatches_Nil", input: nil, batch: 2, expected: nil}, + {name: "TestConvertMapValuesToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertMapValuesToBatches(c.input, c.batch) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + }) + } +} + func TestConvertSliceToAny(t *testing.T) { var cases = []struct { name string From c4605cc4c30e4eeee29662265dfa852d58a96549 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 18 Jan 2024 18:51:17 +0800 Subject: [PATCH 15/21] =?UTF-8?q?feat:=20huge=20=E5=8C=85=20NewInt=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=94=AF=E6=8C=81=20string=E3=80=81bool?= =?UTF-8?q?=E3=80=81float=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/huge/int.go | 67 ++++++++++++++++++++++++++++++++-------- utils/huge/int_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 utils/huge/int_test.go diff --git a/utils/huge/int.go b/utils/huge/int.go index 68db831e..b46bf541 100644 --- a/utils/huge/int.go +++ b/utils/huge/int.go @@ -17,33 +17,70 @@ var ( type Int big.Int -// NewInt 创建一个 Int -func NewInt[T generic.Number](x T) *Int { - return (*Int)(big.NewInt(int64(x))) -} - -// NewIntByString 通过字符串创建一个 Int -// - 如果字符串不是一个合法的数字,则返回 0 -func NewIntByString(i string) *Int { - v, suc := new(big.Int).SetString(i, 10) - if !suc { - return IntZero.Copy() +// NewInt 创建一个 Int 对象,该对象的值为 x +func NewInt[T generic.Basic](x T) *Int { + var xa any = x + switch x := xa.(type) { + case int: + return (*Int)(big.NewInt(int64(x))) + case int8: + return (*Int)(big.NewInt(int64(x))) + case int16: + return (*Int)(big.NewInt(int64(x))) + case int32: + return (*Int)(big.NewInt(int64(x))) + case int64: + return (*Int)(big.NewInt(x)) + case uint: + return (*Int)(big.NewInt(int64(x))) + case uint8: + return (*Int)(big.NewInt(int64(x))) + case uint16: + return (*Int)(big.NewInt(int64(x))) + case uint32: + return (*Int)(big.NewInt(int64(x))) + case uint64: + return (*Int)(big.NewInt(int64(x))) + case string: + si, suc := new(big.Int).SetString(x, 10) + if !suc { + return (*Int)(big.NewInt(0)) + } + return (*Int)(si) + case bool: + if x { + return (*Int)(big.NewInt(1)) + } + return (*Int)(big.NewInt(0)) + case float32: + return (*Int)(big.NewInt(int64(x))) + case float64: + return (*Int)(big.NewInt(int64(x))) } - return (*Int)(v) + return (*Int)(big.NewInt(0)) } +// applyIntOperation 应用一个 Int 操作 func applyIntOperation[T generic.Number](v *Int, i T, op func(*big.Int, *big.Int) *big.Int) *Int { return (*Int)(op(v.ToBigint(), NewInt(i).ToBigint())) } +// Copy 拷贝当前 Int 对象 func (slf *Int) Copy() *Int { return (*Int)(new(big.Int).Set(slf.ToBigint())) } +// Set 设置当前 Int 对象的值为 i func (slf *Int) Set(i *Int) *Int { return (*Int)(slf.ToBigint().Set(i.ToBigint())) } +// SetString 设置当前 Int 对象的值为 i +func (slf *Int) SetString(i string) *Int { + return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) +} + +// SetInt 设置当前 Int 对象的值为 i func (slf *Int) SetInt(i int) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } @@ -92,6 +129,9 @@ func (slf *Int) IsZero() bool { } func (slf *Int) ToBigint() *big.Int { + if slf == nil { + return big.NewInt(0) + } return (*big.Int)(slf) } @@ -130,6 +170,9 @@ func (slf *Int) Int64() int64 { } func (slf *Int) String() string { + if slf == nil { + return "0" + } return slf.ToBigint().String() } diff --git a/utils/huge/int_test.go b/utils/huge/int_test.go new file mode 100644 index 00000000..cfdc4d84 --- /dev/null +++ b/utils/huge/int_test.go @@ -0,0 +1,69 @@ +package huge_test + +import ( + "github.com/kercylan98/minotaur/utils/huge" + "testing" +) + +func TestNewInt(t *testing.T) { + var cases = []struct { + name string + nil bool + in int64 + mul int64 + want string + }{ + {name: "TestNewIntNegative", in: -1, want: "-1"}, + {name: "TestNewIntZero", in: 0, want: "0"}, + {name: "TestNewIntPositive", in: 1, want: "1"}, + {name: "TestNewIntMax", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestNewIntMin", in: -9223372036854775808, want: "-9223372036854775808"}, + {name: "TestNewIntMulNegative", in: -9223372036854775808, mul: 10000000, want: "-92233720368547758080000000"}, + {name: "TestNewIntMulPositive", in: 9223372036854775807, mul: 10000000, want: "92233720368547758070000000"}, + {name: "TestNewIntNil", nil: true, want: "0"}, + {name: "TestNewIntNilMul", nil: true, mul: 10000000, want: "0"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var got *huge.Int + switch { + case c.nil: + if c.mul > 0 { + got = huge.NewInt(0).MulInt64(c.mul) + } + case c.mul == 0: + got = huge.NewInt(c.in) + default: + got = huge.NewInt(c.in).MulInt64(c.mul) + } + if s := got.String(); s != c.want { + t.Errorf("want: %s, got: %s", c.want, got.String()) + } else { + t.Log(s) + } + }) + } + + // other + t.Run("TestNewIntFromString", func(t *testing.T) { + if got := huge.NewInt("1234567890123456789012345678901234567890"); got.String() != "1234567890123456789012345678901234567890" { + t.Fatalf("want: %s, got: %s", "1234567890123456789012345678901234567890", got.String()) + } + }) + t.Run("TestNewIntFromInt", func(t *testing.T) { + if got := huge.NewInt(1234567890); got.String() != "1234567890" { + t.Fatalf("want: %s, got: %s", "1234567890", got.String()) + } + }) + t.Run("TestNewIntFromBool", func(t *testing.T) { + if got := huge.NewInt(true); got.String() != "1" { + t.Fatalf("want: %s, got: %s", "1", got.String()) + } + }) + t.Run("TestNewIntFromFloat", func(t *testing.T) { + if got := huge.NewInt(1234567890.1234567890); got.String() != "1234567890" { + t.Fatalf("want: %s, got: %s", "1234567890", got.String()) + } + }) +} From 756f823ca409477891f7368c5cc33bd1a06174af Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 19 Jan 2024 14:46:24 +0800 Subject: [PATCH 16/21] =?UTF-8?q?feat:=20collection=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20Equel=20=E5=91=BD=E5=90=8D=E5=89=8D=E7=BC=80?= =?UTF-8?q?=E7=9A=84=E7=94=A8=E4=BA=8E=E6=AF=94=E8=BE=83=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=92=8C=20map=20=E5=85=83=E7=B4=A0=E6=98=AF=E5=90=A6=E7=9B=B8?= =?UTF-8?q?=E5=90=8C=E7=9A=84=E5=87=BD=E6=95=B0=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20Loop=20=E5=91=BD=E5=90=8D=E5=89=8D=E7=BC=80=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E9=81=8D=E5=8E=86=E5=88=87=E7=89=87=E5=92=8C?= =?UTF-8?q?=20map=20=E5=85=83=E7=B4=A0=E7=9A=84=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/collection/contains.go | 56 +++ utils/collection/contains_example_test.go | 52 +++ utils/collection/contains_test.go | 100 +++++ utils/collection/loop.go | 188 +++++++++ utils/collection/loop_example_test.go | 159 ++++++++ utils/collection/loop_test.go | 333 ++++++++++++++++ utils/huge/int.go | 39 +- utils/huge/int_example_test.go | 42 ++ utils/huge/int_test.go | 458 +++++++++++++++++++++- 9 files changed, 1421 insertions(+), 6 deletions(-) create mode 100644 utils/collection/loop.go create mode 100644 utils/collection/loop_example_test.go create mode 100644 utils/collection/loop_test.go create mode 100644 utils/huge/int_example_test.go diff --git a/utils/collection/contains.go b/utils/collection/contains.go index 7613122d..dda49397 100644 --- a/utils/collection/contains.go +++ b/utils/collection/contains.go @@ -1,5 +1,61 @@ package collection +// EqualSlice 检查两个切片是否相等,当 handler 返回 true 时,表示 slice1 中的某个元素和 slice2 中的某个元素相匹配 +// - 当两个切片的容量不同时,不会影响最终的比较结果 +func EqualSlice[S ~[]V, V any](slice1 S, slice2 S, handler ComparisonHandler[V]) bool { + if len(slice1) != len(slice2) { + return false + } + for i, v1 := range slice1 { + if !handler(v1, slice2[i]) { + return false + } + } + return true +} + +// EqualComparableSlice 检查两个切片的值是否相同 +// - 当两个切片的容量不同时,不会影响最终的比较结果 +func EqualComparableSlice[S ~[]V, V comparable](slice1 S, slice2 S) bool { + if len(slice1) != len(slice2) { + return false + } + for i, v1 := range slice1 { + if v1 != slice2[i] { + return false + } + } + return true +} + +// EqualMap 检查两个 map 是否相等,当 handler 返回 true 时,表示 map1 中的某个元素和 map2 中的某个元素相匹配 +// - 当两个 map 的容量不同时,不会影响最终的比较结果 +func EqualMap[M ~map[K]V, K comparable, V any](map1 M, map2 M, handler ComparisonHandler[V]) bool { + if len(map1) != len(map2) { + return false + } + for k, v1 := range map1 { + if !handler(v1, map2[k]) { + return false + } + } + return true +} + +// EqualComparableMap 检查两个 map 的值是否相同 +// - 当两个 map 的容量不同时,不会影响最终的比较结果 +func EqualComparableMap[M ~map[K]V, K comparable, V comparable](map1 M, map2 M) bool { + if len(map1) != len(map2) { + return false + } + for k, v1 := range map1 { + if v1 != map2[k] { + return false + } + } + return true +} + // InSlice 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 func InSlice[S ~[]V, V any](slice S, v V, handler ComparisonHandler[V]) bool { if len(slice) == 0 { diff --git a/utils/collection/contains_example_test.go b/utils/collection/contains_example_test.go index 3b96d36e..033f0fec 100644 --- a/utils/collection/contains_example_test.go +++ b/utils/collection/contains_example_test.go @@ -5,6 +5,58 @@ import ( "github.com/kercylan98/minotaur/utils/collection" ) +func ExampleEqualSlice() { + s1 := []int{1, 2, 3} + s2 := []int{1} + s3 := []int{1, 2, 3} + fmt.Println(collection.EqualSlice(s1, s2, func(source, target int) bool { + return source == target + })) + fmt.Println(collection.EqualSlice(s1, s3, func(source, target int) bool { + return source == target + })) + // Output: + // false + // true +} + +func ExampleEqualComparableSlice() { + s1 := []int{1, 2, 3} + s2 := []int{1} + s3 := []int{1, 2, 3} + fmt.Println(collection.EqualComparableSlice(s1, s2)) + fmt.Println(collection.EqualComparableSlice(s1, s3)) + // Output: + // false + // true +} + +func ExampleEqualMap() { + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"a": 1} + m3 := map[string]int{"a": 1, "b": 2} + fmt.Println(collection.EqualMap(m1, m2, func(source, target int) bool { + return source == target + })) + fmt.Println(collection.EqualMap(m1, m3, func(source, target int) bool { + return source == target + })) + // Output: + // false + // true +} + +func ExampleEqualComparableMap() { + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"a": 1} + m3 := map[string]int{"a": 1, "b": 2} + fmt.Println(collection.EqualComparableMap(m1, m2)) + fmt.Println(collection.EqualComparableMap(m1, m3)) + // Output: + // false + // true +} + func ExampleInSlice() { result := collection.InSlice([]int{1, 2, 3}, 2, func(source, target int) bool { return source == target diff --git a/utils/collection/contains_test.go b/utils/collection/contains_test.go index 2c339c3f..f9f1b323 100644 --- a/utils/collection/contains_test.go +++ b/utils/collection/contains_test.go @@ -9,6 +9,106 @@ var intComparisonHandler = func(source, target int) bool { return source == target } +func TestEqualSlice(t *testing.T) { + var cases = []struct { + name string + input []int + inputV []int + expected bool + }{ + {"TestEqualSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true}, + {"TestEqualSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false}, + {"TestEqualSlice_EmptySlice", []int{}, []int{}, true}, + {"TestEqualSlice_NilSlice", nil, nil, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualSlice(c.input, c.inputV, func(source, target int) bool { + return source == target + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", + c.name, c.expected, actual, "not as expected") + } + }) + } +} + +func TestEqualComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + inputV []int + expected bool + }{ + {"TestEqualComparableSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true}, + {"TestEqualComparableSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false}, + {"TestEqualComparableSlice_EmptySlice", []int{}, []int{}, true}, + {"TestEqualComparableSlice_NilSlice", nil, nil, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualComparableSlice(c.input, c.inputV) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", + c.name, c.expected, actual, "not as expected") + } + }) + } +} + +func TestEqualMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + inputV map[int]int + expected bool + }{ + {"TestEqualMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true}, + {"TestEqualMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false}, + {"TestEqualMap_EmptyMap", map[int]int{}, map[int]int{}, true}, + {"TestEqualMap_NilMap", nil, nil, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualMap(c.input, c.inputV, func(source, target int) bool { + return source == target + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", + c.name, c.expected, actual, "not as expected") + } + }) + } +} + +func TestEqualComparableMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + inputV map[int]int + expected bool + }{ + {"TestEqualComparableMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true}, + {"TestEqualComparableMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false}, + {"TestEqualComparableMap_EmptyMap", map[int]int{}, map[int]int{}, true}, + {"TestEqualComparableMap_NilMap", nil, nil, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualComparableMap(c.input, c.inputV) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", + c.name, c.expected, actual, "not as expected") + } + }) + } +} + func TestInSlice(t *testing.T) { var cases = []struct { name string diff --git a/utils/collection/loop.go b/utils/collection/loop.go new file mode 100644 index 00000000..9b3700ca --- /dev/null +++ b/utils/collection/loop.go @@ -0,0 +1,188 @@ +package collection + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "sort" +) + +// LoopSlice 迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopSlice[S ~[]V, V any](slice S, f func(i int, val V) bool) { + for i, v := range slice { + if !f(i, v) { + break + } + } +} + +// ReverseLoopSlice 逆序迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +// - 迭代过程将在 f 函数返回 false 时中断 +func ReverseLoopSlice[S ~[]V, V any](slice S, f func(i int, val V) bool) { + for i := len(slice) - 1; i >= 0; i-- { + if !f(i, slice[i]) { + break + } + } +} + +// LoopMap 迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - m 的迭代顺序是不确定的,因此每次迭代的顺序可能不同 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMap[M ~map[K]V, K comparable, V any](m M, f func(i int, key K, val V) bool) { + var i int + for k, v := range m { + if !f(i, k, v) { + break + } + i++ + } +} + +// LoopMapByOrderedKeyAsc 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByOrderedKeyAsc[M ~map[K]V, K generic.Ordered, V any](m M, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return AscBy(keys[i], keys[j]) + }) + for i, k := range keys { + if !f(i, k, m[k]) { + break + } + } +} + +// LoopMapByOrderedKeyDesc 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByOrderedKeyDesc[M ~map[K]V, K generic.Ordered, V any](m M, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return DescBy(keys[i], keys[j]) + }) + for i, k := range keys { + if !f(i, k, m[k]) { + break + } + } +} + +// LoopMapByOrderedValueAsc 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByOrderedValueAsc[M ~map[K]V, K comparable, V generic.Ordered](m M, f func(i int, key K, val V) bool) { + var keys []K + var values []V + for k, v := range m { + keys = append(keys, k) + values = append(values, v) + } + sort.Slice(values, func(i, j int) bool { + return AscBy(values[i], values[j]) + }) + for i, v := range values { + if !f(i, keys[i], v) { + break + } + } +} + +// LoopMapByOrderedValueDesc 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByOrderedValueDesc[M ~map[K]V, K comparable, V generic.Ordered](m M, f func(i int, key K, val V) bool) { + var keys []K + var values []V + for k, v := range m { + keys = append(keys, k) + values = append(values, v) + } + sort.Slice(values, func(i, j int) bool { + return DescBy(values[i], values[j]) + }) + for i, v := range values { + if !f(i, keys[i], v) { + break + } + } +} + +// LoopMapByKeyGetterAsc 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByKeyGetterAsc[M ~map[K]V, K comparable, V comparable, N generic.Ordered](m M, getter func(k K) N, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return AscBy(getter(keys[i]), getter(keys[j])) + }) + for i, v := range keys { + if !f(i, keys[i], m[v]) { + break + } + } +} + +// LoopMapByValueGetterAsc 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByValueGetterAsc[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, getter func(v V) N, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return AscBy(getter(m[keys[i]]), getter(m[keys[j]])) + }) + for i, v := range keys { + if !f(i, keys[i], m[v]) { + break + } + } +} + +// LoopMapByKeyGetterDesc 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByKeyGetterDesc[M ~map[K]V, K comparable, V comparable, N generic.Ordered](m M, getter func(k K) N, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return DescBy(getter(keys[i]), getter(keys[j])) + }) + for i, v := range keys { + if !f(i, keys[i], m[v]) { + break + } + } +} + +// LoopMapByValueGetterDesc 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +// - 迭代过程将在 f 函数返回 false 时中断 +func LoopMapByValueGetterDesc[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, getter func(v V) N, f func(i int, key K, val V) bool) { + var keys []K + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return DescBy(getter(m[keys[i]]), getter(m[keys[j]])) + }) + for i, v := range keys { + if !f(i, keys[i], m[v]) { + break + } + } +} diff --git a/utils/collection/loop_example_test.go b/utils/collection/loop_example_test.go new file mode 100644 index 00000000..f7256068 --- /dev/null +++ b/utils/collection/loop_example_test.go @@ -0,0 +1,159 @@ +package collection_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/collection" +) + +func ExampleLoopSlice() { + var result []int + collection.LoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool { + result = append(result, val) + if uint(i) == 1 { + return false + } + return true + }) + fmt.Println(result) + // Output: [1 2] +} + +func ExampleReverseLoopSlice() { + var result []int + collection.ReverseLoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool { + result = append(result, val) + if uint(i) == 1 { + return false + } + return true + }) + fmt.Println(result) + // Output: [5 4 3 2] +} + +func ExampleLoopMap() { + var result []int + collection.LoopMap(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) + // Output: + // true +} + +func ExampleLoopMapByOrderedKeyAsc() { + var result []int + collection.LoopMapByOrderedKeyAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) + // Output: + // true +} + +func ExampleLoopMapByOrderedKeyDesc() { + var result []int + collection.LoopMapByOrderedKeyDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) + // Output: + // true +} + +func ExampleLoopMapByOrderedValueAsc() { + var result []int + collection.LoopMapByOrderedValueAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) + // Output: + // true +} + +func ExampleLoopMapByOrderedValueDesc() { + var result []int + collection.LoopMapByOrderedValueDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) + // Output: + // true +} + +func ExampleLoopMapByKeyGetterAsc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByKeyGetterAsc( + m, + func(k string) int { + return m[k] + }, + func(i int, key string, val int) bool { + result = append(result, val) + return true + }, + ) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) + // Output: + // true +} + +func ExampleLoopMapByKeyGetterDesc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByKeyGetterDesc( + m, + func(k string) int { + return m[k] + }, + func(i int, key string, val int) bool { + result = append(result, val) + return true + }, + ) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) + // Output: + // true +} + +func ExampleLoopMapByValueGetterAsc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByValueGetterAsc( + m, + func(v int) int { + return v + }, + func(i int, key string, val int) bool { + result = append(result, val) + return true + }, + ) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) + // Output: + // true +} + +func ExampleLoopMapByValueGetterDesc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByValueGetterDesc( + m, + func(v int) int { + return v + }, + func(i int, key string, val int) bool { + result = append(result, val) + return true + }, + ) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) + // Output: + // true +} diff --git a/utils/collection/loop_test.go b/utils/collection/loop_test.go new file mode 100644 index 00000000..8c0c739f --- /dev/null +++ b/utils/collection/loop_test.go @@ -0,0 +1,333 @@ +package collection_test + +import ( + "github.com/kercylan98/minotaur/utils/collection" + "testing" +) + +func TestLoopSlice(t *testing.T) { + var cases = []struct { + name string + in []int + out []int + breakIndex uint + }{ + {"TestLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{1, 2}, 2}, + {"TestLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}, 0}, + {"TestLoopSlice_Empty", []int{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopSlice(c.in, func(i int, val int) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopSlice(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestReverseLoopSlice(t *testing.T) { + var cases = []struct { + name string + in []int + out []int + breakIndex uint + }{ + {"TestReverseLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{5, 4}, 2}, + {"TestReverseLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}, 0}, + {"TestReverseLoopSlice_Empty", []int{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.ReverseLoopSlice(c.in, func(i int, val int) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == uint(len(c.in))-c.breakIndex { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("ReverseLoopSlice(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMap(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out map[int]string + breakIndex uint + }{ + {"TestLoopMap_Part", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2"}, 2}, + {"TestLoopMap_All", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2", 3: "3"}, 0}, + {"TestLoopMap_Empty", map[int]string{}, map[int]string{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result = make(map[int]string) + collection.LoopMap(c.in, func(i int, key int, val string) bool { + result[key] = val + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableMap(result, c.out) { + t.Errorf("LoopMap(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByOrderedKeyAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{ + {"TestLoopMapByOrderedKeyAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2}, + {"TestLoopMapByOrderedKeyAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0}, + {"TestLoopMapByOrderedKeyAsc_Empty", map[int]string{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByOrderedKeyAsc(c.in, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedKeyAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByOrderedKeyDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{ + {"TestLoopMapByOrderedKeyDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2}, + {"TestLoopMapByOrderedKeyDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0}, + {"TestLoopMapByOrderedKeyDesc_Empty", map[int]string{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByOrderedKeyDesc(c.in, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedKeyDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByOrderedValueAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{ + {"TestLoopMapByOrderedValueAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2}, + {"TestLoopMapByOrderedValueAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0}, + {"TestLoopMapByOrderedValueAsc_Empty", map[int]string{}, []string{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByOrderedValueAsc(c.in, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedValueAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByOrderedValueDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{ + {"TestLoopMapByOrderedValueDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2}, + {"TestLoopMapByOrderedValueDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0}, + {"TestLoopMapByOrderedValueDesc_Empty", map[int]string{}, []string{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByOrderedValueDesc(c.in, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedValueDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByKeyGetterAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{ + {"TestLoopMapByKeyGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2}, + {"TestLoopMapByKeyGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0}, + {"TestLoopMapByKeyGetterAsc_Empty", map[int]string{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByKeyGetterAsc(c.in, func(key int) int { + return key + }, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByKeyGetterAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByKeyGetterDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{ + {"TestLoopMapByKeyGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2}, + {"TestLoopMapByKeyGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0}, + {"TestLoopMapByKeyGetterDesc_Empty", map[int]string{}, []int{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByKeyGetterDesc(c.in, func(key int) int { + return key + }, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByKeyGetterDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByValueGetterAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{ + {"TestLoopMapByValueGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2}, + {"TestLoopMapByValueGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0}, + {"TestLoopMapByValueGetterAsc_Empty", map[int]string{}, []string{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByValueGetterAsc(c.in, func(val string) string { + return val + }, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByValueGetterAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +func TestLoopMapByValueGetterDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{ + {"TestLoopMapByValueGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2}, + {"TestLoopMapByValueGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0}, + {"TestLoopMapByValueGetterDesc_Empty", map[int]string{}, []string{}, 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByValueGetterDesc(c.in, func(val string) string { + return val + }, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByValueGetterDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} diff --git a/utils/huge/int.go b/utils/huge/int.go index b46bf541..44bc7420 100644 --- a/utils/huge/int.go +++ b/utils/huge/int.go @@ -85,42 +85,67 @@ func (slf *Int) SetInt(i int) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetInt8 设置当前 Int 对象的值为 i func (slf *Int) SetInt8(i int8) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetInt16 设置当前 Int 对象的值为 i func (slf *Int) SetInt16(i int16) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetInt32 设置当前 Int 对象的值为 i func (slf *Int) SetInt32(i int32) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetInt64 设置当前 Int 对象的值为 i func (slf *Int) SetInt64(i int64) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetUint 设置当前 Int 对象的值为 i func (slf *Int) SetUint(i uint) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetUint8 设置当前 Int 对象的值为 i func (slf *Int) SetUint8(i uint8) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetUint16 设置当前 Int 对象的值为 i func (slf *Int) SetUint16(i uint16) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetUint32 设置当前 Int 对象的值为 i func (slf *Int) SetUint32(i uint32) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetUint64 设置当前 Int 对象的值为 i func (slf *Int) SetUint64(i uint64) *Int { return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) } +// SetFloat32 设置当前 Int 对象的值为 i 向下取整后的值 +func (slf *Int) SetFloat32(i float32) *Int { + return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) +} + +// SetFloat64 设置当前 Int 对象的值为 i 向下取整后的值 +func (slf *Int) SetFloat64(i float64) *Int { + return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) +} + +// SetBool 设置当前 Int 对象的值为 i,当 i 为 true 时,值为 1,当 i 为 false 时,值为 0 +func (slf *Int) SetBool(i bool) *Int { + return (*Int)(slf.ToBigint().Set((*big.Int)(NewInt(i)))) +} + +// IsZero 判断当前 Int 对象的值是否为 0 func (slf *Int) IsZero() bool { if slf == nil || slf.EqualTo(IntZero) { return true @@ -128,6 +153,7 @@ func (slf *Int) IsZero() bool { return false } +// ToBigint 转换为 *big.Int func (slf *Int) ToBigint() *big.Int { if slf == nil { return big.NewInt(0) @@ -140,35 +166,37 @@ func (slf *Int) Cmp(i *Int) int { return slf.ToBigint().Cmp(i.ToBigint()) } -// GreaterThan 大于 +// GreaterThan 检查 slf 是否大于 i func (slf *Int) GreaterThan(i *Int) bool { return slf.Cmp(i) > 0 } -// GreaterThanOrEqualTo 大于或等于 +// GreaterThanOrEqualTo 检查 slf 是否大于或等于 i func (slf *Int) GreaterThanOrEqualTo(i *Int) bool { return slf.Cmp(i) >= 0 } -// LessThan 小于 +// LessThan 检查 slf 是否小于 i func (slf *Int) LessThan(i *Int) bool { return slf.Cmp(i) < 0 } -// LessThanOrEqualTo 小于或等于 +// LessThanOrEqualTo 检查 slf 是否小于或等于 i func (slf *Int) LessThanOrEqualTo(i *Int) bool { return slf.Cmp(i) <= 0 } -// EqualTo 等于 +// EqualTo 检查 slf 是否等于 i func (slf *Int) EqualTo(i *Int) bool { return slf.Cmp(i) == 0 } +// Int64 转换为 int64 类型进行返回 func (slf *Int) Int64() int64 { return slf.ToBigint().Int64() } +// String 转换为 string 类型进行返回 func (slf *Int) String() string { if slf == nil { return "0" @@ -176,6 +204,7 @@ func (slf *Int) String() string { return slf.ToBigint().String() } +// Add 使用 i 对 slf 进行加法运算,slf 的值会变为运算后的值。返回 slf func (slf *Int) Add(i *Int) *Int { x := slf.ToBigint() return (*Int)(x.Add(x, i.ToBigint())) diff --git a/utils/huge/int_example_test.go b/utils/huge/int_example_test.go new file mode 100644 index 00000000..c4ce3f0e --- /dev/null +++ b/utils/huge/int_example_test.go @@ -0,0 +1,42 @@ +package huge_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/huge" +) + +// 该案例展示了 NewInt 对各种基本类型的支持及用法 +func ExampleNewInt() { + fmt.Println(huge.NewInt("12345678900000000")) + fmt.Println(huge.NewInt(1234567890)) + fmt.Println(huge.NewInt(true)) + fmt.Println(huge.NewInt(123.123)) + fmt.Println(huge.NewInt(byte(1))) + // Output: + // 12345678900000000 + // 1234567890 + // 1 + // 123 + // 1 +} + +func ExampleInt_Copy() { + var a = huge.NewInt(1234567890) + var b = a.Copy().SetInt64(9876543210) + fmt.Println(a) + fmt.Println(b) + // Output: + // 1234567890 + // 9876543210 +} + +func ExampleInt_Set() { + var a = huge.NewInt(1234567890) + var b = huge.NewInt(9876543210) + fmt.Println(a) + a.Set(b) + fmt.Println(a) + // Output: + // 1234567890 + // 9876543210 +} diff --git a/utils/huge/int_test.go b/utils/huge/int_test.go index cfdc4d84..851ac30a 100644 --- a/utils/huge/int_test.go +++ b/utils/huge/int_test.go @@ -2,6 +2,7 @@ package huge_test import ( "github.com/kercylan98/minotaur/utils/huge" + "math/big" "testing" ) @@ -38,7 +39,7 @@ func TestNewInt(t *testing.T) { got = huge.NewInt(c.in).MulInt64(c.mul) } if s := got.String(); s != c.want { - t.Errorf("want: %s, got: %s", c.want, got.String()) + t.Fatalf("want: %s, got: %s", c.want, got.String()) } else { t.Log(s) } @@ -67,3 +68,458 @@ func TestNewInt(t *testing.T) { } }) } + +func TestInt_Copy(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{ + {name: "TestIntCopyNegative", in: -1, want: "-1"}, + {name: "TestIntCopyZero", in: 0, want: "0"}, + {name: "TestIntCopyPositive", in: 1, want: "1"}, + {name: "TestIntCopyMax", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntCopyMin", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in = huge.NewInt(c.in) + var got = in.Copy() + if in.Int64() != c.in { + t.Fatalf("want: %d, got: %d", c.in, in.Int64()) + } + if s := got.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, got.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_Set(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{ + {name: "TestIntSetNegative", in: -1, want: "-1"}, + {name: "TestIntSetZero", in: 0, want: "0"}, + {name: "TestIntSetPositive", in: 1, want: "1"}, + {name: "TestIntSetMax", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntSetMin", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.Set(huge.NewInt(c.in)) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetString(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "TestIntSetStringNegative", in: "-1", want: "-1"}, + {name: "TestIntSetStringZero", in: "0", want: "0"}, + {name: "TestIntSetStringPositive", in: "1", want: "1"}, + {name: "TestIntSetStringMax", in: "9223372036854775807", want: "9223372036854775807"}, + {name: "TestIntSetStringMin", in: "-9223372036854775808", want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetString(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetInt(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{ + {name: "TestIntSetIntNegative", in: -1, want: "-1"}, + {name: "TestIntSetIntZero", in: 0, want: "0"}, + {name: "TestIntSetIntPositive", in: 1, want: "1"}, + {name: "TestIntSetIntMax", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntSetIntMin", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetInt8(t *testing.T) { + var cases = []struct { + name string + in int8 + want string + }{ + {name: "TestIntSetInt8Negative", in: -1, want: "-1"}, + {name: "TestIntSetInt8Zero", in: 0, want: "0"}, + {name: "TestIntSetInt8Positive", in: 1, want: "1"}, + {name: "TestIntSetInt8Max", in: 127, want: "127"}, + {name: "TestIntSetInt8Min", in: -128, want: "-128"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt8(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetInt16(t *testing.T) { + var cases = []struct { + name string + in int16 + want string + }{ + {name: "TestIntSetInt16Negative", in: -1, want: "-1"}, + {name: "TestIntSetInt16Zero", in: 0, want: "0"}, + {name: "TestIntSetInt16Positive", in: 1, want: "1"}, + {name: "TestIntSetInt16Max", in: 32767, want: "32767"}, + {name: "TestIntSetInt16Min", in: -32768, want: "-32768"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt16(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetInt32(t *testing.T) { + var cases = []struct { + name string + in int32 + want string + }{ + {name: "TestIntSetInt32Negative", in: -1, want: "-1"}, + {name: "TestIntSetInt32Zero", in: 0, want: "0"}, + {name: "TestIntSetInt32Positive", in: 1, want: "1"}, + {name: "TestIntSetInt32Max", in: 2147483647, want: "2147483647"}, + {name: "TestIntSetInt32Min", in: -2147483648, want: "-2147483648"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetInt64(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{ + {name: "TestIntSetInt64Negative", in: -1, want: "-1"}, + {name: "TestIntSetInt64Zero", in: 0, want: "0"}, + {name: "TestIntSetInt64Positive", in: 1, want: "1"}, + {name: "TestIntSetInt64Max", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntSetInt64Min", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetUint(t *testing.T) { + var cases = []struct { + name string + in uint64 + want string + }{ + {name: "TestIntSetUintNegative", in: 0, want: "0"}, + {name: "TestIntSetUintZero", in: 0, want: "0"}, + {name: "TestIntSetUintPositive", in: 1, want: "1"}, + {name: "TestIntSetUintMax", in: 18446744073709551615, want: "18446744073709551615"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetUint8(t *testing.T) { + var cases = []struct { + name string + in uint8 + want string + }{ + {name: "TestIntSetUint8Negative", in: 0, want: "0"}, + {name: "TestIntSetUint8Zero", in: 0, want: "0"}, + {name: "TestIntSetUint8Positive", in: 1, want: "1"}, + {name: "TestIntSetUint8Max", in: 255, want: "255"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint8(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetUint16(t *testing.T) { + var cases = []struct { + name string + in uint16 + want string + }{ + {name: "TestIntSetUint16Negative", in: 0, want: "0"}, + {name: "TestIntSetUint16Zero", in: 0, want: "0"}, + {name: "TestIntSetUint16Positive", in: 1, want: "1"}, + {name: "TestIntSetUint16Max", in: 65535, want: "65535"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint16(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetUint32(t *testing.T) { + var cases = []struct { + name string + in uint32 + want string + }{ + {name: "TestIntSetUint32Negative", in: 0, want: "0"}, + {name: "TestIntSetUint32Zero", in: 0, want: "0"}, + {name: "TestIntSetUint32Positive", in: 1, want: "1"}, + {name: "TestIntSetUint32Max", in: 4294967295, want: "4294967295"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetUint64(t *testing.T) { + var cases = []struct { + name string + in uint64 + want string + }{ + {name: "TestIntSetUint64Negative", in: 0, want: "0"}, + {name: "TestIntSetUint64Zero", in: 0, want: "0"}, + {name: "TestIntSetUint64Positive", in: 1, want: "1"}, + {name: "TestIntSetUint64Max", in: 18446744073709551615, want: "18446744073709551615"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetFloat32(t *testing.T) { + var cases = []struct { + name string + in float32 + want string + }{ + {name: "TestIntSetFloat32Negative", in: -1.1, want: "-1"}, + {name: "TestIntSetFloat32Zero", in: 0, want: "0"}, + {name: "TestIntSetFloat32Positive", in: 1.1, want: "1"}, + {name: "TestIntSetFloat32Max", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntSetFloat32Min", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetFloat32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetFloat64(t *testing.T) { + var cases = []struct { + name string + in float64 + want string + }{ + {name: "TestIntSetFloat64Negative", in: -1.1, want: "-1"}, + {name: "TestIntSetFloat64Zero", in: 0, want: "0"}, + {name: "TestIntSetFloat64Positive", in: 1.1, want: "1"}, + {name: "TestIntSetFloat64Max", in: 9223372036854775807, want: "9223372036854775807"}, + {name: "TestIntSetFloat64Min", in: -9223372036854775808, want: "-9223372036854775808"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetFloat64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_SetBool(t *testing.T) { + var cases = []struct { + name string + in bool + want string + }{ + {name: "TestIntSetBoolFalse", in: false, want: "0"}, + {name: "TestIntSetBoolTrue", in: true, want: "1"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetBool(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +func TestInt_IsZero(t *testing.T) { + var cases = []struct { + name string + in int64 + want bool + }{ + {name: "TestIntIsZeroNegative", in: -1, want: false}, + {name: "TestIntIsZeroZero", in: 0, want: true}, + {name: "TestIntIsZeroPositive", in: 1, want: false}, + {name: "TestIntIsZeroMax", in: 9223372036854775807, want: false}, + {name: "TestIntIsZeroMin", in: -9223372036854775808, want: false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := huge.NewInt(c.in).IsZero(); got != c.want { + t.Fatalf("want: %t, got: %t", c.want, got) + } + }) + } +} + +func TestInt_ToBigint(t *testing.T) { + var cases = []struct { + name string + in int64 + want *big.Int + }{ + {name: "TestIntToBigintNegative", in: -1, want: big.NewInt(-1)}, + {name: "TestIntToBigintZero", in: 0, want: big.NewInt(0)}, + {name: "TestIntToBigintPositive", in: 1, want: big.NewInt(1)}, + {name: "TestIntToBigintMax", in: 9223372036854775807, want: big.NewInt(9223372036854775807)}, + {name: "TestIntToBigintMin", in: -9223372036854775808, want: big.NewInt(-9223372036854775808)}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := huge.NewInt(c.in).ToBigint(); got.Cmp(c.want) != 0 { + t.Fatalf("want: %s, got: %s", c.want.String(), got.String()) + } + }) + } +} From 959abff85f4cbf0c4c81b586317e39bf3dee3a80 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 19 Jan 2024 17:15:55 +0800 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20super=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20TryReadChannel=E3=80=81TryReadChannelByHandler=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=94=A8=E4=BA=8E=E5=AF=B9=20channel=20?= =?UTF-8?q?=E5=B0=9D=E8=AF=95=E5=86=99=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/channel.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/utils/super/channel.go b/utils/super/channel.go index 242510cc..3d3e24b7 100644 --- a/utils/super/channel.go +++ b/utils/super/channel.go @@ -20,3 +20,25 @@ func TryWriteChannelByHandler[T any](ch chan<- T, data T, handler func()) { handler() } } + +// TryReadChannel 尝试读取 channel,如果 channel 无法读取则忽略,返回是否读取成功 +// - 无法读取的情况包括:channel 已空、channel 已关闭 +func TryReadChannel[T any](ch <-chan T) (v T, suc bool) { + select { + case data := <-ch: + return data, true + default: + return v, false + } +} + +// TryReadChannelByHandler 尝试读取 channel,如果 channel 无法读取则执行 handler +// - 无法读取的情况包括:channel 已空、channel 已关闭 +func TryReadChannelByHandler[T any](ch <-chan T, handler func(ch <-chan T) T) (v T) { + select { + case data := <-ch: + return data + default: + return handler(ch) + } +} From f08f06891c94a2589d60203e2d4427e35f620625 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 22 Jan 2024 14:05:11 +0800 Subject: [PATCH 18/21] =?UTF-8?q?test:=20super.BitSet=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/bit_set.go | 13 +++ utils/super/bit_set_example_test.go | 74 ++++++++++++++++ utils/super/bit_set_test.go | 125 ++++++++++++++++++++++++---- 3 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 utils/super/bit_set_example_test.go diff --git a/utils/super/bit_set.go b/utils/super/bit_set.go index a7cce232..43efac2d 100644 --- a/utils/super/bit_set.go +++ b/utils/super/bit_set.go @@ -7,9 +7,13 @@ import ( ) // NewBitSet 通过指定的 Bit 位创建一个 BitSet +// - 当指定的 Bit 位存在负数时,将会 panic func NewBitSet[Bit generic.Integer](bits ...Bit) *BitSet[Bit] { set := &BitSet[Bit]{set: make([]uint64, 0, 1)} for _, bit := range bits { + if bit < 0 { + panic(fmt.Errorf("bit %v is negative", bit)) + } set.Set(bit) } return set @@ -43,6 +47,15 @@ func (slf *BitSet[Bit]) Del(bit Bit) *BitSet[Bit] { // Shrink 将 BitSet 中的比特位集合缩小到最小 // - 正常情况下当 BitSet 中的比特位超出 64 位时,将自动增长,当 BitSet 中的比特位数量减少时,可以使用该方法将 BitSet 中的比特位集合缩小到最小 func (slf *BitSet[Bit]) Shrink() *BitSet[Bit] { + switch len(slf.set) { + case 0: + return slf + case 1: + if slf.set[0] == 0 { + slf.set = nil + return slf + } + } index := len(slf.set) - 1 if slf.set[index] != 0 { return slf diff --git a/utils/super/bit_set_example_test.go b/utils/super/bit_set_example_test.go new file mode 100644 index 00000000..ead6ea70 --- /dev/null +++ b/utils/super/bit_set_example_test.go @@ -0,0 +1,74 @@ +package super_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/super" +) + +func ExampleNewBitSet() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Set(10) + fmt.Println(bs.Bits()) + // Output: + // [1 2 3 4 5 6 7 8 9 10] +} + +func ExampleBitSet_Set() { + var bs = super.NewBitSet[int]() + bs.Set(10) + fmt.Println(bs.Bits()) + // Output: + // [10] +} + +func ExampleBitSet_Del() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Del(1) + fmt.Println(bs.Bits()) + // Output: + // [2 3 4 5 6 7 8 9] +} + +func ExampleBitSet_Shrink() { + var bs = super.NewBitSet(111, 222, 333, 444) + fmt.Println(bs.Cap()) + bs.Del(444) + fmt.Println(bs.Cap()) + bs.Shrink() + fmt.Println(bs.Cap()) + // Output: + // 448 + // 448 + // 384 +} + +func ExampleBitSet_Cap() { + var bs = super.NewBitSet(63) + fmt.Println(bs.Cap()) + // Output: + // 64 +} + +func ExampleBitSet_Has() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(bs.Has(1)) + fmt.Println(bs.Has(10)) + // Output: + // true + // false +} + +func ExampleBitSet_Clear() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Clear() + fmt.Println(bs.Bits()) + // Output: + // [] +} + +func ExampleBitSet_Len() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(bs.Len()) + // Output: + // 9 +} diff --git a/utils/super/bit_set_test.go b/utils/super/bit_set_test.go index f6541390..e74e9b90 100644 --- a/utils/super/bit_set_test.go +++ b/utils/super/bit_set_test.go @@ -5,29 +5,118 @@ import ( "testing" ) +func TestNewBitSet(t *testing.T) { + var cases = []struct { + name string + in []int + shouldPanic bool + }{ + {name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + {name: "empty", in: make([]int, 0)}, + {name: "nil", in: nil}, + {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet(c.in...) + t.Log(bs) + }) + } +} + func TestBitSet_Set(t *testing.T) { - bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - bs.Set(11) - bs.Set(12) - bs.Set(13) - t.Log(bs) + var cases = []struct { + name string + in []int + shouldPanic bool + }{ + {name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + {name: "empty", in: make([]int, 0)}, + {name: "nil", in: nil}, + {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet[int]() + for _, bit := range c.in { + bs.Set(bit) + } + for _, bit := range c.in { + if !bs.Has(bit) { + t.Fatalf("bit %v not set", bit) + } + } + }) + } } func TestBitSet_Del(t *testing.T) { - bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - bs.Del(11) - bs.Del(12) - bs.Del(13) - bs.Del(10) - t.Log(bs) + var cases = []struct { + name string + in []int + shouldPanic bool + }{ + {name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + {name: "empty", in: make([]int, 0)}, + {name: "nil", in: nil}, + {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet[int]() + for _, bit := range c.in { + bs.Set(bit) + } + for _, bit := range c.in { + bs.Del(bit) + } + for _, bit := range c.in { + if bs.Has(bit) { + t.Fatalf("bit %v not del", bit) + } + } + }) + } } func TestBitSet_Shrink(t *testing.T) { - bs := super.NewBitSet(63) - t.Log(bs.Cap()) - bs.Set(200) - t.Log(bs.Cap()) - bs.Del(200) - bs.Shrink() - t.Log(bs.Cap()) + var cases = []struct { + name string + in []int + }{ + {name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + {name: "empty", in: make([]int, 0)}, + {name: "nil", in: nil}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + bs := super.NewBitSet(c.in...) + for _, v := range c.in { + bs.Del(v) + } + bs.Shrink() + if bs.Cap() != 0 { + t.Fatalf("cap %v != 0", bs.Cap()) + } + }) + } } From acc468492fc76faa69d493c83098bfecbb1e720d Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 23 Jan 2024 16:53:42 +0800 Subject: [PATCH 19/21] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=90=91=20ser?= =?UTF-8?q?ver.Server=20=E7=BB=91=E5=AE=9A=E4=B8=80=E4=BA=9B=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/server.go b/server/server.go index 19234862..6062f091 100644 --- a/server/server.go +++ b/server/server.go @@ -84,6 +84,7 @@ type Server struct { systemSignal chan os.Signal // 系统信号 closeChannel chan struct{} // 关闭信号 multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误 + data map[string]any // 服务器全局数据 messageCounter atomic.Int64 // 消息计数器 addr string // 侦听地址 @@ -92,6 +93,28 @@ type Server struct { services []func() // 服务 } +// LoadData 加载绑定的服务器数据 +func LoadData[T any](srv *Server, name string, data any) T { + return srv.data[name].(T) +} + +// BindData 绑定数据到特定服务器 +func BindData(srv *Server, name string, data any) { + srv.BindData(name, data) +} + +// BindData 绑定数据到特定服务器 +func (srv *Server) BindData(name string, data any) { + if srv.data == nil { + srv.data = map[string]any{} + } + _, exist := srv.data[name] + if exist { + panic(fmt.Errorf("data with duplicate names is bound, got: %s", name)) + } + srv.data[name] = data +} + // preCheckAndAdaptation 预检查及适配 func (srv *Server) preCheckAndAdaptation(addr string) (startState <-chan error, err error) { if srv.event == nil { From ebe7a7049692e8aa4cf2e8cae9b1e5bfdd2836e4 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 24 Jan 2024 09:49:04 +0800 Subject: [PATCH 20/21] =?UTF-8?q?feat:=20server.Server.LoadData=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=94=AF=E6=8C=81=E5=8A=A0=E8=BD=BD=20any=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/server.go b/server/server.go index 6062f091..72c37e77 100644 --- a/server/server.go +++ b/server/server.go @@ -103,6 +103,11 @@ func BindData(srv *Server, name string, data any) { srv.BindData(name, data) } +// LoadData 加载绑定的服务器数据 +func (srv *Server) LoadData(name string, data any) any { + return srv.data[name] +} + // BindData 绑定数据到特定服务器 func (srv *Server) BindData(name string, data any) { if srv.data == nil { From fc14e7380125d6c4880e643ea01f1d2056b0ddc4 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 24 Jan 2024 11:12:34 +0800 Subject: [PATCH 21/21] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=E6=A0=B9?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=20README.md=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=AE=9E=E8=B7=B5=E8=AE=B0=E5=BD=95=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E3=80=82=E7=94=9F=E6=88=90=E5=AD=90=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=20README.md=20=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/images/pod.png | Bin 0 -> 2452 bytes .github/images/yc-cpu.png | Bin 0 -> 17830 bytes .github/images/yc-event.png | Bin 0 -> 23286 bytes .github/images/yc-memory.png | Bin 0 -> 29146 bytes .github/images/yc1.png | Bin 0 -> 30721 bytes .github/images/yc2.png | Bin 0 -> 30752 bytes README.md | 48 +- server/README.md | 25 + utils/collection/README.md | 1188 +++++++++++++++++++++++++++++++--- utils/huge/README.md | 721 ++++++++++++++++++++- utils/super/README.md | 226 ++++++- 11 files changed, 2100 insertions(+), 108 deletions(-) create mode 100644 .github/images/pod.png create mode 100644 .github/images/yc-cpu.png create mode 100644 .github/images/yc-event.png create mode 100644 .github/images/yc-memory.png create mode 100644 .github/images/yc1.png create mode 100644 .github/images/yc2.png diff --git a/.github/images/pod.png b/.github/images/pod.png new file mode 100644 index 0000000000000000000000000000000000000000..30a16c7768eda116fa1c56ec3001f31e62bbd7a6 GIT binary patch literal 2452 zcmai$dpOgJAIHB`XCWh9obWvzgkt0pSt|}hq+AX;n9McV9OgEftzwC}baW9Nii()R zB-Rj}CSo1rHkY}TrPvrFrpwihHN8?%-VR6Ehvkfw}=mY7z6aCZ*;iUN38_+*RY<$OWZXC#|ld;f@onyzr5U z0!5y(oGMP@1sUmDTX>u`nCjZB$!X{q#i`~oZ8^pQzk$ve;Uk5n!_b`6bi zKD>%_%$8b+gFKo+UxysD-^ftopd8hUnuO;+p(lxgwjgeImlHAiiN`QJYD9%n1#iUf zl6FFQW$Bm~7m8Ufk8v9HOu5yb&BHZ+b-<48`iefKAQm#w>HJm8!nzkQYSo= zT&lfBJs>T2M_Xp>5_7@0hx*9p)(w~m@Exi)8EorzH>xYT)TmzB1)ZwAUt~}P9>IIR zi-9m|-S*Q9nfX4g8P=V@`ae~f^FN%&`jSY@8pve$YIA>DFJ3ffFk9@LCHlyyJM0?2 z-(=X@7~E`r)II;OM6)P$#8qTE>6#6epVMu}A8x;mg~Vq!(kj6v4S%BrHMStcw~Ntt z6Rg0-it-Vj2VScx_pPR$i!vP5e9yUUa2$QuotTHeS$adR@$3HWz2s6voIu@$U3j?P zY7Zp||2=};T6TNK4~uOmt{(qKLI&?hn?)URe+^vo7s}u>Faz7(wMHO5E&9Cx5y#(t zs|0%=u|p_LG&RgEuSRK;#+C?W_sKu-S5+Dq^13XC#C=C&DCf^=u|OHZxA{-X`1&IT8COO zQ`onK&`-6gx^{Pm<}Imi*X7&x5D$7#i9ikE`0dk92UV|vnq=^lXZMWjJs#EdK_m|; z;S%73+qMg%`_(Ij742KE!^VF?^q7#`pB10%U0lk`y1DP|hZB9+ZSv|vGLyk%Q1@@s z;je%q*A~g5-evJ(`^4JG`TvNLzk>fOYyJj!B@rxhh0KXr$rk}DPj6)-J^=E<;C>5f zqcp}6o=Q=%tr4@+!l;tC@`A{ve`AZ$@KKXUq0j9qT%?#Sm)qg;>c zP=&v&!kGLvU)mOqg79gQcfvsvkr(2#&lqJYV zev~ZED{uBZ%S@Ut`XTrO3b0`bBxX^Nd|X-cIw=O^hA*E8jH7Z1ThQ!jC`3 zJ8Jd^!Nvy~8xn6^mK0(W-SF8hzuwP(v~urD;I4IsK(j=3V}lhD4$0lE!&_NJ$pm0C zz}}^4n*JijCgOY=>^RAG-Dm#A9$*|^F!TV{J;EzCYvtN2PgF2fI zPDZ|6oD5zXq;Kd`7ekuI%4Gc|*A3?TnvYa!bGBBX*_mM?_d7!F{0nJVtb1_B+&?!b zUn~&hx|8=>sx%FEv(6wmM7pK2i7i*>`8m@`>;+W6u;Y~3Cb80S^`Jb2!G+19~;GQiSzfFI=nZgO}_ zjdf|Y;kI}5h%~`5?kH(q(2$goRfqJ+%K4K}X=A^ZA88q{7>#XW^z5{%LpI7#O;mfM^nc94JN8A`-rt=LjtrVy&`kM%PtE@ZO}F(vs}pd5 T*EzfSZ2?a9E_PM60V)3gn|-v; literal 0 HcmV?d00001 diff --git a/.github/images/yc-cpu.png b/.github/images/yc-cpu.png new file mode 100644 index 0000000000000000000000000000000000000000..65a32e10fa73fb042a5d5acfbc6eb257eb1a0e60 GIT binary patch literal 17830 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 EACsU(3jhEB literal 0 HcmV?d00001 diff --git a/.github/images/yc-event.png b/.github/images/yc-event.png new file mode 100644 index 0000000000000000000000000000000000000000..ddfa32245fceb501bfb7d59daba4998415b51675 GIT binary patch literal 23286 zcmd?RcUV(fw=ay_t!_nhyAco&QBhH zT3ovXc5!fUa9zH1@j3^`)-n!`EsWoO0asS!v(z~_%*!rcJa2f{da`#V=2(?KV~r_m zjQ(?V!cECxtsz;JL9y}Mv-Bl2^cbh}WZ^C6;8Jt5*8E!QhVaHq(H7?SCmnMfU9kI_ zpPtOFOd;$J{`I$=2c!%Hq+}1@maz=lLb@i~HaADB&@daeY+u;hP{nY;Bl{ViXw5fUOZ>D(`_yKFke*L`<)63tO6?$QJmT<_HgSgx8AmoUX)azzzCY2R*2iGaZlO`O ziH2saiD{x&sQuYzHL{@cBkKa-ZHLvsRJE#@^wm%S23snpldEe<^9%^*#>kHRA`s=X z{kws*rk)>zj4xrM{JnN={OMHeu?=BbssGQ4R$$|~1Hm*f9LH15|JlcS4iLPjIkB{2 z_rYq(5mHv^V69uQAOub;mcJ_~4I%1q{7JtXZPql!@#ikEm4E;35IaAyhvUsT`1U{l z{{PY+;BW0hRf~`eGJ!zA=9HWF>O)JMIc~qVtai=PT9kK^a!T4*TH~*Mo`XXM7x0Dy z#oBTNZ^VwhhYas~N(q)Y(WIoMF=3g!1m~^K+w<&d+Lu~240XJr_gpy=PGw7a_5j(1VJIFI9YQ;$QMY{U_eu+v-D_q~fvi9m{lbr=pxc|jGY^HABx6~9WxgG|GV}|(gO?hV{xfM`Ye{Rtv>)t zuGn{y*{__PUt^=AikRP@&Zt-0HkfUwJNZP7E3%0M>ARa8pu)pC($Xm1AvC{|6QC04 zRx{YDw`3RVI-fW;$xo~*bt0>`J~Y$l`{W9_ASBUZP)nji%L9+=aZLwQU6 zn3yFkLo(!1lveTmLDQ}j#l2BDBdxOGU4bau{&Ih^%s^?p)9$8V7E6+2&n&(|Wkty2 zSZ|eUZw6WEWv~%!R)rWNLon;l6Ms!^_I#4z%ku?(3tqG}DYgHU$6D9%-cwmz^b5jF zEj8YVqIrWXL6Sm80yev(e$OW=E`qdNhfn&QAzvc5g8P%HkAKQD;<{YeTtP0ksSXG& zvXJUsosOQpQN))p)_Rv3Y_LPygGw;NkGH04{VlLScCxoMp_Y5U>aog=I^=5~c-76; zcVS1(d!9z6K!RCQI>G|q6YFlV<*7Q0S=d}amK^1eUJ!mVUeGRQ97__USjAj4fa=ud zM!?Hugy~m<KN$8dHn)hsM%if8RmgF&;vNsd=C`}Fn$?_J+Ly0=m<&=c3?;xOSnHbyC`XMd!M!C%rv zeHtz$8FcN6TRR>LRMNGOk*3()JD3G~1P=2X@00qN-TOF!!gxl{^a@mxlxOdvrYD3@ zLyq$_moMzHKF@cZyGo8|Ze60-U{;7JuzGp=Kwana6ujN8@_^mT=f`SbeIoI+c)c>E zinxB~3ROyr>k3THtLUTLSdC+%6wYk+BPTj%ZQXu4ZtZQOT^rF3YPWn|ZLV3Jn)#gA zd!@ri%P=R#CV;Yfv`lNPtTCXk$Su*gl`2!AFRzqS4$r(>z$0UlX_JO7NOGEe(pcG< zV>Pf+Tup_?H*)F&Gv;B=Sfr(-o*IIwj;TfKTr8@239G-CfYEWx9S_MiRuDqs9gJ39 zJ&|hlch1L#4r%)KbyzrtYdYFt6p+xRqKSiHVHAy)H@f$9DF4Br5#7P;M1g#Z5N=Aolxq2=%^UPFF*4ewCUu1JEB&})|eKm{TZ2XC> zl;G5Yp0L`>vL~KiF}`7BWCFEDlH}>3-ZlCyQ-{~G;H=uT< zj5blaWwhFrV&K5IG%G$~9T*&V*|5K5qNd1oe0@yR#_dXy~B zZEYsdMA5{%?*pPpTgCkfe1ujg4Q<+_?XC+7uFL!4cEH$6w$RRR_;J_kj++qU`C->6 z$+c2DJRh}0=WC0&QkNkT4nhxQ6UPl*UD?>xHhJ*q4xHX|-8(zgIf^9Z&(>HGEfeO) zGr4MX9gGIy_49Bccsfg_yfCfeMfBUa?a5l9LRK zZR~Sj$@-uhAdkfLR`n@wE>By7f$4x5?FkqRh5$X^M=Ad;p|5t~Y5AuJN;xI9%!>+X z5+CKeWoR39W0bGj+;HfWmj=|yr5>OYO9EYGy zIgz4&Or*e6l*ONWmd#w}eL;HLHb`^nWKNd&J+~P+>9?BiU3h7QRlYndC!)ZD+Hu&m z@I?cqzPg6n=Hrh?VU@-oC~SaS@BYim&b%-!-)&YJDE$swvRsdQJ=xYmdz3dY{Svi& zUhaz7V}vnII3u#jO+maHBS~9)FZtY>9^$>ytoIj_C$ORm-cD_Ddl%qB7Fzp_1k@A- z9{^fMLK*h^&FbL1VDh4o)CvSzU>hdV$ zD1HDIrx~QweLRaq<3ZnS?VODzT9o1#Ni*Ykp@w7Ul)GA}4tb^ow z`j#O|m01itjjW8NxXGOeufIqz%Sp%QIBXxAqQ|iDC^d#z#$4np%Ma*_x9a3R-MCd@ zZ(}Li6_3DC^4A3f0NPgLT~+&EL*Uaf$b{cl?ootD*@~f^0mhtBS=XX5*iha+&*Xfw zKedp)yy-X?*g)sA5X?<;O_TUAf|*;|oolhbxSC&Ob|<#jSuc|K4C>ZA%R>5IoI1V? z9j0C(-w~u&RG2s*Guor)o3mKL5*(AKc~oq?#d!E2kv4M8!Z`E9q8ZJUTkwO5uFxNx#;KMXj>9zQ4IYiP}wT#BMz< z+$P;X(x%j06+gt|MoiCmQ_1tBr|S-U>acv!q&s%XuvD1i`P23Kt#*Iu!@QCiUOtI~&y9D}V0Hry_9N79W!^;}utzBcrM27u}FUBXqzk62w{nca}X@VUU5Z+bK4) z`-=oFv3JXjGrgzSfA~A_e}Pn2dt_$09<mE0nT? z1E{KV?dRdu9tUldV}(0#zRC+~1b))zjTs!6&Pp&WR861?OdD_=hS(|TCMw71CCU0I zYr-{=Dh-(qUL4gj2w6le>G>Xn?6q24qmSo~8_zIdT42P;VY+fm469d54KXjzaxz!@ zlCBr)mTa`h*=6!%JqOL2UTG@3)^fXFinxj>d-tAaPx<@;Yc^>;!umX*-^PnaA<5?x z?K^SG9LW+88|GeRwe>HorB>_?orak5Ri3q}7jITjC2yi1&v(Q*`Io&HMK88J+#{TB zdfrv6!i>?y=slM737 zg#0G0gCep}(!Sf)s0r5ak0&6Id$gycwG5a)@~`~mFL?9{s|@L_SV8|H(Ga(q@hN{7 z&5870(_tm!#hZ?~HS}Uw=vgYOO*%k7Nw<@c)uFUv-EGtIg!Xj1_r}rcDPT_cnnV4A zLol8vMs@+Dd?~RVn9R)g*@Aca`@7egD}C;d^@j$`LE>*@&E`#|Ok=s?PG_0V`FFTC zBp=+IHS93p}`uB#e+%5RW1fuz~lCR57$UQpWYDeX`4XDLb$^e2O)j42Jc z=Kan6)g;j@&!)YP*C%_qBZ_eH{bNr!Sji#{087nN_nZ^Fn3$W#kcqVNHJ*hk>AhP0 z=7&jF|4JaZv=lQilOe)uBW`EY;%5o5v!tveK!@Y%N%UQUFfpWUGR|M<@5z+~=@JRA zKT1mY2>e_#n|eQhm=(=x+Q&J}78@vxP0)wEb9X@IR%z=`cDU*4Ys z_o6#?=Pl)viY>c=E>lb6<5QcC+9X&>sY|w|MbC=wUuYFP_KzyV-~p$I64iO6b1I0$ zAnOcVe#A=>sXVv0i1c^ndWXL0Lr33jW!sSZlt{sYUln*BiMyP4Vhe_rIX#}@`ApKK zz>R(d2@4D(5OQP6|It@{C`F`dvsb3HM#!cDt7`fb{EwRD(^Rlu_wZk>`5ETkg-v6;PkX8Yp1x1pz5lDYhG0WsKq5o1 z7tc1J>g{2ONGLFYlTD;Fgrw}9(ZOG--YYWB?M5V!wX zoY39f{YOF_N$0jaWYI@8BFISJc)(O+C=2U%F01+!qri;7Z2wX?a97QLxq$dbvnqQg zzKLpKFZG-_o=aN1k|hv!P7cG2{9>Y`8!vOB9BgfEPaklxa{hakWc30un>Qa(Nn2e{ z=N?a^Rh=_oMw)$@N;J{OZuu}>IdT=u-j`q&Wo6_yK18?9iGlKz&5Ur8<2plH)`##U}8X-Jo1KM;w0$(^7ef7AHfW1ZR7kKQlj&pQ;ah1KjmsN^dB zK2ui9ZKkuc^SzGv%qiTbURAvqd*8%rVoeCop3hx-g;Nt2%p6%0!F=nUixg+Hrw@Z$ zYjR;+7@FetsBjwXyJpeg07Yf3uBkgLl$O8~qYVSGtyy%KZ@6VZ0CGM}uO0VMA!THh zIXhP6X%T@NuUT_B2K8nIcVoiv8sitAQ&0{UXcI>Go zTJ&2fKgT3ZL37Z4Q_oi0^oApz%q&8~%G}^q@;e7IJg@{a1<^(d->-nUmEXnD2qYKrmDG)|~P`zrHHPY|TPA+k_mOyY&Fu>dK%$LcP8!nuMvy zlUOV}si!u39&RjYQ=!$Gm~|emt5^J3qK~b_LhLt;oLrxen@~I4ZxN=QEtq(te2s4g zn>?lDJp>zktDOH?mf47!S$jBCcYmtYY=8EZ2+Ux(C%imM8olyy$6`~0GQPY#d#ZHC znOQb|{ojn=dD7T%TC1tHYbp|IDWGc;FvO!ML$;eXgd4Y$GrX+v9f*Zem8J02j(oqD z5Wk!KYE#*#)!!RAB9NAUkV!d*!t_|z`uhC%b>PPE$-Pb7uOI&-sYRt)(#|L7{W(d| zNm=z!RWbBx^1wrCbYeK8-?J9BS##`kd_2-z<zBFL= z{81Q2%V&s(@JWrCoPby}Kzvs9a%|k3@O2Ui&-zr!FAx`jK{MeB79#JgL?NOlEj4(j z0o5wogFwE&DOly(AL*R*3`*3InyizD3hI`r%36QMUVvwh2HPPj4Q7~_A&bSjP@CxR zP_ydemQg1lHOK2{rE#R@v$sf*`B1*ww0sDOA}~asdw{L|WCeh&3v8_E?&PJB5|z2| z)!xuys10=W<5JezK+TN{BZlzob;}z%P*#-R;(6wM!J!?Y4}J9G{@C2kTk!Sjna^%c zMek`6ltTGKm!^9DMMwNLZ~QuN=wZ zXdN@46{CA|gk%3eAbs8&-TER0*z)*ch-hYl{%WTTfw7HA-a7M0gSoI3(KK5by%2^G zbocOI+LsC?x-JRP$Ly^*1D4vqO2QPrPA{9<%~i>?d@(srNeT%6kM^=nsA^>oSJO%K zyeD%tbSOz!Fws#>zJpO{KrmBL<1;m2kp_$kI#_|8Dy?JvT)O}a4$+HWM`AkA=PHQv z0s=*kX{$5Dnb`5+-iwLb5y-!On)DZazlotcsWV2&%SLVTU0Pb3Lz2 z1q9rkVv6G%gjR;iJ}ol8$~e3Zh-26bZ>?UbABKs-ET+m@0MUL!lMNejcG_RzbL+0q z(;Vr&(EOPSw1l`c7b{L!5P`fVU9K9xxH>Alhj9+NRzj33*tC#ah}khy6*a(!P84k^u{DyVKJ zJj5UmSAZ^8tQM}0Hw`bkJtIU7v@bNjHyZtu;B9YjFV!|aPqXJQe80N7dgJrJZ`2J^ z(O%Ym-PsB;+i0qKCNq&oR_?Vm03enQ_w#R%XctW>UGAOqu`o_ad2RG?A zG8|??OIZg}xJDp!jmL^*UcrggXUMr(X&9TBOwuxqv1b`AVP{mw3Cs|G5!mh9^T=7Y zMLW%R$*x8|!*K7n|?{NVhQmJPnWL;^orZ>?e`p{(m?JLD%LdPK3ze@`WUf;}HZ>g&ZYRl%Fi!!lR zgg_YaeZM1SC6+o`26hL>#pDPx3FijsWiu5H-bLFtoo>4n&i=%RwP(3m(oA;+8cHDC zCotb{^QLn#@ioIw@Vnhu%S)fbe`81&k2e+Q4#Q$w8NWCydH5|co@o2d1P>2%n2-~^ zOb(cAPGlE$Y{Haa#s^25m&D|m0LPm+N?QfGgcz2Wa{)a17Eb<P)EVoW1YIeM8i)}^&-M*GEJb(FPQC4Zh7p+?a(=a^qRCQ} zHG}jX74mZQ2M+%kP30XE=72z!KM`w7Q#z-*SGFV#ujaczT}<+_Xh>HzVyWM;&{kp5 zV_AM>rSpBKqe%znQ?VIQ)VNpkPpNT73o-Y^^4YSe9?MRXW;opL-lk|1jDmY6MS7%v z!D{AF;~wKf64xo>4$%woE99*VC@Y!MrebBaU)96Yo)s%13d`>cvCqW6{Pm43)|28I z9Ihnc=CeG(=T?4)nLXcDtX7UX#vCF+i zvAubA4;A0pwh8xoK3~p6;#OY27SG$d5}fRC$e%eeua`&-=w~+d-5m(^vR+zPW4LuA zr_w+cgzoFmo;dclW!o5+$uJBrZ{_6Bu@XiVz^mpYtPvlQ(N0Ufy^O{3fKDq*Da)um z;h3uWde0zFn9bRNZp*(!DpF7DiNN~>9D;(L&Jxa1?-W(hS7O)WpIekd9N!AcPn-ROu}!TSS& zW!e6Sv*(!r)SzYFcaN-EEY#!NYHt{x8Y7?NMa=H@VN=ZHO7Y)U*D6KIgU15?(Q>AE(y=JyLtnt)2X%)_ zC-yKu>MH&3ehbXPfYO(fl>$>dA4MAS!G)bu=xj#~Y2sgrRFC|Ghib2H3tj6k- zih}`pVVmhU%8v5{3tTlp47ODo>_itJpGUD^_z6Gr#Jf6ukMNH}n}3|)#Y9X_vD?z4 zY0bLc0LW!Qu|qix`}G&wZSw)>xo*|sdq>;swuF) z;`}QtBQi-yUfkJ=fcDIpiZnsz9`Ab{5=NOHq&ZRu+ft^2+yAgAE z-hj19#FNwLuW8{+}}&Kg}b;&NJ&MbMlZ#N$U^$(CDQY zjJHZs49+v)1lY@8#pXX7I1ge&1`b9jPhFGSSS&Vh-aJ2iG9CfIKIAmS2AmN6khoul zVRhMAUj}P8Y#WGUgHILrHjI6~Qcc7EhatZarTV<}yiBQ>LWZ8(Zv^ivhBy2AX!3GT z?+}<(m6guUrF;fgk8cX1$G)hMJzB1lKFX9uYR+b`XZMes>qr^x85dOC;ZyTI%-J@f zQ*HXg^sfF0R!)oa?QQ`U5bf+}@wdroPoA-W(WdNCriG17P2)@w`PbXvcv>R+IK_@& zwq$uj{jVYVqbA7sl?2wc7r>2K?ub`JY69Pkx{_Ty{XW~D(c2mHMzzE4?1Kg`D^tba z$HGFjxvb`)zk*PXk5uMQOlC4Mr_x+Hc^X3?FKZ=WdPPC5Wi=y)>Z)VtrTK;M)=hoi zb!BS0h~l-VJy95=unyswh@M$YN2s#rL@rCA*+17Sjp}NGr<%v-@$-Vujf6@2N zSbzAjNMVT5rLg`nh|^6-aa8Jg&rwonOpzaun0vC|^$%sUuOhTB=v_P9g6V<@6qhGE=DCYMXY)k87j{Q_CbERC!8z z_0J0amPX42hlc|Q+?7U?UjYDSCD2wsQ7hdp@LyHV5C6gT*nx}hYRh)n?a0~B7Py|Ho^JP}1e>2{nUhz$@YDw;~H{qRK&A$O3e_Ilwy`Xav z&B~>A3;c*c>@)n2Pz@egUsyvpEb$9yVhCpW6oIyqZwH5sXdR$8+?_wd)Kzgv|5~Y~ zdlL14VFQ5~h)+}a8971lXoK(|TQ>s>so|J@l@m#(kzGA;FON(D05AUH$b9Iy;a0!n zH#>;4X(h2u+Y|ypFe!bAKCRL`wwo*VIz0Lm>Ax250&-Thslf)XtPYZ;m?Y>i5XB7f zU#fC&{n1H$eaSz&5{;b2)C@&DnY2=`d~OUkj!ty){jZl_5Ra+AS$Iw&1*t7Fj9*+? z$QTvcr++!avxN*;QXp8%1F#kaFg@w3wn0HMRkYPrYSzEq$$zHRiI}yZ><;bUd9z0XZOZ{5x1TC%Z~p{1V`g3kSCTITVVR+^uGCT4Wr zXDUBDWvm=-X=zF8?w;|Q16JXQ05h(|PoV^711 z&tFY2s_IfiTNZm9PZsPDV(iueUWt>ybW8Q(;t~m`Wm*<#AKK~X`ZdPFpVr7LfL|FK zbFzgODQ=4ev9j-s4IF-qezP_=zdmfr8fhQSPkFqndHGmD*(iMmM;cb!%XF1|@upG8BSSnmaMHkreNC0+I$IskwKIlrF)IkDKy*@;j4loxyemqaIAA zCs_P>?lw=g?S)bhzifbx8UF=vaZy!7Z(pb5WZ=BUunx9#~?8Y`q|b! z4I`SeU1qG4W^n6i4wUIbYTM*u0+AUj3m=bsvw0Ry8^Yp~(4VW?K-B+LPuq)jct!%s zf}7{f(XT#=hhUb}P>asBV89mnFPnG;efFu*%xg^-F`INrv!78&{ksJKvC+YN!W|W5}U$eJjrtG2Zvf&8{IYn#s7M1NnVL~_>*!Ar^Vd%)_Pb1pN;InkBD}3_1KAn%U7ScOoAhMigK;f8{Q%d9+5xFPJ}0Lm*fYd!>u;^2MrC z=!VPI@dPF~hiPre{-ze+(*ZvLdoY8pUW4Noa0ediZ&`@8f`y${8}461xZl+hgsD@$ z&{iSD;RT!GU;{o^c=QD3+96kuZ>IR~VOkD@nf5c0?hUoag#Ft0h(ZG3> zzjXeI7=LxnU-yO0>_rGnqCN=RDsXSWAk@FZ^&sERNA{n6+7g5AYZK`vK@84|jiQxK zOEL$Ang2CL^EJ`@MvDEGAI>;^kd-Sit0UE;d~dDmM!WW2H=&jT6ThPkUTy!cTCz0siFjl9?WPyidAQJP5sx6Va^Q zK<^dQlQ`u9zC`;4y!y76Y*v45ZHfnFs-TWDuYtF3(g?O7)cCq1zr|n0axyA1c6FI$ z@JdQcOG`*R!#FGX-Mq^cY!Wb8RO7-6qK{pjOKD%eot^#}7Qa970WSjC3P{GLqh%v> zm6xNVBWnJBRELoA4ikxvLSNSU+7Nx2ekjs{Q+KC^t0odR(?K21Gr#{){hx4VI;HH@ z9_Qu#)n9|Kt6QG^v~=J}0*#IrXC94_B=xt5-v+QhNT-7cErkFm0Fgjo(Lp55^c!W2 z24|3~B=(_!^+we3xomUU(@|}ZdHSw6JL*Us4rg|;F}S-iMZAHuY<3AFW;(w(2O@w{ z1l zxq^10!qCSwqDBpbUL?jZ`!0hRA_#K1tar(WFtu7vhlw%s05aT<+x@&$UV+6~O3@Ae4DIAujER<-_bFFA=y~DaJ_(3c{44CvE}h=i#0f z$Uem*F^xU)atCD-Fj2y4r&x0a_`KgYfn$U4jDho(XOx18m#3}<$YuP)i?<+=IHl{U z&U89~hhX2C{n8fG%eHO=aW%90&woQf0c-!pbCn3p^)(PUq*YVGHQ{|2D+@dS_P57= zh;94}Z4DzZ6S!x5yXvI-QB((Q0fHG}p|0h-+xqv8M+7;$XbhVMlndmx>SNplW&R!V zuuWu>w`U+{x9%7k$N6z_Q8NMXj#Qjt&8KGkTW|1#1K|O4D|88}matK;jJ~=N5n!FF z_^+LnA)xwMwUWNQ)s(2~0B9@&z7e*6GLJu=T23{P_BO7Aje0apFLSF;q8%G|pm~A- z(@#0!@0QL^O~0qDl6SJR?lVu>69XxZV2~|F2Q<3wM<5^nlrG$)?o7)YpcTRWR3oV$ z4e<7MP|&eBjI<>?!5pwIjt5xxQ!enQBM+#lTD;c|yM5^u!OVLhc)%q%!r~hu{gVp+ z@kIF~0@F{*ei;VHyJQC~Q$pC-DQ%0g>Mq+}3LTS3Y4yM>Mfa2k1cJr&>*0#e2uh~ue?ZnZB$FBjudWXF^GfIqx9lQ z6`iBK<^C+D;G)M(JpiqifHOfkro-PiD_}XU-2<&~`!*|W|2uz3O~L>rSY7p7Uw&afnW15G z-B8BHHpRqP(!|<3g$eKOj-1ptBNFaryqX%Xe%~7!?6b3-(-y6NVW9kgkALAM)xC70 zAE%&QTWzAT*P{nYpC@WuG~NUH^ z<j_9kM=O z%04U6py@NP;^uNfIlN}_x+|z4traFY#YCTBCR=7rl*TWtYX!XM>NhA9$&g{8Kdeq^ z)q3;PFKFN%;(hjYXs35dxOn(#zD4U{i61J{&ny>9O4J9nRjDnBJ1w^}ryUcdcs7)U zKke#eeU1F2;9Ac7{X&m@E`gps#$NqG#QJMHwk(x4rZ@P||7JLJVWuWSrme~hX&s{U zFlEh2NiW7pnBxDqS}DA6XG!EE83?AUsQ{}=11CkbkdCAN*1hr7cS1t_T0*tfRE12@ z7f;`Rd1p~%=;_<{{-bV*c+!;(=${J0r#x^w8KK_i6X&CIyVQnebTG-9glRm=pQkkac5lC`*u8b~fkD==0y~1ko z!nuLL?jbkpco^^PIu6#!TE)vVE|G;njvkB~eMb0_NH87<$hj*9B(dfim8aw-*0ABrq5z^5xB47x~44T)BXL?72DpHnU(zTmPMVK_nK|T5AlR%(G%lR zuD7#)bxj$P29pSmRqc1d;?hF$zwUf&!Bx`D^U+z#xA{842rutc>=GQ*XPuKgm@DgF zPKib@#)F9%9n7d`Fvi;V#+Qsf-D~3Eb-WOamKxpemfY&7ypj?BU)iW*w4W*$Ves z@cHX~N9yC41&M-+ko6i}l1k`FkZA=pVbJod%{X zFpmNaa(6AJR4^g_zQu8s_*|I_!9dTnAH=^xjx-->gG(*kS}4eU!jBa&Wx}Lt%QCXNzp*o#>&nfiN`z;pT2acqjUy3%9BF7<*G4^V&TqG*-x$X|IL2y|Fy?lpVCSf1C2m;xceaQ9NpNOT zeC)PAK>~NnVc!4uFQw7*Y7S8eNCu5Y>oBp&ntCJLE6-s{KLdAGEy>YG>aU!{ar`ce znB9L+v?1-_A-n!^!ddY6L-1*xP73%0%pX z^!bJ!ql?DgUF#YO2`@XS8DrR@7o8@1?JQdEQ;1$U_4Vw=@m=+xZIrNVQfL{MXHZ<< z%xC2hwu~n$7-^&a?!ujE3|+7&d`xx#vsm(pPjO z?h#>3H-Q^}6V)g>tx_0ACNWhcDbgKzM!ovt*sLr$`JYt$-_eKkQrl9u~y2_N= z&Zub64FVnRP2Lq=eHlg`iGnOkfoEc}sv=G6EXJjhC)5%n(EcT82HV16u>Uk<{MYoR z+Mave@yQ+HE)C@0F(IBOiXd;M$IDURjGPu=w5NP513O`pLDhL-vJ+(y{k$X82e(}A z)a5l%_6Da@7M5>7_7THTOFGC;sk=PF6H5(mo1ht-%qXH_XNWrhar;{!-89altj{QnL5*Lw>uCm8hoRt4*J-y49(TL zI_dIJJ1j&Y+AT>z^TMf#m4LB-3or&-#6f{HKf^?3J}T8%YbAE(^;s zurzG1eC8}kyHjh?BgQYjL$r$I_X#xpStsfXwg~AgcZs)CQmxbOgv^j>HS05OEV82G zi-yhNAK%}xckG@^Q~d$TKd(Tp%iUprz{1$1pl`PB2hYxdN3F*0PDz4zj%zV-QzttX0AB%i&Olu`Vrr*w1b3J6@U8X^Fvw#B8! z3{$qAZABDA;Hi68S>Q<1Pkb49NHsIci$`yhY*8dM0=4 zIml?knXxU`Q`dJ|m6iXfQ|+2?g(n^h^84E4#yAqDO^eQVAl#W%A3flG&ARFrV(;q*af%$5s4#^?q)D^a zwp?nnZUT zsdWy>QT3vH7@nkPBx}LjRK!;1kco=pekg?~v#d;RqQ!F%T-eC)H+i;MUdCrV~?0;1J$k1TF;j=Q=ud zS&#nfOYfj-uq}sS?54tPV4$1eJ0O3W{@;1Ypn+Y2GCMna|J_?F4i2jh7hvde&bv1H zhDam;w4hA)8D`7d+Zm0&a9ntP0p1C!X2B<9Za5c#3vn83(a2c<93mZsY&|CtCyC?u z6IK<4X-a~!)>o#JM*f(eAnLr`Jw^^IEh_R{o^vGLk$*!0@OazUgJ4!BNs)9^_MRD{ z;Mipe>2KV}+E0OaTw2xmW)|j32w(RoVzTRvd401+IGlltc{CQ`@D%y$hT7 zf^4jSF@KL@dTF@K;v_dH(SHB}BARgcl{NO3C1K*$khsUSppFQO70%3@l8}(ty?rb0 zuacbb0yRO`oKr*e*IyqXv0dOUi2Moz#`Cw)wQMp8sF_ayE|F}@yKxJ6KjD--*whMAPJ1P1GIh#(t=BTdW0CZuTAN(`+%z87R9hB8$&;I{u@YSJnkd0Yq zzPba7%b`lM7;KZ~I4x6;;c?Grt1&xd9ob_bbhhMNig*;L8TUv}FtVe8i-&L}x8$>7 zY@Zgmu_BP4BsbILW@~3j7|dniS-I;~D!~s%=O&62UI&2N3Yv6C!62fN-RvTjVEZY5 zPn%_h87MmZrgy`s^i;&`tqmOmTR@BCtq04x33oaH!T+dj|94%0i+v9i60hE}v$Jz| z*Ux#;DaS5C+f;&jQ-Gh%XCU&Ba8O3DzLAz{W_IgV=kv#PD0wDp{%1;PY|J!GxfK;f zy54d9?jN8I^s)$Bw`q~f>;cs_wJHoW78;vCP0f1EOid5$-VWQw6BiQ`lT$iVPar7z zoA*d;><=3b44k6PINll-r&FL1?7_^x5N7bs2)Rc75$o!?A9Iq_Q|Ef$G}1 zx{@~3udo6DnG)(4Hes^;i8g>sIKlvjhVzPxQ~7(Y)}1z?<_gNw9gjT)7kvR6>^sbBBO{fYS$_ zgA@qx008kJ_L*$pip9hU){)i%Y<7GK4i2uW(ufM}mA}~D%G}LHiT(c;2#t;T%{3f& zUf{FOSUPxnd)wOHqk(MJ2HGRP?-Kwnql=*A#N2A_hCQlgXH7nT>f&_29{dDE6qx=u z#{gin{+$??$q=k9+~(pxYA*q>H~kJXz!xr#zt*u>he!&I(Mo&+ot8j6QNNm{YaqYTg%X~sEXgvx2!BGMw7lY^p zeOJOw=Sok7A&{6KJn-=URTtQ(USD5tnc6k3_(VZLftQbuGX~W2Y~U8*lwB0C)rJjQ zomyPkU{U9^ZbiMEOc~QE{0kJ=`kva*EP#fX>lMIgdl>9}L*{hJI+|+S;t4Uj*jG}& zqupuY6ljb2s<0{F9L_qyOk)TXP<3?dH8%F*<>lRe|6ONc(Aj>FC*fg_3)|Q-wto!+ z{xyP~F8L8A(l?HW1MFS5WccBh-2(#DSYhYEheoK^`7(2Iup>a5uS}-**s%R6`Zs51 z=MB!4ac{cF$J7Rj^79uVQ*XEyr|C{QUHRtD0g?k!J}=68eLDbmr4>}6o_^Pp%J$cH zS!;_%aUK@jg^BJK+$nqR@*HRT+>Jh)`8yNc)p?6{Wm#OToN?Zqp!RbP%Vo6;w>;fp4M9+(8T^4pB3=1K;dr>#4|~O1WLk=3&7oEBMd+zzRnQcFbt!r^9*A l~E zf`L9oBP+9{J84zCkp#D4lj*2``0IqDIf0Vc*0>7e__I=nG(=Yj!x(757Yt<~SYe&9 zwbq!5>kf%Q<~B*%os-P*W(r@Cc>Go!@ILa7@9StHCDB0T`p#kzBo1U_)#S!8gty4^ z?2jt|MEX(6VhtpH8aRgiv@}sLt-}|7SnGe|LfT-B9>GyQQ4oaHO6qk;fSp7|MG>y% zbm6EQc{tI`FSOD;cly$@<}v)cN5OXE#v!7BT$%uK8Tg$(f6ZjpH^cUA>YRKc#t!L1 zdQ4{9p%_FS|BP*bb@gKE*pbX%3f99mp)$OQbW_XWt9aGfwz|n$x+6<)xw)qg?4AqV zq)VNFG>H>RU)ABD66hA(peX_4BY9C;NvH9BxL0m?739&ng+MK(f(jFw?|Q20eLr8y zvTX1A{TgZ7=MSUo|Ka-9(TCJ#lhc|Q)P~Na-i59{pNBx&3BZJ!34~=oTm&Wuy&73Q z>G$b&_#|ufvv1&}{dkkd3Vuq_qT9M?{VHBs#it~<%w^5Y%>;Zq1N;&TN3TyBuvdCw zW2FEBKw0Q|ODy>Crd8)9V0yoPVh?YYnK^OmHpfVDDj>jzf8DtEIPrB*c>vCTpAxvf zoqdRN2hIdYm}6!mZU~VFi@st0zv1_X4W#?;ZUC4=zoX}ScD%~@lKFgkf4ev~T0Ez; z)z8c;-lxoZz~l&T5!PSd_qA8&1Uog@{v^ekZ`Oa5Zr?g%<7#JZIqzZ!(Y#b4wV=y$ z2pi-rG?`Xw`Cesx4HeQI_RxOd@5CtWqEO#s4HvD%z{fi|&Rv5$?FqxoFaDCT_{Y54 zx|M-Lh#%1>86`09>5=RM$?wO$dbRlGs4#=dS>aZiE2A)WJ|n>jPzF=e)6x%nbXK$; zdQa}Mxki*xQ2Imi)`UtV)R?PRBPX*nHp{JELM2>$PeXnf+>;ida8s5NrUH)%J0S&l z18;G<;zhW^%$EygH4^qSdFmhsSkdEl>5FKnU+J~Qjzo6a&hDYPBDysXB*Ju!o28v? z3&OJ=-t&&$B?z71j#$l$0k874X`f9_JU%}(B60+ss-R15&}ZZp*yu1uum3-LtGwI) zVubr9Gso#%|A0+zfxg+ZZl5{(jQ8r6)!XODT9>^@SSa?=zGT+tQ|F}1>{{Jb<^PpH zr{G=)SI?Sj@Ts@3XpytMHUILPnGt)ZwDZevQ(pWo;)caX$=w&`C`44OnWbE*`ncrE z^4QYpGybZ%q1E8Mg^Gkf6ZYNY!b z`%mj~Y=cdsci#LHF=KY`q4WhC47E}{!`WTtt}Ds210FeN_k30O1>lt8=jiU1$+L5x zMVRW(teqlOR^k-Va%$4Lo5%l5%z0mG7bcdcE0lVCUad;_^R&b7g67u)Q+3PZWzVh^ zZT|Z$a&80r?E1U!&cFGYwwc?ndf}nJS~)%Lk-!ZS_n(??^9#Hnn-0ucXX_Sjo!YZ6 z{)XYFxl>OmZ+o+0%Wd|}HT!NplR0^eC06SPsC91vPX4bM693IwId$*sTRU&A36^kN zlw^NP^tJ!(sE4f_TMp?2-H_=2b@5>3!!MhcES`EfqQcaLXN`p;dG=ZqihSx}ZdXO8=hz!G5G-1O^JbLHK1x^0GU zuk_j|pg(>dzGOGaG=0f!>v{j@JBI)#u~6bh#y>Caj*VjO{`A+|7wud-{ZQ}a2;lzn z&n;W#_5-KJp18E{`+8AhvNUjA(Hqh0-g7rh&F=}YGk@M6TCCFjHD|_B=GVoI6Wh~H z$^vaF0-o?We^Fs^%4wiASAk`bcP%U#Ha|;aa9{-%Ge>}xKCon(0qS!PM)uX6Gr?y8 zv+mtFRh&V9*W47?3joeVE!YfdE`Sbw{zM$3=FgA+{7-ATW>m<=BmlKBc)I$ztaD0e F0sue5-$wud literal 0 HcmV?d00001 diff --git a/.github/images/yc-memory.png b/.github/images/yc-memory.png new file mode 100644 index 0000000000000000000000000000000000000000..bf73cfafdcacb4089b944ef14eec620566d5d8bb GIT binary patch literal 29146 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 literal 0 HcmV?d00001 diff --git a/.github/images/yc1.png b/.github/images/yc1.png new file mode 100644 index 0000000000000000000000000000000000000000..30a6f35bb1e07b99d85075609b559ceda92a890d GIT binary patch literal 30721 zcmd>lRZv{P_a+GtJUC&{5Zr@11O^zK5FEnbZowS_fx+E9xCi$c+}+)RySpy={j0Wi zYae#swrcmOr@HQRAN~5A+usfTssO@3B|?RRgTs)P68{DV_qH7l?u{t&+t(RD=#~i_ zoJ*gyxTuON_;49XV`^>z>2w*^eB4j4mO8O^iGzfk!-tsKi8vtR~L%F@#Pd z>XXJ7Q3S+1I&LOB9nn3~JyrgRJn3{hoDm*r6S~f?X>W9HT3Tr%BdL-(;A1x*9Y&M+7I}rLGPLxxlT^ zu5b!Ft}D>=!1SVO)wi~1)|VXKz`^}g{ChEi<=P)^EwkTb;=)e26e>&t-fS`VtGEq= z3F99v4c?F68|`T5Yj!3cDvp1{BY&(4UF+hxJZ9Y`2j3Sg+r@I+R5fkJxQt>dV3P{G zh?~jo2)@d(hI?1aA?^|1D``iO6lT`lD7K!n^jzIM06C%tmwCI z1~r@SX4$WM-7`0x%}#_5;I2nC?&kxtIrn~vlJ3EewiYw6Pzi47=~0>z{4>v<=LjGh zwjD8}L6J3nU>oP$^~+fjXs)&t8hD(@6n@32N}v#rhl@ib?Ah=GgfXgYIS~+VXx`}| zk^1|u(uAdg=;g;>{n;FmpjKL+jw%g+m}9&l_Q(jXIHXN`uHe~Oa3=%h7h6Gj$NuiwsYSi{op|wp0>S`$VJC-*aYua$>^q?1H})oJMOkSD zxq*@+j!&;Xf%|pDF@kkud`t{N8>cz;#M-EF8yQOG$RAK3p_HV=ftNOmE1lpkTSk%} z8s+?D&SL|AI`4>zAa=Wr~ z6lJxv=!Y?!Y#9eFD8yc+yloI(L?0*dbcG;Wr$IobB9a9+X9e5nAAvPIRL#dpLf@p! zrmS>MmS&2-Y}~EaL{~f6w%SO9&1Uc`iwBtD$d(zrt{-*em0f$@b%sl;5JSYu!bJ5+ z7;G`og@PMDvcy+HB||LWP&7bPTgzw~nODg!u0Mq0hOM-P#&>BXE1^{BWbuIYf2Bu_LGv zSQKz*f0vR{(HiD&2wC?1O7z3EoiHWn(&C7#DqBn}XNTNvCTB7$G+{!{APhKQtpm{i zTXcV;9K`lMxx3sdUR=WDq{89m^1Nm;nWkd^OCq>*_hZvUR#b)&o1(bf)3)}p!JaQt zZquc)V_2YBnR}HfYVt>3cy_{ap_(lXHiPUYhixR?lYh~O9y7`YtWifKNup=q{H}5R z_46n)TNL^@nArG_p#J{5gWnT2jDG`oxFcvm>ZpUq?sMutcTa_9)9?l|Z~5 zXIAuuf_S@A#;Xj%7^3Xr9){*>?vmFBOONY3dF1QzcHYMahhdkc6DG&(57S_BZnI@Hw!>%@OP`%`qoOJ{r_47b{KaV*PFl~a3NxeMo#1NNa(nVxXUFs? zjeaj}ML!$(E^5^EE>i2*pn~&6nWmSuwb1MjGv%}7nGHmn(;1A+FyQUo{Vd9Vvs%G< z@QhssT+zg$c$Llblb=!04Vy`|c#|&qsi^p-V$Y3c|IHYf)T%`;hC7Xh&1nHaUz9_f z7Rs_bYtuS_XP5O?j57aEy}O!RF9RisGhJ8~#s{3(ZxeiWN9Hn!CroF;(fFuyt=QI& zG&crm16ZMQnKV#${Fgo{-^0O=?EX=^Nhr9z!J()q;ZaFaF>36lJYdJdnrNZXgdxxGQ#qQM^I33Ua*v>zv5HRb~UL(P%@8BIiJ94a8Z6pr&e+3 zM2BY@^LraGyD?{YLyfY1Mnvu6G)Ex%`(zPUdC#huj*lStc>0{6P=)}roLu~AXA*wv z1J@0kFK-KwHigFjeC+zfRQn^k6j@$t$J?BM5r`Zf6&G!oS~*-&T3dJP^j>zIx;DB) z`SQdROJox|QogS676Y6rIo4x+!Ln)Ny8Cta51$tDzjN=WqFfaES!L~PYZFv|JG=8u z9wDFB&vc3f#y>^Dug1*|4hpdg3K?q})f**ZX*#vych5Ykan7c5JE8wXf*c;FRcJSN zW{YoNHz^+bs zh3(yc$$aE6faO1!mawDa(q}SV5uh?lnzz-IT z=w=nysd)*BdTm*uEc{qNKvX>k)rAKhmo) zMe!82vBp&smDiUq6HEC?C-eUF1~miG$yxH9%NDWv{}9>Q9;Z<3dN;+0b=arhl=!2+ zCjDCZUxz^~t7Q&xOqek;*$N_wHq0|Vq#We4ks8|1#lGyShHGeIH zG@0g8%dtCHZ`MPulEWWpa%<Gd!k za4MVovEG9-_WnOxZutlU9Vfy2O$y(oRQl^K=vTrjI+guB`mvlMofYshDwYfCY8Sm2 z<^dg*$R%g9vH1C~9s#W=6~opeuEozoVz5)8L`@&2v!0HIuMEW2`7sOqzrK6{#n3P{ zTP!N1Tbl^01it%Ex#WWwMbTsyF2JX*BK3WX;Xzo@f917WDN4OJi#87gYBwG`g}1gy z)d6!0T#4x;3s6vkN9yH_YG$9^exwBLUaB%~`h(1t_K#K)r*_}Zpo;gkp`}ktq6IS~ zWlW}usQvVdpxi2w7mYlj{ww#`t;?6)J>Jy@bd;-?cXfOt& zp(t`8A1+9ac|g8s7@EucAhElV`m8#gzfjq^HHx7A4%3g<%C)_oeFR5QOff{bsUaN$Waarllk z4YQ;#1mzV>)Ok+}b4|bgMk0;NuT;2r7{OA!QNT)er-D+BukntLk3BA&9ID;1ab}H& zyc52g5}TndAZrPzPvkGhjvaFw>vO|)*G(k$zVY*22pi|5u)8}NIZOKjTn_Ma9dhe! zB01r(juRJojL3Wy_vdfnNp#T_(im>@)l8$jfgpDE6n>|LFwJuZcS=?f%lj$%Nv9Xk9_i^)>gGFA9;={ks*^9t4`^h0U9-{OiNwL|?z4w#|{XoZJ=WMSJ| zmNjR>ZI&_Q&EIP(>*}iNu0jgj1;2VYtwl>rRI6UG-tIU`laMZ0#(I&IDkLQ* z+j9uGJ=-aa>b_bIj!-Mp9Aak6BrPi^H?Ttn`y8ZYDKogj4a9s+N4T~{(-ew-kDUgK zLjH|#A|HYOhM(k7|Cw*Y{NHw1|JH%?{__9u-+RYQkx6N%Bd-8or{2mW<;WskyH;o0an{kNV4^>%PH=X4u{^)-2P@(l zYq(}IfSCBhsIrxn7N9xBw2PnQN09MX5xser(G@APVxIlHV1IN=ys4Vg>3s>hu9&ev6`uPU^ml^q0kPOuhg(B8OTm2GlC(m&hY$OON{|?y5GZV<;H?D$)rawNH)JhyU!nruwoM})xeN1cb zYt7>Wiw!d;!n8jRDg%F^P-J1B&8s>Zkbx6%@x_KvQ9@-{N>8fb*-9pL@0XgKMCfW! zosmzmxgZM%2YV+uyUek-stR7L542CPd+yPa!eQ7^^fqr9NaeR4O{zk)Fv+A7(6}f4 zYn-mn+Ryt#iG;L}to?y10BzGOjk71+abBcUNi)xZE4@Fg{yV?d zUm^^QX~apjIVu+@(IBsoN2M42m3~FSeahuQF%E}ZXQrmLMnOwU?ESjA>`1;dl!CB< zVic>C_0O2$$#n@}D1Xf>wtzHr&%0-upi0pq!uTCT{kzh=&UCEQd)K>Ioo^d1LX^am z6~(1}&5-lSx+Bd`_o-vzr1gc5`&1m=wX^4gD8|&(Tt85eo>7y`xn(ZoHVBc+`$JGM zF)^X zn3t%isHm}KvQl!3jWgv&JX;0smVX;4H7p!B=fkNv(-yp7%Rbsj{;N_MJm^BQR9^kv zeG2c1Htkv%4nn5(L)kQxblfpaOK_Uz7>&9^wDSVU`2Kd*sdQ?)fJ3u4atLdPcOzIt zK?~-ADKR?6h0XQ}b9my?+_JHRZO7F>cBJ}XM7@k#o~U|Ysy}(n{=%3-bHMPZe-XY< za9GUN@}M${L+pw=H+&;0OsW$F7rire4_!vP>5hc@0f+zBNLQuY#XM?)CjKbvd~4~Z z!sT(XeUIJpj_(^l6MDN`b9SxK-Z}mtKGicLyVHCKK4Sv?Aj%6>Rz=!XE4AzuZ19J_ zx_jZXutmQxF0Nr2pmhw-g^Y^=aB#9bdSAYm_dDBhl`%1*4C}$^mRtm#SlA=6%<^l+ zR_RICj_tUXKspV#>4xwur&-clA8L&ij-A2x5${bH3^yGQNk~aq1(t%#DeJCt;aFWp zY2U>4f_5Gs>haYM$ukAL4-bel7UhhBUP|pvkqn_jZu2K-tUE}DgnR)G7BhHI^>5#K zf+C936Q5fK%2KqdsjoNfYZ>`qwQ%sCcPp%#11Jf;5B2b~i_6AU9%`i1a)~2`Z>Q1Z_OISA_*Jw!krfEH#7*nx$jreGB!-Fl)$mB zX&)_3KxHKHlB@gcj2g^pCC1#(jYg#|kfzNF`_Z_RTUJjF=zHS+-kk@Dw#u&gmlNc37%~Vw)mkT9TrWC5 znvH=dp^J~gnoXP1jvcDD%CsS0YBP#k>E}%DT>7zG&jQ>;N|tRzonGgptYEQd4{*2Q zg!J1Spg%W%Y-rfUrR3-Sntv1U)M7iSBN<=%hmI!+Hy-LLc8Xg{ma)wrdERUiz3R2fdVOaI4<- zjyojaawO7O3Yb=6x>>XJnhcQJFNkr?g< z*KvRB4*2@GK!xVSr9OqWX^Ns=7LSI#s|%xmiHS+UT=B^bB0(b=2L)axqKF@q>@P68 z6JYYX{rbs;Cobg3pGB$o;JwuHagL%5^i%yEnAfpy_jBOieY;lqMbE+w(;Q&n$bBq4 zpgAm1ueF9ofLVy1^Zwa81JKdcY2nN%1#(-z?j|J3)Wr1^8v! z$AOSwTEcsz@##+WW(X^sjUdE8S^iVsA4cea?M<9gHcm;JLo@e)LGlc0PF$JpJ<-tj z{VzJ)VfbM1W$e-!Lc=uj?SY;#@_f9z`+{j_nre@AMe_-;$-}b}J)R|=;+jFd{a3p9ow zyjB_ocSqpJ$;;7w<0V#ja|-@I#XE-tiU@$i`s%|vzb;bej^3rUpabX4wF(rili805 zlPTmpy^a);s?r?aGh!N=A~Q{X_w~BhIA|1g9-SnR+)tjz(I$5SzqAiv8G1*9Tg)dX zf<)f$DuIqrT`AQ4}Ls^1(atw1wYISFu7Efm4CW{gYI6OWF2s zU{qZ;p{4#yR)C_sz~!95*C3pMevW%OCik&|f;a`&YV}P*?6vAlWb;d#W6;Cv*I50? zD&#dZy1B>=Nh7Id=iOJSm-(Bb2C!U%caCd&6hn9yeX4tj%s&AuKBIb~<4Hy;SIS!L z3FqSsHvg8=-qM=8Y6?@hWMWHTt7&hB8flYI7gucm%*-~g7!eKOy<$Fdh0HVl)>7Pnis$Erzg z*!7W@4tDHSmsq*?B!)hC!OeDd95$gblgzgScv~)gj>)VyE3GN&3n*2d+fIGOcTOXK zU&hVQ^BMzvzXa_s0h>RQhgl4bJI1EGW-=(KjQVj%8dkc^Uu3K{9WhEwCD5Vt8yS1| zdz68p!|d)WKWCwW%}&Wn8hqi^?~zO*v`vrf9RoI#@^;o!H8ejh+3)f09o_KX3&Gv` zpYG57=3S2omE^$(P#QF_f=TT<#T*7WWBre(MZh19Y^V$aR*@x}5S*Q1n>p!eb3}zop|WfgCV}g4H!;(_pFv2%v2q zobI(Nu4z7(GS(KlJq`SFR{Ely+B>+@{;^5&#=w{YVe7MF(?jz7>pAgcX`aYb@Tv4K zHO^C)u>{A57;?MS>No8nUS*MKO$6ZfE5C%?=2>uDl_`7dlKkk>8tMCY#q{UJ{pLG* z3SPd651LwcF3l7|!O7d!`bRiPm^vGtuhr(i_U~sa$N!y%IMWi+mRyLjk#Q%A!20HA zWWkdc8szVigNT0{wH_}_vjXS7+a1ka;ARN(lPe0`PhaA7bYBV(Yt8yTk{lA)E^!0CV7CtNzXfH%ZjVzgIQtMrHvLY6WdFpXkYsvFBI>*Y=C zlJYS-od!I=H{hwd_kQZK8sofl(;MYOyDZ#q5m&WwyW+ZIA5=Cr+kI-_8rLWqt5l%* zkJj9a+NKL#(9Y$#g=Y^QjT)^}AML_ywUH@Lo->^4i)o$jtVQw3|LeN>vJFkYb z>j^*i<3uM4R}?+E8uhG8Y`RmlYrGo5Ggx|!Y4)5gf9SqP2p`*TbkWTi^TWh0tuGL) zt}zqjQXKi!Q623YHZM20qrq(!e?*@^N9OUwP5u{7(c+u2yC05R2&Axp9&-KBmwUn{ zm7b@L_kM|RZE&9rFW!48PJvT>kmt`cTM9m>9tbLKR`SeFT)uSvLFDRQFRj~Jsb+C= z&fBaszvjCuEK~7-J7DfohUai2V?$)|9e)%PvOsI5dA}^c^OS#=x+XA<(lX6fKk%ry z{XHsjDm&qs#A7*eq$J3@cJ^+Y<3x;bsEN?0W|KGz)7d=9P9f@}LrcX!jnpXd}x zS;JWHDv7^nh<~$QXSs#{it_79=tfUN`&98JC%p72tA2><#>&1J2m5I^GaKVV z5#Zi!K3yEbRx1^J3~js5+=i^Y&+f+}jkb80mWF;;ilW2ji*swP#wxgxcHUO;-^QM^v#AEARwS)ATN;bo=RC@z=8MeA%$19&E zCQ?i?^)HD95Dqn-^TQs1@5NB`%szX5y`2^R*~j}+tObV`CezW7~jRqMb0yvoQlIc2~SyXGPbx%boQ>_w^IPW z;E@GfW`q~s2pgUstW=-J|8f`|wP0Gy?ttff!T!0JSSV{Hl)Kx_$AkY+$JfGHe%xqU zLxcuN)q#}=S+HP8Uov;5VvSk(!ojVk-%hXW8LjX)mVv>zwchr}xF~f`zmN|ic?h%4 znq%-B?6W@)8^%|)A2K}jbx#jJaa84r1L?LT?8resnB+xLP*E4B7@M{c{zcjwkZOw2 z3nGT9%E!aMA73vQS?GdP=U>oZe=QTUYu;sZ0-SpXLdOf4aHo$0(AnsO?I2RFAuW`a zHyGuvm;vSrAkHe3zXu$2`H^)}Bz$~T#5j21-+w^juZ!#bUMOLq!YSBpEdaTkusvWM zl?7k`=)K4tD%Axj`-<5%e~NlV4r~3!sp#^fJVcKsTG%lYXJX6LawuOxG7h>f4&Bt< zThtR;PHEA8B7D|t?jI*k&2w~M%t%q9LAL!$wow%C`U{4z!g$R1-KKkNSqd8xF%BDf?67Gp)5kHtu0Hrk zAlxSqP5!{%a-si+(u8!-Q@n9#IYmbLlFKkn;SGgl$6OgQ47@a650-6X94z(z#L-oO z{Lt)0a=$n@()tOc21%>3J@Tr}SV)a2dBikL8Wh)Q7+1X3gR}}uaXXoX^(!1b#-hFo z?kLU*p;KZEWRhUsB;cY0tS{TK1INa+y<3GKxSAcabjNTO+cm?Y5JIX<7T{XnG0u4C zSsn$yb~`Kz1DHP}p7-1>oSV@Hi$YV2$d8)+S%KE4bSt#-M*3pWG$xvj;nz~Fm0@HZ2ow9@U?$g`l$owX%(6pe#S*&rB^B(S9ryV{*>-N)H&|fa3DsOw4gm1nNJZAl5BzeuQ4f-@;1h9B~-X4`*gD z00vQ>Rp?4rs86EKY+;Ro?q%hRQ3Dk0Zw^5wc}r-GY-9P9hYb}sT&DSJ)Nr4FXmoIA z|I!;=%C6d;LAc=TT$mnLehhjqfslkZB^Sa zNLb~Ibw>X6$XkybmHnK7S9v9%5JO1d{b(ld^%6MkoEFRh;LSm_EXq@-nUPEztxWj> z?asFaUNbQd{5iNe?bStbP4?&!RS46Q*UDM9zQ()N+5)SE%#Sn%C-j{1f)XXYJ&GOz z)m6UZ<;BRvIef9@64!kN8T2n6h%&eo`x_sW$C_D$-v5fB7*LO;mXnyjnEUk!{w1J; z>g%B#p3pnkEfTG?XMh1_mai^3!aujApetdAdVeCOkaW!vhRAT7MiHjE_C+uK zF!G2_hciK}+IdA-IQYQHOnR4<1*u;ICyr;<((*g{Wo3mdUtgweirWlDK@mQ~@fJ^g^k91qu%~Qh2Kb}<<`{Id$iElKa@3wunQSCs6t{KG`MNKe zpqte&2~UcN`A>-ubl)sxZX?PrZ=-vNhraqH36-Q|czMLfOz%`#yUicT>2RBG=Pv#O z$DJwUBPOQE%tb>|E0hjr_M?|xhiK$DWK}WufOA#+uS(c0&w%1OcmUTZNc0?qU3}4!GsKGcRzx6fcE?%5v`m`j zCo3k#Z~Qk|jPDN-K3^$bll%kTwTcGUL@x9y!^Jk3UH+{^#4g2%Yyn#m@%bb&MBd0v}M?k`}7H9UPs$|7+7oO{Grn& z`oogQZj+3d2=!VR=cKwop{WHr8qdO4EHc>>t1e6P!84|cO#F7Y6v@+VrT#b4zdlfY zc3;?|(mA(~Gib|~??ifbdzXXo`7>|vlK8d3BYd97rV=iu7-X&O{sCukQVD$;?+j_5 zG11{)x|0h5Ybe5V4rQLi62Z(XDdRD}jPhE@lW&}#Op(w5+A)kLz*J>k4n$U8jB-@o z>W#Uljz7HvJy93#A39Sjmli?J-w*9^txZBoe77bo+OGkp^^)Zdu~dwWuVH2EbxVP6 z89HH5Z)6rW5O!eqcCHmHYsTszvm*TtIec|P$4kr2<;{B7tU5cokA80uvwfrH(|b)a zIB0&mtu?1zj^fOva@f;VJ|-+i>=fr60Q@vGFh}X|(X=O%^q#cp!v-2&UB-=GkLVEA zQ%+7Pwh`0qJA9*87yZX8bAW6<3=CR<4=t4vfF~OjIU}=?KT}-FjUViDa-0I^?b?e& zIFwIuc$3a(Vj;s-#}hW%KBhv}D~+CE*xp|Z=1}WLXo+j@4uVE%nF*oF9H;f2R!t(Y zhj_QbGmQDGq`pW{=n|w!sI%f!7W1|!+coj4?h$Xyvar5ONo1gLMa`^GKY!mb0gA{D ziy8HJjE1{(NRu9whJ)3>2BuGO!x}X@zsY$pz#e39PsabCX~7CA;;bTi#>V&0N$@07 zu0})6XZL^W-kUtHCCur>jl0z!mAPXXhuaOSpzXu@wP|P?G=_9YYZS3}@+UeDlySIY zE%R_Q+22hkzR_b2pgtKPn^-%Gx8b&~ zZB{9IN+LXq5n^Oa7(s3+p1m_W89V}N?^!nmPyJf8RnHlG|0XtuTKUhh8^XC5bpWu5 z=P+9qU>8SUoOi)`wt`g=6(@Pv+4b3<%N~K42akzk-&Iwn!|L&SZ;|q+D_c-ag>qG~IqT*45?ILPwvPM?el8#g=(0qF#UQ=FMx3BSmh<=%q5e zJ`9TXeBm?fufKh_g`uah6=;eKt;}v{LM%|vg`y)XlvCV9Y_B)`ZtH_WBILJDuBb7) zdWW%it9U^fy9boD2*&|}TvV0hjd!CCbS0xe>wV=*49)Yt=4CP{y(g%D?ydaAtg|++r>pHG(x+=IcLGMe-%^;Mdw+3YPQVz(8YOkT{EOGy~V8p zY8x%0SU!&OgM}BlyTkrm{GLi3IgkQ|De^e;(V=}4^GH|5;TqFl+%A4&RdU3YIDcTP zTE2PlTiQ09FR3D($omYKd)J?`;9ZUO;T3PszCAg`&jY&uwn~>aC0#Cfq}KGgQezy2 zU-X6y3*z)0M_b7@HHdG{+M&%B4#g&0>cM11cTmn2ud96wHD?_1ZUV9Fax%F@fXr&- zjKG>K7o}aV)>H>B86wG+?)t*go7DGCw`=IW>ijGOTGp;#%|~KX(Wra~lpHW%l};mv zc_nzgkL+5VCCCrN-u>`rb{q(#MRvzTZZV$6(l5JavncD-y2l_%#3eL=XfJ}Pt87qc z6DtlB;k`*!(NO*55JwQ{&mb(c!_Xj%<{Nb1Esc>|n|9-UhWl;T2OSUFXCVQ}H=h&^ zD6jel37%6=>0l?6V2^Zd2Gt#N27*L~_l38qsJ+4ZU4#L;F>lWw1pd^%k`#gI&Vdb(HDqV0mO#d#&@(&6N+Ezj{tnK-wVbU8L%&w+Xg9FCsWizFnSBZoR-;o>p6G zc5#shmyOBv*f^Hxqv)s7JHC#Puht)n_Z-|UNA?DjLW+4gEH{wc(U4}`@tc*pBI!6YZPhv;|qxJ+C^aAv9XAW`%VbYD^>V# zW7<}SCI$npv!krs1>o?r_LkK|q)^P(uzRtkF7cY^hI8L8F4bG+@+1iDZQ{?J__S;r zgD1-v9|c>1x1h!5=BdI6{l7axO_;)M6+Ni#I(%%?>TGUDPDfYm0cz`qj1n#*!{Lm( zCr1shwbE-hal=XaLNIdzwlhBaoDy;+L_R=Dd3VxF-2M4$?cl@-^?U#7RwehbV{9=a z$gaan4=M|DM$G}h2@@tf`}jaKmL!AD#()J&ODk?*oi3U0{<8-p$k|?TDY~|ZV@vsd z!5HW2^dSK(E1;k9&WHsabV3ICo zRwRC%|%H%vONEuD@Bsq*3*eFopA>&^ICs#o9v@B+HNW2t1W9WLg%vl z7ghk#?+gQ(n^(pv)l$pr`k)s^#E4+PwyWNsb#Ns@B_QVSQ6PC#`Z0O4@s;$n-p*;p8AQ+Y~;QHqCafvx(W;jQ$o^C)~5P zp)}4v%I_YZESqbdR5SntHdW>4zdRye59;uE3hy)-ZWeasVWSs`)PkJW^3FGti^+0K zi}pSBlOq)ZY}k}NW^coXu)K~|nX?{f85bPGGWHFJTPuByg09g=xROhy5$uHeVK6M} z7fF_T9EnokiR;lfSNef?)5KGI$@*058{cyDvnR1$83rH~Y2uw%EN1QSIDB1j7)O}V zqpf#8ITujHKg%Zu|Bh$pp|Kh7gCw7pLC5@GTA8 zt*Kt$<)CsLO~u(7W2O`%GxFJ|FjkauOlk%uMf4J{<15IBltV?ig6e9&YIClroL-)W zy@V}Y*z0NY-ZItBKC}vVG_EVjK2FES=CO%lP~TbGUZjFwH1~Yyjw-MopqdV1lDv_` zn(OiPygZ(ikqE7B?5Z4l-R-9x{I9^j185zjfTZ%K=<-+dfl?L^(sr ziz(kX);)1yXE|Z-c`Y&cu>b~5h?O_%sw1-AyH>Uz0~KP*#B2XBdJ|UeAU?R5L7V?3 zitqxm6Q77#VRaVkGoVejpA(BIK9+u_$OKKSA^Gq4J$=DrU+>ve?nt<{vg!n%$4l>;y8GA}YSksUN`%+ zd)In2gd7{lK3S9yhM63*+!(qi0-SpOukfY1b71#((2ZQ!$5;tlBG2hh+eUC+_JNab z{y6&W*qbv0fz`Lx+9*ZwVaH`a!B|JFS98a>gzEWXn5VPlG4%IoZ^x%{9P5}5x zVUI>IlbnT)%}K|6>iA}9O=(f|6d&znICx9$gGIz)G8Cpm>%{8?fZo_b2V}k0B~NU4 zk~d)6tIMxfjAE3tmfvalOs0^@@IpZMd*MygUUJ#XHs!d$rw-jor1dBa$`S5?F1+E6cl{Z6#g_b2Yz* zljpRs^L}PUZRPR4d(rG9q(VEM&D*JQ9zDYa;c)q5Tw9@)_fr+kv8Zu_y76UB%8)uU z`E#hJWW{!I$72lt@e{sn5d%}9RS%#NL}T9rjB2>HH5usFoQ&wcKvn-ig+)@0JS`*< z4=!96E)EvEY+XvYZM~-Uf&AlE3MZ2RvYYwI%cP5WAeTkEaF0&^NQgguJ1ucCmm(RC z^2m`Hk)eB;!zI{nf3{No-?_K>|A|KV7-k3KM-;xy$) zudE_tliJh}=i)Sfy+!lT-k-f^g}hicYqvkm^}6!bf3>7o`}+#bfUaBe@>`R3=zsDA zrvh|p-O_qJ47=DzSfao?aZ6n=IJjr@>uc}_6PIlTGE;#9Q(o)U4W+qCF2iZ+Jm#S2 zHH)KIbN{p=z8q34)Kg3L^TZUj zL+Tz~OAd&H#m-s`UUW?$Na2N0adwrB(4ZFnv@+{n5Wq*pIYQNDM-+ds*|5s%L80!b zB=Z1 z2g$hcw7lM>rRD}V^OCd>VwX8M^2I1)+d+g*gV_$LqD^q&yie+G`6TCiLc&8GhO^!wW+V(`r1x5a`3kVp z^TB~hxmZn*zU16V#y)eQ;9>HwfZ&(~a^QuC=`RvPyGv-zki=VHp|+^{0?U*PnFrOfS!8x!&q~ zN`2>>xm|}=rY1%&F|Y$aD-VAtckE*tVzv-8x3Tw?)gnONU>T-)ylW~L(1+!^ zRmMEf+c{BQ8Z+Uoves-ElHEfT;56c!sNT0HXyRhd8Az&Kd4^iRh*|Dcr>f4IU zvpPsy_QjncHrdsFJE@!CZ*AAR>Hn2+Xph!0C^#dpWfw10lQm)-a2Ty|%&pB1B!SiG zS~OBJ0t?exD@E5av&=yTghP1M<`-7yd~dSougCLso=UFLn~DYSOST8Ain`T4 z_K75X?>ANPUq)jv;tnKDJN0QTS~?`*^~V>PqvsIa5p@?eHw??`I2Fk+U#)%pwDq|~ zhj=GQaqhYSr+4saWcPSwQb1=p7E_;Ytbjut@V0g;4sU05CT84iqKDs3#^*=N;1@#e zoe!i1Pp-yM{Um3ay?&Nw1d6r)Gw-ly1rf?UGI$;~tZEtpt2+$Z%2zndOiM2pk}=;j zLRN=Qj#u!0=n0XPVoXD|2RC=o)b{|b*N?yOKfv~51m&IN$-9JR)W17U@oeqANya{q z3&-mj5g<@OVS9_LH&*BJcKkh?^phN@9BD7?p2}H3{7HEkfTjS+za^iUa}p@{csx#@ zC%wQedGH>@Ej+GdM7=D0x<(RtsHnisScMAfi8ChBz#n^tn|JD{4|1-^_xAD zt=~;EKxX|@S!1-IrlT_4rIqRmZPtCowY{_^o z(h>?HNjhb?r%esCiWwgB&6(elp{vUl8N1&Cmqt22f^iSLx`S~k(iBQ@p6)FxRcQfi1*x@v5`K;enHpeB&sll1*8AJ)WRr-Rg;+)57Mqfu|1SOO{Mt7$ah?AiDYE?| z$0_z#K(_FTW6y2!B#1t3nBiQ5k?j$4+^L>neGc$;u(7nN>rz6^-pp0YX4maX8fSoj zHMF>75cj*wP+SyPe{H0N^H0CJ;*J*LNDyb#w?@qi*8?ijRS4Eo|3Ncn5t~7{sr;(r z?&;r7B(*ToM!kCM10Tpi!9XgFDCD*aBK$uv47v9A|HCl6N*ZADFmLvPWhh$}1uhu< z3hF_)K)eV8UpKrB@;BD#leL}Ds<{1rQ^I|gjdo#-$w!(5-7gklBlBCj+&{KN_F=Uo znH)z|Ql3?!d%9x*3H&h>v)s%&HoTM*dE;VrC`Iq9CFK@=G~a|uzabPXMW-mUaW^Y&xG!4|%0YEV90+C9 zkXT-!Yk8D`<08u*AJuz)i!Tq$QM*ozC}ss0(NS=_FwQ?zcx81><#>-i8Wat;w1Z5S zyWfU2B-!-bFFGl@49`75**f&{^DztX{c3^!#6#abcTvaj$|!lj%OSiaP+s|_;J z#Wl)Tk`P7nH$}w2~-aeG~UImRv&DCvO(Y_a#AF~+_opgO+uJQYv&GmJ}9kl-8`X0CLA>5)vqcC-k zLdIm%3T^|*p})yQH~X1K8F|EWg!;U)2l}$I;O}C5(Kx$X^jV_5{=I=aeaGjX3eO^R zg0;|qojsacGYBUT!3vi^&2G3?Ye|**zY`Ss^Vg{}nrDeb=+`&I&L>*XXPL}Q6(XMHQ%!t@%MZ;({dC%rPT|-?Bp!K{8@-_lW9I{gzk9j{88-6C zE*lGYyvFA@GXhobWuG(`pFBp8e%k)3gz;?8v&M`ys4reS7NkXxSNa+l2P-wo2PGf> zE-I!Hkem(=L3bTS1J0j3pY`^5hKuAml5w3uCVmR|e zl6pPl)Ewf=vc&1N43^wSyzYB;TBI|wkT(0#;Y_jj>-xFr$vmt4&5g1UvoH~UkEbKa zY@LxSmGt6xCIX$+=2%W z3=$-`yK8WF3-0a^+}+*XgADEr&R~Pfo%fvc2i&S#b-&zO)zx3RzwFh$_v*Fw^ZXj; z`NTuir*X;=Dg3d+rqg&l?N=MDS~*T(%VQE(k&YhA?G%_wGJKlzlG!v_mCW0Hcz;T7 zy{hl_hi+R{772`h-?vX|UVAcKQr9V#vSJuZshX}VG}%;K4}W$i_v+|Vxm;+k?oO;`5h?P@fD{!$uG9mY${@ByNdmNSQua1Rre$Hjn@zug&>t z*oQ=zl$+1w>Jwsvk0>CWg73c-jciTKo3Bx?11i(Z<9y!|ZAp2$wiAcqV#~u?z<=o}|8E-yuPoWcp1maa zhXrx`dzhnEBt{%A+Q?vxJI%e{I#5qrEO7V1{(H`d?=_%aLL2aw-<#iL4`)^k0qRA1 z5E3BD><>~m8;pq0AZh?lH%6DLUA}gkndVBqxM^ha1kg$?Gs_xveAvJb(T{&}wH{w@ zM()f_vt_}t`i1^+r35tHqB!5_vChKB+2ca~QMj^N;HbFg%64Q?+8i_~M9pf~Z80W3 z)pgJ6Znob|{1aBUNj(Ifgs0ucx*&?t~I-fF5ouwE9U7w)a#)&8>)aG!6DUFoncWR*ROV0bGbn?&b&M% z{3Uyl>=nebU+9(7QPFW6crB2RxkRSykz8;wSJEO{P9%J5hveRGt&o80m>DGZ5{B9> z_OVAS)K_fDDJ~X`p=M7n9Ycdlx+_c|IqtyT`H0c^62WBOV#WD>1WjvH&@$01na2kHpYplco>{?b@2lsvQpMh_n7(Sm45ibdEk2ox} zx4nP7HR8KJ<=bJlyeG9{xYz2}Ij8_(Oc1x}FJ<0^sIxY+7H>XfcwudYPTsZ8j#!z9 z%gKR0l5*cGEt<}-1TS!oXx)hEcpBHR znp>A$HaTmqKGwC-+$?&s=9fP|2=>Ax=>ZP?Xii=9<|&A-@A3bZoBf?k96b?UA99t` zr!J0A!8xEhHE=YzX_U<~cHuO2;}PppI+X4{^d-qURPo8_*7rwP@| z7#a>MGC6U*4dURpS;O=oqYt|RuCLB!k7hITu6;aqBb)c=ZuQrj&8vZY_^G)*o=uA0 zuru@aootg!mRKbT$~qwDOBMehnXrbgt+62W)S2_r7bdQmgN0kahk-2#gbRh;PHBJC z0HA5MqUlh^yc2Gz{k(dJyZ*zt#!%L4@eS+QHxJKO+ufV(v)Z^Q^Vs~Xna!2v@j$J1 zgGBvhZBpKCABXGkCi7+L$vVB3o0y@;ALfVnslQ2yEc7*gaG%%GP@nfcAd<3{Y@-EF z^H=iv-Ed5BZ*tb5ww~SY)}IdmR`Eej$mu-+_vSEg{slTX&Pi4T21CL6aRWrRy_k=L zlr^SDgFU>9FYkFBu@B`{gmlsfguY|b$h79tX*WY)XJS>GP z@C>M1rIk4RJSqw*^VG$tu{`f?WoKD1z{Pczs}aah8hcKK_3=CnoO}~m8hIC+HY+WE z7Rk~Kd{Z%_g$WW?@Rn*Rf>X2&h*4mD$9h}`2+VLYcEv$j_W1@Bm2mI>Rcjf=WxA&# zqK;w)5}lw17zfZY;3Z-i-n)3-3#L_ZRXBj9*(I2&94A7YU@sp%bFY*Vt!Ool9J$(B zL@;c^U1OZaB7TW5Z>!!LQ@(MJxg#ZG{J!lPZ=?EzU?y+&mO9gBe_}cV?L`Z=Hzg5;=R1Ne zEn^P~o3k!AB1eT=`wt?+eGb@q;q?4n|67z?gwL=bMZ5D z^)EGXkxu%_K6PyAD&1mo>Ykd>;f~YfDC|}Q5fot4*;F>Vkne!b8SfInof@4@dF**p ze4gN1lNC}rhl+gTLjOIIkh1#~4p|j0o|9RoU7OPCh{eZBPAeW$6$=Ua|+uwDrkzt9ak#I7U)IB%+~`y8(I3uhfnXv}{VG}k{1rcdS(slzn$|^;F55u-0dO#*#5FHRv6dUtW2`n4 z5G7HtESuDU50wcMtfB^ty|$>8I3sQ74XoNw%VJZ7P&Df*m!W19SQFefQ_7W2s&TM< z;X3XUiPgmZPPqHN)&Xv%n|{IAPG#)r08&HG`xpOM##nRMtc;jS;}H-Pdl5ec<}1Py z!`HzeF_m{W2c!8M6IPxd6zxen)dWcv5LvI5^HOmNUtD zGH4lWMu=90k{E;)7=sOoGygezg|&J#|QG2sXs0o1iW)yJ887BwrG~VnXjWzQ-y{2v@Mh%6HfDb z#C+ju8_)B0exR=y4*K$5$MmgIm1)raiN8a?34?uG>%hEx{g=uB_-|y+?&76AMm{hP zHvJX2H!N~I5srGrXaXS|r^olp;=KH%`+(xG{7l!S_A=+IZ*tG`&!VrjxyST2WfqA#p5XSI$9O1jq(Hq`Ky`Q-?;%(Ye&Ps&6z8Z>V4~T|X$ziGT zN4pL`)-6BwAO8m9%hN2d!!PBYgxwp>E>t6QK~3Wa=ASlCarN(TD^Ir@JtcKa;x8SQ zDnTV4=2J6?AL(k^v9y>I0@IG!tiyii)w9Y^@2ghwc(eMw9sb)${}p&YYB|nHjbBJ4 zWtkNa@oZ_E(ydj#{|!shGPG+JifHqFVcwp;rZzX>hg9zp)!i5 z-vsoCxN<;q^TI64^#YP%alw2bQho~A-&&bc$s?4RN^wHa=oQ9GYKOO4CsmMntN1vw zN*sk=qSPejdPwF=aj>c+;yyRCJ%guPDGmu)1wYR75E+62Lw~Iycjy!msSzr~b(Ya7 zcS8WDvL~xlGkN9$#p3Vk==8ZPDwVaS>3K)`KU%x6`~-^DYGn}diJ%8*A&`+hlTuqaxGT(TZVl?FKPgc(d;^VGm0n@3?dmE>bjPq+}!ZMyQ z=rX5eno756owL4lkk@Ix;tUOsr!}#m+W_NFNBMBTe*t6^@7Z)igb2(4N%$IzNBqy; zgkzRqybpxCTC&!mwl|jMrHoInNpt3 z!|Tj?qA7fUjeA<|xb>sjYP2>$92fRbv08pgCV7;l*xjMB)-NLe4A*m(#Ja`|+-OT_8ELT2=eqgVZ}7?Yc}&c2 z3(_OvbnEDXcj7BKbiik>fnCWj^7DQ+ZyITO&IC>q&g##BUd<0X3f?0)yu#TF0ld9Tu6xA@~rfUsyir zb;P6Q^w}$JRn^Mu_H3(rXI(0vNw_9O@39%J=&Xm#P{z}9jtq<wekosm_!FX9ER(QI$S%f* zZxDO0ap?l88%pTWUvH};#kWULFX>lExTR=g>@F7ENfBg}Msg)K6t13rDw=;fqI>*{ z31H7pCULUu13QgKx`;gmvp0bWAQ{sO;M+&5z63&+O^95tf3GVlv!d<%%=7jHu3*!* zp!Xp<3Hpq>G`h1k4u9_#E-tUkt7)71$#uj>y@%B`hU7=)B4ZEmA00V6`_LlKi?i&1m+pJpi&PXTdT=rg z`0tERif%znh(V!<9zLM;jFCVn+CbH={GtfsOQG>gGZq8I7A~*Sg}>XQ6M>+l;V$Bl z@a;pYwJm(|CrA>sjq%~C0ZY7|-+s7KGRI9e4IBs?7y_zo3`X5#^v zqwik^qSa9@it~;NUC2A6>s3SMvt5^Rvsx)!w99e7=&%nSk2aB~q-$AasWH08cQ0$3 z{Of;5J};G2dW*n%AX45hX$Of7xeuqni1J+1u2Ni{^BkECYH(fIqhFIFvS_@K2xiXO z^PsA(T${&{zHsrCMdp~HL}z>vYYI3T7UVLsGWr|#V0hby8y)U58RvZhmBUWD9^(7MKKeu>N_Rr5T*8D*vc{jNm; zALF1ATZKkS2uYcFPHMd=XSF+AP0jQnu zj0!KqW8`r*9b>#Eh&@_0*{(fG)KEpQxLML^WNjkfh2 zw&_1_B^G5VC@jQ^r)>I3$fZI^SQjq*+$#=bFpkPJo#5O=?K}S&GfQIz#k`c@^SE%y ztngisQmheV`5}9A)%O(SzFR*l6)eR&Ni1zheb&zu`Mi`0lBjfiw=+Yd#~G`_meqcn{j&4NjY2&0!3L=>eivhGAv*AV%yGc-j+cYYGP2<^0eJ)ky!<%s zxhxz*-Z6&D`Md6Gwf5@*Tc*p(cC*bKjIX^*eXYA-Yb*5Po&B zK^CErN!>Y;-E=X}V}(XBf1(q0R!bzSFEzpvDPD0 zS}TDtDQZqryBhOrA$GI2AjCJ)BsFoV2~0Q}hz;Db0zl4N3CY)MZfE*>r>R8$M(aX8DBM}-7s3lyv2XA?(}8)I5ny*)zaUc) z8q)q4k&$qTPZi)f3!x+gfV>M-#yb7e+oGfpm`YJR!!w%;QUA3S&$zl{jvmPGnDdC$l> zC~ZnxJaMIq(U$h$P`E>GFXg66&u{M5t;4q0iTIxPW$)MLDC@l&Dp~sjU(jgJ6Atk9 z-KU7nq$(MbE+?&-#X8j{HTX^bk^D;&n}jyQZpz# zFNl2Z!WAnm)~HH$_n|8p*$#iny1JPo2fWd~n;k_v2dC?8%GeuTt@L9TDG|RHW05JE zS>AI*uom7kEza2jQebc>%|%DUDahgt@hGRRq7)JSQB{A*uHDisztIn?mCCd zyPsbvbwYm7RPKOW9GRJzfeiGj6s&;G_nlh^DZ=Zgiu<`2gW=84!KvKZX^VpQOxyTz zFXWP8Wys9@RKn#efuJE!z2`X;D~IB{s%_+!_Qh+2`(r>Siq{>YG!nfs5C-}AOHBy# zdjRsVPB?ajnBE|zVhUN_KL`AcD01&{IPBG(3Ne;5s)1Ajo)__7xjC!;Lm`h%9f;p zhA^VT4JoBh`mkQq3@EbhGK88C-Yl6 zeWISEMt|dK$0HZU>s&%r9Jgw1-JNN$aSEN=4ie-@@mgZbMKTZ5B$jWzZv9TmrOR*r zR8JLdBg#5f2GM#vCw)7aR0cD0tPlpO6RF%~R8c?CFE#6q?uYr&5R_a>ci=gcpxR8l z@m+-u*_R8bPp)IDdHymFcpMOtn^!$Yz$>%!P{w$u>0^Q1>x-!5N;7&3k~;sadDYm6 z6|A`|VPf!VFN&0v?DfXu@msJ`$ee~>UL+wNPnOyzi<pOIl1&1zC#u~@9pES;OMAXY{o;$n)`@zBk8sG2zBywf48G50;;38Z+4a}``S`8m zpUo8+etXi546E97o@Op4;Ric;Pw!*@w|cP9>kWAGF}H_xnM8?-xmeZ^j?Nan2P9+~ zs3$eeM)bo&;srv@EG+VfZ4Fgw9i5GOF6QPj?11@0q@x~gxNUkQgH=T_!jAzf#PM7X zg^Qdj^pD(MJ2z3;qMCGcpMjQM%4-EJ^7;pBQD`XUpLV)YE9WHl?DVzGVqQY}8eg|6 zvWRT_a;@~!$7V1)=etmCwUF+u>xAA9DJ_J_FT+E8qQX(z$(c9Djj`EY({bQ$@XIUZ4d?v`>=GB$8s zdCdo!a+dqa%df;6W2!VB$y;){eDNS(*Be#idMX&dr(!rZQc?#;9IShuO}^H{Y?I@;i`Xs zlhqH?@JxhyLQH^G&^!Nul_u!M(?1~ z{QWF>l1EsFgs3s(j6=zJks}r@tb)OJQ%`?hCrOcD8_VR!W(T|%ZK_>AymPZJxGs15w#He_jkdS%XRRUJhCPq&)=l zF3;I?qFDS!F_9x;8l=aCWZTszdMkQ6h@|_qU1d}~Tn6_Mp)>V&l;jNMP|LncAZ*^6V%=Z?Bs#xG z45}g7_Cd0>dAOWM>u4-FhHo@{Xq7%D{80CgsoT>|m;9Z!@;GAYcY#H|TU3vEgpcW_ zSjcu?dH#{MIu@JgYO@)?V71!y8a1+Y?7EQ5f$ZbA-Gv;@>Rr7lkx9vC{NKnVk|6l) zUxXK9mu-2!Vp5a3=&c|4#JMA5@ePFBbQygM*KLvuMy*mOZ!NA}b#Rtbf;SU87gD8s zLw-C^`> zn3oDLP5N6z$JMnFEt}v?AY=ag4j?~Mjk}P{Jw{mAt{E#w+k6;09Ig`-6R!pN3D2RG z9-NcWIjg@Pc0k=cv0GO|jm#8WgYOt6#~DjoC1CuXi4#^>Dtujh6NIw09ZNcDU1|Rx z&=lRT&5q0zKR+tiGD_ta`g;?g)`PYBh+hASSY#^IIgZ&5al5*wa_jH}oVARyXHY2U zFD%9S-eh9j0**e}HDHfp`so}1x&2SAD`ffFK=b#$_KXL@f+Ma0`ST5Pk^W`($sPd0 zz|b{q!6Wc&vk0e@#Yxl3Ge-AOst+Q{Ej2j1VVNcN!J_U?1I2X6<%tomjNd%gm=_CX zlg?J(MbrN@daB|p==-eB((kq%$EMaO7QKZXUlGEb{lQPBp9I9ek}5y%2F3QgxelA>`nK#qca-1bRS9cdK>C~S4_ohV_q&3$vhYwQ zo=krBle*vYcI1jv&+GMGjS9wybj;d=X>Yq?0Lk@B>fb5A@;7k2wJ9+fZv zGmTZDo$ES-yZ!6IsM+Jk{chBu$H;XY?*-xsOp9+0z{A|%(UdG!;_QQVe&U{o*;{W{ z1B6!3wpZ%XgR8bgliaAd$AC;n{dAsGiihuT1B<}`ON@_+N(AzZAjMvvU_|ipP6ZjzD10}y`cYG$;bStx%*mwOUrsX24SMxMr~YNg7|269^2D8L72w8t)aXBS5Xk8~n2#k+V`9TaU2C0svHCqni9Kj(1UUe_S7`%KgHsQlO_ibDQMi{uJOhA&P6dHZjt2dk@kUcwz%ZIqNrt;DlPth2Z?yz}n#inf$E4w}|=2qda z6f+xBS-Xr}JWB!N-!k#T!FhY&X)aI%(bb`}A~6!)+c?%B^jONR_K2* z&sfbhB*#05S+0cBJ}By#>CeCf&EZ;g4zibp_7R+&nr;ulv{0{{K4AT;Q)Xk8@PloE zWWb(V&{&&%EO{D^MIPtISVMtB*F0Ne9Y0Z$*cy#j#txmrk}-fsH|G>bN^BzYeQ4ca%F0%esdCoM0Xb@KEXh zku~069rhta6;xDU;I3=`+Fw-SNyZJ#5jI~WwPpmYq8^Y{W%XAO45_2H(n9Pz0{sd} z#xS3PVf=x+(2?3E=5C5se8$`59%)Ov;O5%6*^5|lT4>3|(GM3nNYi(Rodgk_1X5P-tXRmn}-zYtQ zd4ouR1w%>nJgV-ixS|8gS2HrSR{wAcH|ra^cm2}B6+l2ug4ecUe!A#r+nUa(F~rgk z_;CShF82JbsQ34BfD)ky?XC_Ih{CG*4C{_E8^k^TJJ{MrZclgOAksMt`{Yk1=Cf$a z)W)_W6b;@K#Q0g99Ugql)grqvWz5IBh~EhG0?syMaV5J3^F-)W8bk|uR>&ng5Y(A9 z!eLyd8k&--KC!iC;;TlbMki79z{9Cx>^W)=FV^<^o^*S*Z5V%2Fi=u}0;o%1C2fA& zU>vbLXbm03MO(XDg2{u1%#w1nx;CE?5~s}A5eeP@9QhiQ*?KqB?Nv`LDQo(C;SN#1 ziW2ZG4&E#~cNJ^UaiAGO7=;2Tm z04QcUWWXEkE)ziSV{Dum?Y@yEpPExlXAXf1mJz)nx<)X5D#dqPbI#N-G0v6NF4pTD z0dPh{5oOwkQu9xPXGLxCVip%)Gr4@yQ?D+KL5v4f_f6S@#l>i|+SGuiRc!`iWkgdw7tk*Q_vw zrVFd0`y7Vdwl4T`cUV1mJHHbCr)b9|6Ibm0UXw_%U(iEECA*>i_(9fAzOO6 ztgone4!3Ds$pFd8O~1twBYShd|E(9Um(wDdKFhnWyP8J!3lgu09a+R#q)eRykYVy? zK$U>A={2sLvzpe6q}qU0ouaC0#e=RFRu_G7X%%PLZCKTebbZ;vnnJvuVh{nzIWkhI>hi;h6wgL-d5%o7b4`N}-c0h$f{vCnv}EUcYf)Vk=w*UAVk^;To3Y zeHy)RmF3m&^nlhBqWW?U}hGJW)*<=olwn(Ic* zL(S`?4wF0EQ_0q5Oo8qj8Wu<5gOSr2Y_NkA`OS`>Qb3#E+kH01lTVvTktRo2zqC%N z-xk}nF7aKff4jIyFFH$NweH!L{IKLcWl%j`HW?C=at$BaBzzWSkxQA2nD!*`#I6xc ztM2`{(x~Zl$gHH*tz)^#&@ zRtAl-#D2^Q%A#BQ$U&+JV#b`=slLbDhKeh=w7g%QVhA@{@QX(YZ;gsh-tYH6Xb2zD zP=z@F27-^aCmcyUWCFg_OEXlS zw%kw1E&8?u-yJuYm(UGw(sF#oEa%?5I%&ryF(aH*Zn?1=EgPB!S1F3|Y#VwuWzk-3 z)^zewPW9D_o-P!!jO`%^!}P*cLQ_Z67I5EA>!$iWQpW16KDU)kFYsp1N9eCQ zXfhFaA#Hw{yTY7o1PE6Oy=TQ6J_khvC3xu`vn5tU=VyPjMem0C$`K0=Tq zLaLFt9_~phVjUqrEhuIj)Qs~d&`<4mwcs4Iji*wH9&W~AC2m;|Zs3hR@pQ5C_xSqH zlNyu;suw6@QbB1b!)*-L^1m_Iv+{hK6Q~waDegJR#xNaY;SF}_%s<&4!bx@=FlY%| zvFaHl_wOcc2^qG%|K(Erpkeo7nDQi-NyQziOI7h4!Ft59S_W;z0K9piS1*Reo`tCBjIez`}4P^iC&IcpiEhD+bK$)HOTw2)OhG=aHAfan%4T+1HI zl;cGnPA@Ggn3ew?tH42JO$?;_gLALT@)jeq2^ydBp-r% zSP72&RkP=_8u*LVLB8~@QO)C(M^0;a)7z#{A`)!cms|3IHtRc$4DF8~pcq(Ht!d)O zrrRdJm8ojQRcVndz5j@O8G|ESrt0FJ!P2FcdT70iaaTvT*f6DyqE<+wJ=vRN?g z(Al2A`~AD8>6GtDVfp!x-Q>1hAGRU4o|8rAOh_seeJwt$5|IU}3}E4t zS0_tkTtKHdADx5#g@t;71K!famKLL6SzCVYdm}E+H0Fn_9Q<@Qxg7M-QIEh0sqcA@ z=VzK6NmDf?`zD7#2zq2pXdG{gG96&{^U^tuq#04!3@&(rfT`W-;_UoG-`{~2QnmAv z9+N~j*t!_>0(R(kQ@Il4<)(ciyL)tK*q+NB4dM$-6$gN3RKw~@X^A30Ut z04gO$avZ+MhC<^~qM=LICHr7wjSEZCWi@^)NgW6oIR)e6m1ZU1)_GL_n!`a3h>B43 z;y+p8bP`h_DnO*#f`9PG^g-7e>MeKr7klWAg9S}@kA@e>(r3{|Wae}aqm2H@1AnJP zQYN9qM*%BSomavuE2Z#Jg2-Y~vvJJJl&8wi)mv z07kDTH<1GI1f~9;t=6oCYk>{AhLfDwtT;z9?!}M^a<87*PzewXb4#_dL4bOFq)#ln z*pg65u5rNlmm71E#q*x&t*`+rhiHxG?`M%c&N?mJSOcF=NI^q$LODyPpIya&P1(w0 z41E24xoWzRL&=)igx(T}O>R!M@bE<3H^ zAft@8t2Jt>zCcxcaA}XM&8frI@{1WwLlIM|#m@c&693;y+teS;p4SH{+){|=Eu?+| z@TrR%|DrGy$pRov3p|aND3NS(PIow72g!lg?vqroX|2!zr4f>5I`e=9lV`~nEM}9v zhx^BwzssMj?*)7j=2w6J!`^;*&}ZP_Ez9PxNEyBs@BVN7A1|HV*KhYi_G}$GbO-R; zDt}hVTyK4r$q(3Idi~&A1pDSDg&Nb23iecAR`6ZH&3w4eSpjYggX`0vEpXtPC(tnT zF?>64F3^8NNf#gxsp&aCw8+Y*E)BEos{Zw1dF`WmttSd4i|#dqI0#%M)8bUJEiFT7 z%s?BaOW?iRI<$%)bKXELEC8by3LOHp6qjYN1y7U@+m6YlUv0Z42}Q-qSNt8|l>4Un df7YsaBZCVnY*lD^ANxf5((uwtTu^54DzPkkNlKmvb#wvbIlfBWWg zC??3K=&F6z8Rh~|N_*V;9J|Zddd*@8Vdxvb)0+ew1%qe6g@qxL1oEQk^CRic25w8% z2lKK7ewSiL(1n!3C3M`aa7MWd?~q8M9I30EsG}N*qm)Rh9B85(iF<3G>Qs_9v#a5^ zl-PN;1!`5Cs`NNjUbwA1q<&cd_Q!?v#~rr6DW8Y^I{11&q9_9XIijQ~VMYMuLjYCN zmbLnO2C}XrXO;JFk)a!3qu)OjZ~VvByXk zT_()gD=1_suePL@xj4`CvuA9O)2nfToP{*p&EU_(!6s0Sj=F=!!7u;8nxBplAJj9^ z`;p`qG?_h<*%3;iV~PZ1(= zG~Xum%#}}6#4?v{(&1T|b*Bk8qlESw*L$N9Im`3*?O|W~bw<~Pxs-}bv?g25@nuyf zwRt>?W;>qp-N_&@v|Mvt(FiTHd48!jiwJDqpN)HaSpamd-JhHd5GJR)Ma2eK_KiLG zXssol-HD)I0jg!EkHUR(@zTW(>&wau`aejrHM3TpNzD|p5F=aiicC`0nYr$j&I_!t zx?0WJ^V*8a^G{~?N23sZZ24yh0ol>Ou}3F71o?kv%eS;mF%EPX@eMNfw?3AQGjWo` z)TLU{3`XMy$deVOZnMDYsuO!MgI%;=tRLIH6n$_<<7F^ZWp&Msh8zQb!m@Y`tJnrUe=3>tm9%u!wk{{ZYJsMEhu)k6 zq>Il>+tnzRP8`|u4V>|q|6tCWMi@i)R9KMl=oHfSn9NO2*O+;l_f{y)>wH}A4mj{A zHa(_Nj*f=2q_3uvkJ>N}2X~Zjmh|Iop@mf~9jifFc{k)sE2?cWV`OIEONXHG&u#Lu z(wfyTbA|mj*{m_$AI8DkdMX()%uI=-1ocvwW5gFRPuJ-jqLZ<)(&{`?M^6OgnoTjH zn?X-K86O_LPs)XFa8Ijm{jlzu(MKBm>5x`9Z>{H?qqhL|*(nQ2-EADpfi7 ziiA%{v%bd$4L3+_sM@^`PLxpxIH2thauFY>s4Sd<;0b|TXpr87dMa)`;ATwFu%zzQ-}!m4k8*8`0k% zNlp`&R+s{agCgA$_LS=Tu(Plem6RHy5NKF86{Jl#W0r@ z+2`8#db|P|u*8eVr#S zgH?+fN||0Dzk*MdyvnQ1>xH+Y4=b%yP9OrRE&%u7(lXT`oCgGA~rvu_nA_i7g@$=i-SVT@T^Mh2a7L%1NVCH|Er7BC>hB)Vy=8VJo z-%rM# zeKs;PwX`amX%d$jTzYEKq=U$YrndUjGE-oONCbQ)E0AL)mG#=_5$-}6P9vw=Vc7H% zp|^>Mdvy=OBaolHIGCrUX7jFn@xNAAgI6tH$=9vD6nxZ-+Clf5ijjH>3J7$-T zx{C~S+(pPL4eI*dS{sq+%_j@-ZiU7A-1yAF{I$n^Wv56n{`GCE>GUd;2j`VGu`cVT zfp`kFWI{%n)Sti#{Wv-;+FOg8i>tD3@6OQ#4G2GzRg@b}&XA=YY{t&Z_a_937HX-=Bt7>X_@0drn6qdd;&10n9N=r`eEp(=g#T@cE1^5Y3 z;S=nV`>m$TI6tr+hrvZQZJDD+tWVgzF$XBi`Q*20l+S56o;MGeAJNY`sVv>gWeP3~ z_7@EBdc>hPEtMNJkcipTNKUkZJ3a7PPO{N6n&T*%WyYG-8~1Zu(*3k#n*ofKN9I&r z6sZ=1D^j5{UB^lnG!jR0X_WzqS+$!m98M9$jAdGP&e+(GjJ?LXZziH^b^CMAO;fij7!U^b(9^H zIQicix*p#4n`S)ilwo|bkA_Yc?(dzNt24UsgN&hmoY*)=%iJ$EYaC$4{Ysy(FKqZw z&Zl;xbl2v6vuky=)wj1F`jmzo{B%_!Yh4za?{rI zf5hG}39P`%Q)YNHm5>L}+33{w!!4=x7nayy5o3Q@3c}o3$d^4lVwUo9TSj#j_s$OO zCAkpb86_NojdqQ4OSwlU%&a8D8Er)X7VC9$hPWwYWb8*%p7Z1!(bFnTi=%_jxDh4; zHr$rb)eZ zk%+1Xq|&0&&MFUCB_+I{W6^-f8J#V9NgRB-Y<0&@z-pML12%xbgVHHedAB+8^Uo}*^ zRIoXrd}i#i{>vcbr$&MgF|cdYvwPU=axJyijnhHY)AN>B@Tnhx?941-zMw;l?y8N; z(D<0eGylWB?w=JJoo)UrA8!(AJht zWga8vb+RZPY87K5V3GgJDV?&vC?SV=xY(^(n6hnySyUwbHDW@gt6SE@SJpIU-OlOU zs`0+;8dFxzE~JCj#FEq|cfJ#}_oMKO$HRb!1EKwhT4YN5BNHD8IMG@9A! zd`u>~am8lB(Z z*Xrony_8els5+Pj#P`TY=FqbAv{M@um$Z`VN%$ni_L%x=F~Twn{B%L7cQJ)?m5A(e zX3lpf{8WOy#=H4VQzo{(yz(WAfnc&~F?oeGfsCFEEj5>X{)+ShX_>7J^0EShm>3V< z=}iTvdOcl#kAeYC_3KKDf}hI@0h}l_EI$MU4>5=)Dz!G~TjjZO7~lZ=O!&+SjLmra z%BFfbLXHiUb=*W>_EDOBgnGpLMr2-^HFTtp@O#k(yIeeRX}hPih6qQ7LwL;)EOG)! z+NwJUqU6j7o*UN8pI#E)eybzvvKAtjYxvHivolRgv@+F&3JgUSWtS3<6YF1uvd?N1 zW^+}X&>LGa{RaU(wjV9UlwnfCWPv=2S>aFDD1<^E;XnU#^!JudrD)!%;#S)^=7cXrKx zm`qyL7jIFkF+m?$05I2{+lZX zX)p5ManryS!2fac#-Ph!|2lVkN$CHDzxO6y__HVQJ2J6k3ay@cS77abqt1WfBmP&N zKMGp)(-%^PQuVhah$qO1;ebKHr2?5Fpz?o+Zw4{QY`U5t?Mo9oeE;dcivQ(}4%0-m zD*^ar#JyaJ13BchViE*?)kkRvk>0xap5w71z`yPm({DSAGD$N>kH=%;eh-DR1QjxF zH}Plq<18}q*L{7XZv`~Q%0hG`!w!8P*kOk3um=CcMs0EBCea7LfFJtuV!n_Bc>Xk8 z8R~TJfV>;70ZHa#zvEEuC`_s6DFIU}5;?&m)ZT zMu~S3J$NrOq4ET&7N__51s&LVOd6&wgeZroP;^lH(+9-DLrwHq9@mD#E{NF8^ytn8 z5ZqG|{2h~bfhO|+Ggy44Uc9UN{)v}T>Xfv!j0{I);vaj+E3`~ZLV>_fS|`%hqQOSZaNF^^O&65_(lZETCN%dQX^foY}Vy$el)J>f_Wl`)7UcMayEk>k( zKwlpL0WtCjx_x6bTgH5>4BtG6BCu*(Xf+A&fN?;csxXzYGMYNlZz$d{%s=B8z{CiV zxCimbirWS00aOeGOu50x8Q!=~-(vp+DQsin)FTqqn;9CW@JU#NC8JuVh>JztQ7Vu~ z7&?zK0K_(hIUfsXT&tsM&iXksv(Ly^yKFwA^3I3Mv1&_>hw=+Av3VSyDv0Y40Nr+b zzts%HVJ~^`85B0(%;>S*3xnvNT9a1bF%4ZX>%6L@7=lNpasbZ zHEHheGGu*qfzeU$^np4$C(P+$$M4vsd~~YC3l&MflPguXRag7)=Fgnmf%k1sUxlvp z8VNf_Aj6ys;-^eV<|D-FPoFx0QavXp_$?C(hK~q}^D})z3B*ptc9nCe0XL9EFqZ#r zud4rD4*(g2P|IWtV2XzM3Cm8U*bRSlC}Dw#fj30JEUn)QOs^RcvV^5*@4G#$Z8NUD za!b>#rlG0XVb-@QB{CYRS4U;oakDz;Kf#ejL+<*rFcyHfOvA43<~Y|zMMQH!s^0ug zBh|E7EzXQ)?RwUu!*ewp+*{oIE(5VaQ4HyXqtkrqADI!g++R8CuTm~T{c$Z5*%SH_ zPmq1O0c(?#p$Mj6brWbiWzx=qCeO_IvfZ7inHf5vz4Z#1MM=o(OQxI&3f*5^n?k)@ zcG(V!1e@|C%nB{k2%2rOMVB$%zTJhuX0@e=@mx>DH7{p-7;nqN;s}R^a?UtOL(-h& z+la2Vre1G43^HN1_K+|PI&|f|?d$2~v0Q02Q0OUYEXQ{Mh)I5*D3r5ir6wc0${d9ASd;8$$pYN+tR zA)lq>YtcG&h}FDc1CU)^C%I9y=H|u*RiZc{QfZ)P%!nZu7ndLY7=%kI}+W+eK!915R=L7G~4mFsD;IhI0pQ0jEK+eOgS z#j{&U>ts>xA)o>yy?MCQIG06`kt`66Q=<9Y1k0F+ zde(grSQpGhlCADD`Wu2u|KT$GweQ+my&!ecg*j>SM({2h6+QDTNQ%zn-9#sJL#}$^ zEUUIWEnO*Untz#TdX=z#ye`A4?BHY-?tSk>h!3q)^WZjfQ~X#8LsDsebD2Ebcic&= zn{#dxOu2JU0?*-`UrJ;#LG!j}Xu1CxYbvAhK)anXD57!9_U{}RU7*%zM2zHT8P(Js zJhbD$V!jQPD?TRK&Cwt~Hnp*_vO)|Wn3&i-NJ6udkdV;O0DLV2ln9R!4ZwC?>NjmX zFx9VuWC*3aAB9@zf;7ImwEl~-U>E}0=8h|`jc z`sLu(`3o&)ey(QR#_3#nO%%h{A&h~D4 z{%}E!tw|W{`X=DxUCxJ&ewkPlE|RY zrSsgSKm3kQk~0cE3)eEGp(lNfL@KHi`VTfuZ%cC5+sWz8p1BE0*&k!q$Xm?mBeyiT zd=ptijTUqof*?WHo7Tzsz zn0?w*;b8DoRoTafG8A!q1ou5jis>I9u+>-QeY_x)4Jr^d$hCSuf6~u(rK32cN(#e? zFdXNu+|BBO9T7oRsIEZ5Bg0zCh7q2h5XOa1&&6`K9BKU!9ut=lxcn1Lv z#7!#Jjcg5yJRd=15)!beJkAFqWh|csPv9k@-<@CKt*PPd-K@OsS~56s38JbTh91y) z9&q5RRvh;{X*yr*)vbJG-5)T$qBT_^7qLDz%AFJ=MIyF39FKPI4IrkDuoisW&pme& zdA1++xA;d$Cl9no=O(AABW z`Lo#CwYv->g9}Ti=23}FJDzq|ldD#dWJ+@01b1Et&^Z=H+Zd{?-#5}`N1F_~eH;~z zuO%|cpU;ssvOR?u=_uTvn~f&;T(>;Eq(+qB*&yC=Y?gf-^7KFZIJzxaq(S|=Fpnf; z6s=?bE)$qyU+aX*e3TL=Q24wNUf;f%#b{|9P0)ZvGlEm2cg;u7r|Vv<&wIB8oaT>| zk*lKk?na6B^D<)vjk!kq<_)b9)c1Bv(s;*@aDEP+(HfJo->!QOGD_d{*@} zcJ}rH`kT6~C3r48VdfYlMhL7ZGkx=bP;hB$(a=p(BR{0t79zxrn0wL${zg4u3G?9* zutM=tMI@^n-zV^Ao=8hVU;t$uusVow)HEFCs!v~eWD6T?B(Eh1H>s*G2>on~K;JN8 zJs9{vZx5`y6i*54&sYFu6Q2NJL-3f_1PT?vh>3)=CQdSJp9_ls_%i!LeidciaL_7z z^ri`@ET~uZ>=sS3vb(m~Zjh*71kqPK-69W|R3aco)L9xZyg(Ddqh36X>U^}x6PB@dzUJmVQm{vq&upRCF=o4VmE48+ zC9Dc*-HiEoU1sw5sBL&OI=Q#1yR0<3N;0?Z<145z{PgJ z6lYPl(gU!qUUnyvo6suUj;_6Uj#TO=f)X{(Jd2yl>2F|OO zb}|ubt#$w}@*ieElTDeSflbQ%65%P3L4nFg`1%4>Ih|y*34T#alAx@rjscKx0oaBD z)kqo5fozv>S6fJ734jhDAn+qGpjtkY%qs(CqyWa(d2{NXqgkLIbwK<9r=GQ|D=o2h z?9f5Ou-vS1=9?0G5K`t)5nI!~%Q=4(bNB9+8S&?;FM=-6F%WWl5SWJzzd5taW~I~p z&`4u+O4hgwnEcfVsKq-I+X*$5LFK*lB&Lx^d_90O33ABo zdcd=k=%uo7Id%GTCfuRv4V&Ad$0#L zrTaBKmNW|1`^WP=4!KtkR$)@Ms)fOh@S(I=YClcJ9edu%;%qkZb+BfmODEpiP) zVe zmb-;?wd#4DGCd>a?FTM>^nS?}U@bjKj&)?}PnO+na;Pz(No*F{F1Z zrz;GX&eI1k<`mJs9&fn=>s=f5B%ZHH1C<^h=iYlM+P3OTH;gX6gl@UnX|fi)o6jP7 zHx_fPt~L{Y+I+s*Y4zTQMY3k9B2iTii;Hn3{mfu~67#v)|DYVEdya$SUC55zb9JA= zC9BzS_}gz|U;tu@{2iJu`tBAzocWU7_G;i*i7GU+#FcaALJR57OarlSdA7Bg(#@TvxZ(ojT~%%C7s4wRti1E?8(|1#h@oe{&2Tt^%{;IPR!-e#Mq1SMWBi&u?W&S%vf_RW(ES94CSuIA*A+yU|Z zdUI-6HOu`7PAjx7ise6l`0^zq07Bu@w%O@ zVpN`7n)_a3C-I|lBzJ3~zs+9iUam^52lD`(Eoiij9E-5o{aV!wUs7;# zwBNS^Df)pgIw$^MJA6gSH_cv<$+j>IN#6MwQQW znJuqn!6`v7biOB4agSDmjvi#y6%OdSQMa|BQ?}zV7PArdNv#&T%?=2e=^-0|o%aX_ z6i|6UL3`9*|6I4%ek0HOhK%tjZT?raAQ}~0xw?HHeB~nlWHf|47Sad2Jr7;Kz*us- zpTVfHNb$TRi1fQUp=Mv`_$BYGS-vC(_m}RkGH7ik+d94$Tr#@rT23I5dHIAwB~822c|FVwc@jqlM|`x`W5n?)Cl zM>gs1)YMMr?2i}C;l0~%)t!iZOCnQi4-7Ef@0&k9w~;L3i!fpy#XVhtLA^m6=$MWY z9eXQ$l5xLCal0V6=viq0SPT381H!y|zP5c|AF9JP5Yyt2nzse6CI35M7^;pH| zc18KyzeEv%Fk4Of-=XAx$V0&Y#T_Nk%n85+_~|Mg`Ty4TB%x^Zs{k&Y@csKWo`t{8 zS`CsFC5ry9B}kF_>dy(QFke?d#@QI=GvmuiQe?}g1O4spCQ^q@GmEI`Srlb%wD>Og z_rLMV8wx#3f+YoWhJT)TS8U{}C_vuV3{3t%N`h8Jv#I_5Q;8NNuo~ijv|KR{UZf=Y zPq&wbx&J1I;ky9-FNo=X(9c#9bAlu?BR+j_VRAWcA}=R`V~}gUfGg0=Br=4+Anrzt3aL zGc2)FN!WOizDao8K>iwco&q%)JdaBItHOCUvS%?T$c1yI0YoAH>a#*{-GKSvBDcNv zl37+NG5sdDq7n&}Hn?Qd1vZy|}BF2!2ak zBffncA0Pi5X3GUD!AtPNO!SmEd#|w|_lByKW(fg-p7`^SF~V35UkO*O?5YK71Wtg1 zk`}TuYk&wSi{8x(=qZ2@IZ+e-J1DOqmJ8r>7=q@30^25G>Guj+UksCt?U-1Sc9^72 z&)obMB-mB^ybg4~Nbf}bH*i{w z;lvkvsCW`=br@yv?*_f23bKUSuj4-LHvXHq!nHx-aAJ#OIeh$l7aD2 zehFYzAktWa3^jq@pm~V>p7#Dw8IBG$M#cri^S1tT@C9(V_8qoR8Gj6z^$nRLBVZUz z&8V*H{9zTyMq;y!P7K4wrQM7PM%F?Pv+lqy7Z8&&Q;KinwS?9l&$r|n-c>6J(3sv` z8}2s#gurDl2%_o#VTrtpJgcvprl3lw6`k>&%D$P9 zpA>+z3mM%w+*1=U5@Dp>pR8Rw!j7Y8)=Dx5!3V@b&SBs(Wx(OXjtl;T!MJ0uRL=i- zWC8;NW7??RsVy@P91Hb*NrH=u1O+$cH<(bU>NgvkdUZ)chED0Zt)+5#GE{MY@rCk> z(^H&j(+2j=9$HpLPW>j^R79*lpaLARkj4yiq=E%M?%c8XBtD3YaGAf)=+no>$-;gE z%M~et(4NPv==p&Xn5yWHnWW0U49<@yXc@U8JV78J93Tz7a0&AAJc|(`JbWD*8J|+) z3xob=#|a`46PLAa2~e-{@HS?%h=z`Cvs&Xk7i({jVhR1Tx}&L9_crP<94gEE{zez_ zAv{VMd!xZxVkDg7*P_*@M119Ad0b==0Ndkg37)eq_v+Z{bB^zEl#G94Xz9F7m!O#7 zOI*^hy>Jp6?(?Q#K%4imH+<;s(w9y|qST|9-hr3%yra8=({#38TdF&^w(y|Za#+6s zfF=WAdtGgbS4sEdCu!L5sB{=?`JpqTao)zd`gc?FgJh%gT>bXb0J>rG%3eA3@DWH$ zkav8FBf>^gC(CO}GnlHNSgoyZ$q5KJBNdAlu%QW}-m)lgKGJjv?)ZVvfv>?1J}cZj+9)v;z96S8ppsJ z+w3#7apJcIfx}H%=JhzuR7~EO!WA5$W{KD=*4{2CGy}2o=qg zoMPE$99e7@e=(Lp^OpllfDvS!(E2-K>W?$KpA6#bOJikFei9W79~r}L6iwB?`B!a%$a^Us?K$(I{zp5jz|lM|XM(YXp8GiyXN_x76T zf~vCBJLc%#xQCCHaR1mF+G3o~rX*`$cRZdSsRoRd!saR3)erGj~qC$}Fl||#@w8WM6oD)>A&?1}b<>OEX z$H#eN(R>{mu%H-$BJfjLm zl-F%*C~}R1?d`a9OkD@yP zYu`sYGU_YN=Ce_|9X`U{cVXCSw6M{_X0ewfl(ht5=}jcn0puFfk-l)Pnpi@g4)-s= zF?=y<7*^#J;uPmCS_J!f>9hUdcMuS_sk_fSb@M+nfw5^P(=l?5>Uuf2pu95@5>ysa z&L&N+E3IN=tdCLOm@mW+Y1A>{`!e;#@QOKE1SqZtD5~XAI`2o1h zp{t?rs8U?}F1kf;K};c4WsT|q15%lGj#rL5Z88n(&_=4)ha|tI)D5OBDc6=GKKMrj zf{J&9$cq=i$zDT(IHsa|i7qV2Jx&n3Fo!IYbygl-@-JU@2$cLvuEYCCP|ma%s;9Fk zUH7!mV$~rfHpxgU^Q_QKsVQ6=gTZKn1Sz+89k>hI=vWrCO!Bwr!ecRk!fYPsaM8=WnW!zmr*M{O!CAI&^z4tQ)i zvmFugI->b&Wj!^AQ0|-T|0k>&ZmO^*xE0?fv@8ytios?kp^GZ>JC7)u(!QtY>CXbi z7u)P`eqk6m-7?1}nHs-xT?>)nu`lrRa?+W%#`}8KU*JQfB^H9(w1;MILg|w|X0!PFI=Nb;yCStYu?y!lq z-A#l<#^MSB3^N@MtRFDRzB6P=2yXUJx}fPlQwTz92oeas^sL`lZ%6RQ5e+I9;Au;^ z(2+b0pEi&3qdWHtgASGQj~v&U2^Q&WKyT16;_?UJS;mZfJF_m}Vlkj?Vl+FUZa#v@ zMD<|N7g&z(MV!eZ_uPf0!}@(-<(04d$|k{R++O4H=T&}{p%p>ir^C5#3*1553LzGc z<5vIr0i=*fqp4fLiUcgGwR}`ux>Nv~PGq2Vw)yjIm7>_FRpV{o`=h7o-L4UP1Tp)| z`|T-@B0n@gV~9@+m7vxCg=(sp51{+7!e_j0r+#XxJZL4VyHFY7#CHBeLM1c*jf5UQ zM@1y!A0&Ay%X@BD^!Dit(^V_6a3X4krk#!rOh-V=oeprFMvM6$ANC|a*Q#*OUPrI7 zA2EeTeb`(F%cyiHW0od{r_HVw&^SqSM`wOZ5|Ae_S*{CJk>CLO{^~KbOC)1M`Y^2K z&!Zjt6|fMpKsTrMu>6#wUPd{2&e2_pwtji;D%s%bJeP-aM>niwPFaVXn3#O(#BR3m z95<(f7mxwHZt+3@$dam(1S*oBdDqtn#VIK$v(CP>A5g1#4(ah++gZ$qSn9M%GIB_RGTv|z1dI4|xSl5Zd zt=cHeBt-XtTd|IfKn5{12~{-99UfCnumd7CDM!h8e;VuZNS18YIz852oH(SLW~!1j zRRFw<+nF~e^YeJ0onhydQ2z-yime{?tnzTbi8JAZhq@JHcdItIE%^n|SktU3J1?aC z$VG}yGF~29a(LjiyUqfEa7IVR;VjoY_5;u@4IF+xXMenp;13>j>Xi5SSZU%Vtp9f@+mhjY$XyEYu^P?J-$IX>;5r$71>kGb+ua-@@7+!oe)!^9c;2Ug)!Rgp+b%w zd#zj$(Fs`IW@~79MDLFZ=k}*fWAawh_smabWR9JYV!JiR6>zGS`k`%>DI8@BFfhM! zBtl{PA*5`ve&*0GJH|cLX?;6i@r^}sWCcY(@g-t`!F0r+I6OTDU2Y+YSTr2-vbC_h zLPTy;2d}8@G0uThe)qlSekS5R>OlEXLQY#83!N0mJHuB$?fpYpv#OzHc5oU&vMxv( zUsjm zd$x}u{vum_4bLmv^!MSI91$9Zn^z;2Sb>SK!ghe(flU?g(t+BzdwY*>8Gs zm*p6W}maO>$t!Q&fMN@-pc5LD>xXFN$d{&yOu9{+`^7ANLsBlhFE z{2En~cN%nuZM1`eu`vFtfPkvn0koCfMRFqwXIem$D=A14P-HczxC+j`3b2ub%*w2O z4JW$R26WN}LfRZ=+z#>iEa&lVUPDb-o&SSwKHqE-S5Q#r@)L|T8;v)robUHY4-%#? zXo5gEG^}iOw`Xwu<=jO`fBd+=B+0 zqAY?Ct7fhy28xs3-KwY=0`zdyzzk zagEAo8ip7}B8e#1&|g4i&lB6Zs;cWRa0RIG$03z?acXFxdAROMzJ1{|RCysFTOUnZ z*9G_cq5FK@{X7UDvIs8doYU3m<=^SOfQD-$(!M#vB^DDxLD&kwP6edcvE=jqgH5}( ztLDLfcPBY)09Cl~UcKIqhmmx&{5~f^G$!{Y5QCxZ_1~(O*^qFZ7us<~y3-=u?iOzx ziF56VqrhdQ9HjjRu2H*GGXJP;IoZaGQaTz|;JnO(MB%lU5tv0Ms|grTxN)hyg9{fL zHiToV;Y8yRG^WR4x$npOy02lHknB?C3(>M6E%MUmzbp|lZi=$_-}2x<|CI)3AMmd< zI3nmq!~ne96lM0mwMDz)6IXea{pOAT=FP=;5r>4wWe<&m)rj@*ZhBnT=SD!i}L6Z?27VhO7G#V zl*4LUj3XBA+)5(SK==N(u2&Zkp+XB!<>QWwQnzBBhU&le-*O7*4o>!d7@ax>rAFXG zg?3sYq$wZJ$M}g&j{yOcKf}t%$jM3nh2uQ&R9;j1#IT-G1i5&Ld<(;$-0@{CnMr>c z&FdQ*Mre}~xHfQZQhLDFqVAEy=o4lq2(9x$0 z3kUyfXh?EZ>j^GR*b|+icd8RKF6OL{-LvXiAJEENdZl(XiMgL>P~d4{Ru(c8g5px8}2SQ^Gx2OvP)hkKP2PZ*^X9<#vfZsG|nST(Am(N!mbn*!F?r9b&K(Oc9KPV=t z${QShebp8q4u^obavHfp;qkH0Vi6u5&SIt7ek|HbC)pGfp`yK}TH`i!D+DIf?e0bk z`YI$s5pAvBT(~!YC3M>KSva!zsx&g#3meV#bP9pJBKvgL^m&})v5%5_@#oa}%i)_t zOm9(AzcRlc9^m;XrTZc0W54In*}gLo7hk4ZCdEznFN`R(#<708auFGn02|HyWJ9Dv zq7Mx;0S^KFSz~kLkSBjE0VK#=5{XP*qf5Kt+=Z&HMet z+tbNmr-_S(v)*n^1E0bzYU;1}(;i6v7o1Mr-b+50!2YZ{t`Z^ic)%)jHHr-xo8zAB zNPYWO$mK=NtvMpbQ^%7Z1l&3lB#W_i32H~h1?o?WWc2eo6#6CVWb5{d1z|>d9(a9& zRhE5cHp_hCJlN3qb0sp5y_<^*>rzGhSW%+A07>H0<6}I1Khq#TAlU1*0O9HHIqaq) zAO;!EJiNAs2}fvg>CQRTDk}u1Nsdm=-@X##kj$DTH}iAU?gf+s1jcXIa>~Id`AB_t zXXtw7ij^Pf=f@udZb^3eY`$F!Nb;5Ys^*FhFpD!ur?vii%U2GV^@HK%M6i%$f2g%P z(zm0_r8mcB#>kn!!yNv`Mpo(nLqIQ#6T=T=_6I~{$rYCbJ3*n z2QgSAqQlnur|9wdO38KkF3Bh1LvIn;wGWTBqcLq8!tr~F?ppI*QCMr)CWpHnBy~+p zi{->7KupE>;+xRs2iGGm^g8#`m7d&rd&p#JW?sJ0VL8w8t@e=QFClD1Ss$I3v##a` zp4xW~6MGL4@a_#A_a4bK$xk)AnGtQ%%#`|%dYb-=rzn?Ylcv%N-A|`96IF+s()(hV z*n#LqOwd{A-(JY|KA-D2Q7&_1RzY2ICe??Ives2gZqCiFD4e z4a_5y^>^jIQVW_|Yq{BPVP6YFoOY|MIy9()5dC^sgzQ`_6Va0A6&d^Q}`Kw!0@k@f`=e&fHm=odxP=`C-j=Pt)YWm|ABx=QX2p<~+G0;Z*4HCr<0) zK4WaU6UKMJuUIV+*7rlogU~0y#W3ygBL~N4q0FCV0wy;Zx5g@bV7Kbk{C*(FhJYf- zT=N8_#IBANNP(-~qc+zP%qJpVnV=9Pc)SZ5hm??n)*t&57*jA`wuf)1t|pd1(#Cgv zyG=n}<{3-$OAzVXauyqBfjx&V1V%EyUWh1{O-lOW$%@hF`8qBzfTMI5@dC!qT)ZG$ z@_f(qO5V~NRCxL3yu`yvef%7c<1;7gQ&(_iKUb(g(rrz9s4Zzs_VmhOSQWFQsP_vv zG)jB9{gr~iEWhM=Y(k?;R(wq6bN?yKCVe8zq%ZR^%yk3XosCj3WD14rUPa5W)_@B6 z7;L*+)~lsy?^m{W)JpftjgE*0Nbnp{b;ifdgjc(B=bN`1&olLZUz8dASg(ZFXrHz- zk0y#fa(`VEo;JnPT=XQEs7^xVemqrgaA&YD1c}RC-XRA|P1ZNHL4+{-eo9p{>4}-hNeYQ3S zJPq!%7@-spDA5q}NA0gJn0V*6_xyD6Xq^+q5u5DgmpzKS?-z=cdN|7yuGa&NG+K>z z@S%4GR4im*zEn5JL8sB0XNnZ3lO1jME*9eD#*P08W=btp9PMuGNK0A8&GU-~Amy>BtK|q%T8jy?7;-@s%37uT zj>E9G=PQ*bPZR`vX+ZcsWC#FZzrn*Rd>4IF2^v5`9=5K`39O|B2oTnM;i1sBeIpq6 zrfKc_E&cdvZEXXz=ito+g!jP1t){1iS>)KKQ~3wLVMF?dnr6S(X_|ucgUbP~bMs1z z-{8g*x@!)ml^T^TYFbJeqTsTqH6O#0JaSp)GdiShC0C3cfWwAW zT%*q#+rwwMmVPfOBN?nbY-Z#3x6!U{fY%Z{PAQaNqz)$cSDYZtO*9P{k}(;zL%k0; z1@U~uUu4rrt~{t_FVlbaD}_d4g=E*1``T0P7#_rurH$-fbxb@MST>LPdB3~saWcER z;-XW%?W09<*|Dc+3C9Ok8xNXCKK48}su^l5O`u^ zeC43`i$MRTax;#F3H}#-eEgrnO0hLJCYj&Ik=;Jer{b|CyLT5Mzc#CnXpPUx4Pgh! z6DydNtN#(|?R)GDPs@-s&42Vgut8t$yir1>Lt)WEEuYFtmZ0Q&Jfn#&F_PYD<^`ArCl^dNdmpu$fE%^bBKAumB zC$2P~{>`uTaghTa56PAriuPp=aoRt4YGvk~uAhBAihX?QLTlYFmrhzEhSaI`kHH`z zsyeSu|3`Ia85PI2u4~-gp>aZxpur_TaEIWo!QI_Ggy8NFg1fsm?(QzZ-R(A6Yp=7< zKKG1q|K9%7qi4;kQL}qikE*Ah_bVznb8_bL?Wy!2O(~S41+9idMegkG|6rL{jJs83 z{|lN-T!fG>REi#!1uOjaAHY%lX`g4W3tn7!>;CO-^8@A4h#!bHUoGxPfsRbj+pN|9 zFUF;;(0>`13O#~+W5aSS{ftJLX=DUvKTFKPq`>bIegcGE!r<5J&GVWmTcL4q2OJxJ zd`Ul1L;90&?&BG$ujTGf`>FV)>I0I!4gf_2bm5&}dc$EK~r6@GFo^ zJt^gz9;n+fs8c-#6L11{8h;dlUvPCe7oyEG zL^n126W))~?Q>{vvIVp%%ub7I6!LyP;QC7(3p<+Wg}@~AU-QL&5>THk&jor_M&7oj z&FzsjPm)M%sLXD|u)y_z4Mo0=aI8|Rv%EV_hk3-nLBMs^wH>EFk1>H|mY~h?y(7Nr zU+(zcj1T?o4gTFiyX3Khz0{^1qUg0k`mbf0$ntIgp{@DDm=wVYB@BGgah@`~4s^P5 z84&7YvrfsuUzLkw5_%95w7@&{nS=ktj_n&QYCA)L%CM+*C4yoZn{K!wSzs|91Tk&m zenfN`G40`bE^2gw?;ZO6b}H}4KE~cR6jGtQxsKjkPtvt^VhW#56eLDq-&&$Pjk*3- zE8+gd?EwGyiRuLrcu4TTno0P#z8C2rpRo$zCp#pFV)}0bKuc@z^OID_^ZuJedvI zY+6{t?5ScK0|jH+G1I``VCQ{MMU^?lOm`pj=fICXu=B(a$erD7cZEm|L<$kHBNUhK zV8tQyxa5dGM=zX6k!7GKg43daR@_eP9a8`ZZgfQ3j<}GY3>EUDgmh=iQ~#SGHmlNIc@pl-k;^nZzp2hOAAsuy{< z)^;6|Q?uaRXN@Os44&ozsklBfyhC+n*zEd1HZA|>*u152$z@L(UDvso^2ewaY(Nu< zLdCLu@IOY($Z)mXEdQh!djg;6u)Ki>sD?E3?cTs6A#veOFAIae-QJFaCFGa^1B;~+ zj*~_=toJZ5=-RR8(w6mo$~1wnZ4P5&&bRgnR>TWWQq(jS`T=h|{38sz$ky2S>HH&4 zpG2V|p2PS>HFp$k)RXD{v)w>620i-0kLl`XsrLkD?vUqedEwB{ZD1KBph?dA&Y~6^ z1^s6)uiFS!lb@@#?c*7(+$WchXjyvE@Hp>7LoiH9!7Y=9oWC!W;)I)hb?rALg!(SJ zH9mfJ)*m|Xm0eR*td#vRDWCv_zhjHm*8w}|Bp&3d@U~|ODE6}Fgwe#lVSTKav?N&f%W4@X*_~p2qAJj@HQNZ_c6H< zSUz2!cFz5*$*L&J+STCTc+?fb+_?D{lxw`!UWi1NB4@Y_zDz}mNv(bI{(Yt{^OcO< zd#m5Cx>vmDiw-98-Bb2*O|feh?Bcm_YojTE1sM>rr6!A#wXI~$9l~i`lIkz{FLGU1{BdnSBGT;lXc@=N=v=kLZ#hS|ygOmS02e*YhtaMDL;R(?Xd zr|yJw9(HU?_vXb6yCL(~B6OdN;_b_*$9IVw|05qhOJQqi!kl&OAhlkgx4TzYE({OtZ#HeS{A%a5xIP*!F zsL*@;nr$YN$By5laXl}b<%rlLiC=`C*j^3B3Q-nI!HICTY9956Wdk57gw0H0AKc?V zRar}@n!t`PEKo)}4{d)!puDrl;dXrJB-_2W8lQnkAjJw5AKzzPKYt7>J~cv1pPXCc z2U7?Ng>3~#5R}fwBULF-sEh#p6&jb3zxrD1;rmXL&1N|s8E%aOHw^@hrg<${1qzi1 zi5s=v$2wh#=S`NCmC2S7YimzhyG2T_lurb#S48-kXOrRJ5p_i(qa*F>f#_ljqh!b5 z3ZZ?u6wB_(13~5DXDz#73nJ%{2G-JRq5$F)L&wQ+ngl$exf|>ZrGeL{?H`D-)20U+ z11WO{0e72HjgXDdXq93-#R$9dR;qaR zI7udM@H*S*&RKLpO=sZ|5gQs+^{LovMRC4rdH3(4qHKZw@XND<59cb8@2N5O+Jc(< z5GGO6%|_120pn z_UJ7$w^rd2MAV-X0F|WtC^e#3emO&0+T!=C%Kv$8f3IS9Tk%j1e=`q}jYwV5egu}p z5OdFk*I_ngRcj)x$lCq!>?emdC{Cm4Btcb*@PvzR-FEe`dl!_fd~EPX;zVyND%E;R zg-w_`@)2er4nMRiVs4=nFR*?c$8?d=V)M$(FTaL5xJs5}!Whx_8%1L$41oSzGVTKI z-wCH7RI1S5OqqUMO226=Kxb`cWpSF7BE&!yBecSiWyCe{0|gPCrf20aOyT!`Sdym9 zp{8I19axajfGl?b8-cYjr?|z#8h8iFB+MNF`73JD4v2VPZc`^1a?UJ{!0KOi$N@j% zG-&b>!py>*V-}Z|78f;4=~LiFei9!O#aK61)$3X?pep^F^WIwTjK4-RbL88LAI8|Kr-&$&=PGI?>Z}aBI2O9dJ!&so|XB zKJ9+8ErOD&{q~efv20!>R()`S|z_2IX_Feqz1QbK?mm%!K_Wv#{7*p zf6E9K(Rl6EH@|GyNsMJa-K7RU#8Mh1rZS{+-#Z0x#aYHv{flps?bVVOq0S{}oll+q zl^&m%bp__=T&wbAxaV6fKJDz%>r&_K&WNW9@t|M@_A##6DZw3yO@TUMHtQo!f_=(7L@b;vY^0K_X}1e*W*XE1PRoUU^tYR5aK35aTFEyu5MvS zfD*YxGxujh-~Ozcx|$layw9{>bl1kgW=ySe?S(*frO-wom@)L%FaURwA@*O1mXiwS zU(ZGvmm)Czvc>>##rzD^Pnp(uH3ls&gNwPf96H{nKG6MIYy3BsFC_=LPGjjLu+N7N z-jCe!fXEk*&e52dPdx0nC5+D#Xh;s`LZ(i}^EQC0CPLBLmO~JyaBbDNIzxQa(EYpf-5g%@e{cC*iq!t#{cymC;fq@p(YZ zZv)K+5I@CYy+qUFqhZj2F5|G|ApJ*9Dd#p`3qRvIj-Z*HH4~~~9iQ5AwKmF&bIUwY ztR?V~4#5vCAR%gvh4H$IY@N>sqTRh6!pzHy(=mT`Sly#d zOo44|JRQ~W^(ir#<@N3n-;4@i$shtrAetZFOtwh%bqabpV|9Pk@w2u7mTudrw>;tU z>y@jERn2~QJUma?fu*(7RM<9`Sz^eq6TT-l^X~DrshKhhvy$RMIz~fo$c%Hu@F)X$ zq5=-JeBCwb3HWh*2=_RoEcPT*v~5x5DS!(C0s?8>7uzM06>7sOks@~P4h&@IMtSJi zcS-C8fp;*EjH2vMe#pdH;H7>aMD_4s`fP-2H(2_{;d#zsVhi6P z_1BmJ4nJGPF*o{ouQ3LmlZ4tXLjwIOk#G8rmt0P<2Mqx*xpskLs@Fm60+vj*8>zIk z?Pr~KPCH&VCyRke_+#xc_Bkg9!FxI~OR)h*D?WvrF~&M7*u3rwj3r@9movNSf{9k` zaUU7=-T~E9iJGs5ZV<}Yf2w4%*6Gf3`eg5RFAIgZl)IWeyQtRpoX{D2vGWOFn zR;W8*gutr<-Qrt5zp#UA#jD$Wh;-?Mr*lho;Lr*o|G{$bC zdO>UdRt=Ar)rox;vaDzG z)fHbn!uU_mU>j3cJSGk!G(aFuZ8|#kl%l<3!@37Rg=kr zs!OH_`Yq%kGZ{e!1~c7Ft}G!ciow9zVk5)?3~Yt1b-K4PyIV__=jaFAF4Lz(nO5*_POhL*^AAUDtF+Yh@ZIqO@+%Fn;S>g;p!0aLl!yb zh;1`Zr}&aTZ-=30vA6VYLYgZtZb9{P=}u@R*fo7f zbC3B_7r7o@wJ#c1pKqsepCf$Yzs3Vh9LqkN<6Q-?oU!Dihy?YWjm9NDpU||Wjm^X~ zlfLjThA8f?KurfzbCm3EWc#h2$foTKxLet22aC5lW>88-$P!ASEgWa`M=G7<=Iz+wyG$K-2Xh9rf00 z-YW8>a8`@d>^r@^OF5UFErF6=twYw0CxuQ`bSwr>ixSW*Le5tU8gWa#Ypz%5~D^5J9rwvR3er2%dX#P-*+*Zl<&7yI3t?b z>X}V6UJ~Rz>^e`~{5l{bBRsh4a^dT z9Tvfa_eR9p=%|_F2N#<10Is{S)%y1fUJS3}g`dByc>$&QNt;@I5DHrdO z@U7d7cIKd_MwwRmVT1M%%N@6D@oWIOZ!8y;{uesMuXNuM%qRyznC34_9wUo%vw)~$q_19z}KyRP>{E`7HK zGbTg(YZ2|jQ-*0Jc*>kfO~B7y4VH)-I$_QXzcR>cu0c#p)biV&MEyzlb}5JL?5A_6 zvw4||Wh92B3TCEQNDBc)Jw@tz;k0oAyAtj8z$3m$1X66VELvh02a(`xXwRDmFiLH>cUyay5`D;jAF>Czv+yZsIHTi? z>D^%ue^63@Er#99H@MyTO^G^JcR46b=XHAx{wSE~MEJO|Zmd2!+dP_Iq&oxsq|LZL zSE5q3@F6yo_x5_WbJWy*wB07G#mRO z%(&_qUBNWZ_ z4H#*gB!>`ssl+3HY8<(eu>lLqo8P%f zqbFz%xgNRYiPdrJ6U2K)y&hlj;?iaC8j0as4Cdz>z*XjpI{-sW>D`}ncd!X4Jfk>? zSqcm*H|QjcLX*ctFaTjav@udl6##k5E7Iq2omH`p)hOeSEq;`?nW|j8=>B@UwlLm5 z#!1lA&b`Z?Hs3_op6ZTr6~LCz>M?B;;B5m?=QT_s<^gZmIn)G40r1xJS(@P|``b?X zCuw!b19TYU;jrF5g*taB0ED5UIQ~41K^UN4fDAlPq*eT~J@V&?4HKex=H190!5{=|+6iUqI_8ysI>Cuq zQUAh{PxdFb`BO3VE`X2Xxj*|Qr`%^Ik$Wp^NQ-vQ^q5CWV8Hv@ef$pnE++b(XX)ac;4~tj?1j-PPkmCc+W(fJAGHpoWaM#gI)Ep8P` zhR|*+@4T5NW^j9QS^%WzktMzY7L44Co7buh8^=Pi80f2&qLT2{MEvlFK(7TOSQHaW zE{!-gx>4a&`hl%KE9r2jSJ*hF!=%qXY>MR+S)d}U4dnzwfvCjIDfm31=H>jJ6OjkD z9dy>QgKPd%;BCU>$AuLjm$3wfc#6myD1AfAu?_Wu_^f+on4NF(iT$u#hN4@vdwgBM z$46I}aUAyQ_7)YCyhcGm!AI)d*g(g0TrL0yV~|Ut#@Fu^&F$2~BOo9kB!HUt5kc~u z=r2j;a(-n0tRKiErfJ>HhdJCpg!`eu){?!rd^7;EWcK63L*>gd>@EZk$?WPuEMb*t zn379>`AzlrK5OJlt0gqh)2FTexlBzqr58 zzoyDl`tG0|9wrNbsu1j-oE#h-OuHQTjmjEBIzoQZiCipK+j=V z1d9w^3ATZ9HOp$iiUg{a$VA~K=HUUztwp9q3d)8QW?bZ%$M;aXM(#zAg?2=YrRRGF z3JI|GllNKBE~jE;CdD+7^V#j8KI_Q!?U$wyHM3jtE&ujDB5H_>c2?U<0En>pP14>a z{O+&<;Nv4Na!N+>-k|3$oX4$PY-odp&v0o!nalv{pr%C|fv65FgQ?af;pJzt3(ivr zI~@DCiwr&V++l&m?DCerv$j?Fo~bhk>3oNm3zwcYj8}{Ec8FfQSF=YG#LaHs>ES4l zq=S)AQ30Rn1Z>u(Ru(2^9|V?`)pS)AwY8Pifdyq{g{8&CHl~)A=CgG*yn5+%ZJ2C* z9qa38fmj0BjxPJxTDcW%ts=!%#;lb-wHg)Y;R!!}jFwqXe<)!<8}OwKP=hmilS|#j z#%fR=)r{dE;04Ko$$qgmT^*M;ffT1+kl@tR)TH72sy@`BceiY6YH2-D3rgwTegt+h zfY@#iGXe8OKnx_57}Ry}^F`BLT5n++WWW~P%C*Mi0I@S!OYn!3B$>G-IV;acs*q-9 zo8Eklt;o|Nhaw{TRO(J;JH44F&abN_{I3#TtLyPnAM)m7GQTz%*ziUZ-9n1>7UQiX*vk_Z9A#*x-+oZVSyb^uf&-;nPnZcXh8z`wPJ^bxigkqIc)3br6yro{cmYni9;+{F&1sAU+QjGEa ztJ5y8w`QNs%P*FyQEGV=oA2S}SAYL>X5Z@+S91o#StJ4r* zZYyCuZyY0u>Oyf|^qy<)^cV|J2xoo4!@&u?e;)$Z1$Yz*WRRWMl(CKSfFeP+eqCKT zn=zAB7vBbHf0rj8n9lNVB+(g?8Ib=Vs>EQt;idLG}R5Ocw z7^?`}06Jb$4%eJ8$c^n0))A5)T%5=lSu(%g1}s=DxIun@vVLD9Yj#E%duPYU$S9AT zl9EzHL?pJU?FHvgoBpj_Yj=O}BuX0XmI$kH#G+UhzQTCS&~$NrvKS?f3H&9^7HyVQ&(F}ZbR*VJSobx@^Bab-@C?l`-p^7lAMA_U7>i>j63GcFbXB zOE=yvb|*tAueH0U-*ul>H_G`XeyF<_jBGU|XG>~TD^%|duCSP$4{%Y9rZ47aq+VF* z_Hoc$&T#epI9W?Li%g0)Xk(;YdQEoh zWmU50H5rR0vDqnL8Qb-$=HDBSbT`RTR@&I-PHs6sOs7h!Y5MYh&HjTRGCJ|Y7%sy@ zNzt~}NxO@YPkedewEF%Qee=s*6@Jl?MePXX-l}HRSDU*w_;q2`3Pgcid& z9y5~0S?zHPj=su7$vu798^Crv|aQGUyT--Hf#Pe+ezO{#rnw>BowgxHpMXI0i54I zK8*7k5dJmRHqQ|AHcS4WtADDd9Z&_oCtB;OX(S)?;a!?!P1a!o8yoW~}(4 z;Cgy;?B9~<{je`~!{|Is%E{FGMO~XB12*5@(XpnUZ`}a=tE>?=CLH#T)`z&nT(ziz z2%I;Vi~a_4HJmt|DOf_NMezA0pfiHuQJhiek%B5%953;SiSXtH!ju%0;0PdvVeI#A zj+dm(n5e8*9?r#Vw??(&0JX>?^wQM=--WsBdZ^kqm3~wN01#>`4eK73nW~+XO@QxO zZg-F3q)NJPYL0xoX3S!@c7L@q+!I_imgd)b6zHmI>NvV@wa|yWJ}*{nL6RNmU`0Acn_4}I8R(E<*9P+E)3@Z8I+IZ z?X5eU4VdYi-&{u5TqF1eXnxLweYl`E$sup}Qgykbj>y{FMpMG@%EzOB^H2;^=@rM9 z!R!%y;)(aT*FKzf-?ug<&aZjP~ib5^f+9tvw1X6-YAJ90BwxqPl%~r81oP~8xdTJfwU*xp!m1dCM_rK9u z*A*Gv66tw6WfKR@6xnxU63x7<+{m8ixdp4BS|TJ_dyp*-)JtVP8gBV_@9*Vnw4*ey zqBl3CRhFI!YW07g?wilmvd+#ZB_uMZgOA+>iHI{7piBcwrpj&Nenb841N$TiBM*>f zg|p91+K=FYhK5F4qHZ{jw2ot5AXf$u>4gRRYZdrQU`PX$1U&V9Wbb6zOx|O*%LK;I*PIb;FnkWV`U4hY61iL;Z-!Pgst*<0rqvm&M;nZ9fAcM+ zX%Ad`Tq~qbCWw#TCz*E>jY8|m z9N^RPKMp)D@jpDdkGRPkm3@>7Wb|$3=l5`H*%gLtXAZEA-ZztnBIgjk7 zUW?>|=i1S})t6Ie^VLVwp@-#<`;l6#;9imKV+xWVc5n)>Yks-nCBW&)!ZM}>!o2N>~nypXcgE7)=99C z+wN2bc5>90aiUnfkPC)H8F2`4$Nx02!G=R3Up$GTnj*aBZ@JOt>ds(2!nx3ys=sYN z_}xj>H$sfdyl*aNJ?hIW`RBE)}X-bA|rhY|QaQDE~4!O&+ziHjxVi*Rr9Ks)0YjQ6(){W3l~m=8Pat5V{0{&<9`9)9|x<1X-f$x z|J{*{$@C1lMWDsOWRZ52eF~Nbp{Q6Z0J>~}FX|H8^h8f098}H4L-t(wP+XrOrUK;< zZEVGYMRMf%vR~#&7&?<=k^VkSm-dd1lix$WhGn-_IfYf`8!h|(<+leR__21p%qY;AsZS?Bony&o z-8^80Q*5R(oTKH^S#MbtB zSk#%T!TRYYVcnKrF>-S~jM9=)y0Lie?=D+L@8=clIA!uQ(i7?=^CbDTj3TlaJLQ$+ z+|Iq|v3qz#mSh-Dx7Xf<=de9n;coL3HE_o6Xg8R5Q*jXTNAlGqiGELVhNzMi7=_1= z3h^&S8po?h5bk#k;wlFtT<}fC;v^D;a}pRtwsS&cFvNav*8)4N4qQknP_b1KEkDJ| zeNSyl+6SZS2SE$D`!$7mESM1pTPEcfR)pd3UF(cI5A)p*rf#KzGI`AjGg~ZAAh{Oo zaAW7(+67xpj3{B-%c8$mN6b7*B5bO7DZn`M_)pL?~2U(fe}iPee`W8yhK|^4P+QZUR4>N zu0}B8X0x<=(O)ajwhO@#7u$q5cgOceK!hM3WRK(CTlt%P2S_AS+Z!)a%7-J+D)~Iw@?X ztknOMdW8p3D+5}1T`TdbFz(NGce;4~r%2JQ9KzE3lwGKpt2$kN^>JrcIZ3+HnemQE zH2&yHxH;x%Um;bIH&efm8k40Zq~nc6P;Ku^dBN9k3ZB?yuxOi=b+qHkhKn#Ey5@%u zOY!5R!4K+Qkh{r!Q_{VQ(lz~q*tnB?7uVeb+G#~DAXI-37%}Pt>$Ao2fSY2R!&J~c zE=3mslyI<8C~<_PzNbk2h$pc1X9PpX-sr{{@*?nlu#)JsES<^tZ#WbE83f3{p6t>h zAsU{`qhN`lTQFu{Wz~*ezd+bxlcL!&^0`;9^vKzlhVN)6;LOav>{Z2Xv2h~9b@PZ6#gu$WAVDY ze};f_H)C$-+25=UZU4uGCC$M3C2sW>bR8D_o4F29BGqnOD})KWIjNzhR7kadXeAUg z_}^Wb8>1HAuqB* -Minotaur 是一个基于 Golang 1.20 编写的服务端开发支持库,其中采用了大量泛型设计,主要被用于游戏服务器开发,但由于拥有大量通用的功能,也常被用于 WEB 开发。 +![go version](https://img.shields.io/github/go-mod/go-version/kercylan98/minotaur?logo=go&style=flat) +![tag](https://img.shields.io/github/v/tag/kercylan98/minotaur?logo=github&style=flat) +![views](https://komarev.com/ghpvc/?username=kercylan98&color=blue&style=flat) + +![email](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat&logo=gmail&link=mailto:kercylan@gmail.com) +![qq-group](https://img.shields.io/badge/QQ%20Group-758219443-green.svg?style=flat&logo=tencent-qq&link=https://qm.qq.com/cgi-bin/qm/qr?k=WzRWJIDLzuJbH6-VjdFiTCd1_qA_Ug-D&jump_from=webapi&authKey=ktLEw3XyY9yO+i9rPbI6Fk0UA0uEhACcUidOFdblaiToZtbHcXyU7sFb31FEc9JJ&noverify=0) +![telegram](https://img.shields.io/badge/Telegram-ziv__siren-green.svg?style=flat&logo=telegram&link=https://telegram.me/ziv_siren) + +> - 这是支持快速搭建多功能游戏服务器及 HTTP 服务器的 `Golang` 服务端框架; +> - 网络传输基于 [`gorilla/websocket`](https://github.com/gorilla/websocket)、[`gin-gonic/gin`](https://github.com/gin-gonic/gin)、[`grpc/grpc-go`](https://github.com/grpc/grpc-go)、[`panjf2000/gnet`](https://github.com/panjf2000/gnet)、[`xtaci/kcp-go`](https://github.com/xtaci/kcp-go) 构建; +> - 该项目的目标是提供一个简单、高效、可扩展的游戏服务器框架,让开发者可以专注于游戏逻辑的开发,而不用花费大量时间在网络传输、配置导表、日志、监控等基础功能的开发上; + +*** +在 Minotaur 中不包括任何跨服实现,但支持基于多级路由器快速实现跨服功能。推荐使用 [`NATS.io`](https://nats.io/) 作为跨服消息中间件。 + - 目前已实践的弹幕游戏项目以 `NATS.io` 作为消息队列,实现了跨服、埋点日志收集等功能,部署在 `Kubernetes` 集群中; + - 该项目客户端与服务端采用 `WebSocket` 进行通讯,服务端暴露 `HTTP` 接口接收互动数据消息回调,通过负载均衡器进入 `Kubernetes` 集群中的 `Minotaur` 服务,最终通过 `NATS.io` 消息队列转发至对应所在的 `Pod` 中进行处理; + +

+关于 Pod 配置参数及非极限压测数据 + +> 本次压测 `Pod` 扩容数量为 1,但由于压测连接是最开始就建立好的,所以该扩容的 `Pod` 并没有接受到压力。 +> 理论上来说该 `Pod` 也应该接受 `HTTP` 回调压力,实测过程中,这个扩容的 `Pod` 没有接受到任何压力 + +**Pod 配置参数** + +![pod](.github/images/pod.png) + +**压测结果** + +![压测数据](.github/images/yc1.png) +![压测数据](.github/images/yc2.png) + +**监控数据** + +![事件](./.github/images/yc-event.png) +![CPU](./.github/images/yc-cpu.png) +![内存](./.github/images/yc-memory.png) + +
+ +*** ## 特色内容 ```mermaid diff --git a/server/README.md b/server/README.md index bb0d216f..25df78f9 100644 --- a/server/README.md +++ b/server/README.md @@ -47,6 +47,8 @@ server 提供了包含多种网络类型的服务器实现 |[WithWebsocketMessageType](#WithWebsocketMessageType)|设置仅支持特定类型的Websocket消息 |[WithPProf](#WithPProf)|通过性能分析工具PProf创建服务器 |[New](#New)|根据特定网络类型创建一个服务器 +|[LoadData](#LoadData)|加载绑定的服务器数据 +|[BindData](#BindData)|绑定数据到特定服务器 |[BindService](#BindService)|绑定服务到特定 Server,被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法 @@ -515,6 +517,16 @@ func TestNew(t *testing.T) {
+*** +#### func LoadData\[T any\](srv *Server, name string, data any) T + +> 加载绑定的服务器数据 + +*** +#### func BindData(srv *Server, name string, data any) + +> 绑定数据到特定服务器 + *** #### func BindService(srv *Server, services ...Service) @@ -1340,6 +1352,7 @@ type Server struct { systemSignal chan os.Signal closeChannel chan struct{} multipleRuntimeErrorChan chan error + data map[string]any messageCounter atomic.Int64 addr string network Network @@ -1347,6 +1360,18 @@ type Server struct { services []func() } ``` + + +#### func (*Server) LoadData(name string, data any) any +> 加载绑定的服务器数据 + +*** + + +#### func (*Server) BindData(name string, data any) +> 绑定数据到特定服务器 + +*** #### func (*Server) Run(addr string) (err error) diff --git a/utils/collection/README.md b/utils/collection/README.md index 4c5f5667..64f38b01 100644 --- a/utils/collection/README.md +++ b/utils/collection/README.md @@ -22,6 +22,10 @@ collection 定义了各种对于集合操作有用的各种函数 |[CloneMapN](#CloneMapN)|通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map 为 n 个 map |[CloneSlices](#CloneSlices)|对 slices 中的每一项元素进行克隆,最终返回一个新的二维切片 |[CloneMaps](#CloneMaps)|对 maps 中的每一项元素进行克隆,最终返回一个新的 map 切片 +|[EqualSlice](#EqualSlice)|检查两个切片是否相等,当 handler 返回 true 时,表示 slice1 中的某个元素和 slice2 中的某个元素相匹配 +|[EqualComparableSlice](#EqualComparableSlice)|检查两个切片的值是否相同 +|[EqualMap](#EqualMap)|检查两个 map 是否相等,当 handler 返回 true 时,表示 map1 中的某个元素和 map2 中的某个元素相匹配 +|[EqualComparableMap](#EqualComparableMap)|检查两个 map 的值是否相同 |[InSlice](#InSlice)|检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 |[InComparableSlice](#InComparableSlice)|检查 v 是否被包含在 slice 中 |[AllInSlice](#AllInSlice)|检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 @@ -50,6 +54,9 @@ collection 定义了各种对于集合操作有用的各种函数 |[AnyValueInMaps](#AnyValueInMaps)|检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 |[KeyInAllMaps](#KeyInAllMaps)|检查 key 是否被包含在 maps 的每一个元素中 |[AnyKeyInAllMaps](#AnyKeyInAllMaps)|检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 +|[ConvertSliceToBatches](#ConvertSliceToBatches)|将切片 s 转换为分批次的切片,当 batchSize 小于等于 0 或者 s 长度为 0 时,将会返回 nil +|[ConvertMapKeysToBatches](#ConvertMapKeysToBatches)|将映射的键转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil +|[ConvertMapValuesToBatches](#ConvertMapValuesToBatches)|将映射的值转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil |[ConvertSliceToAny](#ConvertSliceToAny)|将切片转换为任意类型的切片 |[ConvertSliceToIndexMap](#ConvertSliceToIndexMap)|将切片转换为索引为键的映射 |[ConvertSliceToIndexOnlyMap](#ConvertSliceToIndexOnlyMap)|将切片转换为索引为键的映射 @@ -99,6 +106,17 @@ collection 定义了各种对于集合操作有用的各种函数 |[FindMin2MaxFromComparableMap](#FindMin2MaxFromComparableMap)|获取 map 中的最小值和最大值 |[FindMin2MaxFromMap](#FindMin2MaxFromMap)|获取 map 中的最小值和最大值 |[SwapSlice](#SwapSlice)|将切片中的两个元素进行交换 +|[LoopSlice](#LoopSlice)|迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +|[ReverseLoopSlice](#ReverseLoopSlice)|逆序迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +|[LoopMap](#LoopMap)|迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByOrderedKeyAsc](#LoopMapByOrderedKeyAsc)|按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByOrderedKeyDesc](#LoopMapByOrderedKeyDesc)|按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByOrderedValueAsc](#LoopMapByOrderedValueAsc)|按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByOrderedValueDesc](#LoopMapByOrderedValueDesc)|按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByKeyGetterAsc](#LoopMapByKeyGetterAsc)|按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByValueGetterAsc](#LoopMapByValueGetterAsc)|按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByKeyGetterDesc](#LoopMapByKeyGetterDesc)|按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +|[LoopMapByValueGetterDesc](#LoopMapByValueGetterDesc)|按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 |[MappingFromSlice](#MappingFromSlice)|将切片中的元素进行转换 |[MappingFromMap](#MappingFromMap)|将 map 中的元素进行转换 |[MergeSlices](#MergeSlices)|合并切片 @@ -491,6 +509,214 @@ func TestCloneMaps(t *testing.T) {
+*** +#### func EqualSlice\[S ~[]V, V any\](slice1 S, slice2 S, handler ComparisonHandler[V]) bool + +> 检查两个切片是否相等,当 handler 返回 true 时,表示 slice1 中的某个元素和 slice2 中的某个元素相匹配 +> - 当两个切片的容量不同时,不会影响最终的比较结果 + +**示例代码:** + +```go + +func ExampleEqualSlice() { + s1 := []int{1, 2, 3} + s2 := []int{1} + s3 := []int{1, 2, 3} + fmt.Println(collection.EqualSlice(s1, s2, func(source, target int) bool { + return source == target + })) + fmt.Println(collection.EqualSlice(s1, s3, func(source, target int) bool { + return source == target + })) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestEqualSlice(t *testing.T) { + var cases = []struct { + name string + input []int + inputV []int + expected bool + }{{"TestEqualSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true}, {"TestEqualSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false}, {"TestEqualSlice_EmptySlice", []int{}, []int{}, true}, {"TestEqualSlice_NilSlice", nil, nil, true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualSlice(c.input, c.inputV, func(source, target int) bool { + return source == target + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected") + } + }) + } +} + +``` + + +
+ + +*** +#### func EqualComparableSlice\[S ~[]V, V comparable\](slice1 S, slice2 S) bool + +> 检查两个切片的值是否相同 +> - 当两个切片的容量不同时,不会影响最终的比较结果 + +**示例代码:** + +```go + +func ExampleEqualComparableSlice() { + s1 := []int{1, 2, 3} + s2 := []int{1} + s3 := []int{1, 2, 3} + fmt.Println(collection.EqualComparableSlice(s1, s2)) + fmt.Println(collection.EqualComparableSlice(s1, s3)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestEqualComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + inputV []int + expected bool + }{{"TestEqualComparableSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true}, {"TestEqualComparableSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false}, {"TestEqualComparableSlice_EmptySlice", []int{}, []int{}, true}, {"TestEqualComparableSlice_NilSlice", nil, nil, true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualComparableSlice(c.input, c.inputV) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected") + } + }) + } +} + +``` + + +
+ + +*** +#### func EqualMap\[M ~map[K]V, K comparable, V any\](map1 M, map2 M, handler ComparisonHandler[V]) bool + +> 检查两个 map 是否相等,当 handler 返回 true 时,表示 map1 中的某个元素和 map2 中的某个元素相匹配 +> - 当两个 map 的容量不同时,不会影响最终的比较结果 + +**示例代码:** + +```go + +func ExampleEqualMap() { + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"a": 1} + m3 := map[string]int{"a": 1, "b": 2} + fmt.Println(collection.EqualMap(m1, m2, func(source, target int) bool { + return source == target + })) + fmt.Println(collection.EqualMap(m1, m3, func(source, target int) bool { + return source == target + })) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestEqualMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + inputV map[int]int + expected bool + }{{"TestEqualMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true}, {"TestEqualMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false}, {"TestEqualMap_EmptyMap", map[int]int{}, map[int]int{}, true}, {"TestEqualMap_NilMap", nil, nil, true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualMap(c.input, c.inputV, func(source, target int) bool { + return source == target + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected") + } + }) + } +} + +``` + + +
+ + +*** +#### func EqualComparableMap\[M ~map[K]V, K comparable, V comparable\](map1 M, map2 M) bool + +> 检查两个 map 的值是否相同 +> - 当两个 map 的容量不同时,不会影响最终的比较结果 + +**示例代码:** + +```go + +func ExampleEqualComparableMap() { + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"a": 1} + m3 := map[string]int{"a": 1, "b": 2} + fmt.Println(collection.EqualComparableMap(m1, m2)) + fmt.Println(collection.EqualComparableMap(m1, m3)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestEqualComparableMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + inputV map[int]int + expected bool + }{{"TestEqualComparableMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true}, {"TestEqualComparableMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false}, {"TestEqualComparableMap_EmptyMap", map[int]int{}, map[int]int{}, true}, {"TestEqualComparableMap_NilMap", nil, nil, true}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual = collection.EqualComparableMap(c.input, c.inputV) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected") + } + }) + } +} + +``` + + +
+ + *** #### func InSlice\[S ~[]V, V any\](slice S, v V, handler ComparisonHandler[V]) bool @@ -1814,17 +2040,19 @@ func TestAnyKeyInAllMaps(t *testing.T) { *** -#### func ConvertSliceToAny\[S ~[]V, V any\](s S) []any - -> 将切片转换为任意类型的切片 +#### func ConvertSliceToBatches\[S ~[]V, V any\](s S, batchSize int) []S + +> 将切片 s 转换为分批次的切片,当 batchSize 小于等于 0 或者 s 长度为 0 时,将会返回 nil **示例代码:** ```go -func ExampleConvertSliceToAny() { - result := collection.ConvertSliceToAny([]int{1, 2, 3}) - fmt.Println(reflect.TypeOf(result).String(), len(result)) +func ExampleConvertSliceToBatches() { + result := collection.ConvertSliceToBatches([]int{1, 2, 3}, 2) + for _, v := range result { + fmt.Println(v) + } } ``` @@ -1835,25 +2063,32 @@ func ExampleConvertSliceToAny() { ```go -func TestConvertSliceToAny(t *testing.T) { +func TestConvertSliceToBatches(t *testing.T) { var cases = []struct { name string input []int - expected []interface{} - }{{name: "TestConvertSliceToAny_NonEmpty", input: []int{1, 2, 3}, expected: []any{1, 2, 3}}, {name: "TestConvertSliceToAny_Empty", input: []int{}, expected: []any{}}, {name: "TestConvertSliceToAny_Nil", input: nil, expected: nil}} + batch int + expected [][]int + }{{name: "TestConvertSliceToBatches_NonEmpty", input: []int{1, 2, 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, {name: "TestConvertSliceToBatches_Empty", input: []int{}, batch: 2, expected: nil}, {name: "TestConvertSliceToBatches_Nil", input: nil, batch: 2, expected: nil}, {name: "TestConvertSliceToBatches_NonPositive", input: []int{1, 2, 3}, batch: 0, expected: nil}} for _, c := range cases { t.Run(c.name, func(t *testing.T) { - actual := collection.ConvertSliceToAny(c.input) + actual := collection.ConvertSliceToBatches(c.input, c.batch) if len(actual) != len(c.expected) { t.Errorf("expected: %v, actual: %v", c.expected, actual) } for i := 0; i < len(actual); i++ { av, ev := actual[i], c.expected[i] - if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() { + if len(av) != len(ev) { t.Errorf("expected: %v, actual: %v", c.expected, actual) } - if av != ev { - t.Errorf("expected: %v, actual: %v", c.expected, actual) + for j := 0; j < len(av); j++ { + aj, ej := av[j], ev[j] + if reflect.TypeOf(aj).Kind() != reflect.TypeOf(ej).Kind() { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + if aj != ej { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } } } }) @@ -1867,20 +2102,17 @@ func TestConvertSliceToAny(t *testing.T) { *** -#### func ConvertSliceToIndexMap\[S ~[]V, V any\](s S) map[int]V - -> 将切片转换为索引为键的映射 +#### func ConvertMapKeysToBatches\[M ~map[K]V, K comparable, V any\](m M, batchSize int) [][]K + +> 将映射的键转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil **示例代码:** ```go -func ExampleConvertSliceToIndexMap() { - slice := []int{1, 2, 3} - result := collection.ConvertSliceToIndexMap(slice) - for i, v := range slice { - fmt.Println(result[i], v) - } +func ExampleConvertMapKeysToBatches() { + result := collection.ConvertMapKeysToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2) + fmt.Println(len(result)) } ``` @@ -1891,23 +2123,19 @@ func ExampleConvertSliceToIndexMap() { ```go -func TestConvertSliceToIndexMap(t *testing.T) { +func TestConvertMapKeysToBatches(t *testing.T) { var cases = []struct { name string - input []int - expected map[int]int - }{{name: "TestConvertSliceToIndexMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]int{0: 1, 1: 2, 2: 3}}, {name: "TestConvertSliceToIndexMap_Empty", input: []int{}, expected: map[int]int{}}, {name: "TestConvertSliceToIndexMap_Nil", input: nil, expected: nil}} + input map[int]int + batch int + expected [][]int + }{{name: "TestConvertMapKeysToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, {name: "TestConvertMapKeysToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil}, {name: "TestConvertMapKeysToBatches_Nil", input: nil, batch: 2, expected: nil}, {name: "TestConvertMapKeysToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil}} for _, c := range cases { t.Run(c.name, func(t *testing.T) { - actual := collection.ConvertSliceToIndexMap(c.input) + actual := collection.ConvertMapKeysToBatches(c.input, c.batch) if len(actual) != len(c.expected) { t.Errorf("expected: %v, actual: %v", c.expected, actual) } - for k, v := range actual { - if c.expected[k] != v { - t.Errorf("expected: %v, actual: %v", c.expected, actual) - } - } }) } } @@ -1919,21 +2147,17 @@ func TestConvertSliceToIndexMap(t *testing.T) { *** -#### func ConvertSliceToIndexOnlyMap\[S ~[]V, V any\](s S) map[int]struct {} - -> 将切片转换为索引为键的映射 +#### func ConvertMapValuesToBatches\[M ~map[K]V, K comparable, V any\](m M, batchSize int) [][]V + +> 将映射的值转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil **示例代码:** ```go -func ExampleConvertSliceToIndexOnlyMap() { - slice := []int{1, 2, 3} - result := collection.ConvertSliceToIndexOnlyMap(slice) - expected := map[int]bool{0: true, 1: true, 2: true} - for k := range result { - fmt.Println(expected[k]) - } +func ExampleConvertMapValuesToBatches() { + result := collection.ConvertMapValuesToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2) + fmt.Println(len(result)) } ``` @@ -1944,26 +2168,19 @@ func ExampleConvertSliceToIndexOnlyMap() { ```go -func TestConvertSliceToIndexOnlyMap(t *testing.T) { +func TestConvertMapValuesToBatches(t *testing.T) { var cases = []struct { name string - input []int - expected map[int]struct{} - }{{name: "TestConvertSliceToIndexOnlyMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{0: {}, 1: {}, 2: {}}}, {name: "TestConvertSliceToIndexOnlyMap_Empty", input: []int{}, expected: map[int]struct{}{}}, {name: "TestConvertSliceToIndexOnlyMap_Nil", input: nil, expected: nil}} + input map[int]int + batch int + expected [][]int + }{{name: "TestConvertMapValuesToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}}, {name: "TestConvertMapValuesToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil}, {name: "TestConvertMapValuesToBatches_Nil", input: nil, batch: 2, expected: nil}, {name: "TestConvertMapValuesToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil}} for _, c := range cases { t.Run(c.name, func(t *testing.T) { - actual := collection.ConvertSliceToIndexOnlyMap(c.input) + actual := collection.ConvertMapValuesToBatches(c.input, c.batch) if len(actual) != len(c.expected) { t.Errorf("expected: %v, actual: %v", c.expected, actual) } - for k, v := range actual { - if _, ok := c.expected[k]; !ok { - t.Errorf("expected: %v, actual: %v", c.expected, actual) - } - if v != struct{}{} { - t.Errorf("expected: %v, actual: %v", c.expected, actual) - } - } }) } } @@ -1975,18 +2192,17 @@ func TestConvertSliceToIndexOnlyMap(t *testing.T) { *** -#### func ConvertSliceToMap\[S ~[]V, V comparable\](s S) map[V]struct {} - -> 将切片转换为值为键的映射 +#### func ConvertSliceToAny\[S ~[]V, V any\](s S) []any + +> 将切片转换为任意类型的切片 **示例代码:** ```go -func ExampleConvertSliceToMap() { - slice := []int{1, 2, 3} - result := collection.ConvertSliceToMap(slice) - fmt.Println(collection.AllKeyInMap(result, slice...)) +func ExampleConvertSliceToAny() { + result := collection.ConvertSliceToAny([]int{1, 2, 3}) + fmt.Println(reflect.TypeOf(result).String(), len(result)) } ``` @@ -1997,23 +2213,24 @@ func ExampleConvertSliceToMap() { ```go -func TestConvertSliceToMap(t *testing.T) { +func TestConvertSliceToAny(t *testing.T) { var cases = []struct { name string input []int - expected map[int]struct{} - }{{name: "TestConvertSliceToMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{1: {}, 2: {}, 3: {}}}, {name: "TestConvertSliceToMap_Empty", input: []int{}, expected: map[int]struct{}{}}, {name: "TestConvertSliceToMap_Nil", input: nil, expected: nil}} + expected []interface{} + }{{name: "TestConvertSliceToAny_NonEmpty", input: []int{1, 2, 3}, expected: []any{1, 2, 3}}, {name: "TestConvertSliceToAny_Empty", input: []int{}, expected: []any{}}, {name: "TestConvertSliceToAny_Nil", input: nil, expected: nil}} for _, c := range cases { t.Run(c.name, func(t *testing.T) { - actual := collection.ConvertSliceToMap(c.input) + actual := collection.ConvertSliceToAny(c.input) if len(actual) != len(c.expected) { t.Errorf("expected: %v, actual: %v", c.expected, actual) } - for k, v := range actual { - if _, ok := c.expected[k]; !ok { + for i := 0; i < len(actual); i++ { + av, ev := actual[i], c.expected[i] + if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() { t.Errorf("expected: %v, actual: %v", c.expected, actual) } - if v != struct{}{} { + if av != ev { t.Errorf("expected: %v, actual: %v", c.expected, actual) } } @@ -2028,15 +2245,176 @@ func TestConvertSliceToMap(t *testing.T) { *** -#### func ConvertSliceToBoolMap\[S ~[]V, V comparable\](s S) map[V]bool - -> 将切片转换为值为键的映射 +#### func ConvertSliceToIndexMap\[S ~[]V, V any\](s S) map[int]V + +> 将切片转换为索引为键的映射 **示例代码:** ```go -func ExampleConvertSliceToBoolMap() { +func ExampleConvertSliceToIndexMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToIndexMap(slice) + for i, v := range slice { + fmt.Println(result[i], v) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestConvertSliceToIndexMap(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]int + }{{name: "TestConvertSliceToIndexMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]int{0: 1, 1: 2, 2: 3}}, {name: "TestConvertSliceToIndexMap_Empty", input: []int{}, expected: map[int]int{}}, {name: "TestConvertSliceToIndexMap_Nil", input: nil, expected: nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertSliceToIndexMap(c.input) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for k, v := range actual { + if c.expected[k] != v { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func ConvertSliceToIndexOnlyMap\[S ~[]V, V any\](s S) map[int]struct {} + +> 将切片转换为索引为键的映射 + +**示例代码:** + +```go + +func ExampleConvertSliceToIndexOnlyMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToIndexOnlyMap(slice) + expected := map[int]bool{0: true, 1: true, 2: true} + for k := range result { + fmt.Println(expected[k]) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestConvertSliceToIndexOnlyMap(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]struct{} + }{{name: "TestConvertSliceToIndexOnlyMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{0: {}, 1: {}, 2: {}}}, {name: "TestConvertSliceToIndexOnlyMap_Empty", input: []int{}, expected: map[int]struct{}{}}, {name: "TestConvertSliceToIndexOnlyMap_Nil", input: nil, expected: nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertSliceToIndexOnlyMap(c.input) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for k, v := range actual { + if _, ok := c.expected[k]; !ok { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + if v != struct{}{} { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func ConvertSliceToMap\[S ~[]V, V comparable\](s S) map[V]struct {} + +> 将切片转换为值为键的映射 + +**示例代码:** + +```go + +func ExampleConvertSliceToMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToMap(slice) + fmt.Println(collection.AllKeyInMap(result, slice...)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestConvertSliceToMap(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]struct{} + }{{name: "TestConvertSliceToMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{1: {}, 2: {}, 3: {}}}, {name: "TestConvertSliceToMap_Empty", input: []int{}, expected: map[int]struct{}{}}, {name: "TestConvertSliceToMap_Nil", input: nil, expected: nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertSliceToMap(c.input) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for k, v := range actual { + if _, ok := c.expected[k]; !ok { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + if v != struct{}{} { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func ConvertSliceToBoolMap\[S ~[]V, V comparable\](s S) map[V]bool + +> 将切片转换为值为键的映射 + +**示例代码:** + +```go + +func ExampleConvertSliceToBoolMap() { slice := []int{1, 2, 3} result := collection.ConvertSliceToBoolMap(slice) for _, v := range slice { @@ -2090,6 +2468,7 @@ func TestConvertSliceToBoolMap(t *testing.T) { func ExampleConvertMapKeysToSlice() { result := collection.ConvertMapKeysToSlice(map[int]int{1: 1, 2: 2, 3: 3}) + sort.Ints(result) for i, v := range result { fmt.Println(i, v) } @@ -4273,6 +4652,669 @@ func TestSwapSlice(t *testing.T) {
+*** +#### func LoopSlice\[S ~[]V, V any\](slice S, f func (i int, val V) bool) + +> 迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopSlice() { + var result []int + collection.LoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool { + result = append(result, val) + if uint(i) == 1 { + return false + } + return true + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopSlice(t *testing.T) { + var cases = []struct { + name string + in []int + out []int + breakIndex uint + }{{"TestLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{1, 2}, 2}, {"TestLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}, 0}, {"TestLoopSlice_Empty", []int{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopSlice(c.in, func(i int, val int) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopSlice(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func ReverseLoopSlice\[S ~[]V, V any\](slice S, f func (i int, val V) bool) + +> 逆序迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleReverseLoopSlice() { + var result []int + collection.ReverseLoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool { + result = append(result, val) + if uint(i) == 1 { + return false + } + return true + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestReverseLoopSlice(t *testing.T) { + var cases = []struct { + name string + in []int + out []int + breakIndex uint + }{{"TestReverseLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{5, 4}, 2}, {"TestReverseLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}, 0}, {"TestReverseLoopSlice_Empty", []int{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.ReverseLoopSlice(c.in, func(i int, val int) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == uint(len(c.in))-c.breakIndex { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("ReverseLoopSlice(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMap\[M ~map[K]V, K comparable, V any\](m M, f func (i int, key K, val V) bool) + +> 迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - m 的迭代顺序是不确定的,因此每次迭代的顺序可能不同 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMap() { + var result []int + collection.LoopMap(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMap(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out map[int]string + breakIndex uint + }{{"TestLoopMap_Part", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2"}, 2}, {"TestLoopMap_All", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2", 3: "3"}, 0}, {"TestLoopMap_Empty", map[int]string{}, map[int]string{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result = make(map[int]string) + collection.LoopMap(c.in, func(i int, key int, val string) bool { + result[key] = val + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableMap(result, c.out) { + t.Errorf("LoopMap(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByOrderedKeyAsc\[M ~map[K]V, K generic.Ordered, V any\](m M, f func (i int, key K, val V) bool) + +> 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByOrderedKeyAsc() { + var result []int + collection.LoopMapByOrderedKeyAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByOrderedKeyAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{{"TestLoopMapByOrderedKeyAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2}, {"TestLoopMapByOrderedKeyAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0}, {"TestLoopMapByOrderedKeyAsc_Empty", map[int]string{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByOrderedKeyAsc(c.in, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedKeyAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByOrderedKeyDesc\[M ~map[K]V, K generic.Ordered, V any\](m M, f func (i int, key K, val V) bool) + +> 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByOrderedKeyDesc() { + var result []int + collection.LoopMapByOrderedKeyDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByOrderedKeyDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{{"TestLoopMapByOrderedKeyDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2}, {"TestLoopMapByOrderedKeyDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0}, {"TestLoopMapByOrderedKeyDesc_Empty", map[int]string{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByOrderedKeyDesc(c.in, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedKeyDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByOrderedValueAsc\[M ~map[K]V, K comparable, V generic.Ordered\](m M, f func (i int, key K, val V) bool) + +> 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByOrderedValueAsc() { + var result []int + collection.LoopMapByOrderedValueAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByOrderedValueAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{{"TestLoopMapByOrderedValueAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2}, {"TestLoopMapByOrderedValueAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0}, {"TestLoopMapByOrderedValueAsc_Empty", map[int]string{}, []string{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByOrderedValueAsc(c.in, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedValueAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByOrderedValueDesc\[M ~map[K]V, K comparable, V generic.Ordered\](m M, f func (i int, key K, val V) bool) + +> 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByOrderedValueDesc() { + var result []int + collection.LoopMapByOrderedValueDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByOrderedValueDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{{"TestLoopMapByOrderedValueDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2}, {"TestLoopMapByOrderedValueDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0}, {"TestLoopMapByOrderedValueDesc_Empty", map[int]string{}, []string{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByOrderedValueDesc(c.in, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByOrderedValueDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByKeyGetterAsc\[M ~map[K]V, K comparable, V comparable, N generic.Ordered\](m M, getter func (k K) N, f func (i int, key K, val V) bool) + +> 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByKeyGetterAsc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByKeyGetterAsc(m, func(k string) int { + return m[k] + }, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByKeyGetterAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{{"TestLoopMapByKeyGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2}, {"TestLoopMapByKeyGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0}, {"TestLoopMapByKeyGetterAsc_Empty", map[int]string{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByKeyGetterAsc(c.in, func(key int) int { + return key + }, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByKeyGetterAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByValueGetterAsc\[M ~map[K]V, K comparable, V any, N generic.Ordered\](m M, getter func (v V) N, f func (i int, key K, val V) bool) + +> 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByValueGetterAsc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByValueGetterAsc(m, func(v int) int { + return v + }, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByValueGetterAsc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{{"TestLoopMapByValueGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2}, {"TestLoopMapByValueGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0}, {"TestLoopMapByValueGetterAsc_Empty", map[int]string{}, []string{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByValueGetterAsc(c.in, func(val string) string { + return val + }, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByValueGetterAsc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByKeyGetterDesc\[M ~map[K]V, K comparable, V comparable, N generic.Ordered\](m M, getter func (k K) N, f func (i int, key K, val V) bool) + +> 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByKeyGetterDesc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByKeyGetterDesc(m, func(k string) int { + return m[k] + }, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByKeyGetterDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []int + breakIndex uint + }{{"TestLoopMapByKeyGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2}, {"TestLoopMapByKeyGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0}, {"TestLoopMapByKeyGetterDesc_Empty", map[int]string{}, []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []int + collection.LoopMapByKeyGetterDesc(c.in, func(key int) int { + return key + }, func(i int, key int, val string) bool { + result = append(result, key) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByKeyGetterDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + +*** +#### func LoopMapByValueGetterDesc\[M ~map[K]V, K comparable, V any, N generic.Ordered\](m M, getter func (v V) N, f func (i int, key K, val V) bool) + +> 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数 +> - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数 +> - 迭代过程将在 f 函数返回 false 时中断 + +**示例代码:** + +```go + +func ExampleLoopMapByValueGetterDesc() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result []int + collection.LoopMapByValueGetterDesc(m, func(v int) int { + return v + }, func(i int, key string, val int) bool { + result = append(result, val) + return true + }) + fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestLoopMapByValueGetterDesc(t *testing.T) { + var cases = []struct { + name string + in map[int]string + out []string + breakIndex uint + }{{"TestLoopMapByValueGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2}, {"TestLoopMapByValueGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0}, {"TestLoopMapByValueGetterDesc_Empty", map[int]string{}, []string{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var result []string + collection.LoopMapByValueGetterDesc(c.in, func(val string) string { + return val + }, func(i int, key int, val string) bool { + result = append(result, val) + if c.breakIndex != 0 && uint(i) == c.breakIndex-1 { + return false + } + return true + }) + if !collection.EqualComparableSlice(result, c.out) { + t.Errorf("LoopMapByValueGetterDesc(%v) got %v, want %v", c.in, result, c.out) + } + }) + } +} + +``` + + +
+ + *** #### func MappingFromSlice\[S ~[]V, NS []N, V any, N any\](slice S, handler func (value V) N) NS diff --git a/utils/huge/README.md b/utils/huge/README.md index c79ece4c..627fcaa9 100644 --- a/utils/huge/README.md +++ b/utils/huge/README.md @@ -18,8 +18,7 @@ |:--|:-- |[NewFloat](#NewFloat)|创建一个 Float |[NewFloatByString](#NewFloatByString)|通过字符串创建一个 Float -|[NewInt](#NewInt)|创建一个 Int -|[NewIntByString](#NewIntByString)|通过字符串创建一个 Int +|[NewInt](#NewInt)|创建一个 Int 对象,该对象的值为 x > 类型定义 @@ -45,15 +44,88 @@ > - 如果字符串不是一个合法的数字,则返回 0 *** -#### func NewInt\[T generic.Number\](x T) *Int +#### func NewInt\[T generic.Basic\](x T) *Int -> 创建一个 Int +> 创建一个 Int 对象,该对象的值为 x + +**示例代码:** + +该案例展示了 NewInt 对各种基本类型的支持及用法 + + +```go + +func ExampleNewInt() { + fmt.Println(huge.NewInt("12345678900000000")) + fmt.Println(huge.NewInt(1234567890)) + fmt.Println(huge.NewInt(true)) + fmt.Println(huge.NewInt(123.123)) + fmt.Println(huge.NewInt(byte(1))) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewInt(t *testing.T) { + var cases = []struct { + name string + nil bool + in int64 + mul int64 + want string + }{{name: "TestNewIntNegative", in: -1, want: "-1"}, {name: "TestNewIntZero", in: 0, want: "0"}, {name: "TestNewIntPositive", in: 1, want: "1"}, {name: "TestNewIntMax", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestNewIntMin", in: -9223372036854775808, want: "-9223372036854775808"}, {name: "TestNewIntMulNegative", in: -9223372036854775808, mul: 10000000, want: "-92233720368547758080000000"}, {name: "TestNewIntMulPositive", in: 9223372036854775807, mul: 10000000, want: "92233720368547758070000000"}, {name: "TestNewIntNil", nil: true, want: "0"}, {name: "TestNewIntNilMul", nil: true, mul: 10000000, want: "0"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var got *huge.Int + switch { + case c.nil: + if c.mul > 0 { + got = huge.NewInt(0).MulInt64(c.mul) + } + case c.mul == 0: + got = huge.NewInt(c.in) + default: + got = huge.NewInt(c.in).MulInt64(c.mul) + } + if s := got.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, got.String()) + } else { + t.Log(s) + } + }) + } + t.Run("TestNewIntFromString", func(t *testing.T) { + if got := huge.NewInt("1234567890123456789012345678901234567890"); got.String() != "1234567890123456789012345678901234567890" { + t.Fatalf("want: %s, got: %s", "1234567890123456789012345678901234567890", got.String()) + } + }) + t.Run("TestNewIntFromInt", func(t *testing.T) { + if got := huge.NewInt(1234567890); got.String() != "1234567890" { + t.Fatalf("want: %s, got: %s", "1234567890", got.String()) + } + }) + t.Run("TestNewIntFromBool", func(t *testing.T) { + if got := huge.NewInt(true); got.String() != "1" { + t.Fatalf("want: %s, got: %s", "1", got.String()) + } + }) + t.Run("TestNewIntFromFloat", func(t *testing.T) { + if got := huge.NewInt(1234567890.1234567890); got.String() != "1234567890" { + t.Fatalf("want: %s, got: %s", "1234567890", got.String()) + } + }) +} + +``` + + +
-*** -#### func NewIntByString(i string) *Int - -> 通过字符串创建一个 Int -> - 如果字符串不是一个合法的数字,则返回 0 *** @@ -190,71 +262,689 @@ type Int big.Int #### func (*Int) Copy() *Int +> 拷贝当前 Int 对象 + +**示例代码:** + +```go + +func ExampleInt_Copy() { + var a = huge.NewInt(1234567890) + var b = a.Copy().SetInt64(9876543210) + fmt.Println(a) + fmt.Println(b) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestInt_Copy(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{{name: "TestIntCopyNegative", in: -1, want: "-1"}, {name: "TestIntCopyZero", in: 0, want: "0"}, {name: "TestIntCopyPositive", in: 1, want: "1"}, {name: "TestIntCopyMax", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntCopyMin", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in = huge.NewInt(c.in) + var got = in.Copy() + if in.Int64() != c.in { + t.Fatalf("want: %d, got: %d", c.in, in.Int64()) + } + if s := got.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, got.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) Set(i *Int) *Int +> 设置当前 Int 对象的值为 i + +**示例代码:** + +```go + +func ExampleInt_Set() { + var a = huge.NewInt(1234567890) + var b = huge.NewInt(9876543210) + fmt.Println(a) + a.Set(b) + fmt.Println(a) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestInt_Set(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{{name: "TestIntSetNegative", in: -1, want: "-1"}, {name: "TestIntSetZero", in: 0, want: "0"}, {name: "TestIntSetPositive", in: 1, want: "1"}, {name: "TestIntSetMax", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntSetMin", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.Set(huge.NewInt(c.in)) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Int) SetString(i string) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetString(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{{name: "TestIntSetStringNegative", in: "-1", want: "-1"}, {name: "TestIntSetStringZero", in: "0", want: "0"}, {name: "TestIntSetStringPositive", in: "1", want: "1"}, {name: "TestIntSetStringMax", in: "9223372036854775807", want: "9223372036854775807"}, {name: "TestIntSetStringMin", in: "-9223372036854775808", want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetString(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetInt(i int) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetInt(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{{name: "TestIntSetIntNegative", in: -1, want: "-1"}, {name: "TestIntSetIntZero", in: 0, want: "0"}, {name: "TestIntSetIntPositive", in: 1, want: "1"}, {name: "TestIntSetIntMax", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntSetIntMin", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetInt8(i int8) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetInt8(t *testing.T) { + var cases = []struct { + name string + in int8 + want string + }{{name: "TestIntSetInt8Negative", in: -1, want: "-1"}, {name: "TestIntSetInt8Zero", in: 0, want: "0"}, {name: "TestIntSetInt8Positive", in: 1, want: "1"}, {name: "TestIntSetInt8Max", in: 127, want: "127"}, {name: "TestIntSetInt8Min", in: -128, want: "-128"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt8(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetInt16(i int16) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetInt16(t *testing.T) { + var cases = []struct { + name string + in int16 + want string + }{{name: "TestIntSetInt16Negative", in: -1, want: "-1"}, {name: "TestIntSetInt16Zero", in: 0, want: "0"}, {name: "TestIntSetInt16Positive", in: 1, want: "1"}, {name: "TestIntSetInt16Max", in: 32767, want: "32767"}, {name: "TestIntSetInt16Min", in: -32768, want: "-32768"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt16(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetInt32(i int32) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetInt32(t *testing.T) { + var cases = []struct { + name string + in int32 + want string + }{{name: "TestIntSetInt32Negative", in: -1, want: "-1"}, {name: "TestIntSetInt32Zero", in: 0, want: "0"}, {name: "TestIntSetInt32Positive", in: 1, want: "1"}, {name: "TestIntSetInt32Max", in: 2147483647, want: "2147483647"}, {name: "TestIntSetInt32Min", in: -2147483648, want: "-2147483648"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetInt64(i int64) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetInt64(t *testing.T) { + var cases = []struct { + name string + in int64 + want string + }{{name: "TestIntSetInt64Negative", in: -1, want: "-1"}, {name: "TestIntSetInt64Zero", in: 0, want: "0"}, {name: "TestIntSetInt64Positive", in: 1, want: "1"}, {name: "TestIntSetInt64Max", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntSetInt64Min", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetInt64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetUint(i uint) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetUint(t *testing.T) { + var cases = []struct { + name string + in uint64 + want string + }{{name: "TestIntSetUintNegative", in: 0, want: "0"}, {name: "TestIntSetUintZero", in: 0, want: "0"}, {name: "TestIntSetUintPositive", in: 1, want: "1"}, {name: "TestIntSetUintMax", in: 18446744073709551615, want: "18446744073709551615"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetUint8(i uint8) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetUint8(t *testing.T) { + var cases = []struct { + name string + in uint8 + want string + }{{name: "TestIntSetUint8Negative", in: 0, want: "0"}, {name: "TestIntSetUint8Zero", in: 0, want: "0"}, {name: "TestIntSetUint8Positive", in: 1, want: "1"}, {name: "TestIntSetUint8Max", in: 255, want: "255"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint8(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetUint16(i uint16) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetUint16(t *testing.T) { + var cases = []struct { + name string + in uint16 + want string + }{{name: "TestIntSetUint16Negative", in: 0, want: "0"}, {name: "TestIntSetUint16Zero", in: 0, want: "0"}, {name: "TestIntSetUint16Positive", in: 1, want: "1"}, {name: "TestIntSetUint16Max", in: 65535, want: "65535"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint16(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetUint32(i uint32) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetUint32(t *testing.T) { + var cases = []struct { + name string + in uint32 + want string + }{{name: "TestIntSetUint32Negative", in: 0, want: "0"}, {name: "TestIntSetUint32Zero", in: 0, want: "0"}, {name: "TestIntSetUint32Positive", in: 1, want: "1"}, {name: "TestIntSetUint32Max", in: 4294967295, want: "4294967295"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) SetUint64(i uint64) *Int +> 设置当前 Int 对象的值为 i + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetUint64(t *testing.T) { + var cases = []struct { + name string + in uint64 + want string + }{{name: "TestIntSetUint64Negative", in: 0, want: "0"}, {name: "TestIntSetUint64Zero", in: 0, want: "0"}, {name: "TestIntSetUint64Positive", in: 1, want: "1"}, {name: "TestIntSetUint64Max", in: 18446744073709551615, want: "18446744073709551615"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetUint64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Int) SetFloat32(i float32) *Int +> 设置当前 Int 对象的值为 i 向下取整后的值 + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetFloat32(t *testing.T) { + var cases = []struct { + name string + in float32 + want string + }{{name: "TestIntSetFloat32Negative", in: -1.1, want: "-1"}, {name: "TestIntSetFloat32Zero", in: 0, want: "0"}, {name: "TestIntSetFloat32Positive", in: 1.1, want: "1"}, {name: "TestIntSetFloat32Max", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntSetFloat32Min", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetFloat32(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Int) SetFloat64(i float64) *Int +> 设置当前 Int 对象的值为 i 向下取整后的值 + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetFloat64(t *testing.T) { + var cases = []struct { + name string + in float64 + want string + }{{name: "TestIntSetFloat64Negative", in: -1.1, want: "-1"}, {name: "TestIntSetFloat64Zero", in: 0, want: "0"}, {name: "TestIntSetFloat64Positive", in: 1.1, want: "1"}, {name: "TestIntSetFloat64Max", in: 9223372036854775807, want: "9223372036854775807"}, {name: "TestIntSetFloat64Min", in: -9223372036854775808, want: "-9223372036854775808"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetFloat64(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ + +*** + + +#### func (*Int) SetBool(i bool) *Int +> 设置当前 Int 对象的值为 i,当 i 为 true 时,值为 1,当 i 为 false 时,值为 0 + +
+查看 / 收起单元测试 + + +```go + +func TestInt_SetBool(t *testing.T) { + var cases = []struct { + name string + in bool + want string + }{{name: "TestIntSetBoolFalse", in: false, want: "0"}, {name: "TestIntSetBoolTrue", in: true, want: "1"}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var in *huge.Int + in = in.SetBool(c.in) + if s := in.String(); s != c.want { + t.Fatalf("want: %s, got: %s", c.want, in.String()) + } else { + t.Log(s) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) IsZero() bool +> 判断当前 Int 对象的值是否为 0 + +
+查看 / 收起单元测试 + + +```go + +func TestInt_IsZero(t *testing.T) { + var cases = []struct { + name string + in int64 + want bool + }{{name: "TestIntIsZeroNegative", in: -1, want: false}, {name: "TestIntIsZeroZero", in: 0, want: true}, {name: "TestIntIsZeroPositive", in: 1, want: false}, {name: "TestIntIsZeroMax", in: 9223372036854775807, want: false}, {name: "TestIntIsZeroMin", in: -9223372036854775808, want: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := huge.NewInt(c.in).IsZero(); got != c.want { + t.Fatalf("want: %t, got: %t", c.want, got) + } + }) + } +} + +``` + + +
+ *** #### func (*Int) ToBigint() *big.Int +> 转换为 *big.Int + +
+查看 / 收起单元测试 + + +```go + +func TestInt_ToBigint(t *testing.T) { + var cases = []struct { + name string + in int64 + want *big.Int + }{{name: "TestIntToBigintNegative", in: -1, want: big.NewInt(-1)}, {name: "TestIntToBigintZero", in: 0, want: big.NewInt(0)}, {name: "TestIntToBigintPositive", in: 1, want: big.NewInt(1)}, {name: "TestIntToBigintMax", in: 9223372036854775807, want: big.NewInt(9223372036854775807)}, {name: "TestIntToBigintMin", in: -9223372036854775808, want: big.NewInt(-9223372036854775808)}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := huge.NewInt(c.in).ToBigint(); got.Cmp(c.want) != 0 { + t.Fatalf("want: %s, got: %s", c.want.String(), got.String()) + } + }) + } +} + +``` + + +
+ *** @@ -266,46 +956,49 @@ type Int big.Int #### func (*Int) GreaterThan(i *Int) bool -> 大于 +> 检查 slf 是否大于 i *** #### func (*Int) GreaterThanOrEqualTo(i *Int) bool -> 大于或等于 +> 检查 slf 是否大于或等于 i *** #### func (*Int) LessThan(i *Int) bool -> 小于 +> 检查 slf 是否小于 i *** #### func (*Int) LessThanOrEqualTo(i *Int) bool -> 小于或等于 +> 检查 slf 是否小于或等于 i *** #### func (*Int) EqualTo(i *Int) bool -> 等于 +> 检查 slf 是否等于 i *** #### func (*Int) Int64() int64 +> 转换为 int64 类型进行返回 *** #### func (*Int) String() string +> 转换为 string 类型进行返回 *** #### func (*Int) Add(i *Int) *Int +> 使用 i 对 slf 进行加法运算,slf 的值会变为运算后的值。返回 slf *** diff --git a/utils/super/README.md b/utils/super/README.md index b0f1093f..8b408324 100644 --- a/utils/super/README.md +++ b/utils/super/README.md @@ -19,6 +19,8 @@ |[NewBitSet](#NewBitSet)|通过指定的 Bit 位创建一个 BitSet |[TryWriteChannel](#TryWriteChannel)|尝试写入 channel,如果 channel 无法写入则忽略,返回是否写入成功 |[TryWriteChannelByHandler](#TryWriteChannelByHandler)|尝试写入 channel,如果 channel 无法写入则执行 handler +|[TryReadChannel](#TryReadChannel)|尝试读取 channel,如果 channel 无法读取则忽略,返回是否读取成功 +|[TryReadChannelByHandler](#TryReadChannelByHandler)|尝试读取 channel,如果 channel 无法读取则执行 handler |[RegError](#RegError)|通过错误码注册错误,返回错误的引用 |[RegErrorRef](#RegErrorRef)|通过错误码注册错误,返回错误的引用 |[GetError](#GetError)|通过错误引用获取错误码和真实错误信息,如果错误不存在则返回 0,如果错误引用不存在则返回原本的错误 @@ -107,6 +109,50 @@ #### func NewBitSet\[Bit generic.Integer\](bits ...Bit) *BitSet[Bit] > 通过指定的 Bit 位创建一个 BitSet +> - 当指定的 Bit 位存在负数时,将会 panic + +**示例代码:** + +```go + +func ExampleNewBitSet() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Set(10) + fmt.Println(bs.Bits()) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewBitSet(t *testing.T) { + var cases = []struct { + name string + in []int + shouldPanic bool + }{{name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, {name: "empty", in: make([]int, 0)}, {name: "nil", in: nil}, {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet(c.in...) + t.Log(bs) + }) + } +} + +``` + + +
+ *** #### func TryWriteChannel\[T any\](ch chan T, data T) bool @@ -120,6 +166,18 @@ > 尝试写入 channel,如果 channel 无法写入则执行 handler > - 无法写入的情况包括:channel 已满、channel 已关闭 +*** +#### func TryReadChannel\[T any\](ch chan T) (v T, suc bool) + +> 尝试读取 channel,如果 channel 无法读取则忽略,返回是否读取成功 +> - 无法读取的情况包括:channel 已空、channel 已关闭 + +*** +#### func TryReadChannelByHandler\[T any\](ch chan T, handler func (ch chan T) T) (v T) + +> 尝试读取 channel,如果 channel 无法读取则执行 handler +> - 无法读取的情况包括:channel 已空、channel 已关闭 + *** #### func RegError(code int, message string) error @@ -757,6 +815,18 @@ type BitSet[Bit generic.Integer] struct { #### func (*BitSet) Set(bit Bit) *BitSet[Bit] > 将指定的位 bit 设置为 1 +**示例代码:** + +```go + +func ExampleBitSet_Set() { + var bs = super.NewBitSet[int]() + bs.Set(10) + fmt.Println(bs.Bits()) +} + +``` +
查看 / 收起单元测试 @@ -764,11 +834,29 @@ type BitSet[Bit generic.Integer] struct { ```go func TestBitSet_Set(t *testing.T) { - bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - bs.Set(11) - bs.Set(12) - bs.Set(13) - t.Log(bs) + var cases = []struct { + name string + in []int + shouldPanic bool + }{{name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, {name: "empty", in: make([]int, 0)}, {name: "nil", in: nil}, {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet[int]() + for _, bit := range c.in { + bs.Set(bit) + } + for _, bit := range c.in { + if !bs.Has(bit) { + t.Fatalf("bit %v not set", bit) + } + } + }) + } } ``` @@ -783,6 +871,18 @@ func TestBitSet_Set(t *testing.T) { #### func (*BitSet) Del(bit Bit) *BitSet[Bit] > 将指定的位 bit 设置为 0 +**示例代码:** + +```go + +func ExampleBitSet_Del() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Del(1) + fmt.Println(bs.Bits()) +} + +``` +
查看 / 收起单元测试 @@ -790,12 +890,32 @@ func TestBitSet_Set(t *testing.T) { ```go func TestBitSet_Del(t *testing.T) { - bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - bs.Del(11) - bs.Del(12) - bs.Del(13) - bs.Del(10) - t.Log(bs) + var cases = []struct { + name string + in []int + shouldPanic bool + }{{name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, {name: "empty", in: make([]int, 0)}, {name: "nil", in: nil}, {name: "negative", in: []int{-1, -2}, 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("panic: %v", r) + } + }() + bs := super.NewBitSet[int]() + for _, bit := range c.in { + bs.Set(bit) + } + for _, bit := range c.in { + bs.Del(bit) + } + for _, bit := range c.in { + if bs.Has(bit) { + t.Fatalf("bit %v not del", bit) + } + } + }) + } } ``` @@ -811,6 +931,21 @@ func TestBitSet_Del(t *testing.T) { > 将 BitSet 中的比特位集合缩小到最小 > - 正常情况下当 BitSet 中的比特位超出 64 位时,将自动增长,当 BitSet 中的比特位数量减少时,可以使用该方法将 BitSet 中的比特位集合缩小到最小 +**示例代码:** + +```go + +func ExampleBitSet_Shrink() { + var bs = super.NewBitSet(111, 222, 333, 444) + fmt.Println(bs.Cap()) + bs.Del(444) + fmt.Println(bs.Cap()) + bs.Shrink() + fmt.Println(bs.Cap()) +} + +``` +
查看 / 收起单元测试 @@ -818,13 +953,22 @@ func TestBitSet_Del(t *testing.T) { ```go func TestBitSet_Shrink(t *testing.T) { - bs := super.NewBitSet(63) - t.Log(bs.Cap()) - bs.Set(200) - t.Log(bs.Cap()) - bs.Del(200) - bs.Shrink() - t.Log(bs.Cap()) + var cases = []struct { + name string + in []int + }{{name: "normal", in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, {name: "empty", in: make([]int, 0)}, {name: "nil", in: nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + bs := super.NewBitSet(c.in...) + for _, v := range c.in { + bs.Del(v) + } + bs.Shrink() + if bs.Cap() != 0 { + t.Fatalf("cap %v != 0", bs.Cap()) + } + }) + } } ``` @@ -839,24 +983,70 @@ func TestBitSet_Shrink(t *testing.T) { #### func (*BitSet) Cap() int > 返回当前 BitSet 中可以表示的最大比特位数量 +**示例代码:** + +```go + +func ExampleBitSet_Cap() { + var bs = super.NewBitSet(63) + fmt.Println(bs.Cap()) +} + +``` + *** #### func (*BitSet) Has(bit Bit) bool > 检查指定的位 bit 是否被设置为 1 +**示例代码:** + +```go + +func ExampleBitSet_Has() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(bs.Has(1)) + fmt.Println(bs.Has(10)) +} + +``` + *** #### func (*BitSet) Clear() *BitSet[Bit] > 清空所有的比特位 +**示例代码:** + +```go + +func ExampleBitSet_Clear() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + bs.Clear() + fmt.Println(bs.Bits()) +} + +``` + *** #### func (*BitSet) Len() int > 返回当前 BitSet 中被设置的比特位数量 +**示例代码:** + +```go + +func ExampleBitSet_Len() { + var bs = super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9) + fmt.Println(bs.Len()) +} + +``` + ***