寻路服务器

This commit is contained in:
huangxiaolei
2022-12-21 17:42:56 +08:00
parent bfff798470
commit c4bc4b8ca1
22 changed files with 419 additions and 275 deletions

47
pathfinding/app/app.go Normal file
View File

@@ -0,0 +1,47 @@
package app
import (
"context"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
"hk4e/common/config"
"hk4e/common/mq"
"hk4e/pathfinding/handle"
"hk4e/pkg/logger"
)
func Run(ctx context.Context, configFile string) error {
config.InitConfig(configFile)
logger.InitLogger("pathfinding")
logger.Warn("pathfinding start")
messageQueue := mq.NewMessageQueue(mq.PATHFINDING, "1")
defer messageQueue.Close()
_ = handle.NewHandle(messageQueue)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
select {
case <-ctx.Done():
return nil
case s := <-c:
logger.Warn("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
logger.Warn("pathfinding exit")
time.Sleep(time.Second)
return nil
case syscall.SIGHUP:
default:
return nil
}
}
}
}

View File

@@ -0,0 +1,70 @@
package handle
import (
"hk4e/common/mq"
"hk4e/pathfinding/world"
"hk4e/pkg/logger"
"hk4e/protocol/cmd"
pb "google.golang.org/protobuf/proto"
)
type Handle struct {
worldStatic *world.WorldStatic
messageQueue *mq.MessageQueue
}
func NewHandle(messageQueue *mq.MessageQueue) (r *Handle) {
r = new(Handle)
r.worldStatic = world.NewWorldStatic()
r.worldStatic.InitTerrain()
r.messageQueue = messageQueue
go r.run()
return r
}
func (h *Handle) run() {
for i := 0; i < 4; i++ {
go func() {
for {
netMsg := <-h.messageQueue.GetNetMsg()
if netMsg.MsgType != mq.MsgTypeGame {
continue
}
if netMsg.EventId != mq.NormalMsg {
continue
}
gameMsg := netMsg.GameMsg
switch gameMsg.CmdId {
case cmd.QueryPathReq:
h.QueryPath(gameMsg.UserId, gameMsg.PayloadMessage)
case cmd.ObstacleModifyNotify:
h.ObstacleModifyNotify(gameMsg.UserId, gameMsg.PayloadMessage)
}
}
}()
}
}
// SendMsg 发送消息给客户端
func (h *Handle) SendMsg(cmdId uint16, userId uint32, payloadMsg pb.Message) {
if userId < 100000000 || payloadMsg == nil {
return
}
gameMsg := new(mq.GameMsg)
gameMsg.UserId = userId
gameMsg.CmdId = cmdId
gameMsg.ClientSeq = 0
// 在这里直接序列化成二进制数据 防止发送的消息内包含各种游戏数据指针 而造成并发读写的问题
payloadMessageData, err := pb.Marshal(payloadMsg)
if err != nil {
logger.Error("parse payload msg to bin error: %v", err)
return
}
gameMsg.PayloadMessageData = payloadMessageData
h.messageQueue.SendToGate("1", &mq.NetMsg{
MsgType: mq.MsgTypeGame,
EventId: mq.NormalMsg,
GameMsg: gameMsg,
})
}

View File

@@ -0,0 +1,74 @@
package handle
import (
"hk4e/pathfinding/pfalg"
"hk4e/pkg/logger"
"hk4e/protocol/cmd"
"hk4e/protocol/proto"
pb "google.golang.org/protobuf/proto"
)
func (h *Handle) ConvPbVecToMeshVec(pbVec *proto.Vector) pfalg.MeshVector {
return pfalg.MeshVector{
X: int16(pbVec.X),
Y: int16(pbVec.Y),
Z: int16(pbVec.Z),
}
}
func (h *Handle) ConvMeshVecToPbVec(meshVec pfalg.MeshVector) *proto.Vector {
return &proto.Vector{
X: float32(meshVec.X),
Y: float32(meshVec.Y),
Z: float32(meshVec.Z),
}
}
func (h *Handle) ConvPbVecListToMeshVecList(pbVecList []*proto.Vector) []pfalg.MeshVector {
ret := make([]pfalg.MeshVector, 0)
for _, pbVec := range pbVecList {
ret = append(ret, h.ConvPbVecToMeshVec(pbVec))
}
return ret
}
func (h *Handle) ConvMeshVecListToPbVecList(meshVecList []pfalg.MeshVector) []*proto.Vector {
ret := make([]*proto.Vector, 0)
for _, meshVec := range meshVecList {
ret = append(ret, h.ConvMeshVecToPbVec(meshVec))
}
return ret
}
func (h *Handle) QueryPath(userId uint32, payloadMsg pb.Message) {
req := payloadMsg.(*proto.QueryPathReq)
logger.Debug("query path req: %v, uid: %v", req, userId)
var ok = false
var path []pfalg.MeshVector = nil
for _, destinationPos := range req.DestinationPos {
ok, path = h.worldStatic.Pathfinding(h.ConvPbVecToMeshVec(req.SourcePos), h.ConvPbVecToMeshVec(destinationPos))
if ok {
break
}
}
if !ok {
queryPathRsp := &proto.QueryPathRsp{
QueryId: req.QueryId,
QueryStatus: proto.QueryPathRsp_PATH_STATUS_TYPE_FAIL,
}
h.SendMsg(cmd.QueryPathRsp, userId, queryPathRsp)
return
}
queryPathRsp := &proto.QueryPathRsp{
QueryId: req.QueryId,
QueryStatus: proto.QueryPathRsp_PATH_STATUS_TYPE_SUCC,
Corners: h.ConvMeshVecListToPbVecList(path),
}
h.SendMsg(cmd.QueryPathRsp, userId, queryPathRsp)
}
func (h *Handle) ObstacleModifyNotify(userId uint32, payloadMsg pb.Message) {
req := payloadMsg.(*proto.ObstacleModifyNotify)
logger.Debug("obstacle modify req: %v, uid: %v", req, userId)
}

View File

@@ -0,0 +1,200 @@
package pfalg
import (
"hk4e/pkg/alg"
)
const (
NODE_NONE = iota
NODE_START
NODE_END
NODE_BLOCK
)
type PathNode struct {
x int16
y int16
z int16
visit bool
state int
parent *PathNode
}
type BFS struct {
gMap map[int16]map[int16]map[int16]*PathNode
startPathNode *PathNode
endPathNode *PathNode
}
func NewBFS() (r *BFS) {
r = new(BFS)
return r
}
func (b *BFS) InitMap(terrain map[MeshVector]bool, start MeshVector, end MeshVector, extR int16) {
xLen := end.X - start.X
yLen := end.Y - start.Y
zLen := end.Z - start.Z
dx := int16(1)
dy := int16(1)
dz := int16(1)
if xLen < 0 {
dx = -1
xLen *= -1
}
if yLen < 0 {
dy = -1
yLen *= -1
}
if zLen < 0 {
dz = -1
zLen *= -1
}
b.gMap = make(map[int16]map[int16]map[int16]*PathNode)
for x := start.X - extR*dx; x != end.X+extR*dx; x += dx {
b.gMap[x] = make(map[int16]map[int16]*PathNode)
for y := start.Y - extR*dy; y != end.Y+extR*dy; y += dy {
b.gMap[x][y] = make(map[int16]*PathNode)
for z := start.Z - extR*dz; z != end.Z+extR*dz; z += dz {
state := -1
if x == start.X && y == start.Y && z == start.Z {
state = NODE_START
} else if x == end.X && y == end.Y && z == end.Z {
state = NODE_END
} else {
_, exist := terrain[MeshVector{
X: x,
Y: y,
Z: z,
}]
if exist {
state = NODE_NONE
} else {
state = NODE_BLOCK
}
}
node := &PathNode{
x: x,
y: y,
z: z,
visit: false,
state: state,
parent: nil,
}
b.gMap[x][y][z] = node
if node.state == NODE_START {
b.startPathNode = node
} else if node.state == NODE_END {
b.endPathNode = node
}
}
}
}
}
func (b *BFS) GetNeighbor(node *PathNode) []*PathNode {
neighborList := make([]*PathNode, 0)
dir := [][3]int16{
//
{1, 0, 0},
{-1, 0, 0},
{0, 1, 0},
{0, -1, 0},
{0, 0, 1},
{0, 0, -1},
//
{1, 1, 0},
{-1, 1, 0},
{-1, -1, 0},
{1, -1, 0},
//
{1, 0, 1},
{-1, 0, 1},
{-1, 0, -1},
{1, 0, -1},
//
{0, 1, 1},
{0, -1, 1},
{0, -1, -1},
{0, 1, -1},
//
{1, 1, 1},
{1, 1, -1},
{1, -1, 1},
{1, -1, -1},
{-1, 1, 1},
{-1, 1, -1},
{-1, -1, 1},
{-1, -1, -1},
}
for _, v := range dir {
x := node.x + v[0]
y := node.y + v[1]
z := node.z + v[2]
if _, exist := b.gMap[x]; !exist {
continue
}
if _, exist := b.gMap[x][y]; !exist {
continue
}
if _, exist := b.gMap[x][y][z]; !exist {
continue
}
neighborNode := b.gMap[x][y][z]
neighborList = append(neighborList, neighborNode)
}
return neighborList
}
func (b *BFS) GetPath() []*PathNode {
path := make([]*PathNode, 0)
if b.endPathNode.parent == nil {
return nil
}
node := b.endPathNode
for {
if node == nil {
break
}
path = append(path, node)
node = node.parent
}
if len(path) == 0 {
return nil
}
return path
}
func (b *BFS) Pathfinding() []MeshVector {
queue := alg.NewALQueue[*PathNode]()
b.startPathNode.visit = true
queue.EnQueue(b.startPathNode)
for queue.Len() > 0 {
head := queue.DeQueue()
neighborList := b.GetNeighbor(head)
for _, neighbor := range neighborList {
if !neighbor.visit && neighbor.state != NODE_BLOCK {
neighbor.visit = true
neighbor.parent = head
queue.EnQueue(neighbor)
if neighbor.state == NODE_END {
break
}
}
}
}
path := b.GetPath()
if path == nil {
return nil
}
pathVectorList := make([]MeshVector, 0)
for i := len(path) - 1; i >= 0; i-- {
node := path[i]
pathVectorList = append(pathVectorList, MeshVector{
X: node.x,
Y: node.y,
Z: node.z,
})
}
return pathVectorList
}

View File

@@ -0,0 +1,7 @@
package pfalg
type MeshVector struct {
X int16
Y int16
Z int16
}

View File

@@ -0,0 +1,88 @@
package world
import (
"bytes"
"encoding/gob"
"os"
"hk4e/pathfinding/pfalg"
"hk4e/pkg/logger"
)
type WorldStatic struct {
// x y z -> if terrain exist
terrain map[pfalg.MeshVector]bool
}
func NewWorldStatic() (r *WorldStatic) {
r = new(WorldStatic)
r.terrain = make(map[pfalg.MeshVector]bool)
return r
}
func (w *WorldStatic) InitTerrain() bool {
data, err := os.ReadFile("./world_terrain.bin")
if err != nil {
logger.Error("read world terrain file error: %v", err)
return false
}
decoder := gob.NewDecoder(bytes.NewReader(data))
err = decoder.Decode(&w.terrain)
if err != nil {
logger.Error("unmarshal world terrain data error: %v", err)
return false
}
return true
}
func (w *WorldStatic) SaveTerrain() bool {
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(w.terrain)
if err != nil {
logger.Error("marshal world terrain data error: %v", err)
return false
}
err = os.WriteFile("./world_terrain.bin", buffer.Bytes(), 0644)
if err != nil {
logger.Error("write world terrain file error: %v", err)
return false
}
return true
}
func (w *WorldStatic) GetTerrain(x int16, y int16, z int16) (exist bool) {
pos := pfalg.MeshVector{
X: x,
Y: y,
Z: z,
}
exist = w.terrain[pos]
return exist
}
func (w *WorldStatic) SetTerrain(x int16, y int16, z int16) {
pos := pfalg.MeshVector{
X: x,
Y: y,
Z: z,
}
w.terrain[pos] = true
}
func (w *WorldStatic) Pathfinding(startPos pfalg.MeshVector, endPos pfalg.MeshVector) (bool, []pfalg.MeshVector) {
bfs := pfalg.NewBFS()
bfs.InitMap(
w.terrain,
startPos,
endPos,
100,
)
pathVectorList := bfs.Pathfinding()
if pathVectorList == nil {
logger.Error("could not find path")
return false, nil
}
logger.Debug("find path success, path: %v", pathVectorList)
return true, pathVectorList
}