Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 5 additions & 83 deletions lib/tdeck_ui/UI/LXMF/MapScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT

#include "MapScreen.h"
#include "MapTileLookupPolicy.h"
#include "MapTileStreamReader.h"

#ifdef ARDUINO
Expand All @@ -26,8 +25,6 @@ namespace LXMF {
namespace {

constexpr std::size_t TILE_PIXEL_COUNT = 256U * 256U;
constexpr std::uint32_t STORE_BYTE_QUOTA = 64U * 1024U * 1024U;
constexpr std::uint16_t STORE_ENTRY_CAPACITY = 128U;

class PackReadStream final : public Pyxis::MapTileReadStream {
public:
Expand Down Expand Up @@ -61,35 +58,6 @@ class PackReadStream final : public Pyxis::MapTileReadStream {
Hardware::TDeck::MapTilePack& pack_;
};

class LiveReadStream final : public Pyxis::MapTileReadStream {
public:
explicit LiveReadStream(Hardware::TDeck::MapTileStore& store) : store_(store) {}
Pyxis::MapTileStreamResult begin(const Hardware::TDeck::TileKey& key,
std::uint32_t& size) override {
const Hardware::TDeck::TileStoreResult result = store_.beginGet(key, size);
if (result == Hardware::TDeck::TileStoreResult::OK) return Pyxis::MapTileStreamResult::OK;
if (result == Hardware::TDeck::TileStoreResult::MISS) return Pyxis::MapTileStreamResult::MISS;
if (result == Hardware::TDeck::TileStoreResult::STORAGE_UNAVAILABLE ||
result == Hardware::TDeck::TileStoreResult::NOT_INITIALIZED) {
return Pyxis::MapTileStreamResult::STORAGE_UNAVAILABLE;
}
return Pyxis::MapTileStreamResult::IO_ERROR;
}
Pyxis::MapTileStreamResult read(std::uint8_t* output, std::size_t capacity,
std::size_t& count) override {
const Hardware::TDeck::TileStoreResult result =
store_.readGetChunk(output, capacity, count);
if (result == Hardware::TDeck::TileStoreResult::OK) return Pyxis::MapTileStreamResult::OK;
if (result == Hardware::TDeck::TileStoreResult::STORAGE_UNAVAILABLE) {
return Pyxis::MapTileStreamResult::STORAGE_UNAVAILABLE;
}
return Pyxis::MapTileStreamResult::IO_ERROR;
}
void end() override { store_.endGet(); }
private:
Hardware::TDeck::MapTileStore& store_;
};

class AtomicStopSource final : public Pyxis::MapTileStopSource {
public:
explicit AtomicStopSource(const std::atomic<bool>& stop) : stop_(stop) {}
Expand Down Expand Up @@ -117,10 +85,6 @@ lv_obj_t* createToolbarButton(lv_obj_t* parent, const char* text,

} // namespace

using Hardware::TDeck::MapTileStore;

static_assert(MapTileStore::HARD_MAX_ENTRIES == 128,
"map cache index contract changed; revisit bounded RAM budget");
static_assert(sizeof(lv_color_t) == 2U,
"offline tile buffers assume LVGL RGB565 true color");

Expand All @@ -131,14 +95,11 @@ MapScreen::MapScreen(lv_obj_t* parent)
recenter_button_(nullptr), pan_buttons_{}, tile_images_{},
tile_descriptors_{}, tile_pixels_{}, decoded_tile_cache_(TILE_PIXEL_COUNT),
decoded_cache_pixels_{}, approximation_halos_{}, markers_{}, marker_labels_{},
presenter_(), storage_(),
store_config_{STORE_ENTRY_CAPACITY, STORE_BYTE_QUOTA,
MAX_COMPRESSED_TILE_BYTES},
store_(storage_, store_config_), pack_(storage_), pack_attribution_{},
presenter_(), storage_(), pack_(storage_), pack_attribution_{},
screen_visible_(false), pack_refresh_epoch_(0U),
compressed_staging_(nullptr),
state_mutex_(nullptr), worker_task_(nullptr),
worker_exited_(true), worker_started_(false), store_initialized_(false),
worker_exited_(true), worker_started_(false),
requests_released_(false),
has_location_fix_(false), center_initialized_(false), current_location_{}, dragging_(false),
last_drag_point_{0, 0}, back_callback_() {
Expand Down Expand Up @@ -372,8 +333,6 @@ void MapScreen::publishPackAttribution() {
}

void MapScreen::workerLoop() {
Hardware::TDeck::TileStoreResult initialized = store_.initialize();
store_initialized_ = initialized == Hardware::TDeck::TileStoreResult::OK;
std::uint32_t handled_pack_refresh_epoch =
pack_refresh_epoch_.load(std::memory_order_acquire);
(void)pack_.initialize();
Expand Down Expand Up @@ -446,37 +405,16 @@ Pyxis::MapTileLoadResult MapScreen::readTile(
return Pyxis::MapTileLoadResult::READY;
}

struct LocalReadContext {
MapScreen* screen;
const Pyxis::MapTileRequest* request;
} context = {this, &request};
return Pyxis::MapTileLookupPolicy::readLocal(
&context,
[](void* opaque, Pyxis::MapTileLookupPolicy::LocalSource source) ->
Pyxis::MapTileLoadResult {
LocalReadContext* local = static_cast<LocalReadContext*>(opaque);
const CompressedTileSource mapped =
source == Pyxis::MapTileLookupPolicy::LocalSource::PACK
? CompressedTileSource::PACK
: CompressedTileSource::LIVE_STORE;
return local->screen->readCompressedTile(*local->request, mapped);
});
return readCompressedTile(request);
}

Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
const Pyxis::MapTileRequest& request, CompressedTileSource source) {
if (source == CompressedTileSource::LIVE_STORE && !store_initialized_) {
return Pyxis::MapTileLoadResult::STORAGE_UNAVAILABLE;
}
const Pyxis::MapTileRequest& request) {
PackReadStream pack_stream(pack_);
LiveReadStream live_stream(store_);
Pyxis::MapTileReadStream& stream = source == CompressedTileSource::PACK
? static_cast<Pyxis::MapTileReadStream&>(pack_stream)
: static_cast<Pyxis::MapTileReadStream&>(live_stream);
AtomicStopSource stop(stop_requested_);
std::size_t total = 0U;
const Pyxis::MapTileStreamResult read = Pyxis::MapTileStreamReader::readExact(
stream, stop, request.key, compressed_staging_, MAX_COMPRESSED_TILE_BYTES,
pack_stream, stop, request.key, compressed_staging_, MAX_COMPRESSED_TILE_BYTES,
READ_CHUNK_BYTES, total);
if (read == Pyxis::MapTileStreamResult::MISS) {
return Pyxis::MapTileLoadResult::MISS;
Expand All @@ -503,14 +441,6 @@ Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
&width, &height, &decode_state, compressed_staging_, total);
if (decode_error != 0U || width != 256U || height != 256U) {
lodepng_state_cleanup(&decode_state);
if (source == CompressedTileSource::LIVE_STORE) {
const Hardware::TDeck::TileStoreResult removed =
store_.removeTile(request.key);
return (removed == Hardware::TDeck::TileStoreResult::OK ||
removed == Hardware::TDeck::TileStoreResult::MISS)
? Pyxis::MapTileLoadResult::INVALID_PNG
: Pyxis::MapTileLoadResult::IO_ERROR;
}
return Pyxis::MapTileLoadResult::INVALID_PNG;
}
decode_state.info_raw.colortype = LCT_RGB;
Expand All @@ -520,14 +450,6 @@ Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
lodepng_state_cleanup(&decode_state);
if (decode_error != 0U || rgb == nullptr || width != 256U || height != 256U) {
if (rgb) lv_mem_free(rgb);
if (source == CompressedTileSource::LIVE_STORE) {
const Hardware::TDeck::TileStoreResult removed =
store_.removeTile(request.key);
return (removed == Hardware::TDeck::TileStoreResult::OK ||
removed == Hardware::TDeck::TileStoreResult::MISS)
? Pyxis::MapTileLoadResult::INVALID_PNG
: Pyxis::MapTileLoadResult::IO_ERROR;
}
return Pyxis::MapTileLoadResult::INVALID_PNG;
}
if (request.slot_index >= TILE_COUNT || !tile_pixels_[request.slot_index]) {
Expand Down
11 changes: 1 addition & 10 deletions lib/tdeck_ui/UI/LXMF/MapScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#include <freertos/semphr.h>
#include <freertos/task.h>

#include "Hardware/TDeck/MapTileStore.h"
#include "Hardware/TDeck/MapTileStoreSD.h"
#include "Hardware/TDeck/MapTilePack.h"

Expand Down Expand Up @@ -77,8 +76,6 @@ class MapScreen {

Pyxis::MapScreenPresenter presenter_;
Hardware::TDeck::MapTileStoreSD storage_;
Hardware::TDeck::TileStoreConfig store_config_;
Hardware::TDeck::MapTileStore store_;
Hardware::TDeck::MapTilePack pack_;
char pack_attribution_[Pyxis::MapPackManifest::ATTRIBUTION_CAPACITY];
std::atomic<bool> screen_visible_;
Expand All @@ -89,7 +86,6 @@ class MapScreen {
std::atomic<bool> stop_requested_;
std::atomic<bool> worker_exited_;
bool worker_started_;
bool store_initialized_;
bool requests_released_;
bool has_location_fix_;
bool center_initialized_;
Expand All @@ -99,16 +95,11 @@ class MapScreen {
BackCallback back_callback_;

static void workerEntry(void* context);
enum class CompressedTileSource : std::uint8_t {
PACK = 0,
LIVE_STORE
};
void workerLoop();
void publishPackAttribution();
Pyxis::MapTileLoadResult loadTile(const Pyxis::MapTileRequest& request);
Pyxis::MapTileLoadResult readTile(const Pyxis::MapTileRequest& request);
Pyxis::MapTileLoadResult readCompressedTile(
const Pyxis::MapTileRequest& request, CompressedTileSource source);
Pyxis::MapTileLoadResult readCompressedTile(const Pyxis::MapTileRequest& request);
bool startWorker();
void stopWorker();
bool lockState(TickType_t ticks = portMAX_DELAY);
Expand Down
52 changes: 0 additions & 52 deletions lib/tdeck_ui/UI/LXMF/MapTileLookupPolicy.h

This file was deleted.

56 changes: 23 additions & 33 deletions tests/build_scripts/test_map_screen_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def test_fixed_pool_and_cache_contracts():
assert re.search(r"#define\s+LV_IMG_CACHE_DEF_SIZE\s+0\b", lv_conf)
assert "std::vector" not in presenter + screen_h + screen_cpp
assert "std::map" not in presenter + screen_h + screen_cpp
assert "static_assert(MapTileStore::HARD_MAX_ENTRIES == 128" in screen_cpp
assert '#include "Hardware/TDeck/MapTileStore.h"' not in screen_h
assert "Hardware::TDeck::MapTileStore store_;" not in screen_h
assert "TileStoreConfig" not in screen_h + screen_cpp
assert "pack_.metadata().attribution" in screen_cpp
assert '"No active map pack"' in screen_cpp
assert "presenter_.visibleTileStatus" in screen_cpp
Expand All @@ -59,7 +61,7 @@ def test_worker_predecodes_and_render_path_has_no_io():
assert "lodepng_decode(" in source
assert "lodepng_inspect" in source
assert "max_output_size" in source
assert "store_.removeTile(request.key)" in source
assert "store_.removeTile(request.key)" not in source
assert "decode_failed_keys_" not in header
assert "decodeFailedFor" not in source
assert "markDecodeFailed" not in source
Expand All @@ -75,7 +77,7 @@ def test_worker_predecodes_and_render_path_has_no_io():
assert "MAX_COMPLETIONS_PER_TICK = 1" in text(UI / "MapScreen.h")


def test_selected_pack_is_worker_owned_read_only_and_precedes_live_cache():
def test_selected_pack_is_the_only_sd_tile_source():
source = text(UI / "MapScreen.cpp")
header = text(UI / "MapScreen.h")
assert '#include "Hardware/TDeck/MapTilePack.h"' in header
Expand All @@ -85,51 +87,39 @@ def test_selected_pack_is_worker_owned_read_only_and_precedes_live_cache():
assert "pack_(storage_)" in constructor

worker = function_body(source, "void MapScreen::workerLoop()")
assert worker.index("store_.initialize()") < worker.index("pack_.initialize()")
assert "pack_.initialize()" in worker
assert "store_.initialize()" not in worker

read_tile = function_body(source, "Pyxis::MapTileLoadResult MapScreen::readTile(")
assert read_tile.index("decoded_tile_cache_.get") < read_tile.index("PACK")
assert read_tile.index("PACK") < read_tile.index("LIVE_STORE")
assert (read_tile.index("decoded_tile_cache_.get") <
read_tile.index("readCompressedTile(request)"))
assert "LIVE_STORE" not in read_tile

pack_read = function_body(
source, "Pyxis::MapTileLoadResult MapScreen::readCompressedTile(")
assert ("source == CompressedTileSource::LIVE_STORE && !store_initialized_"
in pack_read)
assert "MapTileStreamReader::readExact" in pack_read
assert "PackReadStream pack_stream(pack_)" in pack_read
assert "LiveReadStream live_stream(store_)" in pack_read
assert "LiveReadStream" not in source
pack_adapter = source[source.index("class PackReadStream"):
source.index("class LiveReadStream")]
live_adapter = source[source.index("class LiveReadStream"):
source.index("class AtomicStopSource")]
assert "pack_.beginGet" in pack_adapter
assert "pack_.readGetChunk" in pack_adapter
assert "pack_.endGet" in pack_adapter
assert "remove" not in pack_adapter
assert "store_.beginGet" in live_adapter
assert "store_.readGetChunk" in live_adapter
assert "store_.endGet" in live_adapter
remove_token = "store_.removeTile(request.key)"
remove_offsets = []
cursor = 0
while True:
offset = pack_read.find(remove_token, cursor)
if offset < 0:
break
remove_offsets.append(offset)
cursor = offset + len(remove_token)
assert len(remove_offsets) == 2
for offset in remove_offsets:
remove_block = pack_read[max(0, offset - 220):offset + 80]
assert "source == CompressedTileSource::LIVE_STORE" in remove_block

# Covered-missing, uncovered, and corrupt immutable-pack tiles all continue
# to the mutable legacy cache, but production never starts online acquisition.
assert '#include "Hardware/TDeck/MapTileStore.h"' not in header
assert "Hardware::TDeck::MapTileStore store_;" not in header
assert "MapTileLookupPolicy" not in source + header
assert not (UI / "MapTileLookupPolicy.h").exists()
assert not (UI / "MapTileLookupPolicy.cpp").exists()
assert "store_initialized_" not in source + header
assert "store_config_" not in source + header
assert "LIVE_STORE" not in source + header
assert "/pyxis-map/tiles" not in source + header

# Covered-missing, uncovered, and corrupt immutable-pack tiles remain typed
# pack results. Production never falls through to an untyped style-less cache.
assert "MapTilePackResult::UNCOVERED" in pack_adapter
assert "MapTilePackResult::TILE_MISSING" in pack_adapter
assert "MapTileLookupPolicy::readLocal" in read_tile
assert "static MapTileLoadResult resolveLocal" in text(UI / "MapTileLookupPolicy.h")
assert "MapTileLookupPolicy::shouldStartOnline" not in source
load_tile = function_body(source, "Pyxis::MapTileLoadResult MapScreen::loadTile(")
assert "return readTile(request);" in load_tile

Expand Down
10 changes: 7 additions & 3 deletions tests/build_scripts/test_map_tile_downloader_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_dormant_downloader_chain_remains_verified_but_is_not_map_screen_wired()
for value in fingerprints)


def test_production_map_screen_is_strictly_sd_pack_first_without_network_acquisition():
def test_production_map_screen_uses_only_sd_packs_without_network_acquisition():
screen = MAP_SCREEN.read_text()
header = (ROOT / "lib/tdeck_ui/UI/LXMF/MapScreen.h").read_text()
settings = SETTINGS.read_text()
Expand All @@ -92,7 +92,10 @@ def test_production_map_screen_is_strictly_sd_pack_first_without_network_acquisi
assert forbidden not in screen
assert forbidden not in header
assert "return readTile(request);" in screen
assert "MapTileLookupPolicy::readLocal" in screen
assert "MapTileLookupPolicy" not in screen + header
assert "LiveReadStream" not in screen
assert "LIVE_STORE" not in screen + header
assert "store_.initialize()" not in screen
assert "MapTilePack pack_" in header
assert "pack_.initialize()" in screen
assert "pack_refresh_epoch_.fetch_add" in screen
Expand Down Expand Up @@ -126,7 +129,8 @@ def test_recent_decoded_tiles_use_a_fixed_psram_lru_before_sd_decode():
assert "decoded_tile_cache_.get" in screen
read_tile = screen[screen.index("Pyxis::MapTileLoadResult MapScreen::readTile("):
screen.index("Pyxis::MapTileLoadResult MapScreen::readCompressedTile(")]
assert read_tile.index("decoded_tile_cache_.get") < read_tile.index("MapTileLookupPolicy::readLocal")
assert (read_tile.index("decoded_tile_cache_.get") <
read_tile.index("readCompressedTile(request)"))
assert "decoded_tile_cache_.put" in screen
assert "MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT" in screen
assert "decoded_cache_pixels_[Pyxis::DecodedTileCache::CAPACITY]" in header
Expand Down
Loading
Loading