Skip to content

Commit 8bd654e

Browse files
Fix tile fetcher (#118)
* Avoid blocking in `readHttpHeaders()` by implementing timeout-aware line read * Add RenderMode support to control tile fetching behavior (FAST vs ACCURATE) * Use `OSM_MAX_HEADERLENGTH` to limit header mem allocations * Clean up MemoryBuffer use
1 parent 2a76f9e commit 8bd654e

9 files changed

+146
-264
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,23 @@ If you encounter a problem or want to request support for a new provider, please
151151
char *getProviderName()
152152
```
153153

154+
### Set the render mode
155+
156+
```c++
157+
void setRenderMode(RenderMode mode)
158+
```
159+
160+
Available modes:
161+
162+
- `RenderMode::ACCURATE` (default)
163+
Downloads map tiles **without a timeout**, ensuring a complete map with **no missing tiles** in most cases.
164+
Best suited for reliability and full-quality rendering.
165+
166+
- `RenderMode::FAST`
167+
Downloads map tiles **with a timeout**.
168+
This mode can produce the map **more quickly**, but some **tiles may be missing** if a request times out.
169+
Ideal when operating under time constraints.
170+
154171
## Example code
155172
156173
### Example returning the default 320x240 map

src/HTTPClientRAII.hpp

Lines changed: 0 additions & 63 deletions
This file was deleted.

src/MemoryBuffer.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ bool MemoryBuffer::isAllocated()
4444
{
4545
return buffer_.get() != nullptr;
4646
}
47+
48+
MemoryBuffer MemoryBuffer::empty()
49+
{
50+
return MemoryBuffer(0);
51+
}

src/MemoryBuffer.hpp

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,66 +27,15 @@
2727
#include <Arduino.h>
2828
#include <memory>
2929

30-
/**
31-
* @class MemoryBuffer
32-
* @brief A class that handles memory allocation and deallocation for a buffer.
33-
*
34-
* This class provides an RAII approach to manage a dynamically allocated buffer. It ensures that memory is
35-
* allocated during object creation and automatically freed when the object goes out of scope.
36-
*
37-
* @note It is recommended to use the `MemoryBuffer` class when dealing with dynamic memory allocation,
38-
* to avoid memory leaks and ensure proper memory management.
39-
*
40-
* Example use:
41-
* ```cpp
42-
* {
43-
* MemoryBuffer buffer(512);
44-
* if (buffer.isAllocated()) { // Check if allocated!
45-
* // Access buffer here...
46-
* } else {
47-
* // Handle error (e.g., log error, retry later)
48-
* }
49-
* } // buffer automatically freed
50-
*
51-
* ```
52-
*/
5330
class MemoryBuffer
5431
{
5532
public:
56-
/**
57-
* @brief Constructs a `MemoryBuffer` object and allocates memory of the specified size.
58-
*
59-
* The constructor allocates memory of the specified size for the buffer. If allocation fails,
60-
* the buffer will not be valid.
61-
*
62-
* @param size The size of the buffer in bytes.
63-
*
64-
* @example
65-
* // Example usage of the constructor
66-
* MemoryBuffer buffer(512); // Allocates a buffer of 512 bytes
67-
*/
6833
explicit MemoryBuffer(size_t size);
6934

70-
/**
71-
* @brief Returns a pointer to the allocated memory buffer.
72-
*
73-
* @return A pointer to the allocated memory, or `nullptr` if memory allocation failed.
74-
*/
7535
uint8_t *get();
76-
77-
/**
78-
* @brief Returns the size of the allocated buffer.
79-
*
80-
* @return The size of the allocated buffer in bytes.
81-
*/
8236
size_t size() const;
83-
84-
/**
85-
* @brief Checks whether memory allocation was successful.
86-
*
87-
* @return `true` if memory was successfully allocated, `false` if the buffer is `nullptr`.
88-
*/
8937
bool isAllocated();
38+
static MemoryBuffer empty();
9039

9140
private:
9241
size_t size_;

src/OpenStreetMap-esp32.cpp

Lines changed: 11 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom, Til
205205
if (!jobs.empty())
206206
{
207207
runJobs(jobs);
208-
log_i("Updated %i tiles in %lu ms - %i ms/tile", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
208+
log_d("Finished %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
209209
}
210210
}
211211

@@ -345,84 +345,6 @@ bool OpenStreetMap::fetchMap(LGFX_Sprite &mapSprite, double longitude, double la
345345
return true;
346346
}
347347

348-
bool OpenStreetMap::fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result)
349-
{
350-
size_t readSize = 0;
351-
unsigned long lastReadTime = millis();
352-
while (readSize < contentSize)
353-
{
354-
const size_t availableData = stream->available();
355-
if (!availableData)
356-
{
357-
if (millis() - lastReadTime >= OSM_TILE_TIMEOUT_MS)
358-
{
359-
result = "Timeout: " + String(OSM_TILE_TIMEOUT_MS) + " ms";
360-
return false;
361-
}
362-
taskYIELD();
363-
continue;
364-
}
365-
366-
const size_t remaining = contentSize - readSize;
367-
const size_t toRead = std::min(availableData, remaining);
368-
if (toRead == 0)
369-
continue;
370-
371-
const int bytesRead = stream->readBytes(buffer.get() + readSize, toRead);
372-
if (bytesRead > 0)
373-
{
374-
readSize += bytesRead;
375-
lastReadTime = millis();
376-
}
377-
else
378-
taskYIELD();
379-
}
380-
return true;
381-
}
382-
383-
std::unique_ptr<MemoryBuffer> OpenStreetMap::urlToBuffer(const char *url, String &result)
384-
{
385-
HTTPClientRAII http;
386-
if (!http.begin(url))
387-
{
388-
result = "Failed to initialize HTTP client";
389-
return nullptr;
390-
}
391-
392-
const int httpCode = http.GET();
393-
if (httpCode != HTTP_CODE_OK)
394-
{
395-
result = "HTTP Error: " + String(httpCode);
396-
return nullptr;
397-
}
398-
399-
const size_t contentSize = http.getSize();
400-
if (contentSize < 1)
401-
{
402-
result = "Empty or chunked response";
403-
return nullptr;
404-
}
405-
406-
WiFiClient *stream = http.getStreamPtr();
407-
if (!stream)
408-
{
409-
result = "Failed to get HTTP stream";
410-
return nullptr;
411-
}
412-
413-
auto buffer = std::make_unique<MemoryBuffer>(contentSize);
414-
if (!buffer->isAllocated())
415-
{
416-
result = "Failed to allocate buffer";
417-
return nullptr;
418-
}
419-
420-
if (!fillBuffer(stream, *buffer, contentSize, result))
421-
return nullptr;
422-
423-
return buffer;
424-
}
425-
426348
void OpenStreetMap::PNGDraw(PNGDRAW *pDraw)
427349
{
428350
uint16_t *destRow = currentInstance->currentTileBuffer + (pDraw->y * currentInstance->currentProvider->tileSize);
@@ -431,20 +353,19 @@ void OpenStreetMap::PNGDraw(PNGDRAW *pDraw)
431353

432354
bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result)
433355
{
434-
435356
String url = currentProvider->urlTemplate;
436357
url.replace("{x}", String(x));
437358
url.replace("{y}", String(y));
438359
url.replace("{z}", String(zoom));
439360
if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}"))
440361
url.replace("{apiKey}", currentProvider->apiKey);
441362

442-
const std::unique_ptr<MemoryBuffer> buffer = fetcher.fetchToBuffer(url, result);
443-
if (!buffer)
363+
MemoryBuffer buffer = fetcher.fetchToBuffer(url, result, renderMode);
364+
if (!buffer.isAllocated())
444365
return false;
445366

446367
PNG *png = getPNGCurrentCore();
447-
const int16_t rc = png->openRAM(buffer->get(), buffer->size(), PNGDraw);
368+
const int16_t rc = png->openRAM(buffer.get(), buffer.size(), PNGDraw);
448369
if (rc != PNG_SUCCESS)
449370
{
450371
result = "PNG Decoder Error: " + String(rc);
@@ -488,21 +409,18 @@ void OpenStreetMap::tileFetcherTask(void *param)
488409
String result;
489410
if (!osm->fetchTile(fetcher, *job.tile, job.x, job.y, job.z, result))
490411
{
412+
log_e("Tile fetch failed: %s", result.c_str());
413+
job.tile->valid = false;
491414
const size_t tileByteCount = osm->currentProvider->tileSize * osm->currentProvider->tileSize * 2;
492415
memset(job.tile->buffer, 0, tileByteCount);
493-
job.tile->valid = false;
494-
log_e("Tile fetch failed: %s", result.c_str());
495416
}
496417
else
497418
{
498419
job.tile->valid = true;
499420
log_d("core %i fetched tile z=%u x=%lu, y=%lu in %lu ms", xPortGetCoreID(), job.z, job.x, job.y, millis() - startMS);
500421
}
501-
502422
job.tile->busy = false;
503423
--osm->pendingJobs;
504-
if (!uxQueueMessagesWaiting(osm->jobQueue))
505-
fetcher.close();
506424
}
507425
log_d("task on core %i exiting", xPortGetCoreID());
508426
xTaskNotifyGive(osm->ownerTask);
@@ -577,3 +495,8 @@ bool OpenStreetMap::setTileProvider(int index)
577495
log_i("provider changed to '%s'", currentProvider->name);
578496
return true;
579497
}
498+
499+
void OpenStreetMap::setRenderMode(RenderMode mode)
500+
{
501+
renderMode = mode;
502+
}

src/OpenStreetMap-esp32.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
#include "CachedTile.hpp"
3737
#include "TileJob.hpp"
3838
#include "MemoryBuffer.hpp"
39-
#include "HTTPClientRAII.hpp"
4039
#include "ReusableTileFetcher.hpp"
4140
#include "fonts/DejaVu9-modded.h"
41+
#include "RenderMode.hpp"
4242

4343
constexpr uint16_t OSM_BGCOLOR = lgfx::color565(32, 32, 128);
4444
constexpr uint16_t OSM_TILE_TIMEOUT_MS = 1000;
@@ -94,6 +94,7 @@ class OpenStreetMap
9494
bool fetchMap(LGFX_Sprite &sprite, double longitude, double latitude, uint8_t zoom);
9595
inline void freeTilesCache();
9696

97+
void setRenderMode(RenderMode mode);
9798
bool setTileProvider(int index);
9899
const char *getProviderName() { return currentProvider->name; };
99100
int getMinZoom() const { return currentProvider->minZoom; };
@@ -109,15 +110,14 @@ class OpenStreetMap
109110
void runJobs(const std::vector<TileJob> &jobs);
110111
CachedTile *findUnusedTile(const tileList &requiredTiles, uint8_t zoom);
111112
CachedTile *isTileCached(uint32_t x, uint32_t y, uint8_t z);
112-
std::unique_ptr<MemoryBuffer> urlToBuffer(const char *url, String &result);
113113
bool fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result);
114-
bool fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result);
115114
bool composeMap(LGFX_Sprite &mapSprite, TileBufferList &tilePointers);
116115
static void tileFetcherTask(void *param);
117116
static void PNGDraw(PNGDRAW *pDraw);
118117

119118
static inline thread_local OpenStreetMap *currentInstance = nullptr;
120119
static inline thread_local uint16_t *currentTileBuffer = nullptr;
120+
RenderMode renderMode = RenderMode::ACCURATE;
121121
const TileProvider *currentProvider = &tileProviders[0];
122122
std::vector<CachedTile> tilesCache;
123123

src/RenderMode.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
enum class RenderMode
4+
{
5+
FAST,
6+
ACCURATE
7+
};

0 commit comments

Comments
 (0)