init commit

This commit is contained in:
kercylan98 2023-04-07 11:21:50 +08:00
commit 3248cc9682
51 changed files with 2723 additions and 0 deletions

166
.gitignore vendored Normal file
View File

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

7
app/template/app/app.go Normal file
View File

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

19
app/template/main.go Normal file
View File

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

45
app/template/main_test.go Normal file
View File

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

183
game/channel.go Normal file
View File

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

8
game/conn.go Normal file
View File

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

9
game/feature/actor.go Normal file
View File

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

16
game/feature/chatroom.go Normal file
View File

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

View File

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

7
game/feature/player.go Normal file
View File

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

21
game/feature/room.go Normal file
View File

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

10
game/feature/room_game.go Normal file
View File

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

View File

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

View File

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

20
game/feature/room_team.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

9
game/feature/timer.go Normal file
View File

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

22
game/gametime.go Normal file
View File

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

147
game/gateway.go Normal file
View File

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

View File

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

34
game/message.go Normal file
View File

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

192
game/player.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

8
game/pushfail.go Normal file
View File

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

123
game/router.go Normal file
View File

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

150
game/statemachine.go Normal file
View File

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

44
go.mod Normal file
View File

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

143
go.sum Normal file
View File

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

View File

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

5
utils/bypassflow/item.go Normal file
View File

@ -0,0 +1,5 @@
package bypassflow
type Item interface {
UniqueIdentification() any
}

1
utils/g2d/coordinate.go Normal file
View File

@ -0,0 +1 @@
package g2d

8
utils/g2d/g2d.go Normal file
View File

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

View File

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

View File

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

View File

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

71
utils/hash/consistency.go Normal file
View File

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

96
utils/log/log.go Normal file
View File

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

8
utils/super/if.go Normal file
View File

@ -0,0 +1,8 @@
package super
func If[T any](expression bool, t T, f T) T {
if expression {
return t
}
return f
}

8
utils/timer/constants.go Normal file
View File

@ -0,0 +1,8 @@
package timer
import "time"
var (
// 时间轮一刻度长度
timingWheelTick = time.Millisecond * 10
)

104
utils/timer/manager.go Normal file
View File

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

95
utils/timer/scheduler.go Normal file
View File

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

38
utils/timer/timer.go Normal file
View File

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