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