refactor: 重构 aoi 包实现,移除对 game.Actor、game.Position2D 等接口的依赖
This commit is contained in:
parent
af0165af71
commit
d56ebde2f9
121
game/aoi/2d.go
121
game/aoi/2d.go
|
@ -1,25 +1,26 @@
|
|||
package aoi
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/geometry"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func NewTwoDimensional[E TwoDimensionalEntity](width, height, areaWidth, areaHeight int) *TwoDimensional[E] {
|
||||
aoi := &TwoDimensional[E]{
|
||||
event: new(event[E]),
|
||||
func NewTwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]](width, height, areaWidth, areaHeight int) *TwoDimensional[EID, PosType, E] {
|
||||
aoi := &TwoDimensional[EID, PosType, E]{
|
||||
event: new(event[EID, PosType, E]),
|
||||
width: float64(width),
|
||||
height: float64(height),
|
||||
focus: map[int64]map[int64]E{},
|
||||
focus: map[EID]map[EID]E{},
|
||||
}
|
||||
aoi.SetAreaSize(areaWidth, areaHeight)
|
||||
return aoi
|
||||
}
|
||||
|
||||
type TwoDimensional[E TwoDimensionalEntity] struct {
|
||||
*event[E]
|
||||
type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] struct {
|
||||
*event[EID, PosType, E]
|
||||
rw sync.RWMutex
|
||||
width float64
|
||||
height float64
|
||||
|
@ -27,36 +28,36 @@ type TwoDimensional[E TwoDimensionalEntity] struct {
|
|||
areaHeight float64
|
||||
areaWidthLimit int
|
||||
areaHeightLimit int
|
||||
areas [][]map[int64]E
|
||||
focus map[int64]map[int64]E
|
||||
areas [][]map[EID]E
|
||||
focus map[EID]map[EID]E
|
||||
repartitionQueue []func()
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) AddEntity(entity E) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) AddEntity(entity E) {
|
||||
slf.rw.Lock()
|
||||
slf.addEntity(entity)
|
||||
slf.rw.Unlock()
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) DeleteEntity(entity E) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) DeleteEntity(entity E) {
|
||||
slf.rw.Lock()
|
||||
slf.deleteEntity(entity)
|
||||
slf.rw.Unlock()
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) Refresh(entity E) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) Refresh(entity E) {
|
||||
slf.rw.Lock()
|
||||
defer slf.rw.Unlock()
|
||||
slf.refresh(entity)
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) GetFocus(guid int64) map[int64]E {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) GetFocus(id EID) map[EID]E {
|
||||
slf.rw.RLock()
|
||||
defer slf.rw.RUnlock()
|
||||
return hash.Copy(slf.focus[guid])
|
||||
return hash.Copy(slf.focus[id])
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) SetSize(width, height int) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) SetSize(width, height int) {
|
||||
fw, fh := float64(width), float64(height)
|
||||
if fw == slf.width && fh == slf.height {
|
||||
return
|
||||
|
@ -68,7 +69,7 @@ func (slf *TwoDimensional[E]) SetSize(width, height int) {
|
|||
slf.setAreaSize(int(slf.areaWidth), int(slf.areaHeight))
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) SetAreaSize(width, height int) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) SetAreaSize(width, height int) {
|
||||
fw, fh := float64(width), float64(height)
|
||||
if fw == slf.areaWidth && fh == slf.areaHeight {
|
||||
return
|
||||
|
@ -78,15 +79,15 @@ func (slf *TwoDimensional[E]) SetAreaSize(width, height int) {
|
|||
slf.setAreaSize(width, height)
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) setAreaSize(width, height int) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) setAreaSize(width, height int) {
|
||||
|
||||
// 旧分区备份
|
||||
var oldAreas = make([][]map[int64]E, len(slf.areas))
|
||||
var oldAreas = make([][]map[EID]E, len(slf.areas))
|
||||
for w := 0; w < len(slf.areas); w++ {
|
||||
hs := slf.areas[w]
|
||||
ohs := make([]map[int64]E, len(hs))
|
||||
ohs := make([]map[EID]E, len(hs))
|
||||
for h := 0; h < len(hs); h++ {
|
||||
es := map[int64]E{}
|
||||
es := map[EID]E{}
|
||||
for g, e := range hs[h] {
|
||||
es[g] = e
|
||||
}
|
||||
|
@ -111,11 +112,11 @@ func (slf *TwoDimensional[E]) setAreaSize(width, height int) {
|
|||
slf.areaHeight = float64(height)
|
||||
slf.areaWidthLimit = int(math.Ceil(slf.width / slf.areaWidth))
|
||||
slf.areaHeightLimit = int(math.Ceil(slf.height / slf.areaHeight))
|
||||
areas := make([][]map[int64]E, slf.areaWidthLimit+1)
|
||||
areas := make([][]map[EID]E, slf.areaWidthLimit+1)
|
||||
for i := 0; i < len(areas); i++ {
|
||||
entities := make([]map[int64]E, slf.areaHeightLimit+1)
|
||||
entities := make([]map[EID]E, slf.areaHeightLimit+1)
|
||||
for e := 0; e < len(entities); e++ {
|
||||
entities[e] = map[int64]E{}
|
||||
entities[e] = map[EID]E{}
|
||||
}
|
||||
areas[i] = entities
|
||||
}
|
||||
|
@ -133,50 +134,50 @@ func (slf *TwoDimensional[E]) setAreaSize(width, height int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) addEntity(entity E) {
|
||||
x, y := entity.GetPosition()
|
||||
widthArea := int(x / slf.areaWidth)
|
||||
heightArea := int(y / slf.areaHeight)
|
||||
guid := entity.GetGuid()
|
||||
slf.areas[widthArea][heightArea][guid] = entity
|
||||
focus := map[int64]E{}
|
||||
slf.focus[guid] = focus
|
||||
slf.rangeVisionAreaEntities(entity, func(eg int64, e E) {
|
||||
func (slf *TwoDimensional[EID, PosType, E]) addEntity(entity E) {
|
||||
x, y := entity.GetPosition().GetXY()
|
||||
widthArea := int(float64(x) / slf.areaWidth)
|
||||
heightArea := int(float64(y) / slf.areaHeight)
|
||||
id := entity.GetTwoDimensionalEntityID()
|
||||
slf.areas[widthArea][heightArea][id] = entity
|
||||
focus := map[EID]E{}
|
||||
slf.focus[id] = focus
|
||||
slf.rangeVisionAreaEntities(entity, func(eg EID, e E) {
|
||||
focus[eg] = e
|
||||
slf.OnEntityJoinVisionEvent(entity, e)
|
||||
slf.refresh(e)
|
||||
})
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) refresh(entity E) {
|
||||
x, y := entity.GetPosition()
|
||||
func (slf *TwoDimensional[EID, PosType, E]) refresh(entity E) {
|
||||
x, y := entity.GetPosition().GetXY()
|
||||
vision := entity.GetVision()
|
||||
guid := entity.GetGuid()
|
||||
focus := slf.focus[guid]
|
||||
id := entity.GetTwoDimensionalEntityID()
|
||||
focus := slf.focus[id]
|
||||
for eg, e := range focus {
|
||||
ex, ey := e.GetPosition()
|
||||
if geometry.CalcDistanceWithCoordinate(x, y, ex, ey) > vision {
|
||||
ex, ey := e.GetPosition().GetXY()
|
||||
if geometry.CalcDistanceWithCoordinate(float64(x), float64(y), float64(ex), float64(ey)) > vision {
|
||||
delete(focus, eg)
|
||||
delete(slf.focus[eg], guid)
|
||||
delete(slf.focus[eg], id)
|
||||
}
|
||||
}
|
||||
|
||||
slf.rangeVisionAreaEntities(entity, func(guid int64, e E) {
|
||||
if _, exist := focus[guid]; !exist {
|
||||
focus[guid] = e
|
||||
slf.rangeVisionAreaEntities(entity, func(id EID, e E) {
|
||||
if _, exist := focus[id]; !exist {
|
||||
focus[id] = e
|
||||
slf.OnEntityJoinVisionEvent(entity, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) rangeVisionAreaEntities(entity E, handle func(guid int64, entity E)) {
|
||||
x, y := entity.GetPosition()
|
||||
widthArea := int(x / slf.areaWidth)
|
||||
heightArea := int(y / slf.areaHeight)
|
||||
func (slf *TwoDimensional[EID, PosType, E]) rangeVisionAreaEntities(entity E, handle func(id EID, entity E)) {
|
||||
x, y := entity.GetPosition().GetXY()
|
||||
widthArea := int(float64(x) / slf.areaWidth)
|
||||
heightArea := int(float64(y) / slf.areaHeight)
|
||||
vision := entity.GetVision()
|
||||
widthSpan := int(math.Ceil(vision / slf.areaWidth))
|
||||
heightSpan := int(math.Ceil(vision / slf.areaHeight))
|
||||
guid := entity.GetGuid()
|
||||
id := entity.GetTwoDimensionalEntityID()
|
||||
|
||||
sw := widthArea - widthSpan
|
||||
if sw < 0 {
|
||||
|
@ -211,7 +212,7 @@ func (slf *TwoDimensional[E]) rangeVisionAreaEntities(entity E, handle func(guid
|
|||
} else if w > widthArea {
|
||||
areaX = float64(w * int(slf.areaWidth))
|
||||
} else {
|
||||
areaX = x
|
||||
areaX = float64(x)
|
||||
}
|
||||
if h < heightArea {
|
||||
tempH := h + 1
|
||||
|
@ -219,15 +220,15 @@ func (slf *TwoDimensional[E]) rangeVisionAreaEntities(entity E, handle func(guid
|
|||
} else if h > heightArea {
|
||||
areaY = float64(h * int(slf.areaHeight))
|
||||
} else {
|
||||
areaY = y
|
||||
areaY = float64(y)
|
||||
}
|
||||
areaDistance := geometry.CalcDistanceWithCoordinate(x, y, areaX, areaY)
|
||||
areaDistance := geometry.CalcDistanceWithCoordinate(float64(x), float64(y), areaX, areaY)
|
||||
if areaDistance <= vision {
|
||||
for eg, e := range slf.areas[w][h] {
|
||||
if eg == guid {
|
||||
if eg == id {
|
||||
continue
|
||||
}
|
||||
if ex, ey := e.GetPosition(); geometry.CalcDistanceWithCoordinate(x, y, ex, ey) > vision {
|
||||
if ex, ey := e.GetPosition().GetXY(); geometry.CalcDistanceWithCoordinate(float64(x), float64(y), float64(ex), float64(ey)) > vision {
|
||||
continue
|
||||
}
|
||||
handle(eg, e)
|
||||
|
@ -237,17 +238,17 @@ func (slf *TwoDimensional[E]) rangeVisionAreaEntities(entity E, handle func(guid
|
|||
}
|
||||
}
|
||||
|
||||
func (slf *TwoDimensional[E]) deleteEntity(entity E) {
|
||||
x, y := entity.GetPosition()
|
||||
widthArea := int(x / slf.areaWidth)
|
||||
heightArea := int(y / slf.areaHeight)
|
||||
guid := entity.GetGuid()
|
||||
focus := slf.focus[guid]
|
||||
func (slf *TwoDimensional[EID, PosType, E]) deleteEntity(entity E) {
|
||||
x, y := entity.GetPosition().GetXY()
|
||||
widthArea := int(float64(x) / slf.areaWidth)
|
||||
heightArea := int(float64(y) / slf.areaHeight)
|
||||
id := entity.GetTwoDimensionalEntityID()
|
||||
focus := slf.focus[id]
|
||||
for g, e := range focus {
|
||||
slf.OnEntityLeaveVisionEvent(entity, e)
|
||||
slf.OnEntityLeaveVisionEvent(e, entity)
|
||||
delete(slf.focus[g], guid)
|
||||
delete(slf.focus[g], id)
|
||||
}
|
||||
delete(slf.focus, guid)
|
||||
delete(slf.areas[widthArea][heightArea], guid)
|
||||
delete(slf.focus, id)
|
||||
delete(slf.areas[widthArea][heightArea], id)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package aoi
|
||||
|
||||
import "github.com/kercylan98/minotaur/game"
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/geometry"
|
||||
)
|
||||
|
||||
// TwoDimensionalEntity 基于2D定义的AOI对象功能接口
|
||||
// - AOI 对象提供了 AOI 系统中常用的属性,诸如位置坐标和视野范围等
|
||||
type TwoDimensionalEntity interface {
|
||||
game.Actor
|
||||
game.Position2D
|
||||
type TwoDimensionalEntity[EID generic.Basic, PosType generic.SignedNumber] interface {
|
||||
// GetTwoDimensionalEntityID 获取 AOI 对象 ID
|
||||
GetTwoDimensionalEntityID() EID
|
||||
// GetVision 获取视距
|
||||
GetVision() float64
|
||||
// GetPosition 获取位置
|
||||
GetPosition() geometry.Point[PosType]
|
||||
}
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
package aoi
|
||||
|
||||
import "github.com/kercylan98/minotaur/utils/generic"
|
||||
|
||||
type (
|
||||
EntityJoinVisionEventHandle[E TwoDimensionalEntity] func(entity, target E)
|
||||
EntityLeaveVisionEventHandle[E TwoDimensionalEntity] func(entity, target E)
|
||||
EntityJoinVisionEventHandle[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] func(entity, target E)
|
||||
EntityLeaveVisionEventHandle[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] func(entity, target E)
|
||||
)
|
||||
|
||||
type event[E TwoDimensionalEntity] struct {
|
||||
entityJoinVisionEventHandles []EntityJoinVisionEventHandle[E]
|
||||
entityLeaveVisionEventHandles []EntityLeaveVisionEventHandle[E]
|
||||
type event[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] struct {
|
||||
entityJoinVisionEventHandles []EntityJoinVisionEventHandle[EID, PosType, E]
|
||||
entityLeaveVisionEventHandles []EntityLeaveVisionEventHandle[EID, PosType, E]
|
||||
}
|
||||
|
||||
// RegEntityJoinVisionEvent 在新对象进入视野时将会立刻执行被注册的事件处理函数
|
||||
func (slf *event[E]) RegEntityJoinVisionEvent(handle EntityJoinVisionEventHandle[E]) {
|
||||
func (slf *event[EID, PosType, E]) RegEntityJoinVisionEvent(handle EntityJoinVisionEventHandle[EID, PosType, E]) {
|
||||
slf.entityJoinVisionEventHandles = append(slf.entityJoinVisionEventHandles, handle)
|
||||
}
|
||||
|
||||
// OnEntityJoinVisionEvent 在新对象进入视野时将会立刻执行被注册的事件处理函数
|
||||
func (slf *event[E]) OnEntityJoinVisionEvent(entity, target E) {
|
||||
func (slf *event[EID, PosType, E]) OnEntityJoinVisionEvent(entity, target E) {
|
||||
for _, handle := range slf.entityJoinVisionEventHandles {
|
||||
handle(entity, target)
|
||||
}
|
||||
}
|
||||
|
||||
// RegEntityLeaveVisionEvent 在新对象离开视野时将会立刻执行被注册的事件处理函数
|
||||
func (slf *event[E]) RegEntityLeaveVisionEvent(handle EntityLeaveVisionEventHandle[E]) {
|
||||
func (slf *event[EID, PosType, E]) RegEntityLeaveVisionEvent(handle EntityLeaveVisionEventHandle[EID, PosType, E]) {
|
||||
slf.entityLeaveVisionEventHandles = append(slf.entityLeaveVisionEventHandles, handle)
|
||||
}
|
||||
|
||||
// OnEntityLeaveVisionEvent 在新对象离开视野时将会立刻执行被注册的事件处理函数
|
||||
func (slf *event[E]) OnEntityLeaveVisionEvent(entity, target E) {
|
||||
func (slf *event[EID, PosType, E]) OnEntityLeaveVisionEvent(entity, target E) {
|
||||
for _, handle := range slf.entityLeaveVisionEventHandles {
|
||||
handle(entity, target)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package aoi_test
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/game/aoi"
|
||||
"github.com/kercylan98/minotaur/utils/geometry"
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -10,19 +11,16 @@ import (
|
|||
|
||||
type Ent struct {
|
||||
guid int64
|
||||
x, y, vision float64
|
||||
pos geometry.Point[float64]
|
||||
vision float64
|
||||
}
|
||||
|
||||
func (slf *Ent) SetGuid(guid int64) {
|
||||
slf.guid = guid
|
||||
}
|
||||
|
||||
func (slf *Ent) GetGuid() int64 {
|
||||
func (slf *Ent) GetTwoDimensionalEntityID() int64 {
|
||||
return slf.guid
|
||||
}
|
||||
|
||||
func (slf *Ent) GetPosition() (x, y float64) {
|
||||
return slf.x, slf.y
|
||||
func (slf *Ent) GetPosition() geometry.Point[float64] {
|
||||
return slf.pos
|
||||
}
|
||||
|
||||
func (slf *Ent) GetVision() float64 {
|
||||
|
@ -30,14 +28,13 @@ func (slf *Ent) GetVision() float64 {
|
|||
}
|
||||
|
||||
func TestNewTwoDimensional(t *testing.T) {
|
||||
aoiTW := aoi.NewTwoDimensional[*Ent](10000, 10000, 100, 100)
|
||||
aoiTW := aoi.NewTwoDimensional[int64, float64, *Ent](10000, 10000, 100, 100)
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < 50000; i++ {
|
||||
aoiTW.AddEntity(&Ent{
|
||||
guid: int64(i),
|
||||
x: float64(random.Int(0, 10000)),
|
||||
y: float64(random.Int(0, 10000)),
|
||||
pos: geometry.NewPoint[float64](float64(random.Int64(0, 10000)), float64(random.Int64(0, 10000))),
|
||||
vision: 200,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue