diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/aoi.iml b/.idea/aoi.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/aoi.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..786eef4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aoi.go b/aoi.go
new file mode 100644
index 0000000..987caa3
--- /dev/null
+++ b/aoi.go
@@ -0,0 +1,12 @@
+package aoi
+
+type AOI interface {
+ Add(entity *Entity) // 添加实体
+ Delete(entity *Entity) // 移除实体
+ Search(entity *Entity) (result []*Entity) // 范围查询
+}
+
+type Entity struct {
+ X, Y float64
+ Name string
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e6a9eb9
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module github.com/knight0zh/aoi
+
+go 1.17
+
+require github.com/stretchr/testify v1.8.0
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5164829
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,15 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/grid.go b/grid.go
new file mode 100644
index 0000000..faa9e41
--- /dev/null
+++ b/grid.go
@@ -0,0 +1,112 @@
+package aoi
+
+import "sync"
+
+// Grid 格子
+type Grid struct {
+ GID int //格子ID
+ Entities sync.Map //当前格子内的实体
+}
+
+// GridManger AOI九宫格实现矩形
+type GridManger struct {
+ StartX int // X区域左边界坐标
+ StartY int // Y区域上边界坐标
+ AreaWidth int // 格子宽度(长=宽)
+ GridCount int // 格子数量
+ grids map[int]*Grid
+}
+
+func NewGrid(gid int) *Grid {
+ return &Grid{
+ GID: gid,
+ }
+}
+
+func NewGridManger(startX, startY, areaWidth, gridCount int) AOI {
+ manager := &GridManger{
+ StartX: startX,
+ StartY: startY,
+ AreaWidth: areaWidth,
+ GridCount: gridCount,
+ grids: make(map[int]*Grid),
+ }
+
+ for y := 0; y < gridCount; y++ {
+ for x := 0; x < gridCount; x++ {
+ //格子编号:ID = IDy *nx + IDx (利用格子坐标得到格子编号)
+ gID := y*gridCount + x
+ manager.grids[gID] = NewGrid(gID)
+ }
+ }
+
+ return manager
+}
+
+func (g *GridManger) gridWidth() int {
+ return g.AreaWidth / g.GridCount
+}
+
+// GetGIDByPos 通过横纵坐标获取对应的格子ID
+func (g *GridManger) GetGIDByPos(entity *Entity) int {
+ gx := (int(entity.X) - g.StartX) / g.gridWidth()
+ gy := (int(entity.Y) - g.StartY) / g.gridWidth()
+
+ return gy*g.GridCount + gx
+}
+
+// GetSurroundGrids 根据格子的gID得到当前周边的九宫格信息
+func (g *GridManger) GetSurroundGrids(gID int) (grids []*Grid) {
+ if _, ok := g.grids[gID]; !ok {
+ return
+ }
+ grids = append(grids, g.grids[gID])
+
+ // 根据gID, 得到格子所在的坐标
+ x, y := gID%g.GridCount, gID/g.GridCount
+
+ // 分别将这8个方向的方向向量按顺序写入x, y的分量数组
+ dx := []int{-1, -1, -1, 0, 0, 1, 1, 1}
+ dy := []int{-1, 0, 1, -1, 1, -1, 0, 1}
+
+ surroundGID := make([]int, 0)
+ for i := 0; i < 8; i++ {
+ newX := x + dx[i]
+ newY := y + dy[i]
+
+ if newX >= 0 && newX < g.GridCount && newY >= 0 && newY < g.GridCount {
+ surroundGID = append(surroundGID, newY*g.GridCount+newX)
+ }
+ }
+
+ for _, gID := range surroundGID {
+ grids = append(grids, g.grids[gID])
+ }
+
+ return
+}
+
+func (g *GridManger) Add(entity *Entity) {
+ ID := g.GetGIDByPos(entity)
+ grid := g.grids[ID]
+ grid.Entities.Store(entity.Name, entity)
+}
+
+func (g *GridManger) Delete(entity *Entity) {
+ ID := g.GetGIDByPos(entity)
+ grid := g.grids[ID]
+ grid.Entities.Delete(entity.Name)
+}
+
+func (g *GridManger) Search(entity *Entity) (result []*Entity) {
+ ID := g.GetGIDByPos(entity)
+ grids := g.GetSurroundGrids(ID)
+ for _, grid := range grids {
+ grid.Entities.Range(func(_, value interface{}) bool {
+ result = append(result, value.(*Entity))
+ return true
+ })
+ }
+
+ return
+}
diff --git a/grid_test.go b/grid_test.go
new file mode 100644
index 0000000..9f3086f
--- /dev/null
+++ b/grid_test.go
@@ -0,0 +1,166 @@
+package aoi
+
+import (
+ "fmt"
+ "github.com/stretchr/testify/assert"
+ "math/rand"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+)
+
+/**
+ 0 50 100 150 200
+ -----------------------------
+0 | 0 1 2 3 4
+50 | 5 6 7 8 9
+100 | 10 11 12 13 14
+150 | 15 16 17 18 19
+200 | 20 21 22 23 24
+
+*/
+func TestGridManger_GetSurroundGrids(t *testing.T) {
+ aol := NewGridManger(0, 0, 250, 5)
+ manger := aol.(*GridManger)
+ tests := []struct {
+ entity *Entity
+ want []int
+ }{
+ {
+ entity: &Entity{
+ X: 0, Y: 0,
+ },
+ want: []int{0, 1, 5, 6},
+ },
+ {
+ entity: &Entity{
+ X: 150, Y: 0,
+ },
+ want: []int{2, 3, 4, 7, 8, 9},
+ },
+ {
+ entity: &Entity{
+ X: 50, Y: 50,
+ },
+ want: []int{0, 1, 2, 5, 6, 7, 10, 11, 12},
+ },
+ {
+ entity: &Entity{
+ X: 200, Y: 100,
+ },
+ want: []int{8, 9, 13, 14, 18, 19},
+ },
+ {
+ entity: &Entity{
+ X: 200, Y: 200,
+ },
+ want: []int{18, 19, 23, 24},
+ },
+ }
+
+ for _, tt := range tests {
+ ID := manger.GetGIDByPos(tt.entity)
+ grids := manger.GetSurroundGrids(ID)
+ gID := make([]int, 0)
+ for _, grid := range grids {
+ gID = append(gID, grid.GID)
+ }
+ sort.Ints(gID)
+ assert.Equal(t, tt.want, gID)
+ }
+}
+
+func TestNewGridManger(t *testing.T) {
+ aol := NewGridManger(0, 0, 250, 5)
+ manger := aol.(*GridManger)
+ entities := []*Entity{
+ {
+ X: 0, Y: 0, Name: "a",
+ },
+ {
+ X: 50, Y: 0, Name: "b",
+ },
+ {
+ X: 100, Y: 0, Name: "c",
+ },
+ {
+ X: 50, Y: 0, Name: "d",
+ },
+ {
+ X: 50, Y: 50, Name: "e",
+ },
+ {
+ X: 50, Y: 100, Name: "f",
+ },
+ {
+ X: 100, Y: 0, Name: "g",
+ },
+ {
+ X: 100, Y: 50, Name: "h",
+ },
+ {
+ X: 100, Y: 100, Name: "i",
+ },
+ }
+
+ for _, entity := range entities {
+ manger.Add(entity)
+ }
+
+ search := manger.Search(&Entity{X: 50, Y: 50})
+ result := make([]string, 0)
+ for _, entity := range search {
+ result = append(result, entity.Name)
+ }
+ sort.Strings(result)
+ assert.Equal(t, []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, result)
+
+ manger.Delete(&Entity{X: 100, Y: 100, Name: "i"})
+ search2 := manger.Search(&Entity{X: 50, Y: 50})
+ result2 := make([]string, 0)
+ for _, entity := range search2 {
+ result2 = append(result2, entity.Name)
+ }
+ sort.Strings(result2)
+ assert.Equal(t, []string{"a", "b", "c", "d", "e", "f", "g", "h"}, result2)
+}
+
+func BenchmarkGridManger(b *testing.B) {
+ var wg sync.WaitGroup
+ aol := NewGridManger(0, 0, 250, 5)
+ manger := aol.(*GridManger)
+
+ rand.Seed(time.Now().UnixNano())
+ for i := 0; i < b.N; i++ {
+ wg.Add(3000)
+ for j := 0; j < 1000; j++ {
+ go func() {
+ manger.Add(&Entity{
+ X: float64(rand.Intn(5) * 10),
+ Y: float64(rand.Intn(5) * 10),
+ Name: fmt.Sprintf("name%d", rand.Intn(50)),
+ })
+ wg.Done()
+ }()
+
+ go func() {
+ manger.Delete(&Entity{
+ X: float64(rand.Intn(5) * 10),
+ Y: float64(rand.Intn(5) * 10),
+ Name: fmt.Sprintf("name%d", rand.Intn(50)),
+ })
+ wg.Done()
+ }()
+
+ go func() {
+ manger.Search(&Entity{
+ X: float64(rand.Intn(5) * 10),
+ Y: float64(rand.Intn(5) * 10),
+ })
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ }
+}
diff --git a/quadtree.go b/quadtree.go
new file mode 100644
index 0000000..cb30b8a
--- /dev/null
+++ b/quadtree.go
@@ -0,0 +1,37 @@
+package aoi
+
+import "sync"
+
+const (
+ leftUp int = iota
+ rightUp
+ leftDown
+ rightDown
+)
+
+type Node struct {
+ AreaWidth int // 格子宽度(长=宽)
+ XStart int // 起始范围
+ YStart int // 起始范围
+ Deep int // 深度
+ Leaf bool // 是否为叶子节点
+ Parent *Node // 父节点
+ Child [4]*Node // 子节点
+ Entities sync.Map // 实体
+}
+
+type QuadTree struct {
+ Root *Node
+}
+
+func (q QuadTree) Add(entity *Entity) {
+ panic("implement me")
+}
+
+func (q QuadTree) Delete(entity *Entity) {
+ panic("implement me")
+}
+
+func (q QuadTree) Search(entity *Entity) (result []*Entity) {
+ panic("implement me")
+}
diff --git a/quadtree_test.go b/quadtree_test.go
new file mode 100644
index 0000000..5d87797
--- /dev/null
+++ b/quadtree_test.go
@@ -0,0 +1 @@
+package aoi