Files
galaxy-game/client/client_test.go
T
2026-03-09 14:26:17 +03:00

228 lines
4.7 KiB
Go

package client
import (
"image"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
type testExecutor struct {
mu sync.Mutex
queue []func()
}
func (e *testExecutor) Post(fn func()) {
e.mu.Lock()
e.queue = append(e.queue, fn)
e.mu.Unlock()
}
func (e *testExecutor) FlushAll() {
for {
var fn func()
e.mu.Lock()
if len(e.queue) > 0 {
fn = e.queue[0]
e.queue = e.queue[1:]
}
e.mu.Unlock()
if fn == nil {
return
}
fn()
}
}
type testRefresher struct {
mu sync.Mutex
count int
}
func (r *testRefresher) Refresh() {
r.mu.Lock()
r.count++
r.mu.Unlock()
}
func (r *testRefresher) Count() int {
r.mu.Lock()
defer r.mu.Unlock()
return r.count
}
func TestRasterCoalescer_RequestBeforeDraw_CoalescesToLatest(t *testing.T) {
t.Parallel()
exec := &testExecutor{}
ref := &testRefresher{}
var got []int
co := NewRasterCoalescer(exec, ref, func(w, h int, p int) image.Image {
got = append(got, p)
return image.NewRGBA(image.Rect(0, 0, w, h))
})
co.Request(1)
co.Request(2)
co.Request(3)
// Only a single refresh should be scheduled before the next Draw().
exec.FlushAll()
require.Equal(t, 1, ref.Count())
_ = co.Draw(10, 10)
require.Equal(t, []int{3}, got)
}
func TestRasterCoalescer_RequestDuringDraw_SchedulesOneFollowUpRefresh(t *testing.T) {
t.Parallel()
exec := &testExecutor{}
ref := &testRefresher{}
var got []int
var co *RasterCoalescer[int]
co = NewRasterCoalescer(exec, ref, func(w, h int, p int) image.Image {
got = append(got, p)
if p == 1 {
co.Request(2)
co.Request(3)
}
return image.NewRGBA(image.Rect(0, 0, w, h))
})
co.Request(1)
exec.FlushAll()
require.Equal(t, 1, ref.Count())
// First draw renders 1 and schedules exactly one additional refresh.
_ = co.Draw(10, 10)
exec.FlushAll()
require.Equal(t, 2, ref.Count())
// Second draw renders latest (3).
_ = co.Draw(10, 10)
require.Equal(t, []int{1, 3}, got)
}
func TestRasterCoalescer_ManyRequestsWhileDrawing_StillOnlyOneExtraRefresh(t *testing.T) {
t.Parallel()
exec := &testExecutor{}
ref := &testRefresher{}
var got []int
var co *RasterCoalescer[int]
co = NewRasterCoalescer(exec, ref, func(w, h int, p int) image.Image {
got = append(got, p)
if p == 1 {
for i := 2; i <= 50; i++ {
co.Request(i)
}
}
return image.NewRGBA(image.Rect(0, 0, w, h))
})
co.Request(1)
exec.FlushAll()
require.Equal(t, 1, ref.Count())
_ = co.Draw(10, 10)
exec.FlushAll()
require.Equal(t, 2, ref.Count())
_ = co.Draw(10, 10)
require.Equal(t, []int{1, 50}, got)
}
func TestCopyViewportRGBA_CopiesROIAndIsIndependentFromSource(t *testing.T) {
t.Parallel()
src := image.NewRGBA(image.Rect(0, 0, 20, 20))
dst := image.NewRGBA(image.Rect(0, 0, 5, 6))
// Fill src with a pattern: pixel (x,y) has RGBA = (x, y, 0, 255).
for y := 0; y < 20; y++ {
for x := 0; x < 20; x++ {
off := y*src.Stride + x*4
src.Pix[off+0] = byte(x)
src.Pix[off+1] = byte(y)
src.Pix[off+2] = 0
src.Pix[off+3] = 255
}
}
marginX, marginY := 7, 9
copyViewportRGBA(dst, src, marginX, marginY, 5, 6)
// Verify a few pixels in dst match the expected source ROI.
// dst(0,0) == src(marginX, marginY)
{
off := 0*dst.Stride + 0*4
require.Equal(t, byte(marginX), dst.Pix[off+0])
require.Equal(t, byte(marginY), dst.Pix[off+1])
require.Equal(t, byte(255), dst.Pix[off+3])
}
// dst(4,5) == src(marginX+4, marginY+5)
{
off := 5*dst.Stride + 4*4
require.Equal(t, byte(marginX+4), dst.Pix[off+0])
require.Equal(t, byte(marginY+5), dst.Pix[off+1])
require.Equal(t, byte(255), dst.Pix[off+3])
}
// Mutate src ROI after copy and ensure dst is unchanged (no aliasing).
{
off := (marginY+0)*src.Stride + (marginX+0)*4
src.Pix[off+0] = 200
src.Pix[off+1] = 201
src.Pix[off+3] = 123
}
offDst := 0*dst.Stride + 0*4
require.Equal(t, byte(marginX), dst.Pix[offDst+0])
require.Equal(t, byte(marginY), dst.Pix[offDst+1])
require.Equal(t, byte(255), dst.Pix[offDst+3])
}
func TestEventPosToPixel_FloorMapping(t *testing.T) {
t.Parallel()
e := &client{}
// Pretend raster logical is 100x50, pixel is 1000x500.
e.metaMu.Lock()
e.lastRasterLogicW = 100
e.lastRasterLogicH = 50
e.lastRasterPxW = 1000
e.lastRasterPxH = 500
e.metaMu.Unlock()
x, y, ok := e.eventPosToPixel(0, 0)
require.True(t, ok)
require.Equal(t, 0, x)
require.Equal(t, 0, y)
// Middle
x, y, ok = e.eventPosToPixel(50, 25)
require.True(t, ok)
require.Equal(t, 500, x)
require.Equal(t, 250, y)
// Near max logical should map near max pixel with floor.
x, y, ok = e.eventPosToPixel(99.9, 49.9)
require.True(t, ok)
require.GreaterOrEqual(t, x, 998)
require.GreaterOrEqual(t, y, 498)
// Clamp
x, y, ok = e.eventPosToPixel(-10, 999)
require.True(t, ok)
require.Equal(t, 0, x)
require.Equal(t, 500, y)
}