init commit
This commit is contained in:
commit
3248cc9682
|
@ -0,0 +1,166 @@
|
||||||
|
### Minotaur
|
||||||
|
app/monopoly/
|
||||||
|
|
||||||
|
### VisualStudioCode template
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Linux template
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### Windows template
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
### macOS template
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"minotaur/game"
|
||||||
|
)
|
||||||
|
|
||||||
|
var State = new(game.StateMachine).Init()
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"minotaur/app/template/app"
|
||||||
|
"minotaur/game"
|
||||||
|
"minotaur/game/gateway/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())
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
// Conn 用户连接抽象
|
||||||
|
//
|
||||||
|
// 连接支持使用 tag 进行参数读取
|
||||||
|
type Conn interface {
|
||||||
|
GetConn() any
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package feature
|
||||||
|
|
||||||
|
// Actor 玩家演员接口定义
|
||||||
|
type Actor interface {
|
||||||
|
// GetGuid 获取演员 guid
|
||||||
|
GetGuid() int64
|
||||||
|
// GetPlayer 获取所属玩家
|
||||||
|
GetPlayer() Player
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package feature
|
||||||
|
|
||||||
|
// ChatRoom 聊天室接口定义
|
||||||
|
type ChatRoom interface {
|
||||||
|
// JoinChatRoom 加入聊天室
|
||||||
|
JoinChatRoom(chat PlayerChat)
|
||||||
|
// LeaveChatRoom 离开聊天室
|
||||||
|
LeaveChatRoom(chat PlayerChat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayerChat 玩家聊天接口定义
|
||||||
|
type PlayerChat interface {
|
||||||
|
Player
|
||||||
|
// SendChatMessage 发送聊天消息给该玩家
|
||||||
|
SendChatMessage(message string)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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]]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package feature
|
||||||
|
|
||||||
|
// Player 玩家接口定义
|
||||||
|
type Player interface {
|
||||||
|
// GetGuid 获取玩家 guid
|
||||||
|
GetGuid() int64
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package feature
|
||||||
|
|
||||||
|
// RoomGame 房间游戏接口定义
|
||||||
|
type RoomGame[P Player] interface {
|
||||||
|
Room[P]
|
||||||
|
// GameStart 游戏开始
|
||||||
|
GameStart(startTimeSecond int64, loop func(game RoomGame[P]))
|
||||||
|
// GameOver 游戏结束
|
||||||
|
GameOver()
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package standard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"minotaur/game"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
*game.Player
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package feature
|
||||||
|
|
||||||
|
import "minotaur/utils/timer"
|
||||||
|
|
||||||
|
// Timer 定时器接口定义
|
||||||
|
type Timer interface {
|
||||||
|
// Timer 获取定时器
|
||||||
|
Timer() *timer.Manager
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
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 {
|
||||||
|
server *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("Websocket Upgrade error", 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("ServeWebSocket", zap.Error(err))
|
||||||
|
context.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
playerGuid, err := stateMachine.sonyflake.NextID()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GuidGenerateFailed", 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.server = &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
log.Info("GatewayStart", zap.String("listen", slf.server.Addr))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := slf.server.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.server.Shutdown(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("GatewayShutdown", zap.String("stateMachine", "normal"))
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
// PushFail 消息推送失败信息
|
||||||
|
type PushFail struct {
|
||||||
|
Player *Player
|
||||||
|
Data []byte
|
||||||
|
Err error
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
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]))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
module minotaur
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
|
github.com/gin-gonic/gin v1.9.0
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
|
go.uber.org/zap v1.24.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 // 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/goccy/go-json v0.10.0 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
github.com/lestrrat-go/strftime v1.0.6 // 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/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/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||||
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
|
golang.org/x/crypto v0.5.0 // indirect
|
||||||
|
golang.org/x/net v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.7.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,143 @@
|
||||||
|
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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
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=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
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=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
|
||||||
|
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
|
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||||
|
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
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/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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
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/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/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/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/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=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
|
||||||
|
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/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/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/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.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=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
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/sony/sonyflake v1.1.0 h1:wnrEcL3aOkWmPlhScLEGAXKkLAIslnBteNUq4Bw6MM4=
|
||||||
|
github.com/sony/sonyflake v1.1.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
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=
|
||||||
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
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/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-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
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-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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
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=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
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/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=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,57 @@
|
||||||
|
package bypassflow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"minotaur/utils/hash"
|
||||||
|
"minotaur/utils/log"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BypassFlow 分流器
|
||||||
|
type BypassFlow struct {
|
||||||
|
consistency *hash.Consistency
|
||||||
|
processor []chan func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, nodeCount, nodeBuffer int) *BypassFlow {
|
||||||
|
bypassFlow := &BypassFlow{
|
||||||
|
consistency: &hash.Consistency{},
|
||||||
|
processor: make([]chan func(), nodeCount),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nodeCount; i++ {
|
||||||
|
bypassFlow.consistency.AddNode(i)
|
||||||
|
|
||||||
|
nodeChan := make(chan func(), nodeBuffer)
|
||||||
|
bypassFlow.processor[i] = nodeChan
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-nodeChan:
|
||||||
|
go func() {
|
||||||
|
f()
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("BypassFlow", zap.Any("error", err), zap.Any("stack\n", debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
case <-ctx.Done():
|
||||||
|
close(nodeChan)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return bypassFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *BypassFlow) getNode(item Item) int {
|
||||||
|
return slf.consistency.PickNode(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *BypassFlow) Handle(item Item, handleFunc func()) {
|
||||||
|
node := slf.getNode(item)
|
||||||
|
slf.processor[node] <- handleFunc
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package bypassflow
|
||||||
|
|
||||||
|
type Item interface {
|
||||||
|
UniqueIdentification() any
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package g2d
|
|
@ -0,0 +1,8 @@
|
||||||
|
package g2d
|
||||||
|
|
||||||
|
import "minotaur/utils/g2d/matrix"
|
||||||
|
|
||||||
|
// NewMatrix 生成特定宽高的二维矩阵
|
||||||
|
func NewMatrix[T any](width, height int) *matrix.Matrix[T] {
|
||||||
|
return matrix.NewMatrix[T](width, height)
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package matrix
|
||||||
|
|
||||||
|
// NewMatrix 生成特定宽高的二维矩阵
|
||||||
|
func NewMatrix[T any](width, height int) *Matrix[T] {
|
||||||
|
matrix := &Matrix[T]{
|
||||||
|
w: width, h: height,
|
||||||
|
}
|
||||||
|
matrix.m = make([][]T, width)
|
||||||
|
for x := 0; x < len(matrix.m); x++ {
|
||||||
|
matrix.m[x] = make([]T, height)
|
||||||
|
}
|
||||||
|
return matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix 二维矩阵
|
||||||
|
type Matrix[T any] struct {
|
||||||
|
w, h int
|
||||||
|
m [][]T
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWidth 获取二维矩阵宽度
|
||||||
|
func (slf *Matrix[T]) GetWidth() int {
|
||||||
|
return slf.w
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeight 获取二维矩阵高度
|
||||||
|
func (slf *Matrix[T]) GetHeight() int {
|
||||||
|
return slf.h
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMatrix 获取二维矩阵
|
||||||
|
func (slf *Matrix[T]) GetMatrix() [][]T {
|
||||||
|
return slf.m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取特定坐标的内容
|
||||||
|
func (slf *Matrix[T]) Get(x, y int) T {
|
||||||
|
return slf.m[x][y]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置特定坐标的内容
|
||||||
|
func (slf *Matrix[T]) Set(x, y int, data T) {
|
||||||
|
slf.m[x][y] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap 交换两个位置的内容
|
||||||
|
func (slf *Matrix[T]) Swap(x1, y1, x2, y2 int) {
|
||||||
|
a, b := slf.Get(x1, y1), slf.Get(x2, y2)
|
||||||
|
slf.m[x1][y1], slf.m[x2][y2] = b, a
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrySwap 尝试交换两个位置的内容,交换后不满足表达式时进行撤销
|
||||||
|
func (slf *Matrix[T]) TrySwap(x1, y1, x2, y2 int, expression bool) {
|
||||||
|
a, b := slf.Get(x1, y1), slf.Get(x2, y2)
|
||||||
|
slf.m[x1][y1], slf.m[x2][y2] = b, a
|
||||||
|
if expression {
|
||||||
|
slf.m[x1][y1], slf.m[x2][y2] = a, b
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Consistency 一致性哈希生成
|
||||||
|
//
|
||||||
|
// https://blog.csdn.net/zhpCSDN921011/article/details/126845397
|
||||||
|
type Consistency struct {
|
||||||
|
Replicas int // 虚拟节点的数量
|
||||||
|
keys []int // 所有虚拟节点的哈希值
|
||||||
|
hashMap map[int]int // 虚拟节点的哈希值: 节点(虚拟节点映射到真实节点)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNode 添加节点
|
||||||
|
func (slf *Consistency) AddNode(keys ...int) {
|
||||||
|
if slf.hashMap == nil {
|
||||||
|
slf.hashMap = map[int]int{}
|
||||||
|
}
|
||||||
|
if slf.Replicas == 0 {
|
||||||
|
slf.Replicas = 3
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
for i := 0; i < slf.Replicas; i++ {
|
||||||
|
// 计算虚拟节点哈希值
|
||||||
|
hash := int(crc32.ChecksumIEEE([]byte(strconv.Itoa(i) + strconv.Itoa(key))))
|
||||||
|
|
||||||
|
// 存储虚拟节点哈希值
|
||||||
|
slf.keys = append(slf.keys, hash)
|
||||||
|
|
||||||
|
// 存入map做映射
|
||||||
|
slf.hashMap[hash] = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//排序哈希值,下面匹配的时候要二分搜索
|
||||||
|
sort.Ints(slf.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickNode 获取与 key 最接近的节点
|
||||||
|
func (slf *Consistency) PickNode(key any) int {
|
||||||
|
if len(slf.keys) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
partitionKey := fmt.Sprintf("%#v", key)
|
||||||
|
beg := strings.Index(partitionKey, "{")
|
||||||
|
end := strings.Index(partitionKey, "}")
|
||||||
|
if beg != -1 && !(end == -1 || end == beg+1) {
|
||||||
|
partitionKey = partitionKey[beg+1 : end]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算传入key的哈希值
|
||||||
|
hash := int(crc32.ChecksumIEEE([]byte(partitionKey)))
|
||||||
|
|
||||||
|
// sort.Search使用二分查找满足m.Keys[i]>=hash的最小哈希值
|
||||||
|
idx := sort.Search(len(slf.keys), func(i int) bool { return slf.keys[i] >= hash })
|
||||||
|
|
||||||
|
// 若key的hash值大于最后一个虚拟节点的hash值,则选择第一个虚拟节点
|
||||||
|
if idx == len(slf.keys) {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return slf.hashMap[slf.keys[idx]]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (
|
||||||
|
debug = true
|
||||||
|
logPath = "./logs"
|
||||||
|
logTime = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Logger = newLogger()
|
||||||
|
|
||||||
|
Info = Logger.Info
|
||||||
|
Warn = Logger.Warn
|
||||||
|
Debug = Logger.Debug
|
||||||
|
Error = Logger.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogger() *zap.Logger {
|
||||||
|
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||||||
|
MessageKey: "msg",
|
||||||
|
LevelKey: "level",
|
||||||
|
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||||
|
TimeKey: "ts",
|
||||||
|
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||||
|
enc.AppendString(t.Format("2006-01-02 15:04:05"))
|
||||||
|
},
|
||||||
|
CallerKey: "file",
|
||||||
|
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||||
|
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
|
||||||
|
enc.AppendInt64(int64(d) / 1000000)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl == zapcore.InfoLevel
|
||||||
|
})
|
||||||
|
debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl <= zapcore.FatalLevel
|
||||||
|
})
|
||||||
|
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl >= zapcore.ErrorLevel
|
||||||
|
})
|
||||||
|
|
||||||
|
var cores zapcore.Core
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
cores = zapcore.NewTee(
|
||||||
|
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
infoWriter := getWriter(fmt.Sprintf("%s/info.log", logPath), logTime)
|
||||||
|
warnWriter := getWriter(fmt.Sprintf("%s/error.log", logPath), logTime)
|
||||||
|
cores = zapcore.NewTee(
|
||||||
|
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
|
||||||
|
zapcore.NewCore(encoder, zapcore.AddSync(warnWriter), warnLevel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zap.New(cores, zap.AddCaller())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWriter(filename string, times int32) io.Writer {
|
||||||
|
// 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH
|
||||||
|
// demo.log是指向最新日志的链接
|
||||||
|
// //保存7天内的日志,每天分割一次日志
|
||||||
|
hook, err := rotatelogs.New(
|
||||||
|
filename+".%Y%m%d", // 没有使用go风格反人类的format格式
|
||||||
|
rotatelogs.WithLinkName(filename),
|
||||||
|
rotatelogs.WithMaxAge(time.Hour*24*7),
|
||||||
|
rotatelogs.WithRotationTime(time.Hour*time.Duration(times)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return hook
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package super
|
||||||
|
|
||||||
|
func If[T any](expression bool, t T, f T) T {
|
||||||
|
if expression {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 时间轮一刻度长度
|
||||||
|
timingWheelTick = time.Millisecond * 10
|
||||||
|
)
|
|
@ -0,0 +1,104 @@
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RussellLuo/timingwheel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager 管理器
|
||||||
|
type Manager struct {
|
||||||
|
timer *Timer
|
||||||
|
wheel *timingwheel.TimingWheel
|
||||||
|
timers map[string]*Scheduler
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release 释放管理器,并将管理器重新放回 Timer 池中
|
||||||
|
func (slf *Manager) Release() {
|
||||||
|
slf.timer.lock.Lock()
|
||||||
|
defer slf.timer.lock.Unlock()
|
||||||
|
|
||||||
|
slf.lock.Lock()
|
||||||
|
|
||||||
|
for name, scheduler := range slf.timers {
|
||||||
|
scheduler.close()
|
||||||
|
delete(slf.timers, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
slf.lock.Unlock()
|
||||||
|
|
||||||
|
slf.timer.Managers = append(slf.timer.Managers, slf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTimer 停止特定名称的调度器
|
||||||
|
func (slf *Manager) StopTimer(name string) {
|
||||||
|
slf.lock.Lock()
|
||||||
|
defer slf.lock.Unlock()
|
||||||
|
|
||||||
|
if s, ok := slf.timers[name]; ok {
|
||||||
|
s.close()
|
||||||
|
delete(slf.timers, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStopped 特定名称的调度器是否已停止
|
||||||
|
func (slf *Manager) IsStopped(name string) bool {
|
||||||
|
slf.lock.RLock()
|
||||||
|
defer slf.lock.RUnlock()
|
||||||
|
if s, ok := slf.timers[name]; ok {
|
||||||
|
return s.isClosed()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSchedulers 获取所有调度器名称
|
||||||
|
func (slf *Manager) GetSchedulers() []string {
|
||||||
|
slf.lock.RLock()
|
||||||
|
defer slf.lock.RUnlock()
|
||||||
|
names := make([]string, 0, len(slf.timers))
|
||||||
|
for name := range slf.timers {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// After 设置一个在特定时间后运行一次的调度器
|
||||||
|
func (slf *Manager) After(name string, after time.Duration, handleFunc interface{}, args ...interface{}) {
|
||||||
|
slf.Loop(name, after, timingWheelTick, 1, handleFunc, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop 设置一个在特定时间后仿佛运行的调度器
|
||||||
|
func (slf *Manager) Loop(name string, after, interval time.Duration, times int, handleFunc interface{}, args ...interface{}) {
|
||||||
|
slf.StopTimer(name)
|
||||||
|
|
||||||
|
if after < timingWheelTick {
|
||||||
|
after = timingWheelTick
|
||||||
|
}
|
||||||
|
if interval < timingWheelTick {
|
||||||
|
interval = timingWheelTick
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = make([]reflect.Value, len(args))
|
||||||
|
for i, v := range args {
|
||||||
|
values[i] = reflect.ValueOf(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler := &Scheduler{
|
||||||
|
name: name,
|
||||||
|
after: after,
|
||||||
|
interval: interval,
|
||||||
|
total: times,
|
||||||
|
cbFunc: reflect.ValueOf(handleFunc),
|
||||||
|
cbArgs: values,
|
||||||
|
manager: slf,
|
||||||
|
}
|
||||||
|
|
||||||
|
slf.lock.Lock()
|
||||||
|
slf.timers[name] = scheduler
|
||||||
|
scheduler.timer = slf.wheel.ScheduleFunc(scheduler, scheduler.caller)
|
||||||
|
slf.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RussellLuo/timingwheel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scheduler 调度器
|
||||||
|
type Scheduler struct {
|
||||||
|
name string
|
||||||
|
after time.Duration
|
||||||
|
interval time.Duration
|
||||||
|
|
||||||
|
total int
|
||||||
|
trigger int
|
||||||
|
kill bool
|
||||||
|
|
||||||
|
cbFunc reflect.Value
|
||||||
|
cbArgs []reflect.Value
|
||||||
|
|
||||||
|
timer *timingwheel.Timer
|
||||||
|
|
||||||
|
manager *Manager
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name 获取调度器名称
|
||||||
|
func (slf *Scheduler) Name() string {
|
||||||
|
return slf.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next 获取下一次执行的时间
|
||||||
|
func (slf *Scheduler) Next(prev time.Time) time.Time {
|
||||||
|
slf.lock.RLock()
|
||||||
|
defer slf.lock.RUnlock()
|
||||||
|
|
||||||
|
if slf.kill || (slf.total > 0 && slf.trigger >= slf.total) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
if slf.trigger == 0 {
|
||||||
|
return prev.Add(slf.after)
|
||||||
|
}
|
||||||
|
return prev.Add(slf.interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际执行的任务
|
||||||
|
func (slf *Scheduler) caller() {
|
||||||
|
// TODO: 直接调用可能会导致更高的并发复杂度
|
||||||
|
slf.Caller()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller 可由外部发起调用的执行函数
|
||||||
|
func (slf *Scheduler) Caller() {
|
||||||
|
slf.lock.Lock()
|
||||||
|
|
||||||
|
if slf.kill {
|
||||||
|
slf.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slf.trigger++
|
||||||
|
if slf.total > 0 && slf.trigger >= slf.total {
|
||||||
|
slf.lock.Unlock()
|
||||||
|
slf.manager.StopTimer(slf.name)
|
||||||
|
} else {
|
||||||
|
slf.lock.Unlock()
|
||||||
|
}
|
||||||
|
slf.cbFunc.Call(slf.cbArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isClosed 检查调度器是否已关闭
|
||||||
|
func (slf *Scheduler) isClosed() bool {
|
||||||
|
slf.lock.RLock()
|
||||||
|
defer slf.lock.RUnlock()
|
||||||
|
|
||||||
|
return slf.kill
|
||||||
|
}
|
||||||
|
|
||||||
|
// close 关闭调度器
|
||||||
|
func (slf *Scheduler) close() {
|
||||||
|
slf.lock.Lock()
|
||||||
|
defer slf.lock.Unlock()
|
||||||
|
|
||||||
|
if slf.kill {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slf.kill = true
|
||||||
|
if slf.total <= 0 || slf.trigger < slf.total {
|
||||||
|
slf.timer.Stop()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/RussellLuo/timingwheel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var timer = new(Timer)
|
||||||
|
|
||||||
|
func GetManager(size int) *Manager {
|
||||||
|
return timer.NewManager(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timer struct {
|
||||||
|
Managers []*Manager
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *Timer) NewManager(size int) *Manager {
|
||||||
|
slf.lock.Lock()
|
||||||
|
defer slf.lock.Unlock()
|
||||||
|
|
||||||
|
var manager *Manager
|
||||||
|
if len(slf.Managers) > 0 {
|
||||||
|
manager = slf.Managers[0]
|
||||||
|
slf.Managers = slf.Managers[1:]
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
manager = &Manager{
|
||||||
|
timer: slf,
|
||||||
|
wheel: timingwheel.NewTimingWheel(timingWheelTick, int64(size)),
|
||||||
|
timers: make(map[string]*Scheduler),
|
||||||
|
}
|
||||||
|
manager.wheel.Start()
|
||||||
|
return manager
|
||||||
|
}
|
Loading…
Reference in New Issue