Files
mc/server/world/world_test.go
TarnaWijaya 26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
up3
2026-07-09 08:33:57 +08:00

194 lines
4.4 KiB
Go

package world
import (
"testing"
"time"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/go-gl/mathgl/mgl64"
)
// TestSynchronousWorldExec verifies that Exec on a synchronous World runs the
// transaction on the calling goroutine, with the returned channel closed once
// Exec returns.
func TestSynchronousWorldExec(t *testing.T) {
w := Config{Synchronous: true}.New()
defer w.Close()
var ran bool
c := w.Exec(func(tx *Tx) { ran = true })
if !ran {
t.Fatal("expected transaction to have run when Exec returned")
}
select {
case <-c:
default:
t.Fatal("expected channel returned by Exec to be closed when Exec returned")
}
}
// TestSynchronousWorldAdvanceTick verifies that a synchronous World does not
// tick on its own and that AdvanceTick advances the current tick exactly once
// per call, even without any viewers.
func TestSynchronousWorldAdvanceTick(t *testing.T) {
w := Config{Synchronous: true}.New()
defer w.Close()
current := func() int64 {
w.set.Lock()
defer w.set.Unlock()
return w.set.CurrentTick
}
start := current()
time.Sleep(time.Second / 10)
if got := current(); got != start {
t.Fatalf("expected no automatic ticking, tick advanced from %v to %v", start, got)
}
for range 5 {
w.AdvanceTick()
}
if got := current(); got != start+5 {
t.Fatalf("expected current tick %v after 5 AdvanceTick calls, got %v", start+5, got)
}
}
func TestSynchronousExecWorldCanRemoveEntity(t *testing.T) {
w := Config{Synchronous: true}.New()
defer w.Close()
h := EntitySpawnOpts{Position: mgl64.Vec3{0, 4, 0}}.New(testEntityType{}, testEntityConfig{})
<-w.Exec(func(tx *Tx) {
tx.AddEntity(h)
})
done := make(chan struct{})
go func() {
h.ExecWorld(func(tx *Tx, e Entity) {
tx.RemoveEntity(e)
})
close(done)
}()
select {
case <-done:
case <-time.After(time.Second * 5):
t.Fatal("ExecWorld self-removal did not complete")
}
}
func TestSynchronousAdvanceTickTicksViewerlessEntities(t *testing.T) {
w := Config{Synchronous: true}.New()
defer w.Close()
h := EntitySpawnOpts{Position: mgl64.Vec3{0, 4, 0}}.New(testEntityType{}, testEntityConfig{})
<-w.Exec(func(tx *Tx) {
tx.AddEntity(h)
})
start := h.data.Pos
for range 3 {
w.AdvanceTick()
}
if got := h.data.Pos; got == start {
t.Fatalf("expected entity position to change after ticking, got %v", got)
}
}
func TestSynchronousAdvanceTickTicksViewerlessBlockEntities(t *testing.T) {
w := Config{Synchronous: true}.New()
defer w.Close()
pos := cube.Pos{0, 4, 0}
tb := &testTickerBlock{}
<-w.Exec(func(tx *Tx) {
col := tx.World().chunk(chunkPosFromBlockPos(pos))
chest, ok := tx.World().conf.Blocks.BlockByName("minecraft:chest", map[string]any{"minecraft:cardinal_direction": "north"})
if !ok {
t.Fatal("expected chest block to be registered")
}
col.SetBlock(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0, tx.World().conf.Blocks.BlockRuntimeID(chest))
col.BlockEntities[pos] = tb
})
w.AdvanceTick()
if tb.ticks == 0 {
t.Fatal("expected block entity to tick")
}
}
type testEntityConfig struct{}
func (testEntityConfig) Apply(*EntityData) {}
type testEntityType struct{}
func (testEntityType) Open(_ *Tx, handle *EntityHandle, data *EntityData) Entity {
return &testEntity{handle: handle, data: data}
}
func (testEntityType) EncodeEntity() string {
return "dragonfly:test_entity"
}
func (testEntityType) BBox(Entity) cube.BBox {
return cube.Box(0, 0, 0, 1, 1, 1)
}
func (testEntityType) DecodeNBT(map[string]any, *EntityData) {}
func (testEntityType) EncodeNBT(*EntityData) map[string]any {
return nil
}
type testEntity struct {
handle *EntityHandle
data *EntityData
}
func (e *testEntity) Close() error {
return nil
}
func (e *testEntity) H() *EntityHandle {
return e.handle
}
func (e *testEntity) Position() mgl64.Vec3 {
return e.data.Pos
}
func (e *testEntity) Rotation() cube.Rotation {
return e.data.Rot
}
func (e *testEntity) Tick(*Tx, int64) {
e.data.Pos = e.data.Pos.Add(mgl64.Vec3{0, -0.1, 0})
}
type testTickerBlock struct {
ticks int
}
func (*testTickerBlock) EncodeBlock() (string, map[string]any) {
return "dragonfly:test_ticker", nil
}
func (*testTickerBlock) Hash() (uint64, uint64) {
return 1<<32 - 1, 0
}
func (*testTickerBlock) Model() BlockModel {
return unknownModel{}
}
func (*testTickerBlock) DecodeNBT(map[string]any) any {
return &testTickerBlock{}
}
func (*testTickerBlock) EncodeNBT() map[string]any {
return nil
}
func (b *testTickerBlock) Tick(int64, cube.Pos, *Tx) {
b.ticks++
}