diff --git a/server/clientinfo/clientinfo.go b/server/clientinfo/clientinfo.go new file mode 100644 index 0000000..8c07b68 --- /dev/null +++ b/server/clientinfo/clientinfo.go @@ -0,0 +1,73 @@ +package clientinfo + +import ( + "context" + + "github.com/google/uuid" + + "github.com/Tnze/go-mc/data/packetid" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/server" +) + +type ClientInformation struct { + Players map[uuid.UUID]*Info +} + +type Info struct { + Locale string + ViewDistance int + ChatMode byte + ChatColors bool + DisplayedSkinParts byte + MainHand byte // 0: Left, 1: Right. + EnableTextFiltering bool + AllowServerListings bool +} + +func (c *ClientInformation) Init(g *server.Game) { + c.Players = make(map[uuid.UUID]*Info) + g.AddHandler(&server.PacketHandler{ + ID: packetid.ServerboundClientInformation, + F: func(player *server.Player, p server.Packet758) error { + var ( + Locale pk.String + ViewDistance pk.Byte + ChatMode pk.VarInt + ChatColors pk.Boolean + DisplayedSkinParts pk.UnsignedByte + MainHand pk.VarInt + EnableTextFiltering pk.Boolean + AllowServerListings pk.Boolean + ) + err := pk.Packet(p).Scan( + &Locale, + &ViewDistance, + &ChatMode, + &ChatColors, + &DisplayedSkinParts, + &MainHand, + &EnableTextFiltering, + &AllowServerListings, + ) + if err != nil { + return err + } + c.Players[player.UUID] = &Info{ + Locale: string(Locale), + ViewDistance: int(ViewDistance), + ChatMode: byte(ChatMode), + ChatColors: bool(ChatColors), + DisplayedSkinParts: byte(DisplayedSkinParts), + MainHand: byte(MainHand), + EnableTextFiltering: bool(EnableTextFiltering), + AllowServerListings: bool(AllowServerListings), + } + return nil + }, + }) +} + +func (c *ClientInformation) Run(ctx context.Context) {} +func (c *ClientInformation) AddPlayer(p *server.Player) {} +func (c *ClientInformation) RemovePlayer(p *server.Player) {} diff --git a/server/dimension/loadmanager.go b/server/dimension/loadmanager.go index 37c3640..a51a6c3 100644 --- a/server/dimension/loadmanager.go +++ b/server/dimension/loadmanager.go @@ -3,51 +3,100 @@ package dimension import ( "container/list" + "github.com/Tnze/go-mc/data/packetid" "github.com/Tnze/go-mc/level" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/server" + "github.com/Tnze/go-mc/server/clientinfo" + "github.com/Tnze/go-mc/server/internal/bvh" ) +type vec2d = bvh.Vec2[float64] +type sphere2d = bvh.Sphere[float64, vec2d] + type manager struct { storage - elements map[level.ChunkPos]*list.Element activeChunks *list.List - - chunkLoadQueue []level.ChunkPos - chunkUnloadQueue []level.ChunkPos + chunkElement map[level.ChunkPos]*list.Element + players bvh.Tree[float64, sphere2d, *server.Player] + clients *clientinfo.ClientInformation } type chunkHandler struct { level.ChunkPos *level.Chunk + // records all players loaded this chunk + players map[*server.Player]struct{} } -func (m *manager) refresh(players [][2]int) { - m.chunkLoadQueue = m.chunkLoadQueue[:0] - m.chunkUnloadQueue = m.chunkUnloadQueue[:0] - - newActives := list.New() - const N = 16 - for _, p := range players { - for i := 1 - N; i < N; i++ { - for j := 1 - N; j < N; j++ { +func (m *manager) refresh() error { + all := func(b sphere2d) bool { return true } + m.players.Find(all, func(n *bvh.Node[float64, sphere2d, *server.Player]) bool { + p := n.Value + v := m.clients.Players[p.UUID].ViewDistance + for i := 1 - v; i < v; i++ { + for j := 1 - v; j < v; j++ { pos := level.ChunkPos{ - X: p[0] + i, - Z: p[1] + j, + X: int(n.Box.Center[0]) >> 4, + Z: int(n.Box.Center[1]) >> 4, } - if e := m.elements[pos]; e != nil { - // chunk exist, move into newActives - v := m.activeChunks.Remove(e) - m.elements[pos] = newActives.PushBack(v) - } else { - // not exist, load from storage - m.chunkLoadQueue = append(m.chunkLoadQueue, pos) + point := vec2d{float64(pos.X + i), float64(pos.Z + j)} + if _, exist := m.chunkElement[pos]; !exist && n.Box.WithIn(point) { + c, err := m.GetChunk(pos) + if err != nil { + return false + } + m.chunkElement[pos] = m.activeChunks.PushBack(c) } } } + return true + }) + for e := m.activeChunks.Front(); e != nil; { + ch := e.Value.(*chunkHandler) + point := vec2d{float64(ch.X), float64(ch.Z)} + filter := bvh.TouchPoint[vec2d, sphere2d](point) + newPlayers := make(map[*server.Player]struct{}) + m.players.Find(filter, func(n *bvh.Node[float64, sphere2d, *server.Player]) bool { + p := n.Value + if _, ok := ch.players[p]; ok { + delete(ch.players, p) + } else { + playerLoadChunk(p, ch) + } + newPlayers[p] = struct{}{} + return true + }) + for p := range ch.players { + playerUnloadChunk(p, ch) + } + if len(newPlayers) > 0 { + ch.players = newPlayers + e = e.Next() + } else { + // no player around this chunk, unload it + if err := m.PutChunk(ch.ChunkPos, ch.Chunk); err != nil { + return err + } + next := e.Next() + m.activeChunks.Remove(e) + delete(m.chunkElement, ch.ChunkPos) + e = next + } } - for e := m.activeChunks.Front(); e != nil; e = e.Next() { - pos := e.Value.(chunkHandler).ChunkPos - m.chunkUnloadQueue = append(m.chunkUnloadQueue, pos) - m.elements[pos] = newActives.PushBack(e.Value) - } - m.activeChunks = newActives + return nil +} + +func playerLoadChunk(p *server.Player, c *chunkHandler) { + p.WritePacket(server.Packet758(pk.Marshal( + packetid.ClientboundLevelChunkWithLight, + c.ChunkPos, c.Chunk, + ))) +} + +func playerUnloadChunk(p *server.Player, c *chunkHandler) { + p.WritePacket(server.Packet758(pk.Marshal( + packetid.ClientboundForgetLevelChunk, + c.ChunkPos, + ))) } diff --git a/server/dimension/loadmanager_test.go b/server/dimension/loadmanager_test.go new file mode 100644 index 0000000..258223d --- /dev/null +++ b/server/dimension/loadmanager_test.go @@ -0,0 +1,7 @@ +package dimension + +import "testing" + +func Test_manager(t *testing.T) { + +} diff --git a/server/internal/bvh/bound.go b/server/internal/bvh/bound.go index 85f7144..ce9758a 100644 --- a/server/internal/bvh/bound.go +++ b/server/internal/bvh/bound.go @@ -56,4 +56,4 @@ func (s Sphere[I, V]) Union(other Sphere[I, V]) Sphere[I, V] { R: d + s.R + other.R, } } -func (s Sphere[I, V]) Surface() I { return I(2 * math.Pi * s.R) } +func (s Sphere[I, V]) Surface() I { return 2 * math.Pi * s.R } diff --git a/server/internal/bvh/bvh.go b/server/internal/bvh/bvh.go index e39af8d..4515e69 100644 --- a/server/internal/bvh/bvh.go +++ b/server/internal/bvh/bvh.go @@ -10,7 +10,7 @@ type Node[I constraints.Float, B interface { Union(B) B Surface() I }, V any] struct { - box B + Box B Value V parent *Node[I, B, V] children [2]*Node[I, B, V] @@ -35,17 +35,14 @@ func (n *Node[I, B, V]) findChildPointer(child *Node[I, B, V]) **Node[I, B, V] { panic("unreachable, please make sure the 'not' is the n's child") } -func (n *Node[I, B, V]) each(test func(bound B) bool, foreach func(v V)) { +func (n *Node[I, B, V]) each(test func(bound B) bool, foreach func(n *Node[I, B, V]) bool) bool { if n == nil { - return + return true } if n.isLeaf { - if test(n.box) { - foreach(n.Value) - } + return !test(n.Box) || foreach(n) } else { - n.children[0].each(test, foreach) - n.children[1].each(test, foreach) + return n.children[0].each(test, foreach) && n.children[1].each(test, foreach) } } @@ -58,7 +55,7 @@ type Tree[I constraints.Float, B interface { func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { n = &Node[I, B, V]{ - box: leaf, + Box: leaf, Value: value, parent: nil, children: [2]*Node[I, B, V]{nil, nil}, @@ -71,7 +68,7 @@ func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { // Stage 1: find the best sibling for the new leaf sibling := t.root - bestCost := t.root.box.Union(leaf).Surface() + bestCost := t.root.Box.Union(leaf).Surface() parentTo := &t.root // the parent's children pointer which point to the sibling var queue searchHeap[I, Node[I, B, V]] @@ -81,8 +78,8 @@ func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { for queue.Len() > 0 { p := heap.Pop(&queue).(searchItem[I, Node[I, B, V]]) // determine if node p has the best cost - mergeSurface := p.pointer.box.Union(leaf).Surface() - deltaCost := mergeSurface - p.pointer.box.Surface() + mergeSurface := p.pointer.Box.Union(leaf).Surface() + deltaCost := mergeSurface - p.pointer.Box.Surface() cost := p.inheritedCost + mergeSurface if cost <= bestCost { bestCost = cost @@ -107,7 +104,7 @@ func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { // Stage 2: create a new parent *parentTo = &Node[I, B, V]{ - box: sibling.box.Union(leaf), // we will calculate in Stage3 + Box: sibling.Box.Union(leaf), // we will calculate in Stage3 parent: sibling.parent, children: [2]*Node[I, B, V]{sibling, n}, isLeaf: false, @@ -117,7 +114,7 @@ func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { // Stage 3: walk back up the tree refitting AABBs for p := *parentTo; p != nil; p = p.parent { - p.box = p.children[0].box.Union(p.children[1].box) + p.Box = p.children[0].Box.Union(p.children[1].Box) t.rotate(p) } return @@ -140,7 +137,7 @@ func (t *Tree[I, B, V]) Delete(n *Node[I, B, V]) interface{} { *p = sibling sibling.parent = grand for p := sibling.parent; p.parent != nil; p = p.parent { - p.box = p.children[0].box.Union(p.children[1].box) + p.Box = p.children[0].Box.Union(p.children[1].Box) t.rotate(p) } } @@ -153,23 +150,23 @@ func (t *Tree[I, B, V]) rotate(n *Node[I, B, V]) { } // trying to swap n's sibling and children sibling := n.parent.findAnotherChild(n) - current := n.box.Surface() - if n.children[1].box.Union(sibling.box).Surface() < current { + current := n.Box.Surface() + if n.children[1].Box.Union(sibling.Box).Surface() < current { // swap n.children[0] and sibling t1 := [2]*Node[I, B, V]{n, n.children[0]} t2 := [2]*Node[I, B, V]{sibling, n.children[1]} n.parent.children, n.children, n.children[0].parent, sibling.parent = t1, t2, n.parent, n - n.box = n.children[0].box.Union(n.children[1].box) - } else if n.children[0].box.Union(sibling.box).Surface() < current { + n.Box = n.children[0].Box.Union(n.children[1].Box) + } else if n.children[0].Box.Union(sibling.Box).Surface() < current { // swap n.children[1] and sibling t1 := [2]*Node[I, B, V]{n, n.children[1]} t2 := [2]*Node[I, B, V]{sibling, n.children[0]} n.parent.children, n.children, n.children[1].parent, sibling.parent = t1, t2, n.parent, n - n.box = n.children[0].box.Union(n.children[1].box) + n.Box = n.children[0].Box.Union(n.children[1].Box) } } -func (t *Tree[I, B, V]) Find(test func(bound B) bool, foreach func(v V)) { +func (t *Tree[I, B, V]) Find(test func(bound B) bool, foreach func(n *Node[I, B, V]) bool) { t.root.each(test, foreach) } diff --git a/server/internal/bvh/bvh_test.go b/server/internal/bvh/bvh_test.go index 67c165b..6103fc2 100644 --- a/server/internal/bvh/bvh_test.go +++ b/server/internal/bvh/bvh_test.go @@ -23,8 +23,9 @@ func TestTree2_Insert(t *testing.T) { // visualize t.Log(bvh) } - bvh.Find(TouchPoint[Vec2[float64], AABB[float64, Vec2[float64]]](Vec2[float64]{0.5, 0.5}), func(v int) { - t.Logf("find! %v", v) + bvh.Find(TouchPoint[Vec2[float64], AABB[float64, Vec2[float64]]](Vec2[float64]{0.5, 0.5}), func(n *Node[float64, AABB[float64, Vec2[float64]], int]) bool { + t.Logf("find! %v", n.Value) + return true }) } @@ -46,8 +47,9 @@ func TestTree2_Find_vec(t *testing.T) { } find := func(test func(bound AABBVec2d) bool) []int { var result []int - bvh.Find(test, func(i int) { - result = append(result, i) + bvh.Find(test, func(n *Node[float64, AABBVec2d, int]) bool { + result = append(result, n.Value) + return true }) return result } @@ -108,7 +110,7 @@ func BenchmarkTree2_Find_random(b *testing.B) { b.ResetTimer() for _, v := range poses { - bvh.Find(TouchPoint[Vec2d, AABBVec2d](v), func(v any) {}) + bvh.Find(TouchPoint[Vec2d, AABBVec2d](v), func(n *Node[float64, AABBVec2d, any]) bool { return true }) } }