Every configure the compositor sent during an interactive resize ran a whole frame inside Wayland event dispatch, including one at each stale intermediate size, so a fast drag queued more frames than the app could draw and the window lagged further and further behind. Configures and input now only enter the coalescing queue during dispatch; each frame applies the newest configure, acknowledges it and delivers the merged input, and the main loop waits for the compositor's frame callback before drawing again, so it renders at most once per display refresh. The synchronous refresh path is removed. Platform input is muted before the queue while tests inject theirs into it. Leo keeps every pointer sample so strokes stay smooth; other applications merge motion.
124 lines
4.5 KiB
C++
124 lines
4.5 KiB
C++
#include "AppDriver.hpp"
|
|
#include "LibraryViewerApplication.hpp"
|
|
|
|
#include "UnitTest++/UnitTest++.h"
|
|
|
|
using namespace tp;
|
|
using namespace tp::ui;
|
|
|
|
namespace {
|
|
constexpr halnf kPaintBudgetMs = 4;
|
|
constexpr int kMeasuredFrames = 30;
|
|
constexpr ualni kPaintedWidgetsBudget = 150;
|
|
|
|
halnf averagePaintMs(LibraryViewerApplication& application, AppDriver& driver) {
|
|
halnf total = 0;
|
|
for (int frame = 0; frame < kMeasuredFrames; frame++) {
|
|
application.ui().resetStats();
|
|
application.gui().requestPaint();
|
|
driver.frame();
|
|
total += (halnf) application.ui().stats().paintNs / 1.0e6f;
|
|
}
|
|
return total / kMeasuredFrames;
|
|
}
|
|
}
|
|
|
|
SUITE(LibraryViewerIntegration) {
|
|
TEST(ThousandsOfTracksPaintWithinBudget) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
CHECK(application.library().mTraks.size() > 2000);
|
|
|
|
const halnf atTop = averagePaintMs(application, driver);
|
|
CHECK(atTop < kPaintBudgetMs);
|
|
CHECK(application.ui().stats().paintedWidgets < kPaintedWidgetsBudget);
|
|
|
|
for (int notch = 0; notch < 200; notch++)
|
|
driver.scroll({ 300, 300 }, -3);
|
|
driver.advance(500);
|
|
const halnf scrolled = averagePaintMs(application, driver);
|
|
CHECK(scrolled < kPaintBudgetMs);
|
|
printf("library paint: %.3f ms at top, %.3f ms scrolled\n", atTop, scrolled);
|
|
}
|
|
|
|
TEST(ClickingTrackSelectsIt) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
driver.click(*application.gui().visibleTracks()[0]);
|
|
CHECK(application.gui().selectedTrack() != nullptr);
|
|
}
|
|
}
|
|
|
|
SUITE(LibraryViewerChrome) {
|
|
TEST(DefaultMenuBarCarriesLibraryMenus) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
CHECK(application.isMenuBarVisible());
|
|
CHECK_EQUAL(3u, application.menuBar().titles().size());
|
|
CHECK(application.menuBar().windowRect().pos.y < application.gui().windowRect().pos.y + 1);
|
|
const RectF player = application.gui().player.windowRect();
|
|
CHECK(player.pos.y + player.size.y > application.gui().windowRect().size.y - 4);
|
|
}
|
|
|
|
TEST(SearchingNarrowsTheRealLibrary) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
const size_t all = application.gui().visibleTracks().size();
|
|
driver.click(application.gui().search);
|
|
driver.type("jack");
|
|
CHECK(application.gui().visibleTracks().size() < all);
|
|
CHECK(!application.gui().visibleTracks().empty());
|
|
}
|
|
}
|
|
|
|
SUITE(LibraryViewerInputCoalescing) {
|
|
TEST(MotionBurstWithinOneFrameIsDeliveredOnce) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
const CoalescingStatistics before = application.window().inputStatistics();
|
|
|
|
WindowInputObserver& input = application.window().injectInput();
|
|
for (int step = 0; step < 50; step++)
|
|
input.onPointerMoved({ 100.0f + (halnf) step, 200.0f });
|
|
driver.frame();
|
|
|
|
const CoalescingStatistics after = application.window().inputStatistics();
|
|
CHECK_EQUAL(50u, after.received - before.received);
|
|
CHECK_EQUAL(1u, after.delivered - before.delivered);
|
|
}
|
|
|
|
TEST(ClickInsideBurstStillLandsOnItsPosition) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
const RectF target = application.gui().visibleTracks()[1]->windowRect();
|
|
|
|
WindowInputObserver& input = application.window().injectInput();
|
|
input.onPointerMoved({ 5, 5 });
|
|
input.onPointerMoved(target.center());
|
|
input.onPointerButton(InputID::MOUSE1, true);
|
|
input.onPointerButton(InputID::MOUSE1, false);
|
|
input.onPointerMoved({ 5, 5 });
|
|
driver.frame();
|
|
|
|
CHECK(application.gui().selectedTrack() == &application.gui().visibleTracks()[1]->track());
|
|
}
|
|
}
|
|
|
|
SUITE(LibraryViewerResize) {
|
|
TEST(MaximizeAppliesOneConfigurePerFrameAndRelayouts) {
|
|
LibraryViewerApplication application("library/Library.json");
|
|
AppDriver driver(application);
|
|
const Vec2F before = application.window().getSize();
|
|
const CoalescingStatistics start = application.window().inputStatistics();
|
|
|
|
application.window().toggleMaximize();
|
|
for (int frame = 0; frame < 30 && application.window().getSize() == before; frame++)
|
|
driver.frame();
|
|
|
|
const CoalescingStatistics end = application.window().inputStatistics();
|
|
CHECK(application.window().getSize().x > before.x || application.window().getSize().y > before.y);
|
|
CHECK(end.configuresApplied - start.configuresApplied >= 1);
|
|
CHECK(end.configuresApplied - start.configuresApplied <= end.configuresReceived - start.configuresReceived);
|
|
CHECK_CLOSE(application.window().getSize().x, application.gui().windowRect().size.x, 1.0f);
|
|
}
|
|
}
|