refactor: 从 builtin 包中单独抽离到 aoi 包,更名为 TwoDimensional

This commit is contained in:
kercylan98 2023-07-27 12:05:40 +08:00
parent c8f181f63e
commit bca8a98463
6 changed files with 83 additions and 103 deletions

View File

@ -1,24 +1,25 @@
package builtin package aoi
import ( import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/geometry" "github.com/kercylan98/minotaur/utils/geometry"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/hash"
"math" "math"
"sync" "sync"
) )
func NewAOI2D(width, height, areaWidth, areaHeight int) *AOI2D { func NewTwoDimensional[E TwoDimensionalEntity](width, height, areaWidth, areaHeight int) *TwoDimensional[E] {
aoi := &AOI2D{ aoi := &TwoDimensional[E]{
event: new(event[E]),
width: float64(width), width: float64(width),
height: float64(height), height: float64(height),
focus: map[int64]map[int64]game.AOIEntity2D{}, focus: map[int64]map[int64]E{},
} }
aoi.SetAreaSize(areaWidth, areaHeight) aoi.SetAreaSize(areaWidth, areaHeight)
return aoi return aoi
} }
type AOI2D struct { type TwoDimensional[E TwoDimensionalEntity] struct {
*event[E]
rw sync.RWMutex rw sync.RWMutex
width float64 width float64
height float64 height float64
@ -26,39 +27,36 @@ type AOI2D struct {
areaHeight float64 areaHeight float64
areaWidthLimit int areaWidthLimit int
areaHeightLimit int areaHeightLimit int
areas [][]map[int64]game.AOIEntity2D areas [][]map[int64]E
focus map[int64]map[int64]game.AOIEntity2D focus map[int64]map[int64]E
repartitionQueue []func() repartitionQueue []func()
entityJoinVisionEventHandles []game.EntityJoinVisionEventHandle
entityLeaveVisionEventHandles []game.EntityLeaveVisionEventHandle
} }
func (slf *AOI2D) AddEntity(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) AddEntity(entity E) {
slf.rw.Lock() slf.rw.Lock()
slf.addEntity(entity) slf.addEntity(entity)
slf.rw.Unlock() slf.rw.Unlock()
} }
func (slf *AOI2D) DeleteEntity(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) DeleteEntity(entity E) {
slf.rw.Lock() slf.rw.Lock()
slf.deleteEntity(entity) slf.deleteEntity(entity)
slf.rw.Unlock() slf.rw.Unlock()
} }
func (slf *AOI2D) Refresh(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) Refresh(entity E) {
slf.rw.Lock() slf.rw.Lock()
defer slf.rw.Unlock() defer slf.rw.Unlock()
slf.refresh(entity) slf.refresh(entity)
} }
func (slf *AOI2D) GetFocus(guid int64) map[int64]game.AOIEntity2D { func (slf *TwoDimensional[E]) GetFocus(guid int64) map[int64]E {
slf.rw.RLock() slf.rw.RLock()
defer slf.rw.RUnlock() defer slf.rw.RUnlock()
return hash.Copy(slf.focus[guid]) return hash.Copy(slf.focus[guid])
} }
func (slf *AOI2D) SetSize(width, height int) { func (slf *TwoDimensional[E]) SetSize(width, height int) {
fw, fh := float64(width), float64(height) fw, fh := float64(width), float64(height)
if fw == slf.width && fh == slf.height { if fw == slf.width && fh == slf.height {
return return
@ -70,7 +68,7 @@ func (slf *AOI2D) SetSize(width, height int) {
slf.setAreaSize(int(slf.areaWidth), int(slf.areaHeight)) slf.setAreaSize(int(slf.areaWidth), int(slf.areaHeight))
} }
func (slf *AOI2D) SetAreaSize(width, height int) { func (slf *TwoDimensional[E]) SetAreaSize(width, height int) {
fw, fh := float64(width), float64(height) fw, fh := float64(width), float64(height)
if fw == slf.areaWidth && fh == slf.areaHeight { if fw == slf.areaWidth && fh == slf.areaHeight {
return return
@ -80,35 +78,15 @@ func (slf *AOI2D) SetAreaSize(width, height int) {
slf.setAreaSize(width, height) slf.setAreaSize(width, height)
} }
func (slf *AOI2D) RegEntityJoinVisionEvent(handle game.EntityJoinVisionEventHandle) { func (slf *TwoDimensional[E]) setAreaSize(width, height int) {
slf.entityJoinVisionEventHandles = append(slf.entityJoinVisionEventHandles, handle)
}
func (slf *AOI2D) OnEntityJoinVisionEvent(entity, target game.AOIEntity2D) {
for _, handle := range slf.entityJoinVisionEventHandles {
handle(entity, target)
}
}
func (slf *AOI2D) RegEntityLeaveVisionEvent(handle game.EntityLeaveVisionEventHandle) {
slf.entityLeaveVisionEventHandles = append(slf.entityLeaveVisionEventHandles, handle)
}
func (slf *AOI2D) OnEntityLeaveVisionEvent(entity, target game.AOIEntity2D) {
for _, handle := range slf.entityLeaveVisionEventHandles {
handle(entity, target)
}
}
func (slf *AOI2D) setAreaSize(width, height int) {
// 旧分区备份 // 旧分区备份
var oldAreas = make([][]map[int64]game.AOIEntity2D, len(slf.areas)) var oldAreas = make([][]map[int64]E, len(slf.areas))
for w := 0; w < len(slf.areas); w++ { for w := 0; w < len(slf.areas); w++ {
hs := slf.areas[w] hs := slf.areas[w]
ohs := make([]map[int64]game.AOIEntity2D, len(hs)) ohs := make([]map[int64]E, len(hs))
for h := 0; h < len(hs); h++ { for h := 0; h < len(hs); h++ {
es := map[int64]game.AOIEntity2D{} es := map[int64]E{}
for g, e := range hs[h] { for g, e := range hs[h] {
es[g] = e es[g] = e
} }
@ -133,11 +111,11 @@ func (slf *AOI2D) setAreaSize(width, height int) {
slf.areaHeight = float64(height) slf.areaHeight = float64(height)
slf.areaWidthLimit = int(math.Ceil(slf.width / slf.areaWidth)) slf.areaWidthLimit = int(math.Ceil(slf.width / slf.areaWidth))
slf.areaHeightLimit = int(math.Ceil(slf.height / slf.areaHeight)) slf.areaHeightLimit = int(math.Ceil(slf.height / slf.areaHeight))
areas := make([][]map[int64]game.AOIEntity2D, slf.areaWidthLimit+1) areas := make([][]map[int64]E, slf.areaWidthLimit+1)
for i := 0; i < len(areas); i++ { for i := 0; i < len(areas); i++ {
entities := make([]map[int64]game.AOIEntity2D, slf.areaHeightLimit+1) entities := make([]map[int64]E, slf.areaHeightLimit+1)
for e := 0; e < len(entities); e++ { for e := 0; e < len(entities); e++ {
entities[e] = map[int64]game.AOIEntity2D{} entities[e] = map[int64]E{}
} }
areas[i] = entities areas[i] = entities
} }
@ -155,22 +133,22 @@ func (slf *AOI2D) setAreaSize(width, height int) {
} }
} }
func (slf *AOI2D) addEntity(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) addEntity(entity E) {
x, y := entity.GetPosition() x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth) widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight) heightArea := int(y / slf.areaHeight)
guid := entity.GetGuid() guid := entity.GetGuid()
slf.areas[widthArea][heightArea][guid] = entity slf.areas[widthArea][heightArea][guid] = entity
focus := map[int64]game.AOIEntity2D{} focus := map[int64]E{}
slf.focus[guid] = focus slf.focus[guid] = focus
slf.rangeVisionAreaEntities(entity, func(eg int64, e game.AOIEntity2D) { slf.rangeVisionAreaEntities(entity, func(eg int64, e E) {
focus[eg] = e focus[eg] = e
slf.OnEntityJoinVisionEvent(entity, e) slf.OnEntityJoinVisionEvent(entity, e)
slf.refresh(e) slf.refresh(e)
}) })
} }
func (slf *AOI2D) refresh(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) refresh(entity E) {
x, y := entity.GetPosition() x, y := entity.GetPosition()
vision := entity.GetVision() vision := entity.GetVision()
guid := entity.GetGuid() guid := entity.GetGuid()
@ -183,7 +161,7 @@ func (slf *AOI2D) refresh(entity game.AOIEntity2D) {
} }
} }
slf.rangeVisionAreaEntities(entity, func(guid int64, e game.AOIEntity2D) { slf.rangeVisionAreaEntities(entity, func(guid int64, e E) {
if _, exist := focus[guid]; !exist { if _, exist := focus[guid]; !exist {
focus[guid] = e focus[guid] = e
slf.OnEntityJoinVisionEvent(entity, e) slf.OnEntityJoinVisionEvent(entity, e)
@ -191,7 +169,7 @@ func (slf *AOI2D) refresh(entity game.AOIEntity2D) {
}) })
} }
func (slf *AOI2D) rangeVisionAreaEntities(entity game.AOIEntity2D, handle func(guid int64, entity game.AOIEntity2D)) { func (slf *TwoDimensional[E]) rangeVisionAreaEntities(entity E, handle func(guid int64, entity E)) {
x, y := entity.GetPosition() x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth) widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight) heightArea := int(y / slf.areaHeight)
@ -259,7 +237,7 @@ func (slf *AOI2D) rangeVisionAreaEntities(entity game.AOIEntity2D, handle func(g
} }
} }
func (slf *AOI2D) deleteEntity(entity game.AOIEntity2D) { func (slf *TwoDimensional[E]) deleteEntity(entity E) {
x, y := entity.GetPosition() x, y := entity.GetPosition()
widthArea := int(x / slf.areaWidth) widthArea := int(x / slf.areaWidth)
heightArea := int(y / slf.areaHeight) heightArea := int(y / slf.areaHeight)

12
game/aoi/2d_entity.go Normal file
View File

@ -0,0 +1,12 @@
package aoi
import "github.com/kercylan98/minotaur/game"
// TwoDimensionalEntity 基于2D定义的AOI对象功能接口
// - AOI 对象提供了 AOI 系统中常用的属性,诸如位置坐标和视野范围等
type TwoDimensionalEntity interface {
game.Actor
game.Position2D
// GetVision 获取视距
GetVision() float64
}

35
game/aoi/2d_events.go Normal file
View File

@ -0,0 +1,35 @@
package aoi
type (
EntityJoinVisionEventHandle[E TwoDimensionalEntity] func(entity, target E)
EntityLeaveVisionEventHandle[E TwoDimensionalEntity] func(entity, target E)
)
type event[E TwoDimensionalEntity] struct {
entityJoinVisionEventHandles []EntityJoinVisionEventHandle[E]
entityLeaveVisionEventHandles []EntityLeaveVisionEventHandle[E]
}
// RegEntityJoinVisionEvent 在新对象进入视野时将会立刻执行被注册的事件处理函数
func (slf *event[E]) RegEntityJoinVisionEvent(handle EntityJoinVisionEventHandle[E]) {
slf.entityJoinVisionEventHandles = append(slf.entityJoinVisionEventHandles, handle)
}
// OnEntityJoinVisionEvent 在新对象进入视野时将会立刻执行被注册的事件处理函数
func (slf *event[E]) OnEntityJoinVisionEvent(entity, target E) {
for _, handle := range slf.entityJoinVisionEventHandles {
handle(entity, target)
}
}
// RegEntityLeaveVisionEvent 在新对象离开视野时将会立刻执行被注册的事件处理函数
func (slf *event[E]) RegEntityLeaveVisionEvent(handle EntityLeaveVisionEventHandle[E]) {
slf.entityLeaveVisionEventHandles = append(slf.entityLeaveVisionEventHandles, handle)
}
// OnEntityLeaveVisionEvent 在新对象离开视野时将会立刻执行被注册的事件处理函数
func (slf *event[E]) OnEntityLeaveVisionEvent(entity, target E) {
for _, handle := range slf.entityLeaveVisionEventHandles {
handle(entity, target)
}
}

View File

@ -1,8 +1,8 @@
package builtin_test package aoi_test
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/game/builtin" "github.com/kercylan98/minotaur/game/aoi"
"github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/random"
"testing" "testing"
"time" "time"
@ -29,12 +29,12 @@ func (slf *Ent) GetVision() float64 {
return slf.vision return slf.vision
} }
func TestNewAOI2D(t *testing.T) { func TestNewTwoDimensional(t *testing.T) {
aoi := builtin.NewAOI2D(10000, 10000, 100, 100) aoiTW := aoi.NewTwoDimensional[*Ent](10000, 10000, 100, 100)
start := time.Now() start := time.Now()
for i := 0; i < 50000; i++ { for i := 0; i < 50000; i++ {
aoi.AddEntity(&Ent{ aoiTW.AddEntity(&Ent{
guid: int64(i), guid: int64(i),
x: float64(random.Int(0, 10000)), x: float64(random.Int(0, 10000)),
y: float64(random.Int(0, 10000)), y: float64(random.Int(0, 10000)),
@ -44,9 +44,9 @@ func TestNewAOI2D(t *testing.T) {
fmt.Println("添加耗时:", time.Since(start)) fmt.Println("添加耗时:", time.Since(start))
//start = time.Now() //start = time.Now()
//aoi.SetAreaSize(1000, 1000) //aoiTW.SetAreaSize(1000, 1000)
//fmt.Println("重设区域大小耗时:", time.Since(start)) //fmt.Println("重设区域大小耗时:", time.Since(start))
start = time.Now() start = time.Now()
aoi.SetSize(10100, 10100) aoiTW.SetSize(10100, 10100)
fmt.Println("重设大小耗时:", time.Since(start)) fmt.Println("重设大小耗时:", time.Since(start))
} }

View File

@ -1,35 +0,0 @@
package game
// AOI2D 基于2D定义的AOI功能接口
// - AOIArea Of Interest翻译过来叫感兴趣的区域是大型多人在线的游戏服务器中一个非常重要的基础模块用于游戏对象在场景中的视野管理
// - 透过 AOI 系统可以在其他玩家或怪物进入视野时得到感知,从而进行相应处理
// - 内置实现builtin.AOI2D
// - 构建函数builtin.NewAOI2D
type AOI2D interface {
// AddEntity 添加对象
AddEntity(entity AOIEntity2D)
// DeleteEntity 移除对象
DeleteEntity(entity AOIEntity2D)
// Refresh 刷新对象焦点
Refresh(entity AOIEntity2D)
// GetFocus 获取对象焦点列表
GetFocus(guid int64) map[int64]AOIEntity2D
// SetSize 设置总区域大小
// - 将会导致区域的重新划分
SetSize(width, height int)
// SetAreaSize 设置区域大小
// - 将会导致区域的重新划分
SetAreaSize(width, height int)
// RegEntityJoinVisionEvent 在新对象进入视野时将会立刻执行被注册的事件处理函数
RegEntityJoinVisionEvent(handle EntityJoinVisionEventHandle)
OnEntityJoinVisionEvent(entity, target AOIEntity2D)
// RegEntityLeaveVisionEvent 在对象离开视野时将会立刻执行被注册的事件处理函数
RegEntityLeaveVisionEvent(handle EntityLeaveVisionEventHandle)
OnEntityLeaveVisionEvent(entity, target AOIEntity2D)
}
type (
EntityJoinVisionEventHandle func(entity, target AOIEntity2D)
EntityLeaveVisionEventHandle func(entity, target AOIEntity2D)
)

View File

@ -1,10 +0,0 @@
package game
// AOIEntity2D 基于2D定义的AOI对象功能接口
// - AOI 对象提供了 AOI 系统中常用的属性,诸如位置坐标和视野范围等
type AOIEntity2D interface {
Actor
Position2D
// GetVision 获取视距
GetVision() float64
}