Skip to content

Commit

Permalink
WIP: world/entity.go: Move entity movement sending and chunk updating…
Browse files Browse the repository at this point in the history
… into world.EntityData.
  • Loading branch information
Sandertv committed Jan 6, 2025
1 parent 38f7fae commit b7e9a58
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 73 deletions.
3 changes: 2 additions & 1 deletion server/entity/ent.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ func (e *Ent) Tick(tx *world.Tx, current int64) {
e.SetOnFire(e.OnFireDuration() - time.Second/20)

if m := e.Behaviour().Tick(e, tx); m != nil {
m.Send()
e.data.Move(e, tx, m.pos, m.rot, m.onGround)
e.data.SetVelocity(e, tx, m.vel)
}
e.data.Age += time.Second / 20
}
Expand Down
16 changes: 0 additions & 16 deletions server/entity/movement.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@ type Movement struct {
onGround bool
}

// Send sends the Movement to any viewers watching the entity at the time of the movement. If the position/velocity
// changes were negligible, nothing is sent.
func (m *Movement) Send() {
posChanged := !m.dpos.ApproxEqualThreshold(zeroVec3, epsilon)
velChanged := !m.dvel.ApproxEqualThreshold(zeroVec3, epsilon)

for _, v := range m.v {
if posChanged {
v.ViewEntityMovement(m.e, m.pos, m.rot, m.onGround)
}
if velChanged {
v.ViewEntityVelocity(m.e, m.vel)
}
}
}

// Position returns the position as a result of the Movement as an mgl64.Vec3.
func (m *Movement) Position() mgl64.Vec3 {
return m.pos
Expand Down
7 changes: 4 additions & 3 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -2409,10 +2409,11 @@ func (p *Player) Tick(tx *world.Tx, current int64) {

if p.session() == session.Nop && !p.Immobile() {
m := p.mc.TickMovement(p, p.Position(), p.Velocity(), p.Rotation(), p.tx)
m.Send()

p.data.Vel = m.Velocity()
p.Move(m.Position().Sub(p.Position()), 0, 0)
// TODO: Move p.data.Move into p.Move()
// p.Move(m.Position().Sub(p.Position()), 0, 0)
p.data.Move(p, tx, m.Position(), m.Rotation(), p.onGround)
p.data.SetVelocity(p, tx, m.Velocity())
} else {
p.data.Vel = mgl64.Vec3{}
}
Expand Down
2 changes: 1 addition & 1 deletion server/world/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (conf Config) New() *World {
s := conf.Provider.Settings()
w := &World{
scheduledUpdates: newScheduledTickQueue(s.CurrentTick),
entities: make(map[*EntityHandle]ChunkPos),
entities: make(map[*EntityHandle]struct{}),
viewers: make(map[*Loader]Viewer),
chunks: make(map[ChunkPos]*Column),
closing: make(chan struct{}),
Expand Down
57 changes: 57 additions & 0 deletions server/world/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package world
import (
"encoding/binary"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"github.com/go-gl/mathgl/mgl64"
"github.com/google/uuid"
"io"
Expand Down Expand Up @@ -297,6 +298,62 @@ type EntityData struct {
Data any
}

func (edata *EntityData) SetVelocity(e Entity, tx *Tx, vel mgl64.Vec3) {
if !vel.ApproxEqualThreshold(edata.Vel, epsilon) {
// Only send velocity if the delta is big enough.
for _, v := range tx.Viewers(edata.Pos) {
v.ViewEntityVelocity(e, vel)
}
}
edata.Vel = vel
}

func (edata *EntityData) Move(e Entity, tx *Tx, pos mgl64.Vec3, rot cube.Rotation, onGround bool) {
prevChunkPos, chunkPos := chunkPosFromVec3(edata.Pos), chunkPosFromVec3(pos)
edata.updatePosition(e, tx, pos, rot, onGround)
if prevChunkPos == chunkPos {
// Entity remains in the same chunk.
return
}

// The entity's chunk position changed: Move it from the old chunk to the
// new chunk.
prevChunk, chunk := tx.World().chunk(prevChunkPos), tx.World().chunk(chunkPos)

chunk.Entities = append(chunk.Entities, e.H())
prevChunk.Entities = sliceutil.DeleteVal(prevChunk.Entities, e.H())

for _, viewer := range prevChunk.viewers {
if slices.Index(chunk.viewers, viewer) == -1 {
// First we hide the entity from all viewers that were previously
// viewing it, but no longer are.
viewer.HideEntity(e)
}
}
for _, viewer := range chunk.viewers {
if slices.Index(prevChunk.viewers, viewer) == -1 {
// Then we show the entity to all loaders that are now viewing the
// entity in the new chunk.
showEntity(e, viewer)
}
}
}

// epsilon is the epsilon used for thresholds for change used for change in
// position and velocity.
const epsilon = 0.001

func (edata *EntityData) updatePosition(e Entity, tx *Tx, pos mgl64.Vec3, rot cube.Rotation, onGround bool) {
if !pos.ApproxEqualThreshold(edata.Pos, epsilon) || !mgl64.Vec2(rot).ApproxEqualThreshold(mgl64.Vec2(edata.Rot), epsilon) {
// Only send movement if the delta of either rotation or position is
// significant enough.
for _, v := range tx.Viewers(edata.Pos) {
v.ViewEntityMovement(e, pos, rot, onGround)
}
}
edata.Pos, edata.Rot = pos, rot
}

// Entity represents an Entity in the world, typically an object that may be moved around and can be
// interacted with by other entities.
// Viewers of a world may view an Entity when near it.
Expand Down
49 changes: 3 additions & 46 deletions server/world/tick.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package world

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"maps"
"math/rand/v2"
"slices"
Expand Down Expand Up @@ -185,51 +184,9 @@ func (t ticker) anyWithinDistance(pos ChunkPos, loaded []ChunkPos, r int32) bool
// tickEntities ticks all entities in the world, making sure they are still located in the correct chunks and
// updating where necessary.
func (t ticker) tickEntities(tx *Tx, tick int64) {
for handle, lastPos := range tx.World().entities {
e := handle.mustEntity(tx)
chunkPos := chunkPosFromVec3(handle.data.Pos)

c, ok := tx.World().chunks[chunkPos]
if !ok {
continue
}

if lastPos != chunkPos {
// The entity was stored using an outdated chunk position. We update it and make sure it is ready
// for loaders to view it.
tx.World().entities[handle] = chunkPos
c.Entities = append(c.Entities, handle)

var viewers []Viewer

// When changing an entity's world, then teleporting it immediately, we could end up in a situation
// where the old chunk of the entity was not loaded. In this case, it should be safe simply to ignore
// the loaders from the old chunk. We can assume they never saw the entity in the first place.
if old, ok := tx.World().chunks[lastPos]; ok {
old.Entities = sliceutil.DeleteVal(old.Entities, handle)
viewers = old.viewers
}

for _, viewer := range viewers {
if slices.Index(c.viewers, viewer) == -1 {
// First we hide the entity from all loaders that were previously viewing it, but no
// longer are.
viewer.HideEntity(e)
}
}
for _, viewer := range c.viewers {
if slices.Index(viewers, viewer) == -1 {
// Then we show the entity to all loaders that are now viewing the entity in the new
// chunk.
showEntity(e, viewer)
}
}
}

if len(c.viewers) > 0 {
if te, ok := e.(TickerEntity); ok {
te.Tick(tx, tick)
}
for handle := range tx.World().entities {
if te, ok := handle.mustEntity(tx).(TickerEntity); ok {
te.Tick(tx, tick)
}
}
}
Expand Down
11 changes: 5 additions & 6 deletions server/world/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type World struct {
// entities holds a map of entities currently loaded and the last ChunkPos
// that the Entity was in. These are tracked so that a call to RemoveEntity
// can find the correct Entity.
entities map[*EntityHandle]ChunkPos
entities map[*EntityHandle]struct{}

r *rand.Rand

Expand Down Expand Up @@ -662,7 +662,7 @@ func (w *World) playSound(tx *Tx, pos mgl64.Vec3, s Sound) {
func (w *World) addEntity(tx *Tx, handle *EntityHandle) Entity {
handle.setAndUnlockWorld(w)
pos := chunkPosFromVec3(handle.data.Pos)
w.entities[handle] = pos
w.entities[handle] = struct{}{}

c := w.chunk(pos)
c.Entities, c.modified = append(c.Entities, handle), true
Expand All @@ -682,14 +682,13 @@ func (w *World) addEntity(tx *Tx, handle *EntityHandle) Entity {
// from the World, the Entity is no longer usable.
func (w *World) removeEntity(e Entity, tx *Tx) *EntityHandle {
handle := e.H()
pos, found := w.entities[handle]
if !found {
if _, ok := w.entities[handle]; !ok {
// The entity currently isn't in this world.
return nil
}
w.Handler().HandleEntityDespawn(tx, e)

c := w.chunk(pos)
c := w.chunk(chunkPosFromVec3(e.Position()))
c.Entities, c.modified = sliceutil.DeleteVal(c.Entities, handle), true

for _, v := range c.viewers {
Expand Down Expand Up @@ -1125,8 +1124,8 @@ func (w *World) loadChunk(pos ChunkPos) (*Column, error) {
col := w.columnFrom(column, pos)
w.chunks[pos] = col
for _, e := range col.Entities {
w.entities[e] = pos
e.w = w
w.entities[e] = struct{}{}
}
return col, nil
case errors.Is(err, leveldb.ErrNotFound):
Expand Down

0 comments on commit b7e9a58

Please sign in to comment.