重构服务器

This commit is contained in:
kercylan98 2023-04-19 17:13:34 +08:00
parent 45a9d9b4e3
commit 481ccc182a
54 changed files with 946 additions and 1836 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
### Minotaur
app/monopoly/
old/app/monopoly/
### VisualStudioCode template
.vscode/*

View File

@ -1,7 +0,0 @@
package app
import (
"minotaur/game"
)
var State = new(game.StateMachine).Init()

View File

@ -1,19 +0,0 @@
package main
import (
"fmt"
"minotaur/app/template/app"
"minotaur/game"
"minotaur/game/conn"
"minotaur/game/protobuf/protobuf"
)
func init() {
app.State.RegMessagePlayer(int32(protobuf.MessageCode_SystemHeartbeat), func(player *game.Player) {
fmt.Println("hhha")
})
}
func main() {
app.State.Run("/test", 8888, conn.NewOrdinary())
}

View File

@ -1,45 +0,0 @@
package main
import (
"fmt"
"github.com/gorilla/websocket"
"google.golang.org/protobuf/proto"
"minotaur/game/protobuf/protobuf"
"testing"
"time"
)
func TestA(t *testing.T) {
run:
{
}
for {
ws := `ws://127.0.0.1:9000/test`
c, _, err := websocket.DefaultDialer.Dial(ws, nil)
if err != nil {
continue
}
req := &protobuf.SystemHeartbeatClient{}
d, _ := proto.Marshal(req)
data, err := proto.Marshal(&protobuf.Message{
Code: int32(protobuf.MessageCode_SystemHeartbeat),
Data: d,
})
if err != nil {
panic(err)
}
for {
//fmt.Println(c, data)
if err := c.WriteMessage(2, data); err != nil {
fmt.Println(err)
goto run
}
time.Sleep(3 * time.Second)
}
}
}

View File

@ -1,183 +0,0 @@
package game
import (
"fmt"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"minotaur/utils/log"
"minotaur/utils/super"
"minotaur/utils/timer"
"reflect"
"strconv"
"sync"
"time"
)
// channel 频道
type channel struct {
id any // 频道ID
size int // 最大人数
stateMachine *StateMachine // 状态机
timer *timer.Manager // 定时器
lock sync.RWMutex // 玩家锁
players map[int64]*Player // 所有玩家
alone bool // 频道运行逻辑是否单线程
// alone mode
messages chan *Message
// multi mode
}
// run 开始运行频道
func (slf *channel) run() {
slf.messages = make(chan *Message, 4096*1000)
go func() {
for {
msg, open := <-slf.messages
if !open {
return
}
slf.dispatch(msg)
}
}()
}
// tryRelease 尝试释放频道
func (slf *channel) tryRelease() {
slf.timer.After("channelRelease", time.Second*10, func() {
slf.stateMachine.channelRWMutex.Lock()
defer slf.stateMachine.channelRWMutex.Unlock()
if len(slf.players) > 0 {
return
}
slf.timer.Release()
close(slf.messages)
delete(slf.stateMachine.channels, slf.id)
log.Info("ChannelRelease", zap.Any("channelID", slf.id), zap.Int("channelCount", len(slf.stateMachine.channels)))
})
}
// tryRelease 强制释放频道
func (slf *channel) release() {
slf.stateMachine.channelRWMutex.Lock()
defer slf.stateMachine.channelRWMutex.Unlock()
slf.timer.Release()
close(slf.messages)
delete(slf.stateMachine.channels, slf.id)
log.Info("ChannelRelease",
zap.Any("channelID", slf.id),
zap.Int("channelCount", len(slf.stateMachine.channels)),
zap.String("type", "force"),
)
}
// join 加入频道
func (slf *channel) join(player *Player) (*Player, error) {
slf.lock.Lock()
if slf.size > 0 && len(slf.players) > slf.size {
return player, fmt.Errorf("join channel[%v] failed, maximum[%d] number of player", slf.id, slf.size)
}
if player.channel != nil {
player.channel.lock.Lock()
delete(player.channel.players, player.guid)
player.channel.lock.Unlock()
player.channel.tryRelease()
}
slf.players[player.guid] = player
slf.lock.Unlock()
player.channel = slf
player.ChannelTimer = slf.timer
log.Info("ChannelJoin",
zap.Any("channelID", slf.id),
zap.Int64("playerGuid", player.guid),
zap.String("size", super.If(slf.size <= 0, "NaN", strconv.Itoa(slf.size))),
zap.Int("headcount", len(slf.players)),
zap.String("address", player.ip),
)
return player, nil
}
// push 向频道内推送消息
func (slf *channel) push(message *Message) {
switch message.Type() {
case MessageTypePlayer:
if slf.alone {
slf.messages <- message
} else {
slf.dispatch(message)
}
case MessageTypeEvent:
slf.messages <- message
}
}
// dispatch 消息分发
func (slf *channel) dispatch(message *Message) {
args := message.Args()
switch message.Type() {
case MessageTypePlayer:
player, code, data := args[0], args[1], args[2]
h := slf.stateMachine.handles[code.(int32)]
var in = []reflect.Value{reflect.ValueOf(player)}
if h[1] != nil {
messageType := reflect.New(h[1].(reflect.Type).Elem())
if err := proto.Unmarshal(data.([]byte), messageType.Interface().(proto.Message)); err != nil {
panic(err)
}
in = append(in, messageType)
}
h[0].(reflect.Value).Call(in)
case MessageTypeEvent:
event, player := args[0].(byte), args[1].(*Player)
switch event {
case EventTypeGuestJoin:
slf.stateMachine.router.OnGuestPlayerJoinEvent(player)
case EventTypeGuestLeave:
callback := args[2].(func(player *Player))
slf.stateMachine.router.OnGuestPlayerLeaveEvent(player)
callback(player)
}
}
}
// GetPlayers 获取频道内所有玩家
func (slf *channel) GetPlayers() map[int64]*Player {
var players = make(map[int64]*Player)
if !slf.alone {
slf.lock.RLock()
defer slf.lock.RUnlock()
}
for id, player := range slf.players {
players[id] = player
}
return players
}
// GetAllChannelPlayers 获取所有频道玩家
func (slf *channel) GetAllChannelPlayers() map[int64]*Player {
if !slf.alone {
slf.stateMachine.channelRWMutex.RLock()
}
var channels = make([]*channel, 0, len(slf.stateMachine.channels))
for _, channel := range slf.stateMachine.channels {
channels = append(channels, channel)
}
if !slf.alone {
slf.stateMachine.channelRWMutex.RUnlock()
}
var players = make(map[int64]*Player)
for _, channel := range channels {
if !slf.alone {
channel.lock.RLock()
}
for id, player := range channel.players {
players[id] = player
}
if !slf.alone {
channel.lock.RUnlock()
}
}
return players
}

View File

@ -1,8 +0,0 @@
package game
// Conn 用户连接抽象
//
// 连接支持使用 tag 进行参数读取
type Conn interface {
GetConn() any
}

View File

@ -1,18 +0,0 @@
package conn
import (
"minotaur/game"
)
func NewOrdinary() game.OnCreateConnHandleFunc {
return func() game.Conn {
return new(Ordinary)
}
}
type Ordinary struct {
}
func (slf *Ordinary) GetConn() any {
return slf
}

View File

@ -1,9 +0,0 @@
package feature
// Actor 玩家演员接口定义
type Actor interface {
// GetGuid 获取演员 guid
GetGuid() int64
// GetPlayer 获取所属玩家
GetPlayer() Player
}

View File

@ -1,16 +0,0 @@
package feature
// ChatRoom 聊天室接口定义
type ChatRoom interface {
// JoinChatRoom 加入聊天室
JoinChatRoom(chat PlayerChat)
// LeaveChatRoom 离开聊天室
LeaveChatRoom(chat PlayerChat)
}
// PlayerChat 玩家聊天接口定义
type PlayerChat interface {
Player
// SendChatMessage 发送聊天消息给该玩家
SendChatMessage(message string)
}

View File

@ -1,48 +0,0 @@
package components
import (
"minotaur/game/feature"
"reflect"
)
// NewRoomManager 创建房间管理组件
func NewRoomManager[P feature.Player, R feature.Room[P]]() *RoomManager[P, R] {
return &RoomManager[P, R]{}
}
// RoomManager 房间管理组件
type RoomManager[P feature.Player, R feature.Room[P]] struct {
rooms map[int64]R
playerRoomRef map[int64]int64
}
func (slf *RoomManager[P, R]) JoinRoom(player feature.Player, room R) {
if slf.rooms == nil {
slf.rooms = map[int64]R{}
slf.playerRoomRef = map[int64]int64{}
}
slf.rooms[room.GetGuid()] = room
slf.playerRoomRef[player.GetGuid()] = room.GetGuid()
}
func (slf *RoomManager[P, R]) LeaveRoom(playerGuid int64) {
roomId := slf.playerRoomRef[playerGuid]
room := slf.rooms[roomId]
if !reflect.ValueOf(room).IsNil() {
room.LeaveRoom(playerGuid)
}
delete(slf.rooms, roomId)
delete(slf.playerRoomRef, playerGuid)
}
func (slf *RoomManager[P, R]) GetRoom(guid int64) R {
return slf.rooms[guid]
}
func (slf *RoomManager[P, R]) GetPlayer(guid int64) P {
return slf.rooms[slf.playerRoomRef[guid]].GetPlayer(guid)
}
func (slf *RoomManager[P, R]) GetPlayerRoom(guid int64) R {
return slf.rooms[slf.playerRoomRef[guid]]
}

View File

@ -1,7 +0,0 @@
package feature
// Player 玩家接口定义
type Player interface {
// GetGuid 获取玩家 guid
GetGuid() int64
}

View File

@ -1,21 +0,0 @@
package feature
// Room 游戏房间接口定义
type Room[P Player] interface {
// GetGuid 获取房间 guid
GetGuid() int64
// JoinRoom 加入房间
JoinRoom(player P) error
// LeaveRoom 离开房间
LeaveRoom(guid int64)
// GetPlayerMaximum 获取游戏参与人上限
GetPlayerMaximum() int
// GetPlayer 获取特定玩家
GetPlayer(guid int64) P
// GetPlayers 获取所有玩家
GetPlayers() map[int64]P
// GetPlayerCount 获取玩家数量
GetPlayerCount() int
// IsExist 玩家是否存在
IsExist(playerGuid int64) bool
}

View File

@ -1,10 +0,0 @@
package feature
// RoomGame 房间游戏接口定义
type RoomGame[P Player] interface {
Room[P]
// GameStart 游戏开始
GameStart(startTimeSecond int64, loop func(game RoomGame[P]))
// GameOver 游戏结束
GameOver()
}

View File

@ -1,14 +0,0 @@
package feature
// RoomReady 房间准备接口定义
type RoomReady[P Player] interface {
Room[P]
// Ready 设置玩家准备状态
Ready(playerGuid int64, ready bool)
// IsAllReady 是否全部玩家已准备
IsAllReady() bool
// GetReadyCount 获取已准备玩家数量
GetReadyCount() int
// GetUnready 获取未准备的玩家
GetUnready() map[int64]P
}

View File

@ -1,16 +0,0 @@
package feature
// RoomSpectator 房间观众席接口定义
type RoomSpectator[P Player] interface {
Room[P]
// GetSpectatorMaximum 获取观众人数上限
GetSpectatorMaximum() int
// JoinSpectator 加入观众席
JoinSpectator(player P) error
// LeaveSpectator 离开观众席
LeaveSpectator(guid int64) error
// GetSpectatorPlayer 获取特定观众席玩家
GetSpectatorPlayer(guid int64) P
// GetSpectatorPlayers 获取观众席玩家
GetSpectatorPlayers() map[int64]P
}

View File

@ -1,20 +0,0 @@
package feature
// RoomTeam 房间小队接口定义
type RoomTeam interface {
// GetTeamMaximum 获取最大小队数量
GetTeamMaximum() int
// GetTeam 获取特定队伍
GetTeam(guid int64) Team
// GetTeams 获取所有队伍
GetTeams() map[int64]Team
}
type Team interface {
// GetGuid 获取小队 guid
GetGuid() int64
// GetPlayer 获取小队特定玩家
GetPlayer(guid int64) Player
// GetPlayers 获取小队玩家
GetPlayers() map[int64]Player
}

View File

@ -1,9 +0,0 @@
package standard
import (
"minotaur/game"
)
type Player struct {
*game.Player
}

View File

@ -1,61 +0,0 @@
package standard
import (
"errors"
"minotaur/game/feature"
"reflect"
)
// NewRoom 普通房间实例
func NewRoom[P feature.Player](guid int64, playerMaximum int) *Room[P] {
room := &Room[P]{
guid: guid,
playerMaximum: playerMaximum,
}
return room
}
type Room[P feature.Player] struct {
guid int64 // 房间 guid
playerMaximum int // 房间最大人数小于等于0不限
players map[int64]P // 房间玩家
}
func (slf *Room[P]) GetGuid() int64 {
return slf.guid
}
func (slf *Room[P]) JoinRoom(player P) error {
if slf.playerMaximum > 0 && len(slf.players) >= slf.playerMaximum {
return errors.New("maximum number of player")
}
if slf.players == nil {
slf.players = map[int64]P{}
}
slf.players[player.GetGuid()] = player
return nil
}
func (slf *Room[P]) LeaveRoom(guid int64) {
delete(slf.players, guid)
}
func (slf *Room[P]) GetPlayerMaximum() int {
return slf.playerMaximum
}
func (slf *Room[P]) GetPlayer(guid int64) P {
return slf.players[guid]
}
func (slf *Room[P]) GetPlayers() map[int64]P {
return slf.players
}
func (slf *Room[P]) GetPlayerCount() int {
return len(slf.players)
}
func (slf *Room[P]) IsExist(playerGuid int64) bool {
return !reflect.ValueOf(slf.players[playerGuid]).IsNil()
}

View File

@ -1,39 +0,0 @@
package standard
import (
"minotaur/game/feature"
"time"
)
// NewRoomGame 对普通房间附加游戏功能的实例
//
// startCountDown 游戏开始倒计时大于0生效
func NewRoomGame[P feature.Player](room *Room[P], startCountDown time.Duration) *RoomGame[P] {
return &RoomGame[P]{
Room: room,
startCountDown: startCountDown,
}
}
type RoomGame[P feature.Player] struct {
*Room[P]
*Timer
startTimeSecond int64 // 游戏开始时间戳(秒)
startCountDown time.Duration // 游戏开始倒计时
}
func (slf *RoomGame[P]) GameStart(startTimeSecond int64, loop func(game feature.RoomGame[P])) {
slf.startTimeSecond = startTimeSecond
if slf.startCountDown > 0 {
slf.After("RoomGame.GameStart.StartCountDown", slf.startCountDown, func() {
loop(slf)
})
} else {
loop(slf)
}
}
func (slf *RoomGame[P]) GameOver() {
slf.Release()
}

View File

@ -1,48 +0,0 @@
package standard
import "minotaur/game/feature"
// NewRoomReady 对普通房间附加准备功能的实例
func NewRoomReady[P feature.Player](room *Room[P]) *RoomReady[P] {
return &RoomReady[P]{}
}
type RoomReady[P feature.Player] struct {
*Room[P]
readies map[int64]bool
}
func (slf *RoomReady[P]) Ready(playerGuid int64, ready bool) {
if ready {
if !slf.IsExist(playerGuid) {
if _, exist := slf.readies[playerGuid]; exist {
delete(slf.readies, playerGuid)
}
return
}
if slf.readies == nil {
slf.readies = map[int64]bool{}
}
slf.readies[playerGuid] = true
} else {
delete(slf.readies, playerGuid)
}
}
func (slf *RoomReady[P]) IsAllReady() bool {
return len(slf.readies) >= slf.GetPlayerCount()
}
func (slf *RoomReady[P]) GetReadyCount() int {
return len(slf.readies)
}
func (slf *RoomReady[P]) GetUnready() map[int64]P {
var unreadiest = make(map[int64]P)
for guid, player := range slf.GetPlayers() {
if !slf.readies[guid] {
unreadiest[guid] = player
}
}
return unreadiest
}

View File

@ -1,72 +0,0 @@
package standard
import (
"errors"
"minotaur/game/feature"
"reflect"
)
// NewRoomSpectator 对普通房间附加观众席功能的实例
func NewRoomSpectator[P feature.Player](room *Room[P], spectatorMaximum int) *RoomSpectator[P] {
return &RoomSpectator[P]{
Room: room,
spectatorMaximum: spectatorMaximum,
}
}
type RoomSpectator[P feature.Player] struct {
*Room[P]
spectatorMaximum int // 观众席最大人数小于等于0不限
spectatorPlayers map[int64]P // 观众席玩家
recordInRoom map[int64]bool // 记录玩家是否从房间中移动到观众席
}
func (slf *RoomSpectator[P]) GetSpectatorMaximum() int {
return slf.spectatorMaximum
}
func (slf *RoomSpectator[P]) JoinSpectator(player P) error {
if slf.spectatorMaximum > 0 && len(slf.spectatorPlayers) >= slf.spectatorMaximum {
return errors.New("maximum number of player")
}
var guid = player.GetGuid()
if !reflect.ValueOf(slf.GetPlayer(guid)).IsNil() {
if slf.recordInRoom == nil {
slf.recordInRoom = map[int64]bool{}
}
slf.recordInRoom[guid] = true
slf.LeaveRoom(player.GetGuid())
}
if slf.spectatorPlayers == nil {
slf.spectatorPlayers = map[int64]P{}
}
slf.spectatorPlayers[guid] = player
return nil
}
func (slf *RoomSpectator[P]) LeaveSpectator(guid int64) error {
var (
inRoom = slf.recordInRoom[guid]
inSpectator = !reflect.ValueOf(slf.spectatorPlayers[guid]).IsNil()
)
if inSpectator {
delete(slf.spectatorPlayers, guid)
delete(slf.recordInRoom, guid)
if inRoom {
return slf.JoinRoom(slf.GetPlayer(guid))
}
}
return nil
}
func (slf *RoomSpectator[P]) GetSpectatorPlayer(guid int64) P {
return slf.spectatorPlayers[guid]
}
func (slf *RoomSpectator[P]) GetSpectatorPlayers() map[int64]P {
return slf.spectatorPlayers
}

View File

@ -1,15 +0,0 @@
package standard
import "minotaur/utils/timer"
// Timer 附加定时器功能
type Timer struct {
*timer.Manager
}
func (slf *Timer) Timer() *timer.Manager {
if slf.Manager == nil {
slf.Manager = timer.GetManager(64)
}
return slf.Manager
}

View File

@ -1,9 +0,0 @@
package feature
import "minotaur/utils/timer"
// Timer 定时器接口定义
type Timer interface {
// Timer 获取定时器
Timer() *timer.Manager
}

View File

@ -1,22 +0,0 @@
package game
import "time"
// Time 带有偏移的游戏时间
type Time struct {
Offset time.Duration
}
// Now 获取包含偏移的当前时间
func (slf *Time) Now() time.Time {
t := time.Now()
if slf.Offset == 0 {
return t
}
return t.Add(slf.Offset)
}
// Since 获取两个时间之间的差
func (slf *Time) Since(t time.Time) time.Duration {
return slf.Now().Sub(t)
}

View File

@ -1,147 +0,0 @@
package game
import (
"context"
"fmt"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"minotaur/game/protobuf/protobuf"
"minotaur/utils/gin/middlewares"
"minotaur/utils/log"
"minotaur/utils/timer"
"net/http"
"strings"
"time"
)
const (
loginTimeoutTimerName = "player_login_timeout_timer"
)
type OnCreateConnHandleFunc func() Conn
type gateway struct {
httpServer *http.Server
}
func (slf *gateway) run(stateMachine *StateMachine, appName string, port int, onCreateConnHandleFunc OnCreateConnHandleFunc) *gateway {
// Gin WebSocket
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(middlewares.Logger(log.Logger), middlewares.Cors())
pprof.Register(router) // pprof 可视化分析
var upgrade = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
router.GET(fmt.Sprintf("/%s", appName), func(context *gin.Context) {
ip := context.GetHeader("X-Real-IP")
ws, err := upgrade.Upgrade(context.Writer, context.Request, nil)
if err != nil {
log.Error("Gateway", zap.Error(err))
return
}
if len(ip) == 0 {
addr := ws.RemoteAddr().String()
if index := strings.LastIndex(addr, ":"); index != -1 {
ip = addr[0:index]
}
}
conn := onCreateConnHandleFunc()
if err := context.ShouldBind(conn); err != nil {
log.Error("Gateway", zap.Error(err))
context.AbortWithStatus(http.StatusBadRequest)
return
}
playerGuid, err := stateMachine.sonyflake.NextID()
if err != nil {
log.Error("Gateway", zap.Error(err))
context.AbortWithStatus(http.StatusBadRequest)
return
}
player := &Player{
guid: int64(playerGuid),
conn: conn,
ws: ws,
ip: ip,
Manager: timer.GetManager(64),
GameTimer: stateMachine.Manager,
}
channelId, size := stateMachine.channelStrategy()
channel := stateMachine.channel(channelId, size)
player, err = channel.join(player)
if err != nil {
player.exit(err.Error())
return
}
player.channel.push(new(Message).Init(MessageTypeEvent, EventTypeGuestJoin, player))
if stateMachine.loginTimeout > 0 {
player.After(loginTimeoutTimerName, stateMachine.loginTimeout, func() {
player.exit("login timeout")
})
}
defer func() {
if err := recover(); err != nil {
player.exit()
}
}()
for {
if err := player.ws.SetReadDeadline(time.Now().Add(time.Second * 30)); err != nil {
panic(err)
}
_, data, err := player.ws.ReadMessage()
if err != nil {
panic(err)
}
var msg = new(protobuf.Message)
if err = proto.Unmarshal(data, msg); err != nil {
continue
}
player.channel.push(new(Message).Init(MessageTypePlayer, player, msg.Code, msg.Data))
}
})
// HttpServer
slf.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
}
log.Info("GatewayStart", zap.String("listen", slf.httpServer.Addr))
go func() {
if err := slf.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
stateMachine.errChannel <- err
return
}
}()
return slf
}
func (slf *gateway) shutdown(context context.Context) error {
log.Info("GatewayShutdown", zap.String("stateMachine", "start"))
if err := slf.httpServer.Shutdown(context); err != nil {
return err
}
log.Info("GatewayShutdown", zap.String("stateMachine", "normal"))
return nil
}

View File

@ -1,34 +0,0 @@
package game
const (
// MessageTypePlayer 玩家通过客户端发送到服务端的消息
MessageTypePlayer byte = iota
// MessageTypeEvent 服务端内部事件消息
MessageTypeEvent
)
// EventTypeGuestJoin 服务器内部事件消息类型
const (
EventTypeGuestJoin byte = iota // 访客加入事件
EventTypeGuestLeave // 访客离开事件
)
// Message 游戏消息数据结构
type Message struct {
t byte
args []any
}
func (slf *Message) Init(t byte, args ...any) *Message {
slf.t = t
slf.args = args
return slf
}
func (slf *Message) Type() byte {
return slf.t
}
func (slf *Message) Args() []any {
return slf.args
}

View File

@ -1,192 +0,0 @@
package game
import (
"fmt"
"github.com/gorilla/websocket"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"minotaur/game/protobuf/messagepack"
"minotaur/game/protobuf/protobuf"
"minotaur/utils/log"
"minotaur/utils/timer"
)
// Player 玩家数据结构
type Player struct {
*channel // 玩家所在频道信息
guid int64 // 玩家guid
ip string // 玩家IP
conn Conn // 玩家连接信息
ws *websocket.Conn // 玩家 WebSocket 连接
login byte // 登录状态 0: 无需登录 1: 登录成功
// 游戏定时器
//
// GameTimer: 状态机级别定时器;
// ChannelTimer: 频道级别定时器;
// Manager: 玩家级别定时器。
*timer.Manager
GameTimer, ChannelTimer *timer.Manager
}
// Login 设置玩家是否登录成功
func (slf *Player) Login(success bool) {
slf.stateMachine.router.OnPlayerLoginEvent(slf, success)
if success {
slf.StopTimer(loginTimeoutTimerName)
slf.login = 1
} else {
slf.exit("login failed")
}
}
// IsLogin 获取玩家是否已登录
func (slf *Player) IsLogin() bool {
return slf.login == 1
}
// GetGuid 获取玩家GUID
func (slf *Player) GetGuid() int64 {
return slf.guid
}
// GetChannelId 返回玩家所在频道id
func (slf *Player) GetChannelId() any {
return slf.channel.id
}
// GetConn 获取连接信息
func (slf *Player) GetConn() any {
return slf.conn.GetConn()
}
// Close 关闭玩家连接
func (slf *Player) Close() {
_ = slf.ws.Close()
}
// PushToClient 推送消息到客户端
func (slf *Player) PushToClient(messageCode int32, message proto.Message) {
data, err := messagepack.Pack(messageCode, message)
if err != nil {
panic(err)
}
if err = slf.ws.WriteMessage(2, data); err != nil {
panic(err)
}
}
// PushToClients 推送消息到多个玩家客户端
func (slf *Player) PushToClients(messageCode int32, message proto.Message, playerGuids ...int64) []PushFail {
data, err := messagepack.Pack(messageCode, message)
if err != nil {
panic(err)
}
if !slf.channel.alone {
slf.channel.lock.RLock()
defer slf.channel.lock.RUnlock()
}
var pushFails []PushFail
for _, id := range playerGuids {
player := slf.channel.players[id]
if player == nil {
pushFails = append(pushFails, PushFail{
Data: data,
Err: fmt.Errorf("player [%v] not found", id),
})
continue
}
if err = player.ws.WriteMessage(2, data); err != nil {
pushFails = append(pushFails, PushFail{
Player: player,
Data: data,
Err: err,
})
}
}
return pushFails
}
// PushToChannelPlayerClients 推送消息到频道内所有玩家客户端可通过指定玩家或玩家id排除特定玩家
func (slf *Player) PushToChannelPlayerClients(messageCode int32, message proto.Message, excludeGuids ...int64) []PushFail {
data, err := messagepack.Pack(messageCode, message)
if err != nil {
panic(err)
}
var pushFails []PushFail
if !slf.channel.alone {
slf.channel.lock.RLock()
defer slf.channel.lock.RUnlock()
}
for id, player := range slf.channel.players {
var exclude bool
for _, target := range excludeGuids {
if id == target {
exclude = true
break
}
}
if exclude {
break
}
if err = player.ws.WriteMessage(2, data); err != nil {
pushFails = append(pushFails, PushFail{
Player: player,
Data: data,
Err: err,
})
}
}
return pushFails
}
// PushToChannel 推送消息到频道
func (slf *Player) PushToChannel(messageCode protobuf.MessageCode, message proto.Message) {
slf.channel.push(new(Message).Init(MessageTypePlayer, slf, messageCode, message))
}
// ChangeChannel 改变玩家所在频道
func (slf *Player) ChangeChannel(channelId any) error {
channel := slf.channel.stateMachine.channel(channelId, slf.channel.size, slf.channel.alone)
_, err := channel.join(slf)
return err
}
// exit 退出游戏
func (slf *Player) exit(reason ...string) {
var r = "normal exit"
if len(reason) > 0 {
r = reason[0]
}
slf.channel.push(new(Message).Init(MessageTypeEvent, EventTypeGuestLeave, slf, func(player *Player) {
slf.Release()
_ = slf.ws.Close()
if slf.channel != nil {
slf.channel.lock.Lock()
defer slf.channel.lock.Unlock()
delete(slf.channel.players, slf.guid)
log.Info("ChannelLeave",
zap.Any("channelID", slf.channel.id),
zap.Int64("playerGuid", slf.guid),
zap.Int("headcount", len(slf.channel.players)),
zap.String("address", slf.ip),
zap.String("reason", r),
)
slf.channel.tryRelease()
} else {
log.Info("ChannelLeave",
zap.Int64("playerGuid", slf.guid),
zap.String("address", slf.ip),
zap.String("reason", r),
)
}
}))
}

View File

@ -1,15 +0,0 @@
syntax = "proto3";
// protoc --proto_path . --go_out=./game/protobuf/protobuf/ --go-grpc_out=./game/protobuf/protobuf/ ./game/protobuf/*.proto
package protobuf;
option go_package = "./;protobuf";
enum MessageCode {
SystemHeartbeat = 0; //
}
//
message Message {
int32 code = 1;
bytes data = 2;
}

View File

@ -1,21 +0,0 @@
package messagepack
import (
"google.golang.org/protobuf/proto"
"minotaur/game/protobuf/protobuf"
)
// Pack 消息打包
func Pack(messageCode int32, message proto.Message) ([]byte, error) {
data, err := proto.Marshal(message)
if err != nil {
return nil, err
}
msg := &protobuf.Message{
Code: messageCode,
Data: data,
}
return proto.Marshal(msg)
}

View File

@ -1,201 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v4.22.1
// source: game/protobuf/message.proto
package protobuf
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MessageCode int32
const (
MessageCode_SystemHeartbeat MessageCode = 0 // 心跳
)
// Enum value maps for MessageCode.
var (
MessageCode_name = map[int32]string{
0: "SystemHeartbeat",
}
MessageCode_value = map[string]int32{
"SystemHeartbeat": 0,
}
)
func (x MessageCode) Enum() *MessageCode {
p := new(MessageCode)
*p = x
return p
}
func (x MessageCode) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MessageCode) Descriptor() protoreflect.EnumDescriptor {
return file_game_protobuf_message_proto_enumTypes[0].Descriptor()
}
func (MessageCode) Type() protoreflect.EnumType {
return &file_game_protobuf_message_proto_enumTypes[0]
}
func (x MessageCode) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MessageCode.Descriptor instead.
func (MessageCode) EnumDescriptor() ([]byte, []int) {
return file_game_protobuf_message_proto_rawDescGZIP(), []int{0}
}
// 通用消息
type Message struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *Message) Reset() {
*x = Message{}
if protoimpl.UnsafeEnabled {
mi := &file_game_protobuf_message_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Message) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_game_protobuf_message_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
func (*Message) Descriptor() ([]byte, []int) {
return file_game_protobuf_message_proto_rawDescGZIP(), []int{0}
}
func (x *Message) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *Message) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_game_protobuf_message_proto protoreflect.FileDescriptor
var file_game_protobuf_message_proto_rawDesc = []byte{
0x0a, 0x1b, 0x67, 0x61, 0x6d, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x31, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x22, 0x0a, 0x0b, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x10, 0x00, 0x42, 0x0d,
0x5a, 0x0b, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_game_protobuf_message_proto_rawDescOnce sync.Once
file_game_protobuf_message_proto_rawDescData = file_game_protobuf_message_proto_rawDesc
)
func file_game_protobuf_message_proto_rawDescGZIP() []byte {
file_game_protobuf_message_proto_rawDescOnce.Do(func() {
file_game_protobuf_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_game_protobuf_message_proto_rawDescData)
})
return file_game_protobuf_message_proto_rawDescData
}
var file_game_protobuf_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_game_protobuf_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_game_protobuf_message_proto_goTypes = []interface{}{
(MessageCode)(0), // 0: protobuf.MessageCode
(*Message)(nil), // 1: protobuf.Message
}
var file_game_protobuf_message_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_game_protobuf_message_proto_init() }
func file_game_protobuf_message_proto_init() {
if File_game_protobuf_message_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_game_protobuf_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Message); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_game_protobuf_message_proto_rawDesc,
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_game_protobuf_message_proto_goTypes,
DependencyIndexes: file_game_protobuf_message_proto_depIdxs,
EnumInfos: file_game_protobuf_message_proto_enumTypes,
MessageInfos: file_game_protobuf_message_proto_msgTypes,
}.Build()
File_game_protobuf_message_proto = out.File
file_game_protobuf_message_proto_rawDesc = nil
file_game_protobuf_message_proto_goTypes = nil
file_game_protobuf_message_proto_depIdxs = nil
}

View File

@ -1,134 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v4.22.1
// source: game/protobuf/system.proto
package protobuf
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 心跳
type SystemHeartbeatClient struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SystemHeartbeatClient) Reset() {
*x = SystemHeartbeatClient{}
if protoimpl.UnsafeEnabled {
mi := &file_game_protobuf_system_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SystemHeartbeatClient) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SystemHeartbeatClient) ProtoMessage() {}
func (x *SystemHeartbeatClient) ProtoReflect() protoreflect.Message {
mi := &file_game_protobuf_system_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SystemHeartbeatClient.ProtoReflect.Descriptor instead.
func (*SystemHeartbeatClient) Descriptor() ([]byte, []int) {
return file_game_protobuf_system_proto_rawDescGZIP(), []int{0}
}
var File_game_protobuf_system_proto protoreflect.FileDescriptor
var file_game_protobuf_system_proto_rawDesc = []byte{
0x0a, 0x1a, 0x67, 0x61, 0x6d, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42,
0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_game_protobuf_system_proto_rawDescOnce sync.Once
file_game_protobuf_system_proto_rawDescData = file_game_protobuf_system_proto_rawDesc
)
func file_game_protobuf_system_proto_rawDescGZIP() []byte {
file_game_protobuf_system_proto_rawDescOnce.Do(func() {
file_game_protobuf_system_proto_rawDescData = protoimpl.X.CompressGZIP(file_game_protobuf_system_proto_rawDescData)
})
return file_game_protobuf_system_proto_rawDescData
}
var file_game_protobuf_system_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_game_protobuf_system_proto_goTypes = []interface{}{
(*SystemHeartbeatClient)(nil), // 0: protobuf.SystemHeartbeatClient
}
var file_game_protobuf_system_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_game_protobuf_system_proto_init() }
func file_game_protobuf_system_proto_init() {
if File_game_protobuf_system_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_game_protobuf_system_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SystemHeartbeatClient); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_game_protobuf_system_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_game_protobuf_system_proto_goTypes,
DependencyIndexes: file_game_protobuf_system_proto_depIdxs,
MessageInfos: file_game_protobuf_system_proto_msgTypes,
}.Build()
File_game_protobuf_system_proto = out.File
file_game_protobuf_system_proto_rawDesc = nil
file_game_protobuf_system_proto_goTypes = nil
file_game_protobuf_system_proto_depIdxs = nil
}

View File

@ -1,8 +0,0 @@
syntax = "proto3";
// protoc --proto_path . --go_out=./game/protobuf/protobuf/ --go-grpc_out=./game/protobuf/protobuf/ ./game/protobuf/*.proto
package protobuf;
option go_package = "./;protobuf";
//
message SystemHeartbeatClient {}

View File

@ -1,8 +0,0 @@
package game
// PushFail 消息推送失败信息
type PushFail struct {
Player *Player
Data []byte
Err error
}

View File

@ -1,123 +0,0 @@
package game
import (
"fmt"
"go.uber.org/zap"
"minotaur/game/protobuf/protobuf"
"minotaur/utils/log"
"reflect"
)
var allowReplaceMessages = map[int32]bool{
int32(protobuf.MessageCode_SystemHeartbeat): true,
}
type router struct {
handles map[int32][2]any // 玩家消息表
guestPlayerJoinEvents []func(guest *Player)
guestPlayerLeaveEvents []func(guest *Player)
heartbeatEvents []func(player *Player)
playerLoginEvents []func(player *Player, success bool)
gameLaunchEvents []func()
}
func (slf *router) init(stateMachine *StateMachine) *router {
slf.RegMessagePlayer(int32(protobuf.MessageCode_SystemHeartbeat), func(player *Player) {
slf.OnHeartbeatEvent(player)
})
return slf
}
// RegGameLaunchEvent 注册游戏启动完成事件
func (slf *router) RegGameLaunchEvent(handleFunc func()) {
slf.gameLaunchEvents = append(slf.gameLaunchEvents, handleFunc)
}
// OnGameLaunchEvent 游戏启动完成事件发生时
func (slf *router) OnGameLaunchEvent() {
for _, event := range slf.gameLaunchEvents {
event()
}
}
// RegPlayerLoginEvent 注册玩家登录事件
func (slf *router) RegPlayerLoginEvent(handleFunc func(player *Player, success bool)) {
slf.playerLoginEvents = append(slf.playerLoginEvents, handleFunc)
}
// OnPlayerLoginEvent 玩家登录事件发生时
func (slf *router) OnPlayerLoginEvent(player *Player, success bool) {
for _, event := range slf.playerLoginEvents {
event(player, success)
}
}
// RegHeartbeatEvent 注册客户端心跳事件
func (slf *router) RegHeartbeatEvent(handleFunc func(guest *Player)) {
slf.heartbeatEvents = append(slf.heartbeatEvents, handleFunc)
}
// OnHeartbeatEvent 客户端心跳事件发生时
func (slf *router) OnHeartbeatEvent(guest *Player) {
for _, event := range slf.heartbeatEvents {
event(guest)
}
}
// RegGuestPlayerJoinEvent 注册访客玩家加入事件
func (slf *router) RegGuestPlayerJoinEvent(handleFunc func(guest *Player)) {
slf.guestPlayerJoinEvents = append(slf.guestPlayerJoinEvents, handleFunc)
}
// OnGuestPlayerJoinEvent 访问玩家加入时
func (slf *router) OnGuestPlayerJoinEvent(guest *Player) {
for _, event := range slf.guestPlayerJoinEvents {
event(guest)
}
}
// RegGuestPlayerLeaveEvent 注册访客玩家离开事件
func (slf *router) RegGuestPlayerLeaveEvent(handleFunc func(guest *Player)) {
slf.guestPlayerLeaveEvents = append(slf.guestPlayerLeaveEvents, handleFunc)
}
// OnGuestPlayerLeaveEvent 访问玩家离开时
func (slf *router) OnGuestPlayerLeaveEvent(guest *Player) {
for _, event := range slf.guestPlayerLeaveEvents {
event(guest)
}
}
// RegMessagePlayer 注册玩家消息
func (slf *router) RegMessagePlayer(messageCode int32, handleFunc any) {
if slf.handles == nil {
slf.handles = map[int32][2]any{}
}
typeOf := reflect.TypeOf(handleFunc)
if typeOf.Kind() != reflect.Func {
panic(fmt.Errorf("register player message failed, handleFunc must is func(player *Player, message *protobufType)"))
}
if typeOf.NumIn() == 0 {
panic(fmt.Errorf("register player message failed, handleFunc must is func(player *Player) or func(player *Player, message *protobufType)"))
}
handle, exist := slf.handles[messageCode]
if exist && !allowReplaceMessages[messageCode] {
panic(fmt.Errorf("register player message failed, message %s existed and cannot be replaced", messageCode))
}
handle = [2]any{reflect.ValueOf(handleFunc), nil}
if typeOf.NumIn() == 2 {
messageType := typeOf.In(1)
handle[1] = messageType
}
slf.handles[messageCode] = handle
if exist && allowReplaceMessages[messageCode] {
log.Info("RouterRegMessagePlayer", zap.Any("code", messageCode), zap.Any("messageType", handle[1]), zap.String("type", "replace"))
} else {
log.Info("RouterRegMessagePlayer", zap.Any("code", messageCode), zap.Any("messageType", handle[1]))
}
}

View File

@ -1,150 +0,0 @@
package game
import (
"context"
"github.com/sony/sonyflake"
"go.uber.org/zap"
"minotaur/utils/log"
"minotaur/utils/super"
"minotaur/utils/timer"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
)
// StateMachine 游戏状态机,维护全局游戏信息
type StateMachine struct {
*gateway // 网关
*router // 路由器
*timer.Manager // 定时器
*Time // 游戏时间
channelRWMutex sync.RWMutex // 频道锁
channels map[any]*channel // 所有频道
channelStrategy func() (channelId any, size int) // 频道策略
errChannel chan error // 异步错误管道
loginTimeout time.Duration // 玩家登录超时时间
closeErrors []error // 关闭错误集
closingMutex sync.Mutex // 关闭锁
closed bool // 是否已关闭
sonyflake *sonyflake.Sonyflake // 雪花id生成器
}
// SetLoginTimeout 设置玩家登录超时时间,登录超时时,玩家将被踢出游戏(默认无超时)
func (slf *StateMachine) SetLoginTimeout(duration time.Duration) {
slf.loginTimeout = duration
}
// SetChannelStrategy 设置频道策略返回频道id和最大容纳人数
func (slf *StateMachine) SetChannelStrategy(onGetChannelId func() (channelId any, size int)) {
slf.channelStrategy = onGetChannelId
}
func (slf *StateMachine) Init() *StateMachine {
slf.router = new(router).init(slf)
slf.Time = new(Time)
return slf
}
// Run 运行状态机
func (slf *StateMachine) Run(appName string, port int, onCreateConnHandleFunc OnCreateConnHandleFunc) {
slf.errChannel = make(chan error, 1)
slf.channels = map[any]*channel{}
if slf.channelStrategy == nil {
slf.channelStrategy = func() (channelId any, size int) {
return 0, 0
}
}
slf.gateway = new(gateway).run(slf, appName, port, onCreateConnHandleFunc)
slf.Manager = timer.GetManager(64)
slf.sonyflake = sonyflake.NewSonyflake(sonyflake.Settings{})
log.Info("StateMachineRun", zap.String("stateMachine", "finish"))
systemSignal := make(chan os.Signal, 1)
signal.Notify(systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
select {
case <-systemSignal:
slf.Shutdown(nil)
case err := <-slf.errChannel:
slf.Shutdown(err)
}
}
// Shutdown 停止状态机
func (slf *StateMachine) Shutdown(err error) {
if err != nil {
slf.closeErrors = append(slf.closeErrors, err)
}
slf.closingMutex.Lock()
defer slf.closingMutex.Unlock()
if slf.closed {
return
}
slf.closed = true
var shutDownTimeoutContext, cancel = context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err = slf.gateway.shutdown(shutDownTimeoutContext); err != nil {
slf.closeErrors = append(slf.closeErrors, err)
}
for _, c := range slf.channels {
c.release()
}
slf.Manager.Release()
if len(slf.closeErrors) > 0 {
log.Error("StateMachineShutdown", zap.Errors("errors", slf.closeErrors))
} else {
log.Info("StateMachineShutdown", zap.String("stateMachine", "normal"))
}
}
// channel 获取频道,如果频道不存在将会进行创建
//
// size: 指定频道最大容纳人数( <= 0 无限制)
// alone: 指定频道创建时候的逻辑是否以单线程运行 (default: true)
func (slf *StateMachine) channel(channelId any, size int, alone ...bool) *channel {
slf.channelRWMutex.Lock()
defer slf.channelRWMutex.Unlock()
isAlone := true
if len(alone) > 0 {
isAlone = alone[0]
}
c := slf.channels[channelId]
if c == nil {
c = &channel{
id: channelId,
size: size,
stateMachine: slf,
alone: isAlone,
players: map[int64]*Player{},
timer: timer.GetManager(64),
}
slf.channels[channelId] = c
c.run()
log.Info("ChannelCreate",
zap.Any("channelID", channelId),
zap.Bool("alone", isAlone),
zap.Int("count", len(slf.channels)),
zap.String("size", super.If(c.size <= 0, "NaN", strconv.Itoa(c.size))))
}
return c
}
// GenerateGuid 生成一个 Guid
func (slf *StateMachine) GenerateGuid() int64 {
guid, err := slf.sonyflake.NextID()
if err != nil {
panic(err)
}
return int64(guid)
}

34
go.mod
View File

@ -12,38 +12,64 @@ require (
require (
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 // indirect
github.com/aceld/zinx v1.1.16 // indirect
github.com/andeya/cfgo v0.0.0-20220626152948-7980c5d761c8 // indirect
github.com/andeya/erpc/v7 v7.2.0 // indirect
github.com/andeya/goutil v1.0.0 // indirect
github.com/apache/thrift v0.17.0 // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.2 // indirect
github.com/klauspost/reedsolomon v1.11.7 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/lucas-clemente/quic-go v0.31.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.6.0 // indirect
github.com/panjf2000/gnet v1.6.6 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sony/sonyflake v1.1.0 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
github.com/xtaci/kcp-go/v5 v5.6.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.4.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

121
go.sum
View File

@ -2,7 +2,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
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/aceld/zinx v1.1.16 h1:KBgpa0NhpK6+RMDTQXPWGQjV5f/8P6KdPtrh5fIjctY=
github.com/aceld/zinx v1.1.16/go.mod h1:/ITipOv81IO2WIxcfXYI/S0sG62r9opKgHu1r7Qgjb0=
github.com/andeya/cfgo v0.0.0-20220626152948-7980c5d761c8 h1:2+qycdjXX/+EOQ3Rxkt39P5Vy5aQ0GeidW/6L4YWxtA=
github.com/andeya/cfgo v0.0.0-20220626152948-7980c5d761c8/go.mod h1:tVXGyZHyEkmXSzfmP5P3r4ymUI70sKFMaVqEsRnYtBE=
github.com/andeya/erpc/v7 v7.2.0 h1:Hv3c33azBzAk0OAF2JCU2I8/2VMGVzwtpuc0N3OsOxc=
github.com/andeya/erpc/v7 v7.2.0/go.mod h1:FY4XODBtYJnBLLE6xuWcxxyspwEZdECTjkQhRUfggYg=
github.com/andeya/goutil v1.0.0 h1:ZBF/1dParfzTTiZ3SivkrV3Rd50SnXPqqf4IRvULvNQ=
github.com/andeya/goutil v1.0.0/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo=
github.com/apache/thrift v0.17.0 h1:cMd2aj52n+8VoAtvSvLn4kDC3aZ6IAkBuqWQ2IDu7wo=
github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.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.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@ -37,11 +48,23 @@ 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.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
@ -59,22 +82,32 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM=
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
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/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.2 h1:xPMwiykqNK9VK0NYC3+jTMYv9I6Vl3YdjZgPZKG3zO0=
github.com/klauspost/cpuid/v2 v2.2.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
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/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=
@ -87,17 +120,30 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4=
github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
github.com/panjf2000/gnet v1.6.6 h1:P6bApc54hnVcJVgH+SMe41mn47ECCajB6E/dKq27Y0c=
github.com/panjf2000/gnet v1.6.6/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -112,16 +158,25 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.9 h1:cGGLK8twbc1J1S/fHnZW7BylXYaFP+0fR2s+nzsFDiU=
github.com/templexxx/cpu v0.0.9/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -130,52 +185,99 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/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.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
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/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
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/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/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.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.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=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/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.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/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-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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
@ -187,8 +289,20 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
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.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
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=
@ -212,6 +326,13 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/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=

9
main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "minotaur/server"
func main() {
s := server.New(server.NetworkWebsocket)
s.Run(":9999")
}

45
server/conn.go Normal file
View File

@ -0,0 +1,45 @@
package server
import (
"github.com/gorilla/websocket"
"github.com/panjf2000/gnet"
)
func newGNetConn(conn gnet.Conn) *Conn {
return &Conn{
ip: conn.RemoteAddr().String(),
gn: conn,
write: func(data []byte) error {
return conn.AsyncWrite(data)
},
}
}
func newWebsocketConn(ws *websocket.Conn) *Conn {
return &Conn{
ws: ws,
write: func(data []byte) error {
return ws.WriteMessage(websocket.BinaryMessage, data)
},
}
}
type Conn struct {
ip string
ws *websocket.Conn
gn gnet.Conn
write func(data []byte) error
}
func (slf *Conn) Write(data []byte) error {
return slf.write(data)
}
func (slf *Conn) Close() {
if slf.ws != nil {
slf.ws.Close()
} else if slf.gn != nil {
slf.gn.Close()
}
slf.write = nil
}

12
server/errors.go Normal file
View File

@ -0,0 +1,12 @@
package server
import "errors"
var (
ErrConstructed = errors.New("the Server must be constructed using the server.New function")
ErrCanNotSupportNetwork = errors.New("can not support network")
ErrMessageTypePacketAttrs = errors.New("MessageTypePacket must contain *Conn and []byte")
ErrMessageTypeErrorAttrs = errors.New("MessageTypePacket must contain error and MessageErrorAction")
ErrNetworkOnlySupportHttp = errors.New("the current network mode is not compatible with HttpRouter, only NetworkHttp is supported")
ErrNetworkIncompatibleHttp = errors.New("the current network mode is not compatible with NetworkHttp")
)

65
server/event.go Normal file
View File

@ -0,0 +1,65 @@
package server
import (
"go.uber.org/zap"
"minotaur/utils/log"
)
type ReceivePacketEventHandle func(conn *Conn, packet []byte)
type ConnectionOpenedEventHandle func(conn *Conn)
type ConnectionClosedEventHandle func(conn *Conn)
type event struct {
*Server
receivePacketEventHandles []ReceivePacketEventHandle
connectionOpenedEventHandles []ConnectionOpenedEventHandle
connectionClosedEventHandles []ConnectionClosedEventHandle
}
// RegConnectionClosedEvent 在连接关闭后将立刻执行被注册的事件处理函数
func (slf *event) RegConnectionClosedEvent(handle ConnectionClosedEventHandle) {
if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp)
}
slf.connectionClosedEventHandles = append(slf.connectionClosedEventHandles, handle)
}
func (slf *event) OnConnectionClosedEvent(conn *Conn) {
for _, handle := range slf.connectionClosedEventHandles {
handle(conn)
}
}
// RegConnectionOpenedEvent 在连接打开后将立刻执行被注册的事件处理函数
func (slf *event) RegConnectionOpenedEvent(handle ConnectionOpenedEventHandle) {
if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp)
}
slf.connectionOpenedEventHandles = append(slf.connectionOpenedEventHandles, handle)
}
func (slf *event) OnConnectionOpenedEvent(conn *Conn) {
for _, handle := range slf.connectionOpenedEventHandles {
handle(conn)
}
}
// RegReceivePacketEvent 在接收到数据包时将立刻执行被注册的事件处理函数
func (slf *event) RegReceivePacketEvent(handle ReceivePacketEventHandle) {
if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp)
}
slf.receivePacketEventHandles = append(slf.receivePacketEventHandles, handle)
}
func (slf *event) OnReceivePacketEvent(conn *Conn, packet []byte) {
for _, handle := range slf.receivePacketEventHandles {
handle(conn, packet)
}
}
func (slf *event) check() {
if len(slf.receivePacketEventHandles) == 0 {
log.Warn("Server", zap.String("ReceivePacketEvent", "Invalid server, no packets processed"))
}
}

58
server/gserver.go Normal file
View File

@ -0,0 +1,58 @@
package server
import (
"github.com/panjf2000/gnet"
"minotaur/utils/synchronization"
"time"
)
type gServer struct {
*Server
connections *synchronization.Map[string, *Conn]
}
func (slf *gServer) OnInitComplete(server gnet.Server) (action gnet.Action) {
slf.connections = synchronization.NewMap[string, *Conn]()
return
}
func (slf *gServer) OnShutdown(server gnet.Server) {
for k := range slf.connections.Map() {
slf.connections.Delete(k)
}
slf.connections = nil
return
}
func (slf *gServer) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) {
conn := newGNetConn(c)
slf.connections.Set(c.RemoteAddr().String(), conn)
slf.OnConnectionOpenedEvent(conn)
return
}
func (slf *gServer) OnClosed(c gnet.Conn, err error) (action gnet.Action) {
slf.OnConnectionClosedEvent(slf.connections.DeleteGet(c.RemoteAddr().String()))
return
}
func (slf *gServer) PreWrite(c gnet.Conn) {
return
}
func (slf *gServer) AfterWrite(c gnet.Conn, b []byte) {
return
}
func (slf *gServer) React(packet []byte, c gnet.Conn) (out []byte, action gnet.Action) {
if conn, exist := slf.connections.GetExist(c.RemoteAddr().String()); exist {
slf.Server.PushMessage(MessageTypePacket, conn, packet)
return nil, gnet.None
} else {
return nil, gnet.Close
}
}
func (slf *gServer) Tick() (delay time.Duration, action gnet.Action) {
return
}

67
server/message.go Normal file
View File

@ -0,0 +1,67 @@
package server
const (
MessageTypePacket MessageType = iota
MessageTypeError
)
var messageNames = map[MessageType]string{
MessageTypePacket: "MessageTypePacket",
MessageTypeError: "MessageTypeError",
}
const (
MessageErrorActionNone MessageErrorAction = iota
MessageErrorActionShutdown
)
var messageErrorActionNames = map[MessageErrorAction]string{
MessageErrorActionNone: "None",
MessageErrorActionShutdown: "Shutdown",
}
type (
MessageType byte
MessageErrorAction byte
)
func (slf MessageErrorAction) String() string {
return messageErrorActionNames[slf]
}
type message struct {
t MessageType
attrs []any
}
func (slf MessageType) String() string {
return messageNames[slf]
}
func (slf MessageType) deconstructPacket(attrs ...any) (conn *Conn, packet []byte) {
if len(attrs) != 2 {
panic(ErrMessageTypePacketAttrs)
}
var ok bool
if conn, ok = attrs[0].(*Conn); !ok {
panic(ErrMessageTypePacketAttrs)
}
if packet, ok = attrs[1].([]byte); !ok {
panic(ErrMessageTypePacketAttrs)
}
return
}
func (slf MessageType) deconstructError(attrs ...any) (err error, action MessageErrorAction) {
if len(attrs) != 2 {
panic(ErrMessageTypeErrorAttrs)
}
var ok bool
if err, ok = attrs[0].(error); !ok {
panic(ErrMessageTypeErrorAttrs)
}
if action, ok = attrs[1].(MessageErrorAction); !ok {
panic(ErrMessageTypeErrorAttrs)
}
return
}

15
server/network.go Normal file
View File

@ -0,0 +1,15 @@
package server
type Network string
const (
NetworkTCP Network = "tcp"
NetworkTCP4 Network = "tcp4"
NetworkTCP6 Network = "tcp6"
NetworkUdp Network = "udp"
NetworkUdp4 Network = "udp4"
NetworkUdp6 Network = "udp6"
NetworkUnix Network = "unix"
NetworkHttp Network = "http"
NetworkWebsocket Network = "websocket"
)

203
server/server.go Normal file
View File

@ -0,0 +1,203 @@
package server
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/panjf2000/gnet"
"go.uber.org/zap"
"minotaur/utils/log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
func New(network Network) *Server {
server := &Server{
network: network,
}
server.event = &event{Server: server}
if network == NetworkHttp {
server.httpServer = gin.New()
}
return server
}
type Server struct {
*event
network Network
addr string
httpServer *gin.Engine
gServer *gServer
messageChannel chan *message
}
// Run 使用特定网络模式运行服务器
//
// server.NetworkTCP (addr:":8888")
// server.NetworkTCP4 (addr:":8888")
// server.NetworkTCP6 (addr:":8888")
// server.NetworkUDP (addr:":8888")
// server.NetworkUDP4 (addr:":8888")
// server.NetworkUDP6 (addr:":8888")
// server.Unix (addr:"socketPath")
// server.Http (addr:":8888")
// server.Websocket (addr:":8888/ws")
func (slf *Server) Run(addr string) error {
if slf.event == nil {
return ErrConstructed
}
slf.event.check()
slf.addr = addr
var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr)
var connectionInitHandle = func() {
slf.messageChannel = make(chan *message, 4096*1000)
if slf.network != NetworkHttp && slf.network != NetworkWebsocket {
slf.gServer = &gServer{Server: slf}
}
for message := range slf.messageChannel {
slf.dispatchMessage(message)
}
}
switch slf.network {
case NetworkTCP, NetworkTCP4, NetworkTCP6, NetworkUdp, NetworkUdp4, NetworkUdp6, NetworkUnix:
go connectionInitHandle()
go func() {
if err := gnet.Serve(slf.gServer, protoAddr); err != nil {
slf.PushMessage(MessageTypeError, err, MessageErrorActionShutdown)
}
}()
case NetworkHttp:
go func() {
if err := slf.httpServer.Run(addr); err != nil {
slf.PushMessage(MessageTypeError, err, MessageErrorActionShutdown)
}
}()
case NetworkWebsocket:
go connectionInitHandle()
var pattern string
var index = strings.Index(addr, "/")
if index == -1 {
pattern = "/"
} else {
pattern = addr[index:]
}
var upgrade = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
http.HandleFunc(pattern, func(writer http.ResponseWriter, request *http.Request) {
ip := request.Header.Get("X-Real-IP")
ws, err := upgrade.Upgrade(writer, request, nil)
if err != nil {
return
}
if len(ip) == 0 {
addr := ws.RemoteAddr().String()
if index := strings.LastIndex(addr, ":"); index != -1 {
ip = addr[0:index]
}
}
conn := newWebsocketConn(ws)
conn.ip = ip
defer func() {
if err := recover(); err != nil {
conn.Close()
slf.OnConnectionClosedEvent(conn)
}
}()
for {
if err := ws.SetReadDeadline(time.Now().Add(time.Second * 30)); err != nil {
panic(err)
}
_, packet, err := ws.ReadMessage()
if err != nil {
panic(err)
}
slf.PushMessage(MessageTypePacket, conn, packet)
}
})
go func() {
if err := http.ListenAndServe(slf.addr, nil); err != nil {
slf.PushMessage(MessageTypeError, err, MessageErrorActionShutdown)
}
}()
default:
return ErrCanNotSupportNetwork
}
log.Info("Server", zap.String("Minotaur Server", "===================================================================="))
log.Info("Server", zap.String("Minotaur Server", "RunningInfo"),
zap.Any("network", slf.network),
zap.String("listen", slf.addr),
)
log.Info("Server", zap.String("Minotaur Server", "===================================================================="))
systemSignal := make(chan os.Signal, 1)
signal.Notify(systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
select {
case <-systemSignal:
slf.Shutdown(nil)
}
return nil
}
func (slf *Server) Shutdown(err error) {
close(slf.messageChannel)
if err != nil {
log.Error("Server", zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err))
} else {
log.Info("Server", zap.String("action", "shutdown"), zap.String("state", "normal"))
}
}
func (slf *Server) HttpRouter() gin.IRouter {
if slf.httpServer == nil {
panic(ErrNetworkOnlySupportHttp)
}
return slf.httpServer
}
func (slf *Server) PushMessage(messageType MessageType, attrs ...any) {
slf.messageChannel <- &message{
t: messageType,
attrs: attrs,
}
}
func (slf *Server) dispatchMessage(msg *message) {
defer func() {
if err := recover(); err != nil {
log.Error("Server", zap.Any("error", err))
}
}()
switch msg.t {
case MessageTypePacket:
conn, packet := msg.t.deconstructPacket(msg.attrs...)
slf.OnReceivePacketEvent(conn, packet)
case MessageTypeError:
err, action := msg.t.deconstructError(msg.attrs...)
switch action {
case MessageErrorActionNone:
log.Error("Server", zap.Error(err))
case MessageErrorActionShutdown:
slf.Shutdown(err)
default:
log.Warn("Server", zap.String("not support message error action", action.String()))
}
default:
log.Warn("Server", zap.String("not support message type", msg.t.String()))
}
}

24
utils/file/file.go Normal file
View File

@ -0,0 +1,24 @@
package file
import "os"
// PathExist 路径是否存在
func PathExist(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// IsDir 路径是否是文件夹
func IsDir(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err == nil {
return fileInfo.IsDir(), nil
}
return false, err
}

View File

@ -1,6 +1,8 @@
package g2d
import "minotaur/utils/g2d/matrix"
import (
"minotaur/utils/g2d/matrix"
)
// NewMatrix 生成特定宽高的二维矩阵
func NewMatrix[T any](width, height int) *matrix.Matrix[T] {

View File

@ -1,43 +0,0 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"minotaur/utils/log"
"net/http"
"runtime/debug"
)
// Cors 设置跨域
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin") // 请求头部
if origin != "" {
// 接收客户端发送的origin (重要!)
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
// 服务器支持的所有跨域请求的方法
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
// 允许跨域设置可以返回其他子段,可以自定义字段
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session")
// 允许浏览器(客户端)可以解析的头部 (重要)
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
// 设置缓存时间
// c.Header("Access-Control-Max-Age", "172800")
// 允许客户端传递校验信息比如 cookie (重要)
c.Header("Access-Control-Allow-Credentials", "true")
}
// 允许类型校验
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
defer func() {
if err := recover(); err != nil {
log.Error("Panic info is", zap.Any("err", err), zap.Any("stack\n", string(debug.Stack())))
}
}()
c.Next()
}
}

View File

@ -1,28 +0,0 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)
func Logger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}

View File

@ -12,11 +12,6 @@ import (
var (
Logger *zap.Logger
Info func(msg string, fields ...zap.Field)
Warn func(msg string, fields ...zap.Field)
Debug func(msg string, fields ...zap.Field)
Error func(msg string, fields ...zap.Field)
)
const (
@ -27,11 +22,6 @@ const (
func init() {
Logger = newLogger()
Info = Logger.Info
Warn = Logger.Warn
Debug = Logger.Debug
Error = Logger.Error
}
func newLogger() *zap.Logger {
@ -94,3 +84,19 @@ func getWriter(filename string, times int32) io.Writer {
}
return hook
}
func Info(msg string, fields ...zap.Field) {
Logger.Info(msg, fields...)
}
func Warn(msg string, fields ...zap.Field) {
Logger.Warn(msg, fields...)
}
func Debug(msg string, fields ...zap.Field) {
Logger.Debug(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
Logger.Error(msg, fields...)
}

49
utils/runtimes/path.go Normal file
View File

@ -0,0 +1,49 @@
package runtimes
import (
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
)
// GetWorkingDir 获取工作目录绝对路径
func GetWorkingDir() string {
dir := GetExecutablePathByBuild()
if strings.Contains(dir, GetTempDir()) {
return GetExecutablePathByCaller()
}
return dir
}
// GetTempDir 获取系统临时目录
func GetTempDir() string {
dir := os.Getenv("TEMP")
if dir == "" {
dir = os.Getenv("TMP")
}
res, _ := filepath.EvalSymlinks(dir)
return res
}
// GetExecutablePathByBuild 获取当前执行文件绝对路径go build
func GetExecutablePathByBuild() string {
exePath, err := os.Executable()
if err != nil {
log.Fatal(err)
}
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
return res
}
// GetExecutablePathByCaller 获取当前执行文件绝对路径go run
func GetExecutablePathByCaller() string {
var abPath string
_, filename, _, ok := runtime.Caller(3)
if ok {
abPath = path.Dir(filename)
}
return abPath
}

37
utils/sole/guid.go Normal file
View File

@ -0,0 +1,37 @@
package sole
import "sync"
var (
global int64
namespace map[any]int64
mutex sync.Mutex
)
func init() {
namespace = map[any]int64{}
}
func Get() int64 {
global++
return global
}
func GetWith(name any) int64 {
namespace[name]++
return namespace[name]
}
func GetSync() int64 {
mutex.Lock()
defer mutex.Unlock()
global++
return global
}
func GetSyncWith(name any) int64 {
mutex.Lock()
defer mutex.Unlock()
namespace[name]++
return namespace[name]
}

29
utils/sole/sonyflake.go Normal file
View File

@ -0,0 +1,29 @@
package sole
import "github.com/sony/sonyflake"
var sonyflakeGenerator *sonyflake.Sonyflake
func init() {
sonyflakeGenerator = sonyflake.NewSonyflake(sonyflake.Settings{})
}
// SonyflakeIDE 获取一个雪花id
func SonyflakeIDE() (int64, error) {
id, err := sonyflakeGenerator.NextID()
return int64(id), err
}
// SonyflakeID 获取一个雪花id
func SonyflakeID() int64 {
id, err := sonyflakeGenerator.NextID()
if err != nil {
panic(err)
}
return int64(id)
}
// SonyflakeSetting 配置雪花id生成策略
func SonyflakeSetting(settings sonyflake.Settings) {
sonyflakeGenerator = sonyflake.NewSonyflake(settings)
}

View File

@ -0,0 +1,162 @@
package synchronization
import "sync"
func NewMap[Key comparable, value any]() *Map[Key, value] {
return &Map[Key, value]{
data: make(map[Key]value),
}
}
type Map[Key comparable, Value any] struct {
lock sync.RWMutex
data map[Key]Value
}
func (slf *Map[Key, Value]) Set(key Key, value Value) {
slf.lock.Lock()
defer slf.lock.Unlock()
slf.data[key] = value
}
func (slf *Map[Key, Value]) Get(key Key) Value {
slf.lock.RLock()
defer slf.lock.RUnlock()
return slf.data[key]
}
func (slf *Map[Key, Value]) Exist(key Key) bool {
slf.lock.RLock()
_, exist := slf.data[key]
slf.lock.RUnlock()
return exist
}
func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) {
slf.lock.RLock()
value, exist := slf.data[key]
slf.lock.RUnlock()
return value, exist
}
func (slf *Map[Key, Value]) Length() int {
slf.lock.RLock()
defer slf.lock.RUnlock()
return len(slf.data)
}
func (slf *Map[Key, Value]) Delete(key Key) {
slf.lock.Lock()
defer slf.lock.Unlock()
delete(slf.data, key)
}
func (slf *Map[Key, Value]) DeleteGet(key Key) Value {
slf.lock.Lock()
defer slf.lock.Unlock()
v := slf.data[key]
delete(slf.data, key)
return v
}
func (slf *Map[Key, Value]) DeleteGetExist(key Key) (Value, bool) {
slf.lock.Lock()
defer slf.lock.Unlock()
v, exist := slf.data[key]
delete(slf.data, key)
return v, exist
}
func (slf *Map[Key, Value]) DeleteExist(key Key) bool {
slf.lock.Lock()
if _, exist := slf.data[key]; !exist {
slf.lock.Unlock()
return exist
}
delete(slf.data, key)
slf.lock.Unlock()
return true
}
func (slf *Map[Key, Value]) Range(handle func(key Key, value Value)) {
slf.lock.RLock()
defer slf.lock.RUnlock()
for k, v := range slf.data {
key, value := k, v
handle(key, value)
}
}
func (slf *Map[Key, Value]) RangeSkip(handle func(key Key, value Value) bool) {
slf.lock.RLock()
defer slf.lock.RUnlock()
for k, v := range slf.data {
key, value := k, v
if !handle(key, value) {
continue
}
}
}
func (slf *Map[Key, Value]) RangeBreakout(handle func(key Key, value Value) bool) {
slf.lock.RLock()
defer slf.lock.RUnlock()
for k, v := range slf.data {
key, value := k, v
if !handle(key, value) {
break
}
}
}
func (slf *Map[Key, Value]) RangeFree(handle func(key Key, value Value, skip func(), breakout func())) {
var skipExec, breakoutExec bool
var skip = func() {
skipExec = true
}
var breakout = func() {
breakoutExec = true
}
slf.lock.RLock()
defer slf.lock.RUnlock()
for k, v := range slf.data {
key, value := k, v
handle(key, value, skip, breakout)
if skipExec {
continue
}
if breakoutExec {
break
}
}
}
func (slf *Map[Key, Value]) Keys() []Key {
slf.lock.RLock()
var s = make([]Key, 0, len(slf.data))
for k, _ := range slf.data {
s = append(s, k)
}
slf.lock.RUnlock()
return s
}
func (slf *Map[Key, Value]) Slice() []Value {
slf.lock.RLock()
var s = make([]Value, 0, len(slf.data))
for _, v := range slf.data {
s = append(s, v)
}
slf.lock.RUnlock()
return s
}
func (slf *Map[Key, Value]) Map() map[Key]Value {
var m = make(map[Key]Value)
slf.lock.RLock()
for k, v := range slf.data {
m[k] = v
}
slf.lock.RUnlock()
return m
}