更新说明
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-09 13:11:59 +08:00
parent 487ee0e726
commit d2cd601802
6 changed files with 442 additions and 24 deletions

163
common/rpc/pvp_match.go Normal file
View 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())
}

View File

@@ -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 //用户登出事件
}