diff --git a/docs/specification/LOCALIZATION_SPEC.md b/docs/specification/LOCALIZATION_SPEC.md index cf168f03..a2aebf32 100644 --- a/docs/specification/LOCALIZATION_SPEC.md +++ b/docs/specification/LOCALIZATION_SPEC.md @@ -358,10 +358,11 @@ English 必须在没有任何外部 pack 的情况下仍可工作。 1. `ensure_content_font_for_text()` 可以发现当前内容字体链缺少某些 codepoint。 2. 它不能在 ESP / display-shared SD-SPI 设备的页面渲染、列表构建、LVGL 事件或 timer 路径里同步加载外部 `font.bin`。 3. ESP 上的内容补充字体加载必须被视为后台/显式动作;在后台 runtime 完成前,页面使用当前已加载字体链并记录缺字诊断。 -4. 固件内置字体已经是运行时 loaded 状态,不经过 SD / Flash pack IO;ESP 可以把这类内置字体追加到 content chain 作为热路径兜底。 +4. 固件内置字体只有在 runtime pack manifest 以 `source=builtin` 显式声明时才算运行时 loaded 状态;ESP 不得隐式注册 CJK 内置字体来替代外部 `zh-hans-core/font.bin`。 5. 已加载字体是否覆盖某个 codepoint 必须以实际 glyph lookup 为准;manifest/range 只能用于选择候选 pack,不能替代渲染能力判断。 6. 外部 `font.bin` 加载失败后必须进入 backoff,不能因为多条联系人名、聊天消息或节点名重复触发同一个失败文件读取。 7. 显式切换 locale 时加载 UI 字体属于 locale 激活流程;这不能被内容文本缺字路径复用成隐式 SD 读。 +8. 外部 `source=binfont` 字体 pack 必须在 catalog 阶段验证 `font.bin` 路径可规范化且可打开;缺少 payload 的 locale 不能进入可选 locale 列表。 这条规则的目标是保护 UI 实时域:联系人页、聊天页、地图 overlay、节点详情页等内容页面不得因为遇到中文/日文/韩文/阿拉伯文本而把 UI 线程拖入 SD 阻塞 IO。 diff --git a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp index 4a36d62d..339ee680 100644 --- a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp +++ b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp @@ -27,13 +27,6 @@ #define UI_I18N_HAVE_BINFONT 0 #endif -#if (defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_ESP32)) && !defined(GAT562_NO_CJK) -#define UI_I18N_HAS_BUILTIN_CJK_CONTENT_FONT 1 -LV_FONT_DECLARE(lv_font_source_han_sans_sc_14_cjk) -#else -#define UI_I18N_HAS_BUILTIN_CJK_CONTENT_FONT 0 -#endif - namespace ui::i18n { namespace @@ -47,7 +40,6 @@ constexpr const char* kLegacyDisplayLanguageKey = "display_language"; constexpr const char* kDefaultLocaleId = "en"; constexpr const char* kBuiltinEnglishName = "English"; constexpr const char* kBuiltinLatinFontPackId = "builtin-latin-ui"; -constexpr const char* kBuiltinCjkContentFontPackId = "builtin-cjk-content"; #if UI_FS_HAS_FLASH_PACK_STORAGE constexpr const char* kFlashPackRoot = "/fs/trailmate/packs"; constexpr const char* kFlashFontPackRoot = "/fs/trailmate/packs/fonts"; @@ -1214,6 +1206,16 @@ bool ensure_font_pack_loaded(FontPackRecord* pack) return load_font_pack(*pack); } +bool can_load_font_from_content_hot_path(const FontPackRecord& pack) +{ +#if defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_ESP32) + return pack.builtin; +#else + (void)pack; + return true; +#endif +} + std::vector current_content_pack_sequence() { std::vector packs; @@ -1242,7 +1244,8 @@ bool ensure_active_content_pack_loaded() if (s_active_content_font_pack && !is_font_runtime_loaded(*s_active_content_font_pack)) { - if (ensure_font_pack_loaded(s_active_content_font_pack)) + if (can_load_font_from_content_hot_path(*s_active_content_font_pack) && + ensure_font_pack_loaded(s_active_content_font_pack)) { changed = true; } @@ -1250,7 +1253,8 @@ bool ensure_active_content_pack_loaded() if (s_active_ui_font_pack && !is_font_runtime_loaded(*s_active_ui_font_pack)) { - if (ensure_font_pack_loaded(s_active_ui_font_pack)) + if (can_load_font_from_content_hot_path(*s_active_ui_font_pack) && + ensure_font_pack_loaded(s_active_ui_font_pack)) { changed = true; } @@ -1548,24 +1552,6 @@ void add_builtin_font_packs() latin_pack.builtin_font = nullptr; latin_pack.estimated_ram_bytes = 0; s_font_packs.push_back(std::move(latin_pack)); - -#if UI_I18N_HAS_BUILTIN_CJK_CONTENT_FONT - FontPackRecord cjk_pack{}; - cjk_pack.id = kBuiltinCjkContentFontPackId; - cjk_pack.display_name = "Built-in CJK Content"; - cjk_pack.usage = FontPackUsage::ContentOnly; - cjk_pack.builtin = true; - cjk_pack.builtin_font = &::lv_font_source_han_sans_sc_14_cjk; - cjk_pack.estimated_ram_bytes = 0; - cjk_pack.source_path = "lv_font_source_han_sans_sc_14_cjk"; - cjk_pack.coverage.push_back({0x3000U, 0x30FFU}); - cjk_pack.coverage.push_back({0x3400U, 0x4DBFU}); - cjk_pack.coverage.push_back({0x4E00U, 0x9FFFU}); - cjk_pack.coverage.push_back({0xF900U, 0xFAFFU}); - cjk_pack.coverage.push_back({0xFF00U, 0xFFEFU}); - normalize_ranges(cjk_pack.coverage); - s_font_packs.push_back(std::move(cjk_pack)); -#endif } void add_builtin_locale_packs() @@ -1645,6 +1631,12 @@ bool catalog_external_font_pack(const std::string& pack_dir) } else { +#if !UI_I18N_HAVE_BINFONT + std::printf("%s skip font pack id=%s reason=binfont_unavailable\n", + kLogTag, + pack.id.c_str()); + return false; +#else const char* file_value = manifest_value(manifest, "file"); const std::string font_path = resolve_pack_file_path(pack_dir, file_value); if (font_path.empty()) @@ -1653,6 +1645,23 @@ bool catalog_external_font_pack(const std::string& pack_dir) return false; } pack.source_path = ::ui::fs::normalize_path(font_path.c_str()); + if (pack.source_path.empty()) + { + std::printf("%s skip font pack id=%s reason=normalize_failed file=%s\n", + kLogTag, + pack.id.c_str(), + font_path.c_str()); + return false; + } + if (!::ui::fs::file_exists(pack.source_path.c_str())) + { + std::printf("%s skip font pack id=%s reason=font_bin_missing source=%s\n", + kLogTag, + pack.id.c_str(), + pack.source_path.c_str()); + return false; + } +#endif } std::string ranges_path; @@ -2583,17 +2592,26 @@ const lv_font_t* locale_preview_font(const char* locale_id, const lv_font_t* asc ::ui::fonts::FontScope::Ui); } -void log_content_font_load_deferred(FontPackRecord& pack) +void log_font_load_deferred(FontPackRecord& pack, const char* role, const char* reason) { if (pack.content_load_deferred_logged) { return; } pack.content_load_deferred_logged = true; - std::printf("%s content font load deferred id=%s reason=ui_hot_path active_locale=%s\n", + const uint32_t now_ms = sys::millis_now(); + const uint32_t retry_ms = pack.load_retry_not_before_ms > now_ms + ? pack.load_retry_not_before_ms - now_ms + : 0U; + std::printf("%s font load deferred id=%s role=%s reason=%s active_locale=%s source=%s failures=%u retry_ms=%lu\n", kLogTag, pack.id.c_str(), - s_active_locale ? s_active_locale->id.c_str() : ""); + safe_text(role), + safe_text(reason), + s_active_locale ? s_active_locale->id.c_str() : "", + pack.source_path.empty() ? "" : pack.source_path.c_str(), + static_cast(pack.load_failure_count), + static_cast(retry_ms)); } bool ensure_content_font_for_text(const char* text) @@ -2606,6 +2624,16 @@ bool ensure_content_font_for_text(const char* text) bool changed = false; changed |= ensure_active_content_pack_loaded(); + if (s_active_content_font_pack && !is_font_runtime_loaded(*s_active_content_font_pack) && + !can_load_font_from_content_hot_path(*s_active_content_font_pack)) + { + log_font_load_deferred(*s_active_content_font_pack, "active_content", "ui_hot_path"); + } + if (s_active_ui_font_pack && !is_font_runtime_loaded(*s_active_ui_font_pack) && + !can_load_font_from_content_hot_path(*s_active_ui_font_pack)) + { + log_font_load_deferred(*s_active_ui_font_pack, "active_ui", "ui_hot_path"); + } std::vector missing = missing_content_codepoints(text); while (!missing.empty()) @@ -2617,7 +2645,7 @@ bool ensure_content_font_for_text(const char* text) } if (!kAllowSynchronousContentSupplementFontLoad && !is_font_runtime_loaded(*candidate)) { - log_content_font_load_deferred(*candidate); + log_font_load_deferred(*candidate, "content_supplement", "ui_hot_path"); break; } if (!ensure_font_pack_loaded(candidate)) diff --git a/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp b/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp index 9e899824..4fdbb20c 100644 --- a/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp +++ b/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp @@ -333,15 +333,6 @@ void prime_visible_tiles(RuntimeImpl& impl, const char* reason) impl.has_map_data ? 1 : 0, impl.has_visible_map_data ? 1 : 0); - for (int attempt = 0; attempt < 2 && !impl.has_visible_map_data; ++attempt) - { - tile_loader_step(impl.tile_ctx); - MAP_VIEWPORT_LOG("prime_visible_tiles step=%d visible_map=%d map_data=%d\n", - attempt + 1, - impl.has_visible_map_data ? 1 : 0, - impl.has_map_data ? 1 : 0); - } - MAP_VIEWPORT_LOG("prime_visible_tiles end reason=%s map_data=%d visible_map=%d\n", reason ? reason : "", impl.has_map_data ? 1 : 0, @@ -627,7 +618,8 @@ void render_overlay(RuntimeImpl& impl) void loader_timer_cb(lv_timer_t* timer) { auto* impl = static_cast(lv_timer_get_user_data(timer)); - if (!impl || !is_runtime_alive(*impl) || !impl->model.focus_point.valid || impl->drag_preview_active) + if (!impl || !is_runtime_alive(*impl) || !impl->model.focus_point.valid || + impl->gesture_pressed || impl->gesture_dragging || impl->drag_preview_active) { return; } diff --git a/platform/esp/arduino_common/include/lv_conf.h b/platform/esp/arduino_common/include/lv_conf.h index 347c413d..fa311274 100644 --- a/platform/esp/arduino_common/include/lv_conf.h +++ b/platform/esp/arduino_common/include/lv_conf.h @@ -518,7 +518,7 @@ #define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/ #define LV_FONT_SIMSUN_14_CJK 0 /*1000 most common CJK radicals*/ #define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/ -#define LV_FONT_SOURCE_HAN_SANS_SC_14_CJK 1 /*Built-in ESP content fallback; no SD/font.bin IO*/ +#define LV_FONT_SOURCE_HAN_SANS_SC_14_CJK 0 /*CJK fonts are runtime packs loaded from font.bin*/ #define LV_FONT_SOURCE_HAN_SANS_SC_16_CJK 0 /*Pixel perfect monospace fonts*/ diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/storage/sd_card_runtime.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/storage/sd_card_runtime.h index c272983e..d5c2993e 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/storage/sd_card_runtime.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/storage/sd_card_runtime.h @@ -1,9 +1,10 @@ #pragma once -#include #include #include +class SPIClass; + namespace platform::esp::arduino_common::storage { diff --git a/platform/esp/arduino_common/src/debug/sd_debug_log.cpp b/platform/esp/arduino_common/src/debug/sd_debug_log.cpp index bef813f9..926e9f83 100644 --- a/platform/esp/arduino_common/src/debug/sd_debug_log.cpp +++ b/platform/esp/arduino_common/src/debug/sd_debug_log.cpp @@ -117,7 +117,6 @@ void write_log_line(const char* line) s_log_file.print(line); s_log_file.print("\n"); - s_log_file.flush(); } void open_debug_log_if_needed() diff --git a/platform/esp/arduino_common/src/storage/sd_card_runtime.cpp b/platform/esp/arduino_common/src/storage/sd_card_runtime.cpp index 0e4cf871..499a5c9f 100644 --- a/platform/esp/arduino_common/src/storage/sd_card_runtime.cpp +++ b/platform/esp/arduino_common/src/storage/sd_card_runtime.cpp @@ -21,6 +21,8 @@ namespace constexpr uint8_t kRuntimeCardNone = 0; constexpr uint32_t kSdSectorSize = 512; +constexpr uint32_t kDefaultSharedSpiSdHz = 4000000U; +constexpr uint32_t kMaxSharedSpiSdHz = 10000000U; #ifndef TRAIL_MATE_SD_IO_LOG_ENABLE #define TRAIL_MATE_SD_IO_LOG_ENABLE 1 @@ -34,9 +36,15 @@ constexpr uint32_t kSdSectorSize = 512; #define TRAIL_MATE_SD_IO_SLOW_MS 20 #endif +#ifndef TRAIL_MATE_SD_IO_LOG_INTERVAL_MS +#define TRAIL_MATE_SD_IO_LOG_INTERVAL_MS 1000 +#endif + SdFs s_sdfat; SdCardInfo s_info{}; bool s_sdfat_mounted = false; +uint32_t s_last_sd_io_log_ms = 0; +uint32_t s_suppressed_sd_io_logs = 0; const char* backend_name_from_info() { @@ -96,14 +104,22 @@ void sd_io_end(const char* op, #if TRAIL_MATE_SD_IO_LOG_ENABLE if (TRAIL_MATE_SD_IO_TRACE_LOG || !ok || elapsed_ms >= TRAIL_MATE_SD_IO_SLOW_MS) { - Serial.printf("[SD][IO] end op=%s backend=%s path=%s ok=%d bytes=%u result=%ld elapsed_ms=%lu\n", - op, - backend_name_from_info(), - safe_path(path), - ok ? 1 : 0, - static_cast(bytes), - static_cast(result), - static_cast(elapsed_ms)); + ++s_suppressed_sd_io_logs; + if (TRAIL_MATE_SD_IO_TRACE_LOG || s_last_sd_io_log_ms == 0 || + end_ms - s_last_sd_io_log_ms >= TRAIL_MATE_SD_IO_LOG_INTERVAL_MS) + { + Serial.printf("[SD][IO] end op=%s backend=%s path=%s ok=%d bytes=%u result=%ld elapsed_ms=%lu suppressed=%lu\n", + op, + backend_name_from_info(), + safe_path(path), + ok ? 1 : 0, + static_cast(bytes), + static_cast(result), + static_cast(elapsed_ms), + static_cast(s_suppressed_sd_io_logs - 1)); + s_suppressed_sd_io_logs = 0; + s_last_sd_io_log_ms = end_ms; + } } #else (void)op; @@ -115,6 +131,15 @@ void sd_io_end(const char* op, #endif } +uint32_t sanitize_sd_spi_hz(uint32_t requested_hz) +{ + if (requested_hz == 0 || requested_hz > kMaxSharedSpiSdHz) + { + return kDefaultSharedSpiSdHz; + } + return requested_hz; +} + uint8_t card_type_from_sdfat(SdFs& fs) { SdCard* card = fs.card(); @@ -261,8 +286,18 @@ bool mount_sd_card(int sd_cs, SD.end(); reset_info(); - const bool arduino_ok = SD.begin(sd_cs, spi, spi_hz, mount_point, max_files, false); - Serial.printf("[SD] Arduino SD.begin -> %d\n", arduino_ok ? 1 : 0); + const uint32_t effective_hz = sanitize_sd_spi_hz(spi_hz); + if (effective_hz != spi_hz) + { + Serial.printf("[SD] clamp shared SPI hz requested=%lu effective=%lu\n", + static_cast(spi_hz), + static_cast(effective_hz)); + } + + const bool arduino_ok = SD.begin(sd_cs, spi, effective_hz, mount_point, max_files, false); + Serial.printf("[SD] Arduino SD.begin hz=%lu -> %d\n", + static_cast(effective_hz), + arduino_ok ? 1 : 0); if (arduino_ok && SD.cardType() != CARD_NONE && SD.sectorSize() != 0) { record_arduino_info(); @@ -277,8 +312,10 @@ bool mount_sd_card(int sd_cs, SD.end(); delay(10); - const bool sdfat_ok = s_sdfat.begin(SdSpiConfig(sd_cs, SHARED_SPI, spi_hz, &spi)); - Serial.printf("[SD] SdFat.begin -> %d\n", sdfat_ok ? 1 : 0); + const bool sdfat_ok = s_sdfat.begin(SdSpiConfig(sd_cs, SHARED_SPI, effective_hz, &spi)); + Serial.printf("[SD] SdFat.begin hz=%lu -> %d\n", + static_cast(effective_hz), + sdfat_ok ? 1 : 0); if (!sdfat_ok || s_sdfat.fatType() == 0) { s_sdfat.initErrorPrint(&Serial); diff --git a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp index 74a5d6f5..fa796ee1 100644 --- a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp +++ b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp @@ -118,12 +118,22 @@ class LvglMapTileFileSystem final : public ui::map_tiles::IMapTileFileSystem public: bool exists(const char* path) const override { +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) + (void)path; + return false; +#else return ui::fs::file_exists(path); +#endif } bool isDirectory(const char* path) const override { +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) + (void)path; + return false; +#else return ui::fs::dir_exists(path); +#endif } bool readFile(const char* path, @@ -131,15 +141,57 @@ class LvglMapTileFileSystem final : public ui::map_tiles::IMapTileFileSystem std::size_t capacity, std::size_t& out_size) const override { - // ESP UI code may resolve paths, but tile payload reads belong to the worker-side SD source. out_size = 0; +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) (void)path; (void)buffer; (void)capacity; return false; +#else + if (!path || !buffer || capacity == 0) + { + return false; + } + + const std::string normalized = ui::fs::normalize_path(path); + if (normalized.empty()) + { + return false; + } + + lv_fs_file_t file; + if (lv_fs_open(&file, normalized.c_str(), LV_FS_MODE_RD) != LV_FS_RES_OK) + { + return false; + } + + uint32_t file_size = 0; + if (lv_fs_seek(&file, 0, LV_FS_SEEK_END) != LV_FS_RES_OK || + lv_fs_tell(&file, &file_size) != LV_FS_RES_OK || + lv_fs_seek(&file, 0, LV_FS_SEEK_SET) != LV_FS_RES_OK || + file_size == 0 || + file_size > capacity) + { + lv_fs_close(&file); + return false; + } + + uint32_t bytes_read = 0; + const lv_fs_res_t res = lv_fs_read(&file, buffer, file_size, &bytes_read); + lv_fs_close(&file); + if (res != LV_FS_RES_OK || bytes_read != file_size) + { + out_size = bytes_read; + return false; + } + + out_size = bytes_read; + return true; +#endif } }; +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) class SdMapTileFileSystem final : public ui::map_tiles::IMapTileFileSystem { public: @@ -189,24 +241,28 @@ class SdMapTileFileSystem final : public ui::map_tiles::IMapTileFileSystem return true; } }; +#endif constexpr std::size_t kMapTileCommandQueueCapacity = 48; constexpr std::size_t kMapTileEventQueueCapacity = 48; constexpr std::size_t kMapTileWorkerScratchBytes = 192U * 1024U; constexpr int kMapTileEventsPerUiDrain = 1; +constexpr uint32_t kMapTileUiDrainBudgetMs = 4; +constexpr uint32_t kMapTileUiEventCooldownMs = 120; constexpr uint32_t kMapTileLayerBusyBackoffMs = 1500; constexpr uint32_t kMapTileLayerCacheBackoffMs = 350; constexpr uint32_t kMapTileMissingCacheTtlMs = 5U * 60U * 1000U; constexpr uint32_t kMapTileGenerationInitial = 1; constexpr uint32_t kMapTileBusResource = 1; constexpr uint32_t kMapTileDiagnosticLogIntervalMs = 1000; -constexpr TickType_t kMapTileWorkerPostCommandYieldTicks = pdMS_TO_TICKS(16); -constexpr uint32_t kMapTileDisplaySpiSlowHoldMs = 16; -constexpr uint32_t kMapTileDisplaySpiNormalCooldownMs = 64; -constexpr uint32_t kMapTileDisplaySpiSlowCooldownMs = 250; +constexpr TickType_t kMapTileWorkerPostCommandYieldTicks = pdMS_TO_TICKS(32); +constexpr uint32_t kMapTileDisplaySpiSlowHoldMs = 8; +constexpr uint32_t kMapTileDisplaySpiNormalCooldownMs = 160; +constexpr uint32_t kMapTileDisplaySpiSlowCooldownMs = 600; uint32_t g_map_tile_decode_log_ms = 0; uint32_t g_map_tile_event_log_ms = 0; +uint32_t g_map_tile_next_event_drain_ms = 0; bool should_log_map_tile_diagnostic(uint32_t& last_ms, uint32_t now_ms) { @@ -826,7 +882,14 @@ class EspMapTileBusArbiter final : public sys::runtime::IBusArbiter private: static uint32_t timeoutFor(sys::runtime::BusAccessPolicy policy) { - (void)policy; + if (policy == sys::runtime::BusAccessPolicy::InteractiveWorkerBounded) + { + return 1; + } + if (policy == sys::runtime::BusAccessPolicy::BackgroundWorkerBounded) + { + return 4; + } return 0; } @@ -849,6 +912,36 @@ class EspMapTileBusArbiter final : public sys::runtime::IBusArbiter uint32_t last_slow_hold_log_ms_ = 0; }; +class EspMapTilePolicyStrategy final : public sys::runtime::RuntimePolicyStrategy +{ + public: + sys::runtime::RuntimePriority selectPriority( + const sys::runtime::RuntimeIntent& intent) const override + { + return intent.priority_hint; + } + + sys::runtime::BusAccessPolicy selectBusPolicy( + const sys::runtime::RuntimeCommand& command) const override + { + if (command.priority == sys::runtime::RuntimePriority::Interactive || + command.priority == sys::runtime::RuntimePriority::Realtime) + { + return sys::runtime::BusAccessPolicy::InteractiveWorkerBounded; + } + return sys::runtime::BusAccessPolicy::BackgroundWorkerBounded; + } + + sys::runtime::RuntimeRetryDecision selectRetry( + const sys::runtime::RuntimeCommand& command, + const sys::runtime::PlatformStorageResult& result) const override + { + (void)command; + (void)result; + return {}; + } +}; + class EspMapTileWorkerBackend final : public ui::map_tiles::IMapTileWorkerBackend { public: @@ -1121,8 +1214,13 @@ ui::map_tiles::FilesystemMapTileSource& tile_source() ui::map_tiles::FilesystemMapTileSource& worker_tile_source() { +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) static SdMapTileFileSystem fs; static ui::map_tiles::FilesystemMapTileSource source(fs, "/"); +#else + static LvglMapTileFileSystem fs; + static ui::map_tiles::FilesystemMapTileSource source(fs, "A:"); +#endif return source; } @@ -1218,7 +1316,8 @@ class MapTileAsyncHost final bus_, events_, scratch_, - kMapTileWorkerScratchBytes); + kMapTileWorkerScratchBytes, + &policy_); if (worker_ == nullptr) { if (!task_start_failed_logged_) @@ -1253,6 +1352,7 @@ class MapTileAsyncHost final MapTileCommandQueue commands_{}; MapTileEventQueue events_{}; EspMapTileBusArbiter bus_{}; + EspMapTilePolicyStrategy policy_{}; EspMapTileWorkerBackend backend_{worker_tile_source()}; uint8_t* scratch_ = nullptr; ui::map_tiles::MapTileWorker* worker_ = nullptr; @@ -1546,13 +1646,22 @@ bool build_contour_tile_path(int z, int x, int y, char* out_path, size_t out_siz bool map_source_directory_available(uint8_t map_source) { +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) + (void)map_source; + return true; +#else return tile_source().layerDirectoryAvailable( ui::map_tiles::mapTileLayerFromBaseSource(sanitize_map_source(map_source))); +#endif } bool contour_directory_available() { +#if defined(ARDUINO) || defined(ARDUINO_ARCH_ESP32) + return true; +#else return tile_source().anyContourDirectoryAvailable(); +#endif } bool take_missing_tile_notice(uint8_t* out_map_source) @@ -2539,8 +2648,15 @@ static bool apply_map_tile_event(TileContext& ctx, ui::map_tiles::MapTileAsyncEv return rendered; } -static void drain_map_tile_events(TileContext& ctx) +static void drain_map_tile_events(TileContext& ctx, uint32_t start_ms, uint32_t budget_ms) { + const uint32_t now_ms = sys::millis_now(); + if (g_map_tile_next_event_drain_ms != 0 && + static_cast(g_map_tile_next_event_drain_ms - now_ms) > 0) + { + return; + } + ui::map_tiles::MapTileAsyncEvent event{}; int drained = 0; while (drained < kMapTileEventsPerUiDrain && map_tile_async_host().popEvent(event)) @@ -2556,6 +2672,11 @@ static void drain_map_tile_events(TileContext& ctx) } ++drained; event = {}; + g_map_tile_next_event_drain_ms = sys::millis_now() + kMapTileUiEventCooldownMs; + if (static_cast(sys::millis_now() - start_ms) >= budget_ms) + { + break; + } } } @@ -3067,12 +3188,16 @@ void tile_loader_step(TileContext& ctx) return; } - sync_render_settings(ctx); - drain_map_tile_events(ctx); - update_visible_map_data_flag(ctx); - const uint32_t start_ms = sys::millis_now(); - const uint32_t budget_ms = 6; + const uint32_t budget_ms = kMapTileUiDrainBudgetMs; + sync_render_settings(ctx); + drain_map_tile_events(ctx, start_ms, budget_ms); + update_visible_map_data_flag(ctx); + if (static_cast(sys::millis_now() - start_ms) >= budget_ms) + { + return; + } + const int max_tiles_per_step = 1; MapTile* attempted[max_tiles_per_step] = {NULL}; int attempted_count = 0; @@ -3215,8 +3340,6 @@ void tile_loader_step(TileContext& ctx) } } - drain_map_tile_events(ctx); - if ((int32_t)(sys::millis_now() - start_ms) >= (int32_t)budget_ms) { break; @@ -3282,7 +3405,6 @@ void tile_loader_step(TileContext& ctx) } } } - drain_map_tile_events(ctx); update_visible_map_data_flag(ctx); rebuild_render_queue(ctx); } diff --git a/platform/esp/boards/src/display/DisplayInterface.cpp b/platform/esp/boards/src/display/DisplayInterface.cpp index c3a23e7e..8b23acc4 100644 --- a/platform/esp/boards/src/display/DisplayInterface.cpp +++ b/platform/esp/boards/src/display/DisplayInterface.cpp @@ -13,8 +13,12 @@ static LilyGoDispArduinoSPI* g_display_spi = nullptr; namespace { -constexpr TickType_t kDisplayFrameLockWait = pdMS_TO_TICKS(8); -constexpr TickType_t kDisplayControlLockWait = pdMS_TO_TICKS(50); +constexpr TickType_t kDisplayFrameLockWait = 0; +constexpr TickType_t kDisplayControlLockWait = pdMS_TO_TICKS(80); +constexpr uint32_t kDisplayLockTimeoutLogIntervalMs = 1000; + +uint32_t s_last_display_lock_timeout_log_ms = 0; +uint32_t s_suppressed_display_lock_timeout_logs = 0; bool lock_or_log(LilyGoDispArduinoSPI& spi, TickType_t wait_ticks, const char* op) { @@ -22,9 +26,19 @@ bool lock_or_log(LilyGoDispArduinoSPI& spi, TickType_t wait_ticks, const char* o { return true; } - Serial.printf("[SPI][DISPLAY] lock_timeout op=%s wait_ticks=%lu\n", - op ? op : "", - static_cast(wait_ticks)); + + const uint32_t now_ms = millis(); + ++s_suppressed_display_lock_timeout_logs; + if (s_last_display_lock_timeout_log_ms == 0 || + now_ms - s_last_display_lock_timeout_log_ms >= kDisplayLockTimeoutLogIntervalMs) + { + Serial.printf("[SPI][DISPLAY] lock_timeout op=%s wait_ticks=%lu suppressed=%lu\n", + op ? op : "", + static_cast(wait_ticks), + static_cast(s_suppressed_display_lock_timeout_logs - 1)); + s_suppressed_display_lock_timeout_logs = 0; + s_last_display_lock_timeout_log_ms = now_ms; + } return false; } } // namespace diff --git a/variants/lilygo_tlora_pager/pins_arduino.h b/variants/lilygo_tlora_pager/pins_arduino.h index 306e29d6..e1568bf9 100644 --- a/variants/lilygo_tlora_pager/pins_arduino.h +++ b/variants/lilygo_tlora_pager/pins_arduino.h @@ -12,7 +12,7 @@ #define DISP_WIDTH (222) #define DISP_HEIGHT (480) #define SD_CS (21) -#define SD_SPI_FREQUENCY (75000000U) +#define SD_SPI_FREQUENCY (4000000U) static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/tdeck/pins_arduino.h b/variants/tdeck/pins_arduino.h index fc3a9865..7cd4619a 100644 --- a/variants/tdeck/pins_arduino.h +++ b/variants/tdeck/pins_arduino.h @@ -83,7 +83,7 @@ static const uint8_t SCK = 40; // SD #define SD_CS (39) -#define SD_SPI_FREQUENCY (75000000U) +#define SD_SPI_FREQUENCY (4000000U) // Battery ADC (per LilyGo utilities.h) #define BOARD_BAT_ADC (4)