From 5866513c532114654c1a0e616be3c64ef0aa92ed Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 16 Feb 2024 15:13:58 +1100 Subject: [PATCH 01/10] Move the list of unexplored files into spiram --- src/database/database.cpp | 7 ++----- src/database/file_gatherer.cpp | 10 +++++----- src/database/include/file_gatherer.hpp | 4 ++-- src/database/include/tag_parser.hpp | 6 +++--- src/database/tag_parser.cpp | 8 ++++---- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index b596063c..ec11455b 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -383,8 +383,7 @@ auto Database::updateIndexes() -> void { ESP_LOGI(kTag, "scanning for new tracks"); uint64_t num_processed = 0; std::pair newest_track = last_update; - file_gatherer_.FindFiles("", [&](const std::string& path, - const FILINFO& info) { + file_gatherer_.FindFiles("", [&](std::string_view path, const FILINFO& info) { num_processed++; events::Ui().Dispatch(event::UpdateProgress{ .stage = event::UpdateProgress::Stage::kScanningForNewTracks, @@ -456,9 +455,7 @@ auto Database::updateIndexes() -> void { dbCreateIndexesForTrack(*t); } else if (existing_data->filepath != std::pmr::string{path.data(), path.size()}) { - ESP_LOGW(kTag, "tag hash collision for %s and %s", - existing_data->filepath.c_str(), path.c_str()); - ESP_LOGI(kTag, "hash components: %s, %s, %s", + ESP_LOGW(kTag, "hash collision: %s, %s, %s", tags->title().value_or("no title").c_str(), tags->artist().value_or("no artist").c_str(), tags->album().value_or("no album").c_str()); diff --git a/src/database/file_gatherer.cpp b/src/database/file_gatherer.cpp index dde363bd..b7b7271e 100644 --- a/src/database/file_gatherer.cpp +++ b/src/database/file_gatherer.cpp @@ -22,12 +22,12 @@ static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR"); auto FileGathererImpl::FindFiles( const std::string& root, - std::function cb) -> void { - std::deque to_explore; - to_explore.push_back(root); + std::function cb) -> void { + std::pmr::deque to_explore{&memory::kSpiRamResource}; + to_explore.push_back({root.data(), root.size()}); while (!to_explore.empty()) { - std::string next_path_str = to_explore.front(); + auto next_path_str = to_explore.front(); to_explore.pop_front(); const TCHAR* next_path = static_cast(next_path_str.c_str()); @@ -56,7 +56,7 @@ auto FileGathererImpl::FindFiles( // System or hidden file. Ignore it and move on. continue; } else { - std::string full_path; + std::pmr::string full_path{&memory::kSpiRamResource}; full_path += next_path_str; full_path += "/"; full_path += info.fname; diff --git a/src/database/include/file_gatherer.hpp b/src/database/include/file_gatherer.hpp index 66127bb7..685bdb2c 100644 --- a/src/database/include/file_gatherer.hpp +++ b/src/database/include/file_gatherer.hpp @@ -21,7 +21,7 @@ class IFileGatherer { virtual auto FindFiles( const std::string& root, - std::function cb) + std::function cb) -> void = 0; }; @@ -29,7 +29,7 @@ class FileGathererImpl : public IFileGatherer { public: virtual auto FindFiles( const std::string& root, - std::function cb) + std::function cb) -> void override; }; diff --git a/src/database/include/tag_parser.hpp b/src/database/include/tag_parser.hpp index f196c479..966258b5 100644 --- a/src/database/include/tag_parser.hpp +++ b/src/database/include/tag_parser.hpp @@ -16,18 +16,18 @@ namespace database { class ITagParser { public: virtual ~ITagParser() {} - virtual auto ReadAndParseTags(const std::string& path) + virtual auto ReadAndParseTags(std::string_view path) -> std::shared_ptr = 0; }; class TagParserImpl : public ITagParser { public: TagParserImpl(); - auto ReadAndParseTags(const std::string& path) + auto ReadAndParseTags(std::string_view path) -> std::shared_ptr override; private: - auto parseNew(const std::string& path) -> std::shared_ptr; + auto parseNew(std::string_view path) -> std::shared_ptr; /* * Cache of tags that have already been extracted from files. Ideally this diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp index c247bb7d..cbcbdcb5 100644 --- a/src/database/tag_parser.cpp +++ b/src/database/tag_parser.cpp @@ -108,7 +108,7 @@ static const std::size_t kBufSize = 1024; TagParserImpl::TagParserImpl() {} -auto TagParserImpl::ReadAndParseTags(const std::string& path) +auto TagParserImpl::ReadAndParseTags(std::string_view path) -> std::shared_ptr { { std::lock_guard lock{cache_mutex_}; @@ -130,7 +130,7 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path) if (!tags->track()) { auto slash_pos = path.find_last_of("/"); if (slash_pos != std::string::npos && path.size() - slash_pos > 1) { - std::string trunc = path.substr(slash_pos + 1); + auto trunc = path.substr(slash_pos + 1); tags->track({trunc.data(), trunc.size()}); } } @@ -143,8 +143,8 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path) return tags; } -auto TagParserImpl::parseNew(const std::string& path) - -> std::shared_ptr { +auto TagParserImpl::parseNew(std::string_view p) -> std::shared_ptr { + std::string path{p}; libtags::Aux aux; auto out = TrackTags::create(); aux.tags = out.get(); From c035ed2b4d9aec9cdb2b91771aff7db324a34287 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 19 Feb 2024 09:35:19 +1100 Subject: [PATCH 02/10] Don't mark the current track as changed when falling off the end of the queue --- src/audio/track_queue.cpp | 57 ++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp index 534da10c..b75230fc 100644 --- a/src/audio/track_queue.cpp +++ b/src/audio/track_queue.cpp @@ -200,39 +200,52 @@ auto TrackQueue::append(Item i) -> void { } auto TrackQueue::next() -> void { - const std::unique_lock lock(mutex_); - if (shuffle_) { - shuffle_->next(); - pos_ = shuffle_->current(); - } else { - if (pos_ + 1 >= tracks_.size()) { - if (replay_) { - pos_ = 0; - } + bool changed = true; + + { + const std::unique_lock lock(mutex_); + if (shuffle_) { + shuffle_->next(); + pos_ = shuffle_->current(); } else { - pos_++; + if (pos_ + 1 >= tracks_.size()) { + if (replay_) { + pos_ = 0; + } else { + pos_ = tracks_.size(); + changed = false; + } + } else { + pos_++; + } } } - notifyChanged(true); + notifyChanged(changed); } auto TrackQueue::previous() -> void { - const std::unique_lock lock(mutex_); - if (shuffle_) { - shuffle_->prev(); - pos_ = shuffle_->current(); - } else { - if (pos_ == 0) { - if (repeat_) { - pos_ = tracks_.size() - 1; - } + bool changed = true; + + { + const std::unique_lock lock(mutex_); + if (shuffle_) { + shuffle_->prev(); + pos_ = shuffle_->current(); } else { - pos_--; + if (pos_ == 0) { + if (repeat_) { + pos_ = tracks_.size() - 1; + } else { + changed = false; + } + } else { + pos_--; + } } } - notifyChanged(true); + notifyChanged(changed); } auto TrackQueue::finish() -> void { From 403bd4672c1aa0bd5f0ac8e6da700bfb1aec9768 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 20 Feb 2024 09:29:58 +1100 Subject: [PATCH 03/10] Update to latest libtags Mostly for wav fixes --- lib/libtags/examples/readtags.c | 6 +++- lib/libtags/id3v1.c | 3 ++ lib/libtags/id3v2.c | 4 +++ lib/libtags/m4a.c | 6 +++- lib/libtags/opus.c | 2 +- lib/libtags/tags.c | 4 +-- lib/libtags/tags.h | 2 ++ lib/libtags/vorbis.c | 51 ++++++++++++++++++--------------- lib/libtags/wav.c | 22 +++++++------- 9 files changed, 62 insertions(+), 38 deletions(-) diff --git a/lib/libtags/examples/readtags.c b/lib/libtags/examples/readtags.c index fb5b5d21..81c5a80e 100644 --- a/lib/libtags/examples/readtags.c +++ b/lib/libtags/examples/readtags.c @@ -35,6 +35,8 @@ static const char *t2s[] = [Ttrackpeak] = "trackpeak", [Tgenre] = "genre", [Timage] = "image", + [Tcomposer] = "composer", + [Tcomment] = "comment", }; static void @@ -43,7 +45,9 @@ tag(Tagctx *ctx, int t, const char *k, const char *v, int offset, int size, Tagr USED(ctx); USED(k); USED(f); if(t == Timage) print("%-12s %s %d %d\n", t2s[t], v, offset, size); - else if(t != Tunknown) + else if(t == Tunknown) + print("%-12s %s\n", k, v); + else print("%-12s %s\n", t2s[t], v); } diff --git a/lib/libtags/id3v1.c b/lib/libtags/id3v1.c index afcf90e9..d375cfd7 100644 --- a/lib/libtags/id3v1.c +++ b/lib/libtags/id3v1.c @@ -36,6 +36,9 @@ tagid3v1(Tagctx *ctx) if((ctx->found & 1<found & 1<found & 1< 0){ snprint((char*)out, Outsz, "%d", in[126]); txtcb(ctx, Ttrack, "", out); diff --git a/lib/libtags/id3v2.c b/lib/libtags/id3v2.c index e1529d4a..78a0a5fe 100644 --- a/lib/libtags/id3v2.c +++ b/lib/libtags/id3v2.c @@ -31,6 +31,8 @@ v2cb(Tagctx *ctx, char *k, char *v) txtcb(ctx, Ttrack, k-1, v); else if(strcmp(k, "LEN") == 0) ctx->duration = atoi(v); + else if(strcmp(k, "CM") == 0 || strcmp(k, "COM") == 0) + txtcb(ctx, Tcomposer, k-1, v); else if(strcmp(k, "CO") == 0 || strcmp(k, "CON") == 0){ for(; v[0]; v++){ if(v[0] == '(' && v[1] <= '9' && v[1] >= '0'){ @@ -64,6 +66,8 @@ v2cb(Tagctx *ctx, char *k, char *v) txtcb(ctx, type, k-1, v+5); else return 0; + }else if(strcmp(k-1, "COM") == 0 || strcmp(k-1, "COMM") == 0){ + txtcb(ctx, Tcomment, k-1, v); }else{ txtcb(ctx, Tunknown, k-1, v); } diff --git a/lib/libtags/m4a.c b/lib/libtags/m4a.c index 924ba51a..ec254c3f 100644 --- a/lib/libtags/m4a.c +++ b/lib/libtags/m4a.c @@ -93,6 +93,10 @@ tagm4a(Tagctx *ctx) type = Timage; else if(memcmp(d, "trkn", 4) == 0) type = Ttrack; + else if(memcmp(d, "\251wrt", 4) == 0) + type = Tcomposer; + else if(memcmp(d, "\251cmt", 4) == 0) + type = Tcomment; else if(memcmp(d, "mdhd", 4) == 0){ if(ctx->read(ctx, d, 4) != 4) return -1; @@ -133,7 +137,7 @@ tagm4a(Tagctx *ctx) sz -= 4; snprint((char*)d, ctx->bufsz, "%d", beuint(d)); txtcb(ctx, type, "", d); - }else if(type == Tgenre){ + }else if(type == Tgenre && dtype != 1){ if(ctx->read(ctx, d, 2) != 2) return -1; sz -= 2; diff --git a/lib/libtags/opus.c b/lib/libtags/opus.c index fcea57d8..fa2c7d40 100644 --- a/lib/libtags/opus.c +++ b/lib/libtags/opus.c @@ -63,7 +63,7 @@ tagopus(Tagctx *ctx) ctx->buf[sz] = 0; if((v = strchr(ctx->buf, '=')) == nil) - continue; + return -1; *v++ = 0; cbvorbiscomment(ctx, ctx->buf, v); } diff --git a/lib/libtags/tags.c b/lib/libtags/tags.c index a027a9ab..750d9077 100644 --- a/lib/libtags/tags.c +++ b/lib/libtags/tags.c @@ -46,8 +46,8 @@ tagscallcb(Tagctx *ctx, int type, const char *k, char *s, int offset, int size, e = s + strlen(s); while(e != s && (uchar)e[-1] <= ' ') e--; - if (*e != 0) - *e = 0; + if(*e != 0) + *e = 0; } if(*s){ ctx->tag(ctx, type, k, s, offset, size, f); diff --git a/lib/libtags/tags.h b/lib/libtags/tags.h index 91045218..4b10349a 100644 --- a/lib/libtags/tags.h +++ b/lib/libtags/tags.h @@ -21,6 +21,8 @@ enum Ttrackpeak, Tgenre, Timage, + Tcomposer, + Tcomment, }; /* Format of the audio file. */ diff --git a/lib/libtags/vorbis.c b/lib/libtags/vorbis.c index c98a0e4e..e57f8989 100644 --- a/lib/libtags/vorbis.c +++ b/lib/libtags/vorbis.c @@ -4,33 +4,38 @@ */ #include "tagspriv.h" +static const struct { + char *s; + int type; +}t[] = { + {"album", Talbum}, + {"title", Ttitle}, + {"artist", Tartist}, + {"albumartist", Talbumartist}, + {"tracknumber", Ttrack}, + {"date", Tdate}, + {"replaygain_track_peak", Ttrackpeak}, + {"replaygain_track_gain", Ttrackgain}, + {"replaygain_album_peak", Talbumpeak}, + {"replaygain_album_gain", Talbumgain}, + {"genre", Tgenre}, + {"composer", Tcomposer}, + {"comment", Tcomment}, +}; + void cbvorbiscomment(Tagctx *ctx, char *k, char *v){ + int i; + if(*v == 0) return; - if(cistrcmp(k, "album") == 0) - txtcb(ctx, Talbum, k, v); - else if(cistrcmp(k, "title") == 0) - txtcb(ctx, Ttitle, k, v); - else if(cistrcmp(k, "artist") == 0) - txtcb(ctx, Tartist, k, v); - else if(cistrcmp(k, "albumartist") == 0) - txtcb(ctx, Talbumartist, k, v); - else if(cistrcmp(k, "tracknumber") == 0) - txtcb(ctx, Ttrack, k, v); - else if(cistrcmp(k, "date") == 0) - txtcb(ctx, Tdate, k, v); - else if(cistrcmp(k, "replaygain_track_peak") == 0) - txtcb(ctx, Ttrackpeak, k, v); - else if(cistrcmp(k, "replaygain_track_gain") == 0) - txtcb(ctx, Ttrackgain, k, v); - else if(cistrcmp(k, "replaygain_album_peak") == 0) - txtcb(ctx, Talbumpeak, k, v); - else if(cistrcmp(k, "replaygain_album_gain") == 0) - txtcb(ctx, Talbumgain, k, v); - else if(cistrcmp(k, "genre") == 0) - txtcb(ctx, Tgenre, k, v); - else + for(i = 0; i < nelem(t); i++){ + if(cistrcmp(k, t[i].s) == 0){ + txtcb(ctx, t[i].type, k, v); + break; + } + } + if(i == nelem(t)) txtcb(ctx, Tunknown, k, v); } diff --git a/lib/libtags/wav.c b/lib/libtags/wav.c index 55e1566b..69b1946a 100644 --- a/lib/libtags/wav.c +++ b/lib/libtags/wav.c @@ -2,7 +2,7 @@ #define le16u(d) (u16int)((d)[0] | (d)[1]<<8) -static struct { +static const struct { char *s; int type; }t[] = { @@ -12,6 +12,8 @@ static struct { {"INAM", Ttitle}, {"IPRD", Talbum}, {"ITRK", Ttrack}, + {"ICMT", Tcomment}, + {"????", Tunknown}, }; int @@ -26,7 +28,7 @@ tagwav(Tagctx *ctx) sz = 1; info = 0; - for(i = 0; i < 8 && sz > 0; i++){ + for(i = 0; sz > 0; i++){ if(ctx->read(ctx, d, 4+4+(i?0:4)) != 4+4+(i?0:4)) return -1; if(i == 0){ @@ -66,20 +68,20 @@ tagwav(Tagctx *ctx) }else if(memcmp(d, "LIST", 4) == 0){ sz = csz - 4; continue; - }else if(memcmp(d, "data", 4) == 0){ - break; - }else if(info){ - csz++; + }else if(info && csz < (u32int)ctx->bufsz){ for(n = 0; n < nelem(t); n++){ - if(memcmp(d, t[n].s, 4) == 0){ - if(ctx->read(ctx, d, csz) != (int)csz) + if(memcmp(d, t[n].s, 4) == 0 || t[n].type == Tunknown){ + if(ctx->read(ctx, d+5, csz) != (int)csz) return -1; - d[csz-1] = 0; - txtcb(ctx, t[n].type, "", d); + d[4] = 0; + d[5+csz] = 0; + txtcb(ctx, t[n].type, t[n].type == Tunknown ? (char*)d : "", d+5); csz = 0; break; } } + if(n < nelem(t)) + continue; } if(ctx->seek(ctx, csz, 1) < 0) From 29a246a7334c0a298053991f37d22e19de936ad9 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 21 Feb 2024 14:12:23 +1100 Subject: [PATCH 04/10] Make the drain buffer very large, and move it into PSRAM the i2s handler and streambuffer metadata are both still in iram for good performance. otherwise, this seems to be enough to make gapless playback work. --- sdkconfig.common | 1 - src/audio/audio_fsm.cpp | 22 ++++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/sdkconfig.common b/sdkconfig.common index 7ae9aea2..45f21e9a 100644 --- a/sdkconfig.common +++ b/sdkconfig.common @@ -22,7 +22,6 @@ CONFIG_SPI_MASTER_IN_IRAM=y # CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT is not set # CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM is not set CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y -CONFIG_I2S_ISR_IRAM_SAFE=y # CONFIG_ETH_USE_ESP32_EMAC is not set # CONFIG_ETH_USE_SPI_ETHERNET is not set # CONFIG_ESP_EVENT_POST_FROM_ISR is not set diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index ba6e5ffe..0119855a 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -13,7 +13,9 @@ #include "audio_sink.hpp" #include "bluetooth_types.hpp" +#include "esp_heap_caps.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/projdefs.h" @@ -192,20 +194,28 @@ void AudioState::react(const TogglePlayPause& ev) { namespace states { +// Two seconds of samples for two channels, at a representative sample rate. +constexpr size_t kDrainBufferSize = sizeof(sample::Sample) * 48000 * 4; +static StreamBufferHandle_t sDrainBuffer; + void Uninitialised::react(const system_fsm::BootComplete& ev) { sServices = ev.services; - constexpr size_t kDrainBufferSize = - drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2 * 8; ESP_LOGI(kTag, "allocating drain buffer, size %u KiB", kDrainBufferSize / 1024); - StreamBufferHandle_t stream = xStreamBufferCreateWithCaps( - kDrainBufferSize, sizeof(sample::Sample), MALLOC_CAP_DMA); + + auto meta = reinterpret_cast( + heap_caps_malloc(sizeof(StaticStreamBuffer_t), MALLOC_CAP_DMA)); + auto storage = reinterpret_cast( + heap_caps_malloc(kDrainBufferSize, MALLOC_CAP_SPIRAM)); + + sDrainBuffer = xStreamBufferCreateStatic( + kDrainBufferSize, sizeof(sample::Sample), storage, meta); sFileSource.reset( new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker())); - sI2SOutput.reset(new I2SAudioOutput(stream, sServices->gpios())); - sBtOutput.reset(new BluetoothAudioOutput(stream, sServices->bluetooth(), + sI2SOutput.reset(new I2SAudioOutput(sDrainBuffer, sServices->gpios())); + sBtOutput.reset(new BluetoothAudioOutput(sDrainBuffer, sServices->bluetooth(), sServices->bg_worker())); auto& nvs = sServices->nvs(); From 28651fa7e1d6a90af65d268e6515ed1d2e3b6037 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 21 Feb 2024 14:13:19 +1100 Subject: [PATCH 05/10] Wait for the sink buffer to drain before stopping playback --- src/audio/audio_fsm.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 0119855a..ea0315eb 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -366,6 +366,12 @@ void Playback::react(const internal::InputFileFinished& ev) { ESP_LOGI(kTag, "finished playing file"); sServices->track_queue().finish(); if (!sServices->track_queue().current()) { + for (int i = 0; i < 20; i++) { + if (xStreamBufferIsEmpty(sDrainBuffer)) { + break; + } + vTaskDelay(pdMS_TO_TICKS(200)); + } transit(); } } From 2a250855036047f4e3e37e51600276746d1c302a Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 21 Feb 2024 14:17:25 +1100 Subject: [PATCH 06/10] version bump --- tools/cmake/common.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cmake/common.cmake b/tools/cmake/common.cmake index ca269b27..f9dcc939 100644 --- a/tools/cmake/common.cmake +++ b/tools/cmake/common.cmake @@ -5,7 +5,7 @@ # For more information about build system see # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html -set(PROJECT_VER "0.5.2") +set(PROJECT_VER "0.6.0") # esp-idf sets the C++ standard weird. Set cmake vars to match. set(CMAKE_CXX_STANDARD 23) From 4b2003c78a5e4270f384283f72d185cd4a40f30e Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 22 Feb 2024 12:37:01 +1100 Subject: [PATCH 07/10] Make property bindings shared amongst all lua threads --- src/app_console/app_console.cpp | 6 +- src/lua/bridge.cpp | 74 +++++++++++++----- src/lua/include/bridge.hpp | 27 +++++-- src/lua/include/lua_thread.hpp | 42 ++++++++-- src/lua/include/property.hpp | 4 +- src/lua/lua_thread.cpp | 98 +++++++++++++----------- src/lua/property.cpp | 78 ++++++++++++------- src/ui/ui_fsm.cpp | 132 ++++++++++++++++---------------- 8 files changed, 286 insertions(+), 175 deletions(-) diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp index 33f41306..4e6c97ba 100644 --- a/src/app_console/app_console.cpp +++ b/src/app_console/app_console.cpp @@ -628,8 +628,7 @@ static const char kReplMain[] = "repl:run()\n"; int CmdLua(int argc, char** argv) { - std::unique_ptr context{ - lua::LuaThread::Start(*AppConsole::sServices)}; + auto context = lua::Registry::instance(*AppConsole::sServices).newThread(); if (!context) { return 1; } @@ -652,8 +651,7 @@ int CmdLua(int argc, char** argv) { } int CmdLuaRun(int argc, char** argv) { - std::unique_ptr context{ - lua::LuaThread::Start(*AppConsole::sServices)}; + auto context = lua::Registry::instance(*AppConsole::sServices).newThread(); if (!context) { return 1; } diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp index a680a521..a26f74bb 100644 --- a/src/lua/bridge.cpp +++ b/src/lua/bridge.cpp @@ -22,6 +22,9 @@ #include "lua_version.hpp" #include "lvgl.h" +#include "font/lv_font_loader.h" +#include "luavgl.h" + #include "event_queue.hpp" #include "property.hpp" #include "service_locator.hpp" @@ -32,34 +35,62 @@ int luaopen_linenoise(lua_State* L); int luaopen_term_core(lua_State* L); } +LV_FONT_DECLARE(font_fusion_12); +LV_FONT_DECLARE(font_fusion_10); + namespace lua { [[maybe_unused]] static constexpr char kTag[] = "lua_bridge"; static constexpr char kBridgeKey[] = "bridge"; +static auto make_font_cb(const char* name, int size, int weight) + -> const lv_font_t* { + if (std::string{"fusion"} == name) { + if (size == 12) { + return &font_fusion_12; + } + if (size == 10) { + return &font_fusion_10; + } + } + return NULL; +} + +static auto delete_font_cb(lv_font_t* font) -> void {} + auto Bridge::Get(lua_State* state) -> Bridge* { lua_pushstring(state, kBridgeKey); lua_gettable(state, LUA_REGISTRYINDEX); return reinterpret_cast(lua_touserdata(state, -1)); } -Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s) - : services_(services), state_(s), bindings_(s) { - lua_pushstring(&s, kBridgeKey); - lua_pushlightuserdata(&s, this); - lua_settable(&s, LUA_REGISTRYINDEX); +Bridge::Bridge(system_fsm::ServiceLocator& services) : services_(services) {} + +auto Bridge::installBaseModules(lua_State* L) -> void { + lua_pushstring(L, kBridgeKey); + lua_pushlightuserdata(L, this); + lua_settable(L, LUA_REGISTRYINDEX); + + bindings_.install(L); - luaL_requiref(&s, "linenoise", luaopen_linenoise, true); - lua_pop(&s, 1); + luaL_requiref(L, "linenoise", luaopen_linenoise, true); + lua_pop(L, 1); - luaL_requiref(&s, "term.core", luaopen_term_core, true); - lua_pop(&s, 1); + luaL_requiref(L, "term.core", luaopen_term_core, true); + lua_pop(L, 1); + + RegisterControlsModule(L); + RegisterDatabaseModule(L); + RegisterQueueModule(L); + RegisterVersionModule(L); +} - RegisterControlsModule(&s); - RegisterDatabaseModule(&s); - RegisterQueueModule(&s); - RegisterVersionModule(&s); +auto Bridge::installLvgl(lua_State* L) -> void { + luavgl_set_pcall(L, CallProtected); + luavgl_set_font_extension(L, make_font_cb, delete_font_cb); + luaL_requiref(L, "lvgl", luaopen_lvgl, true); + lua_pop(L, 1); } static auto new_property_module(lua_State* state) -> int { @@ -76,32 +107,33 @@ static auto new_property_module(lua_State* state) -> int { template inline constexpr bool always_false_v = false; -auto Bridge::AddPropertyModule( +auto Bridge::installPropertyModule( + lua_State* L, const std::string& name, - std::vector>> + std::vector>>& props) -> void { // Create the module (or retrieve it if one with this name already exists) - luaL_requiref(&state_, name.c_str(), new_property_module, true); + luaL_requiref(L, name.c_str(), new_property_module, true); for (const auto& prop : props) { - lua_pushstring(&state_, prop.first.c_str()); + lua_pushstring(L, prop.first.c_str()); std::visit( [&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { - bindings_.Register(&state_, arg); + bindings_.Register(L, arg); } else if constexpr (std::is_same_v) { - bindings_.Register(&state_, arg); + bindings_.Register(L, arg); } else { static_assert(always_false_v, "missing case"); } }, prop.second); - lua_settable(&state_, -3); // metatable.propname = property + lua_settable(L, -3); // metatable.propname = property } - lua_pop(&state_, 1); // pop the module off the stack + lua_pop(L, 1); // pop the module off the stack } } // namespace lua diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp index 62fbc340..64f14e0e 100644 --- a/src/lua/include/bridge.hpp +++ b/src/lua/include/bridge.hpp @@ -16,25 +16,38 @@ namespace lua { +/* + * Responsible for adding C/C++ module bindings to Lua threads. This class + * keeps no thread-specific internal state, and instead uses the LUA_REGISTRY + * table of its host threads to store data. + */ class Bridge { public: + /* + * Utility for retrieving the Bridge from a Lua thread in which the Bridge's + * bindings have been installed. Use by Lua's C callbacks to access the rest + * of the system. + */ static auto Get(lua_State* state) -> Bridge*; - Bridge(system_fsm::ServiceLocator&, lua_State& s); + Bridge(system_fsm::ServiceLocator& s); - auto AddPropertyModule( + system_fsm::ServiceLocator& services() { return services_; } + + auto installBaseModules(lua_State* L) -> void; + auto installLvgl(lua_State* L) -> void; + auto installPropertyModule( + lua_State* L, const std::string&, std::vector< - std::pair>>) + std::pair>>&) -> void; - system_fsm::ServiceLocator& services() { return services_; } - PropertyBindings& bindings() { return bindings_; } + Bridge(const Bridge&) = delete; + Bridge& operator=(const Bridge&) = delete; private: system_fsm::ServiceLocator& services_; - lua_State& state_; PropertyBindings bindings_; }; diff --git a/src/lua/include/lua_thread.hpp b/src/lua/include/lua_thread.hpp index d10dba3a..c12a0bfc 100644 --- a/src/lua/include/lua_thread.hpp +++ b/src/lua/include/lua_thread.hpp @@ -23,8 +23,7 @@ auto CallProtected(lua_State*, int nargs, int nresults) -> int; class LuaThread { public: - static auto Start(system_fsm::ServiceLocator&, lv_obj_t* lvgl_root = nullptr) - -> LuaThread*; + static auto Start(system_fsm::ServiceLocator&) -> LuaThread*; ~LuaThread(); auto RunScript(const std::string& path) -> bool; @@ -32,15 +31,48 @@ class LuaThread { auto DumpStack() -> void; - auto bridge() -> Bridge& { return *bridge_; } auto state() -> lua_State* { return state_; } + LuaThread(const LuaThread&) = delete; + LuaThread& operator=(const LuaThread&) = delete; + private: - LuaThread(std::unique_ptr&, std::unique_ptr&, lua_State*); + LuaThread(std::unique_ptr&, lua_State*); std::unique_ptr alloc_; - std::unique_ptr bridge_; lua_State* state_; }; +class Registry { + public: + static auto instance(system_fsm::ServiceLocator&) -> Registry&; + + auto uiThread() -> std::shared_ptr; + auto newThread() -> std::shared_ptr; + + auto AddPropertyModule( + const std::string&, + std::vector< + std::pair>>) + -> void; + + Registry(const Registry&) = delete; + Registry& operator=(const Registry&) = delete; + + private: + Registry(system_fsm::ServiceLocator&); + + system_fsm::ServiceLocator& services_; + std::unique_ptr bridge_; + + std::shared_ptr ui_thread_; + std::list> threads_; + + std::vector< + std::pair>>>> + modules_; +}; + } // namespace lua diff --git a/src/lua/include/property.hpp b/src/lua/include/property.hpp index 03229bc1..7d160fba 100644 --- a/src/lua/include/property.hpp +++ b/src/lua/include/property.hpp @@ -53,7 +53,9 @@ class Property { class PropertyBindings { public: - PropertyBindings(lua_State&); + PropertyBindings(); + + auto install(lua_State*) -> void; auto Register(lua_State*, Property*) -> void; auto Register(lua_State*, LuaFunction) -> void; diff --git a/src/lua/lua_thread.cpp b/src/lua/lua_thread.cpp index b94b70ab..7d64e3c5 100644 --- a/src/lua/lua_thread.cpp +++ b/src/lua/lua_thread.cpp @@ -9,22 +9,16 @@ #include #include -#include "lauxlib.h" -#include "lua.h" -#include "lua.hpp" - -#include "font/lv_font_loader.h" -#include "luavgl.h" - #include "esp_heap_caps.h" #include "esp_log.h" +#include "lua.hpp" + +#include "bridge.hpp" #include "event_queue.hpp" +#include "memory_resource.hpp" #include "service_locator.hpp" #include "ui_events.hpp" -LV_FONT_DECLARE(font_fusion_12); -LV_FONT_DECLARE(font_fusion_10); - namespace lua { [[maybe_unused]] static constexpr char kTag[] = "lua"; @@ -59,23 +53,7 @@ static int lua_panic(lua_State* L) { return 0; } -static auto make_font_cb(const char* name, int size, int weight) - -> const lv_font_t* { - if (std::string{"fusion"} == name) { - if (size == 12) { - return &font_fusion_12; - } - if (size == 10) { - return &font_fusion_10; - } - } - return NULL; -} - -static auto delete_font_cb(lv_font_t* font) -> void {} - -auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root) - -> LuaThread* { +auto LuaThread::Start(system_fsm::ServiceLocator& services) -> LuaThread* { auto alloc = std::make_unique(); lua_State* state = lua_newstate(lua_alloc, alloc.get()); if (!state) { @@ -85,24 +63,11 @@ auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root) luaL_openlibs(state); lua_atpanic(state, lua_panic); - auto bridge = std::make_unique(services, *state); - - // FIXME: luavgl init should probably be a part of the bridge. - if (lvgl_root) { - luavgl_set_pcall(state, CallProtected); - luavgl_set_font_extension(state, make_font_cb, delete_font_cb); - luavgl_set_root(state, lvgl_root); - luaL_requiref(state, "lvgl", luaopen_lvgl, true); - lua_pop(state, 1); - } - - return new LuaThread(alloc, bridge, state); + return new LuaThread(alloc, state); } -LuaThread::LuaThread(std::unique_ptr& alloc, - std::unique_ptr& bridge, - lua_State* state) - : alloc_(std::move(alloc)), bridge_(std::move(bridge)), state_(state) {} +LuaThread::LuaThread(std::unique_ptr& alloc, lua_State* state) + : alloc_(std::move(alloc)), state_(state) {} LuaThread::~LuaThread() { lua_close(state_); @@ -219,4 +184,51 @@ auto CallProtected(lua_State* s, int nargs, int nresults) -> int { return ret; } +auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& { + static Registry sRegistry{s}; + return sRegistry; +} + +Registry::Registry(system_fsm::ServiceLocator& services) + : services_(services), bridge_(new Bridge(services)) {} + +auto Registry::uiThread() -> std::shared_ptr { + if (!ui_thread_) { + ui_thread_ = newThread(); + bridge_->installLvgl(ui_thread_->state()); + } + return ui_thread_; +} + +auto Registry::newThread() -> std::shared_ptr { + std::shared_ptr thread{LuaThread::Start(services_)}; + bridge_->installBaseModules(thread->state()); + for (auto& module : modules_) { + bridge_->installPropertyModule(thread->state(), module.first, + module.second); + } + threads_.push_back(thread); + return thread; +} + +auto Registry::AddPropertyModule( + const std::string& name, + std::vector>> + properties) -> void { + modules_.push_back(std::make_pair(name, properties)); + + // Any live threads will need to be updated to include the new module. + auto it = threads_.begin(); + while (it != threads_.end()) { + auto thread = it->lock(); + if (!thread) { + // Thread has been destroyed; stop tracking it. + it = threads_.erase(it); + } else { + bridge_->installPropertyModule(thread->state(), name, properties); + it++; + } + } +} + } // namespace lua diff --git a/src/lua/property.cpp b/src/lua/property.cpp index 5357ccc5..f721f9ce 100644 --- a/src/lua/property.cpp +++ b/src/lua/property.cpp @@ -10,10 +10,12 @@ #include #include #include +#include #include #include #include "bluetooth_types.hpp" +#include "lauxlib.h" #include "lua.h" #include "lua.hpp" #include "lua_thread.hpp" @@ -76,10 +78,30 @@ static auto property_bind(lua_State* state) -> int { return 1; } -static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get}, - {"set", property_set}, - {"bind", property_bind}, - {NULL, NULL}}; +static auto property_tostring(lua_State* state) -> int { + Property* p = check_property(state); + p->PushValue(*state); + + std::stringstream str{}; + str << "property { " << luaL_tolstring(state, -1, NULL); + if (!p->IsTwoWay()) { + str << ", read-only"; + } + str << " }"; + + lua_settop(state, 0); + + std::string res = str.str(); + lua_pushlstring(state, res.data(), res.size()); + return 1; +} + +static const struct luaL_Reg kPropertyBindingFuncs[] = { + {"get", property_get}, + {"set", property_set}, + {"bind", property_bind}, + {"__tostring", property_tostring}, + {NULL, NULL}}; static auto generic_function_cb(lua_State* state) -> int { lua_pushstring(state, kBinderKey); @@ -98,45 +120,47 @@ static auto generic_function_cb(lua_State* state) -> int { return std::invoke(fn, state); } -PropertyBindings::PropertyBindings(lua_State& s) { - lua_pushstring(&s, kBinderKey); - lua_pushlightuserdata(&s, this); - lua_settable(&s, LUA_REGISTRYINDEX); +PropertyBindings::PropertyBindings() : functions_(&memory::kSpiRamResource) {} + +auto PropertyBindings::install(lua_State* L) -> void { + lua_pushstring(L, kBinderKey); + lua_pushlightuserdata(L, this); + lua_settable(L, LUA_REGISTRYINDEX); // Create the metatable responsible for the Property API. - luaL_newmetatable(&s, kPropertyMetatable); + luaL_newmetatable(L, kPropertyMetatable); - lua_pushliteral(&s, "__index"); - lua_pushvalue(&s, -2); - lua_settable(&s, -3); // metatable.__index = metatable + lua_pushliteral(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); // metatable.__index = metatable // Add our binding funcs (get, set, bind) to the metatable. - luaL_setfuncs(&s, kPropertyBindingFuncs, 0); + luaL_setfuncs(L, kPropertyBindingFuncs, 0); // We've finished setting up the metatable, so pop it. - lua_pop(&s, 1); + lua_pop(L, 1); // Create a weak table in the registry to hold live bindings. - lua_pushstring(&s, kBindingsTable); - lua_newtable(&s); // bindings = {} + lua_pushstring(L, kBindingsTable); + lua_newtable(L); // bindings = {} // Metatable for the weak table. Values are weak. - lua_newtable(&s); // meta = {} - lua_pushliteral(&s, "__mode"); - lua_pushliteral(&s, "v"); - lua_settable(&s, -3); // meta.__mode='v' - lua_setmetatable(&s, -2); // setmetatable(bindings, meta) + lua_newtable(L); // meta = {} + lua_pushliteral(L, "__mode"); + lua_pushliteral(L, "v"); + lua_settable(L, -3); // meta.__mode='v' + lua_setmetatable(L, -2); // setmetatable(bindings, meta) - lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings + lua_settable(L, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings // Create the metatable for C++ functions. - luaL_newmetatable(&s, kFunctionMetatable); + luaL_newmetatable(L, kFunctionMetatable); - lua_pushliteral(&s, "__call"); - lua_pushcfunction(&s, generic_function_cb); - lua_settable(&s, -3); // metatable.__call = metatable + lua_pushliteral(L, "__call"); + lua_pushcfunction(L, generic_function_cb); + lua_settable(L, -3); // metatable.__call = metatable - lua_pop(&s, 1); // Clean up the function metatable + lua_pop(L, 1); // Clean up the function metatable } auto PropertyBindings::Register(lua_State* s, Property* prop) -> void { diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 24145ead..e46832ba 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -128,29 +128,29 @@ lua::Property UiState::sPlaybackPosition{0}; lua::Property UiState::sQueuePosition{0}; lua::Property UiState::sQueueSize{0}; lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - bool new_val = std::get(val); - sServices->track_queue().repeat(new_val); - return true; -}}; + if (!std::holds_alternative(val)) { + return false; + } + bool new_val = std::get(val); + sServices->track_queue().repeat(new_val); + return true; + }}; lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - bool new_val = std::get(val); - sServices->track_queue().replay(new_val); - return true; -}}; + if (!std::holds_alternative(val)) { + return false; + } + bool new_val = std::get(val); + sServices->track_queue().replay(new_val); + return true; + }}; lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - bool new_val = std::get(val); - sServices->track_queue().random(new_val); - return true; -}}; + if (!std::holds_alternative(val)) { + return false; + } + bool new_val = std::get(val); + sServices->track_queue().random(new_val); + return true; + }}; lua::Property UiState::sVolumeCurrentPct{ 0, [](const lua::LuaValue& val) { @@ -442,27 +442,26 @@ void Lua::entry() { alert_timer_callback); sAlertContainer = lv_obj_create(sCurrentScreen->alert()); - sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content())); - sLua->bridge().AddPropertyModule("power", - { - {"battery_pct", &sBatteryPct}, - {"battery_millivolts", &sBatteryMv}, - {"plugged_in", &sBatteryCharging}, - }); - sLua->bridge().AddPropertyModule( - "bluetooth", { - {"enabled", &sBluetoothEnabled}, - {"connected", &sBluetoothConnected}, - {"paired_device", &sBluetoothPairedDevice}, - {"devices", &sBluetoothDevices}, - }); - sLua->bridge().AddPropertyModule("playback", - { - {"playing", &sPlaybackPlaying}, - {"track", &sPlaybackTrack}, - {"position", &sPlaybackPosition}, - }); - sLua->bridge().AddPropertyModule( + auto& registry = lua::Registry::instance(*sServices); + sLua = registry.uiThread(); + registry.AddPropertyModule("power", { + {"battery_pct", &sBatteryPct}, + {"battery_millivolts", &sBatteryMv}, + {"plugged_in", &sBatteryCharging}, + }); + registry.AddPropertyModule("bluetooth", + { + {"enabled", &sBluetoothEnabled}, + {"connected", &sBluetoothConnected}, + {"paired_device", &sBluetoothPairedDevice}, + {"devices", &sBluetoothDevices}, + }); + registry.AddPropertyModule("playback", { + {"playing", &sPlaybackPlaying}, + {"track", &sPlaybackTrack}, + {"position", &sPlaybackPosition}, + }); + registry.AddPropertyModule( "queue", { {"next", [&](lua_State* s) { return QueueNext(s); }}, @@ -473,40 +472,39 @@ void Lua::entry() { {"repeat_track", &sQueueRepeat}, {"random", &sQueueRandom}, }); - sLua->bridge().AddPropertyModule("volume", - { - {"current_pct", &sVolumeCurrentPct}, - {"current_db", &sVolumeCurrentDb}, - {"left_bias", &sVolumeLeftBias}, - {"limit_db", &sVolumeLimit}, - }); - - sLua->bridge().AddPropertyModule("display", - { - {"brightness", &sDisplayBrightness}, - }); - - sLua->bridge().AddPropertyModule("controls", - { - {"scheme", &sControlsScheme}, - {"scroll_sensitivity", &sScrollSensitivity}, - }); - - sLua->bridge().AddPropertyModule( + registry.AddPropertyModule("volume", + { + {"current_pct", &sVolumeCurrentPct}, + {"current_db", &sVolumeCurrentDb}, + {"left_bias", &sVolumeLeftBias}, + {"limit_db", &sVolumeLimit}, + }); + + registry.AddPropertyModule("display", + { + {"brightness", &sDisplayBrightness}, + }); + + registry.AddPropertyModule("controls", + { + {"scheme", &sControlsScheme}, + {"scroll_sensitivity", &sScrollSensitivity}, + }); + + registry.AddPropertyModule( "backstack", { {"push", [&](lua_State* s) { return PushLuaScreen(s); }}, {"pop", [&](lua_State* s) { return PopLuaScreen(s); }}, }); - sLua->bridge().AddPropertyModule( + registry.AddPropertyModule( "alerts", { {"show", [&](lua_State* s) { return ShowAlert(s); }}, {"hide", [&](lua_State* s) { return HideAlert(s); }}, }); - sLua->bridge().AddPropertyModule("database", - { - {"updating", &sDatabaseUpdating}, - }); + registry.AddPropertyModule("database", { + {"updating", &sDatabaseUpdating}, + }); auto bt = sServices->bluetooth(); sBluetoothEnabled.Update(bt.IsEnabled()); From f9aec8b6906599296417af5414b1c72a3cf53e73 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 22 Feb 2024 14:16:33 +1100 Subject: [PATCH 08/10] split the lua thread registry into its own files --- src/app_console/app_console.cpp | 2 +- src/lua/CMakeLists.txt | 2 +- src/lua/include/lua_registry.hpp | 51 ++++++++++++++++++++++ src/lua/include/lua_thread.hpp | 34 --------------- src/lua/lua_queue.cpp | 3 +- src/lua/lua_thread.cpp | 47 -------------------- src/lua/registry.cpp | 73 ++++++++++++++++++++++++++++++++ src/ui/ui_fsm.cpp | 1 + 8 files changed, 129 insertions(+), 84 deletions(-) create mode 100644 src/lua/include/lua_registry.hpp create mode 100644 src/lua/registry.cpp diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp index 4e6c97ba..94a48955 100644 --- a/src/app_console/app_console.cpp +++ b/src/app_console/app_console.cpp @@ -40,7 +40,7 @@ #include "freertos/projdefs.h" #include "haptics.hpp" #include "index.hpp" -#include "lua_thread.hpp" +#include "lua_registry.hpp" #include "memory_resource.hpp" #include "samd.hpp" #include "service_locator.hpp" diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt index d89b22e8..ff0831c9 100644 --- a/src/lua/CMakeLists.txt +++ b/src/lua/CMakeLists.txt @@ -4,7 +4,7 @@ idf_component_register( SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" - "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" + "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp" INCLUDE_DIRS "include" REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" diff --git a/src/lua/include/lua_registry.hpp b/src/lua/include/lua_registry.hpp new file mode 100644 index 00000000..abc5063e --- /dev/null +++ b/src/lua/include/lua_registry.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "lua.hpp" + +#include "bridge.hpp" +#include "lua_thread.hpp" +#include "service_locator.hpp" + +namespace lua { + +class Registry { + public: + static auto instance(system_fsm::ServiceLocator&) -> Registry&; + + auto uiThread() -> std::shared_ptr; + auto newThread() -> std::shared_ptr; + + auto AddPropertyModule( + const std::string&, + std::vector>>) + -> void; + + Registry(const Registry&) = delete; + Registry& operator=(const Registry&) = delete; + + private: + Registry(system_fsm::ServiceLocator&); + + system_fsm::ServiceLocator& services_; + std::unique_ptr bridge_; + + std::shared_ptr ui_thread_; + std::list> threads_; + + std::vector< + std::pair>>>> + modules_; +}; + +} // namespace lua diff --git a/src/lua/include/lua_thread.hpp b/src/lua/include/lua_thread.hpp index c12a0bfc..384de61d 100644 --- a/src/lua/include/lua_thread.hpp +++ b/src/lua/include/lua_thread.hpp @@ -10,9 +10,7 @@ #include #include "lua.hpp" -#include "lvgl.h" -#include "bridge.hpp" #include "service_locator.hpp" namespace lua { @@ -43,36 +41,4 @@ class LuaThread { lua_State* state_; }; -class Registry { - public: - static auto instance(system_fsm::ServiceLocator&) -> Registry&; - - auto uiThread() -> std::shared_ptr; - auto newThread() -> std::shared_ptr; - - auto AddPropertyModule( - const std::string&, - std::vector< - std::pair>>) - -> void; - - Registry(const Registry&) = delete; - Registry& operator=(const Registry&) = delete; - - private: - Registry(system_fsm::ServiceLocator&); - - system_fsm::ServiceLocator& services_; - std::unique_ptr bridge_; - - std::shared_ptr ui_thread_; - std::list> threads_; - - std::vector< - std::pair>>>> - modules_; -}; - } // namespace lua diff --git a/src/lua/lua_queue.cpp b/src/lua/lua_queue.cpp index 69d3b03d..dfb820c2 100644 --- a/src/lua/lua_queue.cpp +++ b/src/lua/lua_queue.cpp @@ -16,6 +16,7 @@ #include "lua.h" #include "lvgl.h" +#include "bridge.hpp" #include "database.hpp" #include "event_queue.hpp" #include "index.hpp" @@ -70,4 +71,4 @@ auto RegisterQueueModule(lua_State* s) -> void { lua_pop(s, 1); } -} // namespace lua \ No newline at end of file +} // namespace lua diff --git a/src/lua/lua_thread.cpp b/src/lua/lua_thread.cpp index 7d64e3c5..dd72e41d 100644 --- a/src/lua/lua_thread.cpp +++ b/src/lua/lua_thread.cpp @@ -184,51 +184,4 @@ auto CallProtected(lua_State* s, int nargs, int nresults) -> int { return ret; } -auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& { - static Registry sRegistry{s}; - return sRegistry; -} - -Registry::Registry(system_fsm::ServiceLocator& services) - : services_(services), bridge_(new Bridge(services)) {} - -auto Registry::uiThread() -> std::shared_ptr { - if (!ui_thread_) { - ui_thread_ = newThread(); - bridge_->installLvgl(ui_thread_->state()); - } - return ui_thread_; -} - -auto Registry::newThread() -> std::shared_ptr { - std::shared_ptr thread{LuaThread::Start(services_)}; - bridge_->installBaseModules(thread->state()); - for (auto& module : modules_) { - bridge_->installPropertyModule(thread->state(), module.first, - module.second); - } - threads_.push_back(thread); - return thread; -} - -auto Registry::AddPropertyModule( - const std::string& name, - std::vector>> - properties) -> void { - modules_.push_back(std::make_pair(name, properties)); - - // Any live threads will need to be updated to include the new module. - auto it = threads_.begin(); - while (it != threads_.end()) { - auto thread = it->lock(); - if (!thread) { - // Thread has been destroyed; stop tracking it. - it = threads_.erase(it); - } else { - bridge_->installPropertyModule(thread->state(), name, properties); - it++; - } - } -} - } // namespace lua diff --git a/src/lua/registry.cpp b/src/lua/registry.cpp new file mode 100644 index 00000000..a6487858 --- /dev/null +++ b/src/lua/registry.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "lua_registry.hpp" + +#include +#include + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "lua.hpp" + +#include "bridge.hpp" +#include "event_queue.hpp" +#include "memory_resource.hpp" +#include "service_locator.hpp" +#include "ui_events.hpp" + +namespace lua { + +[[maybe_unused]] static constexpr char kTag[] = "lua"; + +auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& { + static Registry sRegistry{s}; + return sRegistry; +} + +Registry::Registry(system_fsm::ServiceLocator& services) + : services_(services), bridge_(new Bridge(services)) {} + +auto Registry::uiThread() -> std::shared_ptr { + if (!ui_thread_) { + ui_thread_ = newThread(); + bridge_->installLvgl(ui_thread_->state()); + } + return ui_thread_; +} + +auto Registry::newThread() -> std::shared_ptr { + std::shared_ptr thread{LuaThread::Start(services_)}; + bridge_->installBaseModules(thread->state()); + for (auto& module : modules_) { + bridge_->installPropertyModule(thread->state(), module.first, + module.second); + } + threads_.push_back(thread); + return thread; +} + +auto Registry::AddPropertyModule( + const std::string& name, + std::vector>> + properties) -> void { + modules_.push_back(std::make_pair(name, properties)); + + // Any live threads will need to be updated to include the new module. + auto it = threads_.begin(); + while (it != threads_.end()) { + auto thread = it->lock(); + if (!thread) { + // Thread has been destroyed; stop tracking it. + it = threads_.erase(it); + } else { + bridge_->installPropertyModule(thread->state(), name, properties); + it++; + } + } +} + +} // namespace lua diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index e46832ba..9668b0f3 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -36,6 +36,7 @@ #include "encoder_input.hpp" #include "event_queue.hpp" #include "gpios.hpp" +#include "lua_registry.hpp" #include "lvgl_task.hpp" #include "nvs.hpp" #include "property.hpp" From 26fc53b18b4c2c55c1fb5f0ed46152860e8f5cc2 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 27 Feb 2024 13:25:49 +1100 Subject: [PATCH 09/10] fix bad copypaste in docs --- ldoc-stubs/alerts.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ldoc-stubs/alerts.lua b/ldoc-stubs/alerts.lua index 9b541d84..6fecdd7c 100644 --- a/ldoc-stubs/alerts.lua +++ b/ldoc-stubs/alerts.lua @@ -1,9 +1,9 @@ ---- Module for interacting with playback volume. The Bluetooth and wired outputs store their current volume separately; this API only allows interacting with the volume of the currently used output device. +--- Module for showing transient popups over the current screen. -- @module alerts local alerts = {} ---- Returns the current volume as a percentage of the current volume limit. +--- Shows a new alert, replacing any already visible alerts. -- @tparam function constructor Called to create the UI for the alert. A new default root object and group will be set before calling this function.i Alerts are non-interactable; the group created for the constructor will not be granted focus. function alerts.show(constructor) end From 5141c9f0bd62e575fff9e132c0ad6de089a18ece Mon Sep 17 00:00:00 2001 From: ailurux Date: Tue, 27 Feb 2024 17:28:55 +1100 Subject: [PATCH 10/10] Add time.ticks to lua bridge --- src/ui/include/ui_fsm.hpp | 2 ++ src/ui/ui_fsm.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index ffaff0bb..6cf2ba4c 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -163,6 +163,8 @@ class Lua : public UiState { auto ShowAlert(lua_State*) -> int; auto HideAlert(lua_State*) -> int; + auto Ticks(lua_State*) -> int; + auto SetPlaying(const lua::LuaValue&) -> bool; auto SetRandom(const lua::LuaValue&) -> bool; auto SetRepeat(const lua::LuaValue&) -> bool; diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 9668b0f3..f5288882 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -24,6 +24,7 @@ #include "core/lv_obj_tree.h" #include "database.hpp" #include "esp_heap_caps.h" +#include "esp_timer.h" #include "haptics.hpp" #include "lauxlib.h" #include "lua_thread.hpp" @@ -503,6 +504,11 @@ void Lua::entry() { {"show", [&](lua_State* s) { return ShowAlert(s); }}, {"hide", [&](lua_State* s) { return HideAlert(s); }}, }); + + registry.AddPropertyModule( + "time", { + {"ticks", [&](lua_State* s) { return Ticks(s); }}, + }); registry.AddPropertyModule("database", { {"updating", &sDatabaseUpdating}, }); @@ -564,6 +570,11 @@ auto Lua::PopLuaScreen(lua_State* s) -> int { return 0; } +auto Lua::Ticks(lua_State* s) -> int { + lua_pushinteger(s, esp_timer_get_time()/1000); + return 1; +} + auto Lua::ShowAlert(lua_State* s) -> int { if (!sCurrentScreen) { return 0;