mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-27 09:58:28 +00:00
537 lines
24 KiB
C++
537 lines
24 KiB
C++
// Copyright (c) 2026 Pyxis contributors
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "Hardware/TDeck/MapTilePack.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
#include <esp_heap_caps.h>
|
|
#endif
|
|
|
|
namespace Hardware {
|
|
namespace TDeck {
|
|
namespace {
|
|
|
|
std::uint16_t selectionU16(const std::uint8_t* input) {
|
|
return static_cast<std::uint16_t>(input[0]) |
|
|
static_cast<std::uint16_t>(static_cast<std::uint16_t>(input[1]) << 8U);
|
|
}
|
|
|
|
std::uint32_t selectionU32(const std::uint8_t* input) {
|
|
return static_cast<std::uint32_t>(input[0]) |
|
|
(static_cast<std::uint32_t>(input[1]) << 8U) |
|
|
(static_cast<std::uint32_t>(input[2]) << 16U) |
|
|
(static_cast<std::uint32_t>(input[3]) << 24U);
|
|
}
|
|
|
|
std::uint32_t selectionCrc32(const std::uint8_t* input, std::size_t length) {
|
|
std::uint32_t crc = UINT32_C(0xffffffff);
|
|
for (std::size_t index = 0U; index < length; ++index) {
|
|
crc ^= input[index];
|
|
for (std::uint8_t bit = 0U; bit < 8U; ++bit) {
|
|
crc = (crc >> 1U) ^ ((crc & 1U) != 0U ? UINT32_C(0xedb88320) : 0U);
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
bool decodeSelection(const std::uint8_t* input, std::size_t length,
|
|
char* pack_id, std::uint32_t& generation) {
|
|
static const std::uint8_t magic[4] = {'P', 'M', 'A', 'S'};
|
|
if (input == NULL || pack_id == NULL || length != MapTilePack::LEGACY_ACTIVE_SELECTION_SIZE ||
|
|
std::memcmp(input, magic, sizeof(magic)) != 0 || input[4] != 1U || input[5] != 0U ||
|
|
selectionU16(input + 6U) != MapTilePack::LEGACY_ACTIVE_SELECTION_SIZE ||
|
|
selectionU32(input + 44U) != selectionCrc32(input, 44U)) {
|
|
return false;
|
|
}
|
|
generation = selectionU32(input + 8U);
|
|
const std::size_t id_length = input[12];
|
|
if (generation == 0U || id_length == 0U ||
|
|
id_length >= Pyxis::MapPackManifest::PACK_ID_CAPACITY) return false;
|
|
for (std::size_t index = 13U + id_length; index < 44U; ++index) {
|
|
if (input[index] != 0U) return false;
|
|
}
|
|
std::memcpy(pack_id, input + 13U, id_length);
|
|
pack_id[id_length] = '\0';
|
|
return MapTilePack::isValidPackId(pack_id);
|
|
}
|
|
|
|
bool readSelectionString(const std::uint8_t* input, std::size_t end, std::size_t& position,
|
|
char* output, std::size_t capacity, bool identifier) {
|
|
if (input == NULL || output == NULL || capacity < 2U || position >= end) return false;
|
|
const std::size_t length = input[position++];
|
|
if (length == 0U || length >= capacity || position + length > end) return false;
|
|
for (std::size_t index = 0U; index < length; ++index) {
|
|
const std::uint8_t value = input[position + index];
|
|
if (value < 0x20U || value > 0x7eU) return false;
|
|
if (identifier && !((value >= 'a' && value <= 'z') ||
|
|
(value >= '0' && value <= '9') || value == '_' || value == '-')) {
|
|
return false;
|
|
}
|
|
}
|
|
std::memcpy(output, input + position, length);
|
|
output[length] = '\0';
|
|
position += length;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char MapTilePack::ACTIVE_PACK_PATH[] = "/pyxis-map/active-pack";
|
|
const char MapTilePack::ACTIVE_PACK_SLOT_0_PATH[] = "/pyxis-map/active-pack.0";
|
|
const char MapTilePack::ACTIVE_PACK_SLOT_1_PATH[] = "/pyxis-map/active-pack.1";
|
|
|
|
MapTilePack::MapTilePack(MapTileStorage& storage)
|
|
: storage_(storage), manifest_(), active_packs_(), active_pack_count_(0U),
|
|
map_set_active_(false), selection_generation_(0U),
|
|
status_(MapTilePackStatus::UNINITIALIZED),
|
|
stream_open_(false), stream_remaining_(0U), selection_buffers_(),
|
|
active_selection_buffer_(0U) {
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
selection_buffers_ = static_cast<std::uint8_t*>(heap_caps_malloc(
|
|
2U * ACTIVE_SELECTION_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
|
#endif
|
|
}
|
|
|
|
MapTilePack::~MapTilePack() {
|
|
endGet();
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
if (selection_buffers_ != NULL) heap_caps_free(selection_buffers_);
|
|
selection_buffers_ = NULL;
|
|
#endif
|
|
}
|
|
|
|
bool MapTilePack::isValidPackId(const char* pack_id) {
|
|
if (pack_id == NULL) return false;
|
|
std::size_t length = 0U;
|
|
while (length < Pyxis::MapPackManifest::PACK_ID_CAPACITY && pack_id[length] != '\0') {
|
|
const char character = pack_id[length];
|
|
if (!((character >= 'a' && character <= 'z') ||
|
|
(character >= '0' && character <= '9') || character == '_' || character == '-')) {
|
|
return false;
|
|
}
|
|
++length;
|
|
}
|
|
return length > 0U && length < Pyxis::MapPackManifest::PACK_ID_CAPACITY;
|
|
}
|
|
|
|
bool MapTilePack::isValidKey(const TileKey& key) {
|
|
if (key.zoom > Pyxis::MapPackManifest::MAX_ZOOM) return false;
|
|
const std::uint32_t edge = UINT32_C(1) << key.zoom;
|
|
return key.x < edge && key.y < edge;
|
|
}
|
|
|
|
std::uint8_t* MapTilePack::selectionBuffer(std::uint8_t index) {
|
|
if (index > 1U) return NULL;
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
return selection_buffers_ == NULL ? NULL : selection_buffers_ +
|
|
static_cast<std::size_t>(index) * ACTIVE_SELECTION_SIZE;
|
|
#else
|
|
return selection_buffers_[index];
|
|
#endif
|
|
}
|
|
|
|
bool MapTilePack::parseMapSetSelection(const std::uint8_t* input, std::size_t length,
|
|
std::uint32_t& generation,
|
|
Pyxis::MapPackManifest& metadata,
|
|
ActivePackView* packs, std::uint8_t& pack_count) {
|
|
static const std::uint8_t magic[4] = {'P', 'M', 'A', 'S'};
|
|
if (input == NULL || packs == NULL || length < 16U || length > ACTIVE_SELECTION_SIZE ||
|
|
std::memcmp(input, magic, sizeof(magic)) != 0 || input[4] != 2U || input[5] != 0U ||
|
|
selectionU16(input + 6U) != length ||
|
|
selectionU32(input + length - 4U) != selectionCrc32(input, length - 4U)) return false;
|
|
generation = selectionU32(input + 8U);
|
|
if (generation == 0U) return false;
|
|
metadata = Pyxis::MapPackManifest();
|
|
std::size_t position = 12U;
|
|
if (!readSelectionString(input, length - 4U, position, metadata.pack_id,
|
|
sizeof(metadata.pack_id), true)) return false;
|
|
std::strncpy(metadata.name, metadata.pack_id, sizeof(metadata.name) - 1U);
|
|
if (!readSelectionString(input, length - 4U, position, metadata.attribution,
|
|
sizeof(metadata.attribution), false) || position >= length - 4U) return false;
|
|
std::strncpy(metadata.source, "active map set", sizeof(metadata.source) - 1U);
|
|
pack_count = input[position++];
|
|
if (pack_count == 0U || pack_count > MAX_ACTIVE_PACKS) return false;
|
|
std::uint16_t total_spans = 0U;
|
|
for (std::uint8_t pack_index = 0U; pack_index < pack_count; ++pack_index) {
|
|
ActivePackView& pack = packs[pack_index];
|
|
if (!readSelectionString(input, length - 4U, position, pack.pack_id,
|
|
sizeof(pack.pack_id), true)) return false;
|
|
for (std::uint8_t previous_pack = 0U; previous_pack < pack_index; ++previous_pack) {
|
|
if (std::strcmp(pack.pack_id, packs[previous_pack].pack_id) == 0) return false;
|
|
}
|
|
if (position + 2U > length - 4U) return false;
|
|
pack.span_count = selectionU16(input + position);
|
|
position += 2U;
|
|
if (pack.span_count == 0U || pack.span_count > MAX_ACTIVE_ROW_SPANS ||
|
|
total_spans > MAX_ACTIVE_ROW_SPANS - pack.span_count ||
|
|
position + static_cast<std::size_t>(pack.span_count) * 13U > length - 4U) return false;
|
|
pack.span_bytes = input + position;
|
|
std::uint8_t previous_zoom = 0U;
|
|
std::uint32_t previous_y = 0U;
|
|
std::uint32_t previous_x_maximum = 0U;
|
|
bool have_previous = false;
|
|
for (std::uint16_t span_index = 0U; span_index < pack.span_count; ++span_index) {
|
|
const std::uint8_t* span = input + position;
|
|
const std::uint8_t zoom = span[0];
|
|
const std::uint32_t y = selectionU32(span + 1U);
|
|
const std::uint32_t x_minimum = selectionU32(span + 5U);
|
|
const std::uint32_t x_maximum = selectionU32(span + 9U);
|
|
if (zoom > Pyxis::MapPackManifest::MAX_ZOOM) return false;
|
|
const std::uint32_t edge = UINT32_C(1) << zoom;
|
|
if (y >= edge || x_minimum > x_maximum || x_maximum >= edge ||
|
|
(have_previous && (zoom < previous_zoom ||
|
|
(zoom == previous_zoom && y < previous_y) ||
|
|
(zoom == previous_zoom && y == previous_y &&
|
|
x_minimum <= previous_x_maximum + 1U)))) return false;
|
|
previous_zoom = zoom;
|
|
previous_y = y;
|
|
previous_x_maximum = x_maximum;
|
|
have_previous = true;
|
|
position += 13U;
|
|
}
|
|
total_spans = static_cast<std::uint16_t>(total_spans + pack.span_count);
|
|
}
|
|
return position == length - 4U;
|
|
}
|
|
|
|
bool MapTilePack::spanCovers(const ActivePackView& pack, const TileKey& key) {
|
|
for (std::uint16_t index = 0U; index < pack.span_count; ++index) {
|
|
const std::uint8_t* span = pack.span_bytes + static_cast<std::size_t>(index) * 13U;
|
|
const std::uint8_t zoom = span[0];
|
|
if (zoom > key.zoom) return false;
|
|
if (zoom != key.zoom) continue;
|
|
const std::uint32_t y = selectionU32(span + 1U);
|
|
if (y > key.y) return false;
|
|
if (y == key.y && key.x >= selectionU32(span + 5U) && key.x <= selectionU32(span + 9U)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::makePath(const char* pack_id, const TileKey* key,
|
|
bool manifest, char* output, std::size_t capacity) {
|
|
if (!isValidPackId(pack_id)) return MapTilePackResult::INVALID_PACK_ID;
|
|
if (output == NULL || capacity == 0U) return MapTilePackResult::INVALID_ARGUMENT;
|
|
if (!manifest && (key == NULL || !isValidKey(*key))) return MapTilePackResult::INVALID_KEY;
|
|
|
|
char temporary[PATH_CAPACITY];
|
|
int count = 0;
|
|
if (manifest) {
|
|
count = std::snprintf(temporary, sizeof(temporary), "/pyxis-map/packs/%s/manifest.pmp", pack_id);
|
|
} else {
|
|
count = std::snprintf(temporary, sizeof(temporary),
|
|
"/pyxis-map/packs/%s/tiles/%u/%lu/%lu.png", pack_id,
|
|
static_cast<unsigned>(key->zoom),
|
|
static_cast<unsigned long>(key->x),
|
|
static_cast<unsigned long>(key->y));
|
|
}
|
|
if (count < 0 || static_cast<std::size_t>(count) >= sizeof(temporary) ||
|
|
static_cast<std::size_t>(count) + 1U > capacity) {
|
|
return MapTilePackResult::PATH_TOO_LONG;
|
|
}
|
|
std::memcpy(output, temporary, static_cast<std::size_t>(count) + 1U);
|
|
return MapTilePackResult::OK;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::manifestPath(const char* pack_id, char* output,
|
|
std::size_t capacity) {
|
|
return makePath(pack_id, NULL, true, output, capacity);
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::tilePath(const char* pack_id, const TileKey& key,
|
|
char* output, std::size_t capacity) {
|
|
return makePath(pack_id, &key, false, output, capacity);
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::loadFile(const char* path, std::uint8_t* output,
|
|
std::size_t capacity, std::size_t& length,
|
|
MapTilePackResult missing_result,
|
|
MapTilePackResult oversized_result) {
|
|
std::uint32_t declared_size = 0U;
|
|
const TileStoreResult begin = storage_.beginRead(path, declared_size);
|
|
if (begin == TileStoreResult::MISS) return missing_result;
|
|
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
|
|
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
|
|
|
|
if (declared_size > capacity) {
|
|
storage_.endRead();
|
|
return oversized_result;
|
|
}
|
|
|
|
std::size_t total = 0U;
|
|
while (total < declared_size) {
|
|
std::size_t count = 0U;
|
|
const TileStoreResult read = storage_.readChunk(output + total,
|
|
static_cast<std::size_t>(declared_size) - total, count);
|
|
if (read != TileStoreResult::OK || count == 0U ||
|
|
count > static_cast<std::size_t>(declared_size) - total) {
|
|
storage_.endRead();
|
|
return MapTilePackResult::IO_ERROR;
|
|
}
|
|
total += count;
|
|
}
|
|
std::uint8_t trailing = 0U;
|
|
std::size_t trailing_count = 0U;
|
|
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
|
|
storage_.endRead();
|
|
if (eof != TileStoreResult::OK || trailing_count != 0U) {
|
|
return MapTilePackResult::IO_ERROR;
|
|
}
|
|
length = total;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::failInitialize(MapTilePackResult result,
|
|
MapTilePackStatus initial_status,
|
|
bool had_selection) {
|
|
if (!had_selection) status_ = initial_status;
|
|
return result;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::initialize() {
|
|
endGet();
|
|
const bool had_selection = hasSelection();
|
|
if (selectionBuffer(0U) == NULL) {
|
|
return failInitialize(MapTilePackResult::INSUFFICIENT_MEMORY,
|
|
MapTilePackStatus::INSUFFICIENT_MEMORY, had_selection);
|
|
}
|
|
if (!storage_.isAvailable()) {
|
|
return failInitialize(MapTilePackResult::STORAGE_UNAVAILABLE,
|
|
MapTilePackStatus::STORAGE_UNAVAILABLE, had_selection);
|
|
}
|
|
|
|
const std::uint8_t candidate_buffer = static_cast<std::uint8_t>(active_selection_buffer_ ^ 1U);
|
|
const char* slot_paths[2] = {ACTIVE_PACK_SLOT_0_PATH, ACTIVE_PACK_SLOT_1_PATH};
|
|
std::uint32_t slot_generations[2] = {0U, 0U};
|
|
std::uint32_t slot_checksums[2] = {0U, 0U};
|
|
bool slot_valid[2] = {false, false};
|
|
MapTilePackResult result = MapTilePackResult::NO_SELECTION;
|
|
for (std::size_t slot = 0U; slot < 2U; ++slot) {
|
|
std::size_t record_length = 0U;
|
|
result = loadFile(slot_paths[slot], selectionBuffer(candidate_buffer), ACTIVE_SELECTION_SIZE,
|
|
record_length, MapTilePackResult::NO_SELECTION,
|
|
MapTilePackResult::INVALID_PACK_ID);
|
|
if (result == MapTilePackResult::OK) {
|
|
char legacy_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {};
|
|
Pyxis::MapPackManifest map_set_metadata = {};
|
|
ActivePackView map_set_packs[MAX_ACTIVE_PACKS] = {};
|
|
std::uint8_t map_set_pack_count = 0U;
|
|
if (decodeSelection(selectionBuffer(candidate_buffer), record_length,
|
|
legacy_id, slot_generations[slot]) ||
|
|
parseMapSetSelection(selectionBuffer(candidate_buffer), record_length,
|
|
slot_generations[slot], map_set_metadata,
|
|
map_set_packs, map_set_pack_count)) {
|
|
slot_valid[slot] = true;
|
|
slot_checksums[slot] = selectionU32(selectionBuffer(candidate_buffer) + record_length - 4U);
|
|
}
|
|
} else if (result != MapTilePackResult::NO_SELECTION &&
|
|
result != MapTilePackResult::INVALID_PACK_ID) {
|
|
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
|
|
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
|
|
return failInitialize(result, state, had_selection);
|
|
}
|
|
}
|
|
if (slot_valid[0] && slot_valid[1] && slot_generations[0] == slot_generations[1] &&
|
|
slot_checksums[0] != slot_checksums[1]) {
|
|
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
|
|
MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
|
|
char selected_pack_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {};
|
|
std::uint32_t selected_generation = 0U;
|
|
if (slot_valid[0] || slot_valid[1]) {
|
|
const std::size_t chosen = !slot_valid[1] ||
|
|
(slot_valid[0] && slot_generations[0] >= slot_generations[1]) ? 0U : 1U;
|
|
std::size_t record_length = 0U;
|
|
result = loadFile(slot_paths[chosen], selectionBuffer(candidate_buffer), ACTIVE_SELECTION_SIZE,
|
|
record_length, MapTilePackResult::NO_SELECTION,
|
|
MapTilePackResult::INVALID_PACK_ID);
|
|
if (result != MapTilePackResult::OK) {
|
|
return failInitialize(result, MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
std::uint32_t generation = 0U;
|
|
Pyxis::MapPackManifest map_set_metadata = {};
|
|
ActivePackView map_set_packs[MAX_ACTIVE_PACKS] = {};
|
|
std::uint8_t map_set_pack_count = 0U;
|
|
if (parseMapSetSelection(selectionBuffer(candidate_buffer), record_length, generation,
|
|
map_set_metadata, map_set_packs, map_set_pack_count)) {
|
|
manifest_ = map_set_metadata;
|
|
std::memcpy(active_packs_, map_set_packs,
|
|
static_cast<std::size_t>(map_set_pack_count) * sizeof(ActivePackView));
|
|
active_pack_count_ = map_set_pack_count;
|
|
map_set_active_ = true;
|
|
selection_generation_ = generation;
|
|
active_selection_buffer_ = candidate_buffer;
|
|
status_ = MapTilePackStatus::READY;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
if (!decodeSelection(selectionBuffer(candidate_buffer), record_length,
|
|
selected_pack_id, selected_generation)) {
|
|
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
|
|
MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
} else {
|
|
std::uint8_t marker[Pyxis::MapPackManifest::PACK_ID_CAPACITY];
|
|
std::size_t marker_length = 0U;
|
|
result = loadFile(ACTIVE_PACK_PATH, marker, sizeof(marker) - 1U,
|
|
marker_length, MapTilePackResult::NO_SELECTION,
|
|
MapTilePackResult::INVALID_PACK_ID);
|
|
if (result == MapTilePackResult::NO_SELECTION ||
|
|
(result == MapTilePackResult::OK && marker_length == 0U)) {
|
|
manifest_ = Pyxis::MapPackManifest();
|
|
active_pack_count_ = 0U;
|
|
map_set_active_ = false;
|
|
selection_generation_ = 0U;
|
|
status_ = MapTilePackStatus::NO_SELECTION;
|
|
return MapTilePackResult::NO_SELECTION;
|
|
}
|
|
if (result != MapTilePackResult::OK) {
|
|
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
|
|
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
|
|
return failInitialize(result, state, had_selection);
|
|
}
|
|
marker[marker_length] = 0U;
|
|
if (std::memchr(marker, 0, marker_length) != NULL ||
|
|
!isValidPackId(reinterpret_cast<const char*>(marker))) {
|
|
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
|
|
MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
std::strcpy(selected_pack_id, reinterpret_cast<const char*>(marker));
|
|
}
|
|
|
|
char manifest_path[PATH_CAPACITY];
|
|
result = manifestPath(selected_pack_id, manifest_path, sizeof(manifest_path));
|
|
if (result != MapTilePackResult::OK) {
|
|
return failInitialize(result, MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
std::size_t manifest_length = 0U;
|
|
result = loadFile(manifest_path, selectionBuffer(candidate_buffer), MANIFEST_BUFFER_CAPACITY,
|
|
manifest_length, MapTilePackResult::MANIFEST_MISSING,
|
|
MapTilePackResult::MANIFEST_TOO_LARGE);
|
|
if (result != MapTilePackResult::OK) {
|
|
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
|
|
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
|
|
return failInitialize(result, state, had_selection);
|
|
}
|
|
|
|
Pyxis::MapPackManifest candidate = {};
|
|
if (Pyxis::MapPackManifest::parse(selectionBuffer(candidate_buffer), manifest_length, candidate) !=
|
|
Pyxis::ManifestResult::OK) {
|
|
return failInitialize(MapTilePackResult::INVALID_MANIFEST,
|
|
MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
if (std::strcmp(selected_pack_id, candidate.pack_id) != 0) {
|
|
return failInitialize(MapTilePackResult::PACK_ID_MISMATCH,
|
|
MapTilePackStatus::INVALID_SELECTION, had_selection);
|
|
}
|
|
|
|
manifest_ = candidate;
|
|
active_pack_count_ = 0U;
|
|
map_set_active_ = false;
|
|
selection_generation_ = selected_generation;
|
|
active_selection_buffer_ = candidate_buffer;
|
|
status_ = MapTilePackStatus::READY;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::beginGet(const TileKey& key, std::uint32_t& size) {
|
|
if (stream_open_) return MapTilePackResult::BUSY;
|
|
if (!hasSelection()) return MapTilePackResult::NOT_INITIALIZED;
|
|
if (!isValidKey(key)) return MapTilePackResult::INVALID_KEY;
|
|
if (!storage_.isAvailable()) return MapTilePackResult::STORAGE_UNAVAILABLE;
|
|
|
|
if (map_set_active_) {
|
|
bool covered = false;
|
|
for (std::uint8_t index = 0U; index < active_pack_count_; ++index) {
|
|
if (!spanCovers(active_packs_[index], key)) continue;
|
|
covered = true;
|
|
char path[PATH_CAPACITY];
|
|
MapTilePackResult result = tilePath(active_packs_[index].pack_id, key, path, sizeof(path));
|
|
if (result != MapTilePackResult::OK) return result;
|
|
std::uint32_t candidate_size = 0U;
|
|
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
|
|
if (begin == TileStoreResult::MISS) continue;
|
|
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
|
|
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
|
|
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
|
|
stream_open_ = true;
|
|
stream_remaining_ = candidate_size;
|
|
size = candidate_size;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
return covered ? MapTilePackResult::TILE_MISSING : MapTilePackResult::UNCOVERED;
|
|
}
|
|
|
|
if (!manifest_.covers(key)) return MapTilePackResult::UNCOVERED;
|
|
char path[PATH_CAPACITY];
|
|
MapTilePackResult result = tilePath(manifest_.pack_id, key, path, sizeof(path));
|
|
if (result != MapTilePackResult::OK) return result;
|
|
std::uint32_t candidate_size = 0U;
|
|
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
|
|
if (begin == TileStoreResult::MISS) return MapTilePackResult::TILE_MISSING;
|
|
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
|
|
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
|
|
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
|
|
stream_open_ = true;
|
|
stream_remaining_ = candidate_size;
|
|
size = candidate_size;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
|
|
MapTilePackResult MapTilePack::readGetChunk(std::uint8_t* output, std::size_t capacity,
|
|
std::size_t& count) {
|
|
count = 0U;
|
|
if (output == NULL || capacity == 0U) {
|
|
endGet();
|
|
return MapTilePackResult::INVALID_ARGUMENT;
|
|
}
|
|
if (!stream_open_) return MapTilePackResult::NOT_STREAMING;
|
|
|
|
if (stream_remaining_ == 0U) {
|
|
std::uint8_t trailing = 0U;
|
|
std::size_t trailing_count = 0U;
|
|
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
|
|
endGet();
|
|
return (eof == TileStoreResult::OK && trailing_count == 0U)
|
|
? MapTilePackResult::OK : MapTilePackResult::IO_ERROR;
|
|
}
|
|
|
|
std::size_t read_count = 0U;
|
|
const std::size_t allowed = capacity < stream_remaining_ ? capacity : stream_remaining_;
|
|
const TileStoreResult read = storage_.readChunk(output, allowed, read_count);
|
|
if (read != TileStoreResult::OK || read_count > allowed ||
|
|
(read_count == 0U && stream_remaining_ != 0U)) {
|
|
endGet();
|
|
return MapTilePackResult::IO_ERROR;
|
|
}
|
|
if (read_count == 0U) {
|
|
endGet();
|
|
return MapTilePackResult::OK;
|
|
}
|
|
stream_remaining_ -= static_cast<std::uint32_t>(read_count);
|
|
if (stream_remaining_ == 0U) {
|
|
std::uint8_t trailing = 0U;
|
|
std::size_t trailing_count = 0U;
|
|
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
|
|
if (eof != TileStoreResult::OK || trailing_count != 0U) {
|
|
endGet();
|
|
return MapTilePackResult::IO_ERROR;
|
|
}
|
|
endGet();
|
|
}
|
|
count = read_count;
|
|
return MapTilePackResult::OK;
|
|
}
|
|
|
|
void MapTilePack::endGet() {
|
|
if (stream_open_) storage_.endRead();
|
|
stream_open_ = false;
|
|
stream_remaining_ = 0U;
|
|
}
|
|
|
|
} // namespace TDeck
|
|
} // namespace Hardware
|