mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-27 21:19:56 +00:00
feat: add bounded offline map screen
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#include "UI/LXMF/MapScreenPresenter.h"
|
||||
|
||||
namespace {
|
||||
int passed = 0;
|
||||
int failures = 0;
|
||||
#define CHECK(expr) do { if (expr) { ++passed; } else { ++failures; std::cerr << "FAIL line " << __LINE__ << ": " #expr << '\n'; } } while (false)
|
||||
|
||||
using Pyxis::MapScreenPresenter;
|
||||
using Pyxis::MapTileCompletion;
|
||||
using Pyxis::MapTileLoadResult;
|
||||
using Pyxis::MapTileRequest;
|
||||
using Pyxis::MapTileSlot;
|
||||
|
||||
Pyxis::MapView::Request requestAt(double latitude, double longitude, std::uint32_t zoom) {
|
||||
Pyxis::MapView::Request request{};
|
||||
request.center = {latitude, longitude};
|
||||
request.zoom = zoom;
|
||||
request.width = MapScreenPresenter::VIEWPORT_WIDTH;
|
||||
request.height = MapScreenPresenter::VIEWPORT_HEIGHT;
|
||||
request.include_tile_border = false;
|
||||
return request;
|
||||
}
|
||||
|
||||
bool sameKey(const Hardware::TDeck::TileKey& a,
|
||||
const Hardware::TDeck::TileKey& b) {
|
||||
return a.zoom == b.zoom && a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
MapTileCompletion completionFor(const MapTileRequest& request,
|
||||
MapTileLoadResult result) {
|
||||
MapTileCompletion completion{};
|
||||
completion.generation = request.generation;
|
||||
completion.frame_epoch = request.frame_epoch;
|
||||
completion.slot_token = request.slot_token;
|
||||
completion.slot_index = request.slot_index;
|
||||
completion.key = request.key;
|
||||
completion.result = result;
|
||||
return completion;
|
||||
}
|
||||
|
||||
void fixedCapacityAndDedupe() {
|
||||
MapScreenPresenter presenter;
|
||||
presenter.show();
|
||||
CHECK(presenter.buildFrame(requestAt(0.0, 0.0, 3)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
CHECK(presenter.frame().tile_count <= MapScreenPresenter::TILE_SLOT_COUNT);
|
||||
CHECK(presenter.requestCount() == presenter.frame().tile_count);
|
||||
|
||||
MapTileRequest requests[MapScreenPresenter::TILE_REQUEST_CAPACITY]{};
|
||||
std::size_t count = 0;
|
||||
while (presenter.takeRequest(requests[count])) ++count;
|
||||
CHECK(count == presenter.frame().tile_count);
|
||||
CHECK(!presenter.takeRequest(requests[0]));
|
||||
CHECK(presenter.buildFrame(requestAt(0.0, 0.0, 3)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
CHECK(presenter.requestCount() == 0);
|
||||
for (std::size_t a = 0; a < count; ++a) {
|
||||
for (std::size_t b = a + 1; b < count; ++b) {
|
||||
CHECK(!sameKey(requests[a].key, requests[b].key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void staleCompletionsRejectedAndAcceptedOnce() {
|
||||
MapScreenPresenter presenter;
|
||||
presenter.show();
|
||||
CHECK(presenter.buildFrame(requestAt(0.0, 0.0, 4)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
MapTileRequest request{};
|
||||
CHECK(presenter.takeRequest(request));
|
||||
|
||||
MapTileCompletion stale = completionFor(request, MapTileLoadResult::READY);
|
||||
++stale.generation;
|
||||
CHECK(!presenter.publishCompletion(stale));
|
||||
stale = completionFor(request, MapTileLoadResult::READY);
|
||||
++stale.frame_epoch;
|
||||
CHECK(!presenter.publishCompletion(stale));
|
||||
stale = completionFor(request, MapTileLoadResult::READY);
|
||||
++stale.slot_token;
|
||||
CHECK(!presenter.publishCompletion(stale));
|
||||
stale = completionFor(request, MapTileLoadResult::READY);
|
||||
stale.key.x ^= 1U;
|
||||
CHECK(!presenter.publishCompletion(stale));
|
||||
MapTileCompletion output{};
|
||||
CHECK(!presenter.takeApplicableCompletion(output));
|
||||
|
||||
const MapTileCompletion good = completionFor(request, MapTileLoadResult::READY);
|
||||
CHECK(presenter.publishCompletion(good));
|
||||
CHECK(presenter.takeApplicableCompletion(output));
|
||||
CHECK(output.slot_index == request.slot_index);
|
||||
CHECK(presenter.slot(request.slot_index).state == MapTileSlot::READY);
|
||||
CHECK(!presenter.takeApplicableCompletion(output));
|
||||
CHECK(!presenter.publishCompletion(good));
|
||||
}
|
||||
|
||||
void newestFrameReusesSlotsAndPurgesOldRequests() {
|
||||
MapScreenPresenter presenter;
|
||||
presenter.show();
|
||||
CHECK(presenter.buildFrame(requestAt(0.0, 0.0, 4)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
const std::uint32_t first_epoch = presenter.frameEpoch();
|
||||
const std::uint32_t tokens_before[MapScreenPresenter::TILE_SLOT_COUNT] = {
|
||||
presenter.slot(0).token, presenter.slot(1).token,
|
||||
presenter.slot(2).token, presenter.slot(3).token,
|
||||
presenter.slot(4).token, presenter.slot(5).token};
|
||||
CHECK(presenter.panPixels(300.0, 0.0));
|
||||
CHECK(presenter.frameEpoch() != first_epoch);
|
||||
CHECK(presenter.buildFrame(requestAt(99.0, 99.0, 1)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
CHECK(presenter.requestCount() <= MapScreenPresenter::TILE_REQUEST_CAPACITY);
|
||||
std::size_t occupied = 0;
|
||||
bool token_advanced = false;
|
||||
for (std::size_t i = 0; i < MapScreenPresenter::TILE_SLOT_COUNT; ++i) {
|
||||
if (presenter.slot(i).state != MapTileSlot::EMPTY) ++occupied;
|
||||
if (presenter.slot(i).token != tokens_before[i]) token_advanced = true;
|
||||
}
|
||||
CHECK(occupied == presenter.frame().tile_count);
|
||||
CHECK(token_advanced);
|
||||
MapTileRequest next{};
|
||||
while (presenter.takeRequest(next)) {
|
||||
CHECK(next.frame_epoch == presenter.frameEpoch());
|
||||
CHECK(next.generation == presenter.generation());
|
||||
}
|
||||
}
|
||||
|
||||
void generationPanZoomAndRecenterBounds() {
|
||||
MapScreenPresenter presenter;
|
||||
const std::uint32_t initial_generation = presenter.generation();
|
||||
presenter.show();
|
||||
CHECK(presenter.generation() != initial_generation);
|
||||
const std::uint32_t shown_generation = presenter.generation();
|
||||
presenter.hide();
|
||||
CHECK(presenter.generation() != shown_generation);
|
||||
CHECK(!presenter.active());
|
||||
presenter.show();
|
||||
|
||||
CHECK(presenter.setView({0.0, 179.99}, 22));
|
||||
CHECK(presenter.panPixels(100000.0, 1000000000.0));
|
||||
CHECK(presenter.center().longitude >= -180.0);
|
||||
CHECK(presenter.center().longitude < 180.0);
|
||||
CHECK(std::fabs(presenter.center().latitude) <=
|
||||
Pyxis::MapProjection::WEB_MERCATOR_MAX_LATITUDE + 1e-9);
|
||||
CHECK(presenter.zoomBy(-100));
|
||||
CHECK(presenter.zoom() == 0U);
|
||||
CHECK(!presenter.zoomBy(-1));
|
||||
CHECK(presenter.zoomBy(100));
|
||||
CHECK(presenter.zoom() == 22U);
|
||||
CHECK(!presenter.zoomBy(1));
|
||||
|
||||
Telemetry::LocationTelemetry fix{};
|
||||
fix.latitude_e6 = 51500000;
|
||||
fix.longitude_e6 = -114000;
|
||||
CHECK(!presenter.recenter(false, fix));
|
||||
CHECK(presenter.recenter(true, fix));
|
||||
CHECK(std::fabs(presenter.center().latitude - 51.5) < 1e-9);
|
||||
CHECK(std::fabs(presenter.center().longitude + 0.114) < 1e-9);
|
||||
}
|
||||
|
||||
void resultStatesAndQueueLimit() {
|
||||
MapScreenPresenter presenter;
|
||||
presenter.show();
|
||||
CHECK(presenter.buildFrame(requestAt(0.0, 0.0, 5)) ==
|
||||
Pyxis::MapView::Result::OK);
|
||||
MapTileRequest requests[MapScreenPresenter::TILE_REQUEST_CAPACITY]{};
|
||||
std::size_t count = 0;
|
||||
while (count < MapScreenPresenter::TILE_REQUEST_CAPACITY &&
|
||||
presenter.takeRequest(requests[count])) ++count;
|
||||
CHECK(count <= MapScreenPresenter::TILE_REQUEST_CAPACITY);
|
||||
const MapTileLoadResult results[] = {
|
||||
MapTileLoadResult::MISS,
|
||||
MapTileLoadResult::STORAGE_UNAVAILABLE,
|
||||
MapTileLoadResult::INVALID_PNG,
|
||||
MapTileLoadResult::TOO_LARGE,
|
||||
MapTileLoadResult::IO_ERROR,
|
||||
MapTileLoadResult::READY};
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
CHECK(presenter.publishCompletion(completionFor(requests[i], results[i])));
|
||||
}
|
||||
CHECK(presenter.completionCount() == count);
|
||||
MapTileCompletion completion{};
|
||||
std::size_t applied = 0;
|
||||
while (presenter.takeApplicableCompletion(completion)) ++applied;
|
||||
CHECK(applied == count);
|
||||
CHECK(presenter.completionCount() == 0);
|
||||
}
|
||||
|
||||
void deterministicHundredThousandOperationStress() {
|
||||
MapScreenPresenter presenter;
|
||||
presenter.show();
|
||||
std::uint32_t state = 0x12345678U;
|
||||
for (std::size_t operation = 0; operation < 100000U; ++operation) {
|
||||
state = state * 1664525U + 1013904223U;
|
||||
if ((state & 15U) == 0U) {
|
||||
presenter.zoomBy((state & 16U) ? 1 : -1);
|
||||
} else {
|
||||
const double dx = static_cast<int>((state >> 8) & 127U) - 63;
|
||||
const double dy = static_cast<int>((state >> 16) & 127U) - 63;
|
||||
(void)presenter.panPixels(dx, dy);
|
||||
}
|
||||
Pyxis::MapView::Request request =
|
||||
requestAt(presenter.center().latitude,
|
||||
presenter.center().longitude,
|
||||
presenter.zoom());
|
||||
CHECK(presenter.buildFrame(request) == Pyxis::MapView::Result::OK);
|
||||
CHECK(presenter.frame().tile_count <= MapScreenPresenter::TILE_SLOT_COUNT);
|
||||
CHECK(presenter.requestCount() <= MapScreenPresenter::TILE_REQUEST_CAPACITY);
|
||||
CHECK(presenter.completionCount() <= MapScreenPresenter::TILE_COMPLETION_CAPACITY);
|
||||
MapTileRequest tile_request{};
|
||||
if (presenter.takeRequest(tile_request)) {
|
||||
CHECK(presenter.publishCompletion(
|
||||
completionFor(tile_request, MapTileLoadResult::READY)));
|
||||
MapTileCompletion completion{};
|
||||
CHECK(presenter.takeApplicableCompletion(completion));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
fixedCapacityAndDedupe();
|
||||
staleCompletionsRejectedAndAcceptedOnce();
|
||||
newestFrameReusesSlotsAndPurgesOldRequests();
|
||||
generationPanZoomAndRecenterBounds();
|
||||
resultStatesAndQueueLimit();
|
||||
deterministicHundredThousandOperationStress();
|
||||
std::cout << "map screen presenter: " << passed << " passed, "
|
||||
<< failures << " failed\n";
|
||||
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Reference in New Issue
Block a user