191 lines
3.9 KiB
Go
191 lines
3.9 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])
|
|
}
|