up3
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
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
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
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++
|
||||
}
|
||||
Reference in New Issue
Block a user