This commit is contained in:
163
common/rpc/pvp_match.go
Normal file
163
common/rpc/pvp_match.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/fight/pvpwire"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
pvpMatchQueueTTL = 12 * time.Second
|
||||
pvpMatchBanPickSecond = 45
|
||||
)
|
||||
|
||||
type PVPMatchJoinPayload struct {
|
||||
RuntimeServerID uint32 `json:"runtimeServerId"`
|
||||
UserID uint32 `json:"userId"`
|
||||
Nick string `json:"nick"`
|
||||
FightMode uint32 `json:"fightMode"`
|
||||
Status uint32 `json:"status"`
|
||||
CatchTimes []uint32 `json:"catchTimes"`
|
||||
}
|
||||
|
||||
type pvpMatchCoordinator struct {
|
||||
mu sync.Mutex
|
||||
queues map[uint32][]pvpwire.QueuePlayerSnapshot
|
||||
lastSeen map[uint32]time.Time
|
||||
}
|
||||
|
||||
var defaultPVPMatchCoordinator = &pvpMatchCoordinator{
|
||||
queues: make(map[uint32][]pvpwire.QueuePlayerSnapshot),
|
||||
lastSeen: make(map[uint32]time.Time),
|
||||
}
|
||||
|
||||
func DefaultPVPMatchCoordinator() *pvpMatchCoordinator {
|
||||
return defaultPVPMatchCoordinator
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) JoinOrUpdate(payload PVPMatchJoinPayload) error {
|
||||
if payload.UserID == 0 || payload.RuntimeServerID == 0 || payload.FightMode == 0 {
|
||||
return fmt.Errorf("invalid pvp match payload: uid=%d server=%d mode=%d", payload.UserID, payload.RuntimeServerID, payload.FightMode)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
player := pvpwire.QueuePlayerSnapshot{
|
||||
RuntimeServerID: payload.RuntimeServerID,
|
||||
UserID: payload.UserID,
|
||||
Nick: payload.Nick,
|
||||
FightMode: payload.FightMode,
|
||||
Status: payload.Status,
|
||||
JoinedAtUnix: now.Unix(),
|
||||
CatchTimes: append([]uint32(nil), payload.CatchTimes...),
|
||||
}
|
||||
|
||||
var match *pvpwire.MatchFoundPayload
|
||||
|
||||
m.mu.Lock()
|
||||
m.pruneExpiredLocked(now)
|
||||
m.removeUserLocked(payload.UserID)
|
||||
m.lastSeen[payload.UserID] = now
|
||||
|
||||
queue := m.queues[payload.FightMode]
|
||||
if len(queue) > 0 {
|
||||
host := queue[0]
|
||||
queue = queue[1:]
|
||||
m.queues[payload.FightMode] = queue
|
||||
delete(m.lastSeen, host.UserID)
|
||||
delete(m.lastSeen, payload.UserID)
|
||||
|
||||
result := pvpwire.MatchFoundPayload{
|
||||
SessionID: buildPVPMatchSessionID(host.UserID, payload.UserID),
|
||||
Stage: pvpwire.StageBanPick,
|
||||
Host: host,
|
||||
Guest: player,
|
||||
BanPickTimeout: pvpMatchBanPickSecond,
|
||||
}
|
||||
match = &result
|
||||
} else {
|
||||
m.queues[payload.FightMode] = append(queue, player)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
if match == nil {
|
||||
return nil
|
||||
}
|
||||
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Host.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
|
||||
return err
|
||||
}
|
||||
if match.Guest.RuntimeServerID != match.Host.RuntimeServerID {
|
||||
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Guest.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) Cancel(userID uint32) {
|
||||
if userID == 0 {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
delete(m.lastSeen, userID)
|
||||
m.removeUserLocked(userID)
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) pruneExpiredLocked(now time.Time) {
|
||||
for mode, queue := range m.queues {
|
||||
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
|
||||
for _, queued := range queue {
|
||||
last := m.lastSeen[queued.UserID]
|
||||
if last.IsZero() || now.Sub(last) > pvpMatchQueueTTL {
|
||||
delete(m.lastSeen, queued.UserID)
|
||||
continue
|
||||
}
|
||||
next = append(next, queued)
|
||||
}
|
||||
m.queues[mode] = next
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) removeUserLocked(userID uint32) {
|
||||
for mode, queue := range m.queues {
|
||||
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
|
||||
for _, queued := range queue {
|
||||
if queued.UserID == userID {
|
||||
continue
|
||||
}
|
||||
next = append(next, queued)
|
||||
}
|
||||
m.queues[mode] = next
|
||||
}
|
||||
}
|
||||
|
||||
func publishPVPMatchMessage(topic, msgType string, body any) error {
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envelope, err := json.Marshal(pvpwire.Envelope{
|
||||
Type: msgType,
|
||||
Body: payload,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := cool.Redis.Conn(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close(context.Background())
|
||||
|
||||
_, err = conn.Do(context.Background(), "publish", topic, envelope)
|
||||
return err
|
||||
}
|
||||
|
||||
func buildPVPMatchSessionID(hostUserID, guestUserID uint32) string {
|
||||
return fmt.Sprintf("xsvr-%d-%d-%d", hostUserID, guestUserID, time.Now().UnixNano())
|
||||
}
|
||||
@@ -98,6 +98,15 @@ func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error
|
||||
|
||||
}
|
||||
|
||||
func (*ServerHandler) MatchJoinOrUpdate(_ context.Context, payload PVPMatchJoinPayload) error {
|
||||
return DefaultPVPMatchCoordinator().JoinOrUpdate(payload)
|
||||
}
|
||||
|
||||
func (*ServerHandler) MatchCancel(_ context.Context, userID uint32) error {
|
||||
DefaultPVPMatchCoordinator().Cancel(userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CServer() *jsonrpc.RPCServer {
|
||||
// create a new server instance
|
||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
|
||||
@@ -114,6 +123,10 @@ func StartClient(id, port uint32, callback any) *struct {
|
||||
Kick func(uint32) error
|
||||
|
||||
RegisterLogic func(uint32, uint32) error
|
||||
|
||||
MatchJoinOrUpdate func(PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(uint32) error
|
||||
} {
|
||||
//cool.Config.File.Domain = "127.0.0.1"
|
||||
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
||||
@@ -144,6 +157,10 @@ var RPCClient struct {
|
||||
|
||||
RegisterLogic func(uint32, uint32) error
|
||||
|
||||
MatchJoinOrUpdate func(PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(uint32) error
|
||||
|
||||
// UserLogin func(int32, int32) error //用户登录事件
|
||||
// UserLogout func(int32, int32) error //用户登出事件
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user