Skip to content

Commit

Permalink
refactor: 重构 aoi 包实现,移除对 game.Actor、game.Position2D 等接口的依赖
Browse files Browse the repository at this point in the history
  • Loading branch information
kercylan98 committed Dec 22, 2023
1 parent af0165a commit d56ebde
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 85 deletions.
121 changes: 61 additions & 60 deletions game/aoi/2d.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,63 @@
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
areaWidth float64
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
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -211,23 +212,23 @@ 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
areaY = float64(tempH * int(slf.areaHeight))
} 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)
Expand All @@ -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)
}
13 changes: 9 additions & 4 deletions game/aoi/2d_entity.go
Original file line number Diff line number Diff line change
@@ -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]
}
20 changes: 11 additions & 9 deletions game/aoi/2d_events.go
Original file line number Diff line number Diff line change
@@ -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)
}
Expand Down
21 changes: 9 additions & 12 deletions game/aoi/2d_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,38 @@ 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"
)

type Ent struct {
guid int64
x, y, vision float64
guid int64
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 {
return slf.vision
}

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,
})
}
Expand Down

0 comments on commit d56ebde

Please sign in to comment.