Rewrite the track queue to work directly with database iterators

custom
jacqueline 1 year ago
parent 67f2f2de83
commit 4f5422e906
  1. 87
      src/app_console/app_console.cpp
  2. 9
      src/audio/audio_fsm.cpp
  3. 82
      src/audio/include/track_queue.hpp
  4. 324
      src/audio/track_queue.cpp
  5. 113
      src/database/database.cpp
  6. 29
      src/database/include/database.hpp
  7. 22
      src/lua/lua_queue.cpp
  8. 14
      src/ui/ui_fsm.cpp

@ -123,7 +123,8 @@ int CmdPlayFile(int argc, char** argv) {
if (is_id) { if (is_id) {
database::TrackId id = std::atoi(argv[1]); database::TrackId id = std::atoi(argv[1]);
AppConsole::sServices->track_queue().AddLast(id); auto editor = AppConsole::sServices->track_queue().Edit();
AppConsole::sServices->track_queue().Append(editor, id);
} else { } else {
std::pmr::string path{&memory::kSpiRamResource}; std::pmr::string path{&memory::kSpiRamResource};
path += '/'; path += '/';
@ -214,89 +215,6 @@ void RegisterDbTracks() {
esp_console_cmd_register(&cmd); esp_console_cmd_register(&cmd);
} }
int CmdDbIndex(int argc, char** argv) {
std::cout << std::endl;
vTaskDelay(1);
static const std::pmr::string usage = "usage: db_index [id] [choices ...]";
auto db = AppConsole::sServices->database().lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
}
auto indexes = db->GetIndexes();
if (argc <= 1) {
std::cout << usage << std::endl;
std::cout << "available indexes:" << std::endl;
std::cout << "id\tname" << std::endl;
for (const database::IndexInfo& info : indexes) {
std::cout << static_cast<int>(info.id) << '\t' << info.name << std::endl;
}
return 0;
}
int index_id = std::atoi(argv[1]);
auto index = std::find_if(indexes.begin(), indexes.end(),
[=](const auto& i) { return i.id == index_id; });
if (index == indexes.end()) {
std::cout << "bad index id" << std::endl;
return -1;
}
std::shared_ptr<database::Result<database::IndexRecord>> res(
db->GetTracksByIndex(index->id, 20).get());
int choice_index = 2;
if (res->values().empty()) {
std::cout << "no entries for this index" << std::endl;
return 1;
}
while (choice_index < argc) {
int choice = std::atoi(argv[choice_index]);
if (choice >= res->values().size()) {
std::cout << "choice out of range" << std::endl;
return -1;
}
if (res->values().at(choice)->track()) {
AppConsole::sServices->track_queue().IncludeLast(
std::make_shared<playlist::IndexRecordSource>(
AppConsole::sServices->database(), res, 0, res, choice));
}
auto cont = res->values().at(choice)->Expand(20);
if (!cont) {
std::cout << "more choices than levels" << std::endl;
return 0;
}
res.reset(db->GetPage<database::IndexRecord>(&*cont).get());
choice_index++;
}
for (const auto& r : res->values()) {
std::cout << r->text().value_or("<unknown>");
if (r->track()) {
std::cout << "\t(id:" << *r->track() << ")";
}
std::cout << std::endl;
}
if (res->next_page()) {
std::cout << "(more results not shown)" << std::endl;
}
return 0;
}
void RegisterDbIndex() {
esp_console_cmd_t cmd{.command = "db_index",
.help = "queries the database by index",
.hint = NULL,
.func = &CmdDbIndex,
.argtable = NULL};
esp_console_cmd_register(&cmd);
}
int CmdDbDump(int argc, char** argv) { int CmdDbDump(int argc, char** argv) {
static const std::pmr::string usage = "usage: db_dump"; static const std::pmr::string usage = "usage: db_dump";
if (argc != 1) { if (argc != 1) {
@ -726,7 +644,6 @@ auto AppConsole::RegisterExtraComponents() -> void {
*/ */
RegisterDbInit(); RegisterDbInit();
RegisterDbTracks(); RegisterDbTracks();
RegisterDbIndex();
RegisterDbDump(); RegisterDbDump();
RegisterTasks(); RegisterTasks();

@ -143,7 +143,7 @@ void Standby::react(const internal::InputFileOpened& ev) {
} }
void Standby::react(const QueueUpdate& ev) { void Standby::react(const QueueUpdate& ev) {
auto current_track = sServices->track_queue().GetCurrent(); auto current_track = sServices->track_queue().Current();
if (!current_track || (sCurrentTrack && *sCurrentTrack == *current_track)) { if (!current_track || (sCurrentTrack && *sCurrentTrack == *current_track)) {
return; return;
} }
@ -187,7 +187,7 @@ void Playback::react(const QueueUpdate& ev) {
if (!ev.current_changed) { if (!ev.current_changed) {
return; return;
} }
auto current_track = sServices->track_queue().GetCurrent(); auto current_track = sServices->track_queue().Current();
if (!current_track) { if (!current_track) {
sFileSource->SetPath(); sFileSource->SetPath();
sCurrentTrack.reset(); sCurrentTrack.reset();
@ -220,8 +220,9 @@ void Playback::react(const internal::InputFileClosed& ev) {}
void Playback::react(const internal::InputFileFinished& ev) { void Playback::react(const internal::InputFileFinished& ev) {
ESP_LOGI(kTag, "finished playing file"); ESP_LOGI(kTag, "finished playing file");
sServices->track_queue().Next(); auto editor = sServices->track_queue().Edit();
if (!sServices->track_queue().GetCurrent()) { sServices->track_queue().Next(editor);
if (!sServices->track_queue().Current()) {
transit<Standby>(); transit<Standby>();
} }
} }

@ -11,6 +11,7 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include "database.hpp"
#include "source.hpp" #include "source.hpp"
#include "track.hpp" #include "track.hpp"
@ -27,67 +28,78 @@ namespace audio {
* *
* Instances of this class are broadly safe to use from multiple tasks; each * Instances of this class are broadly safe to use from multiple tasks; each
* method represents an atomic operation. No guarantees are made about * method represents an atomic operation. No guarantees are made about
* consistency between calls however. For example, there may be data changes * consistency between calls however.
* between consecutive calls to AddNext() and GetUpcoming();
*/ */
class TrackQueue { class TrackQueue {
public: public:
TrackQueue(); TrackQueue();
class Editor {
public:
~Editor();
// Cannot be copied or moved.
Editor(const Editor&) = delete;
Editor& operator=(const Editor&) = delete;
private:
friend TrackQueue;
Editor(TrackQueue&);
std::lock_guard<std::recursive_mutex> lock_;
bool has_current_changed_;
};
auto Edit() -> Editor;
/* Returns the currently playing track. */ /* Returns the currently playing track. */
auto GetCurrent() const -> std::optional<database::TrackId>; auto Current() const -> std::optional<database::TrackId>;
/* Returns, in order, tracks that have been queued to be played next. */ /* Returns, in order, tracks that have been queued to be played next. */
auto GetUpcoming(std::size_t limit) const -> std::vector<database::TrackId>; auto PeekNext(std::size_t limit) const -> std::vector<database::TrackId>;
/* /*
* Enqueues a track, placing it immediately after the current track and * Returns the tracks in the queue that have already been played, ordered
* before anything already queued. * most recently played first.
*
* If there is no current track, the given track will begin playback.
*/ */
auto AddNext(database::TrackId) -> void; auto PeekPrevious(std::size_t limit) const -> std::vector<database::TrackId>;
auto AddNext(std::shared_ptr<playlist::ISource>) -> void;
auto IncludeNext(std::shared_ptr<playlist::IResetableSource>) -> void;
/* auto GetCurrentPosition() const -> size_t;
* Enqueues a track, placing it the end of all enqueued tracks. auto GetTotalSize() const -> size_t;
*
* If there is no current track, the given track will begin playback.
*/
auto AddLast(database::TrackId) -> void;
auto AddLast(std::shared_ptr<playlist::ISource>) -> void;
auto IncludeLast(std::shared_ptr<playlist::IResetableSource>) -> void; using Item = std::variant<database::TrackId, database::TrackIterator>;
auto Insert(Editor&, Item, size_t) -> void;
auto Append(Editor&, Item i) -> void;
/* /*
* Advances to the next track in the queue, placing the current track at the * Advances to the next track in the queue, placing the current track at the
* front of the 'played' queue. * front of the 'played' queue.
*/ */
auto Next() -> void; auto Next(Editor&) -> std::optional<database::TrackId>;
auto Previous() -> void; auto Previous(Editor&) -> std::optional<database::TrackId>;
auto SkipTo(Editor&, database::TrackId) -> void;
/* /*
* Removes all tracks from all queues, and stops any currently playing track. * Removes all tracks from all queues, and stops any currently playing track.
*/ */
auto Clear() -> void; auto Clear(Editor&) -> void;
auto Position() -> size_t;
auto Size() -> size_t;
// Cannot be copied or moved.
TrackQueue(const TrackQueue&) = delete; TrackQueue(const TrackQueue&) = delete;
TrackQueue& operator=(const TrackQueue&) = delete; TrackQueue& operator=(const TrackQueue&) = delete;
private: private:
mutable std::mutex mutex_; // FIXME: Make this a shared_mutex so that multithread reads don't block.
mutable std::recursive_mutex mutex_;
std::list<std::variant<database::TrackId,
std::shared_ptr<playlist::IResetableSource>>> std::optional<database::TrackId> current_;
played_;
std::list<std::variant<database::TrackId, // Note: stored in reverse order, i.e. most recent played it at the *back* of
std::shared_ptr<playlist::ISource>, // this vector.
std::shared_ptr<playlist::IResetableSource>>> std::pmr::vector<database::TrackId> played_;
enqueued_; std::pmr::vector<Item> enqueued_;
}; };
} // namespace audio } // namespace audio

@ -15,6 +15,7 @@
#include "audio_fsm.hpp" #include "audio_fsm.hpp"
#include "database.hpp" #include "database.hpp"
#include "event_queue.hpp" #include "event_queue.hpp"
#include "memory_resource.hpp"
#include "source.hpp" #include "source.hpp"
#include "track.hpp" #include "track.hpp"
#include "ui_fsm.hpp" #include "ui_fsm.hpp"
@ -23,208 +24,207 @@ namespace audio {
[[maybe_unused]] static constexpr char kTag[] = "tracks"; [[maybe_unused]] static constexpr char kTag[] = "tracks";
TrackQueue::TrackQueue() {} TrackQueue::Editor::Editor(TrackQueue& queue)
: lock_(queue.mutex_), has_current_changed_(false) {}
auto TrackQueue::GetCurrent() const -> std::optional<database::TrackId> { TrackQueue::Editor::~Editor() {
const std::lock_guard<std::mutex> lock(mutex_); QueueUpdate ev{.current_changed = has_current_changed_};
if (enqueued_.empty()) { events::Audio().Dispatch(ev);
return {}; events::Ui().Dispatch(ev);
}
auto item = enqueued_.front();
if (std::holds_alternative<database::TrackId>(item)) {
return std::get<database::TrackId>(item);
}
if (std::holds_alternative<std::shared_ptr<playlist::ISource>>(item)) {
return std::get<std::shared_ptr<playlist::ISource>>(item)->Current();
}
if (std::holds_alternative<std::shared_ptr<playlist::IResetableSource>>(
item)) {
return std::get<std::shared_ptr<playlist::IResetableSource>>(item)
->Current();
}
return {};
} }
auto TrackQueue::GetUpcoming(std::size_t limit) const TrackQueue::TrackQueue()
-> std::vector<database::TrackId> { : mutex_(),
const std::lock_guard<std::mutex> lock(mutex_); current_(),
std::vector<database::TrackId> ret; played_(&memory::kSpiRamResource),
enqueued_(&memory::kSpiRamResource) {}
auto it = enqueued_.begin(); auto TrackQueue::Edit() -> Editor {
if (it == enqueued_.end()) { return Editor(*this);
return ret; }
}
// Don't include the current track. This is only relevant to raw track ids, auto TrackQueue::Current() const -> std::optional<database::TrackId> {
// since sources include multiple tracks. const std::lock_guard<std::recursive_mutex> lock(mutex_);
if (std::holds_alternative<database::TrackId>(*it)) { return current_;
it++; }
}
while (limit > 0 && it != enqueued_.end()) { auto TrackQueue::PeekNext(std::size_t limit) const
auto item = *it; -> std::vector<database::TrackId> {
if (std::holds_alternative<database::TrackId>(item)) { const std::lock_guard<std::recursive_mutex> lock(mutex_);
ret.push_back(std::get<database::TrackId>(item)); std::vector<database::TrackId> ret;
for (auto it = enqueued_.begin(); it != enqueued_.end() && limit > 0; it++) {
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::TrackId>) {
ret.push_back(arg);
limit--;
} else if constexpr (std::is_same_v<T, database::TrackIterator>) {
auto copy = arg;
while (limit > 0) {
auto next = copy.Next();
if (!next) {
break;
}
ret.push_back(*next);
limit--; limit--;
} else if (std::holds_alternative<std::shared_ptr<playlist::ISource>>(
item)) {
limit -=
std::get<std::shared_ptr<playlist::ISource>>(item)->Peek(limit, &ret);
} else if (std::holds_alternative<
std::shared_ptr<playlist::IResetableSource>>(item)) {
limit -=
std::get<std::shared_ptr<playlist::IResetableSource>>(item)->Peek(
limit, &ret);
} }
it++; }
},
*it);
} }
return ret; return ret;
} }
auto TrackQueue::AddNext(database::TrackId t) -> void { auto TrackQueue::PeekPrevious(std::size_t limit) const
const std::lock_guard<std::mutex> lock(mutex_); -> std::vector<database::TrackId> {
enqueued_.push_front(t); const std::lock_guard<std::recursive_mutex> lock(mutex_);
std::vector<database::TrackId> ret;
QueueUpdate ev{.current_changed = enqueued_.size() < 2}; ret.reserve(limit);
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
auto TrackQueue::AddNext(std::shared_ptr<playlist::ISource> src) -> void {
const std::lock_guard<std::mutex> lock(mutex_);
enqueued_.push_front(src);
QueueUpdate ev{.current_changed = enqueued_.size() < 2};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
auto TrackQueue::IncludeNext(std::shared_ptr<playlist::IResetableSource> src) for (auto it = played_.rbegin(); it != played_.rend(); it++, limit--) {
-> void { ret.push_back(*it);
assert(src.get() != nullptr); }
const std::lock_guard<std::mutex> lock(mutex_);
enqueued_.push_front(src);
QueueUpdate ev{.current_changed = enqueued_.size() < 2}; return ret;
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
} }
auto TrackQueue::AddLast(database::TrackId t) -> void { auto TrackQueue::GetCurrentPosition() const -> size_t {
const std::lock_guard<std::mutex> lock(mutex_); const std::lock_guard<std::recursive_mutex> lock(mutex_);
enqueued_.push_back(t); size_t played = played_.size();
if (current_) {
QueueUpdate ev{.current_changed = enqueued_.size() < 2}; played += 1;
events::Audio().Dispatch(ev); }
events::Ui().Dispatch(ev); return played;
} }
auto TrackQueue::AddLast(std::shared_ptr<playlist::ISource> src) -> void { auto TrackQueue::GetTotalSize() const -> size_t {
const std::lock_guard<std::mutex> lock(mutex_); const std::lock_guard<std::recursive_mutex> lock(mutex_);
enqueued_.push_back(src); size_t total = GetCurrentPosition();
QueueUpdate ev{.current_changed = enqueued_.size() < 2}; for (const auto& item : enqueued_) {
events::Audio().Dispatch(ev); std::visit(
events::Ui().Dispatch(ev); [&](auto&& arg) {
} using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::TrackId>) {
auto TrackQueue::IncludeLast(std::shared_ptr<playlist::IResetableSource> src) total++;
-> void { } else if constexpr (std::is_same_v<T, database::TrackIterator>) {
assert(src.get() != nullptr); total += arg.Size();
const std::lock_guard<std::mutex> lock(mutex_); }
enqueued_.push_back(src); },
item);
}
QueueUpdate ev{.current_changed = enqueued_.size() < 2}; return total;
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
} }
auto TrackQueue::Next() -> void { auto TrackQueue::Insert(Editor& ed, Item i, size_t index) -> void {
const std::lock_guard<std::mutex> lock(mutex_); if (index == 0) {
if (enqueued_.empty()) { enqueued_.insert(enqueued_.begin(), i);
}
// We can't insert halfway through an iterator, so we need to ensure that the
// first `index` items in the queue are reified into track ids.
size_t current_index = 0;
while (current_index < index && current_index < enqueued_.size()) {
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::TrackId>) {
// This item is already a track id; nothing to do.
current_index++;
} else if constexpr (std::is_same_v<T, database::TrackIterator>) {
// This item is an iterator. Push it back one, replacing its old
// index with the next value from it.
auto next = arg.Next();
auto iterator_index = enqueued_.begin() + current_index;
if (!next) {
// Out of values. Remove the iterator completely.
enqueued_.erase(iterator_index);
// Don't increment current_index, since the next item in the
// queue will have been moved down.
} else {
enqueued_.insert(iterator_index, *next);
current_index++;
}
}
},
enqueued_[current_index]);
}
// Double check the previous loop didn't run out of items.
if (index > enqueued_.size()) {
ESP_LOGE(kTag, "insert index was out of bounds");
return; return;
} }
auto item = enqueued_.front(); // Finally, we can now do the actual insertion.
if (std::holds_alternative<database::TrackId>(item)) { enqueued_.insert(enqueued_.begin() + index, i);
played_.push_front(std::get<database::TrackId>(item)); }
enqueued_.pop_front();
}
if (std::holds_alternative<std::shared_ptr<playlist::ISource>>(item)) {
auto src = std::get<std::shared_ptr<playlist::ISource>>(item);
played_.push_front(*src->Current());
if (!src->Advance()) {
enqueued_.pop_front();
}
}
if (std::holds_alternative<std::shared_ptr<playlist::IResetableSource>>(
item)) {
auto src = std::get<std::shared_ptr<playlist::IResetableSource>>(item);
if (!src->Advance()) {
played_.push_back(src);
enqueued_.pop_front();
}
}
QueueUpdate ev{.current_changed = true}; auto TrackQueue::Append(Editor& ed, Item i) -> void {
events::Audio().Dispatch(ev); enqueued_.push_back(i);
events::Ui().Dispatch(ev); if (!current_) {
Next(ed);
}
} }
auto TrackQueue::Previous() -> void { auto TrackQueue::Next(Editor& ed) -> std::optional<database::TrackId> {
const std::lock_guard<std::mutex> lock(mutex_); if (current_) {
if (!enqueued_.empty() && ed.has_current_changed_ = true;
std::holds_alternative<std::shared_ptr<playlist::IResetableSource>>( played_.push_back(*current_);
enqueued_.front())) { }
auto src = std::get<std::shared_ptr<playlist::IResetableSource>>( current_.reset();
while (!current_ && !enqueued_.empty()) {
ed.has_current_changed_ = true;
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::TrackId>) {
current_ = arg;
enqueued_.erase(enqueued_.begin());
} else if constexpr (std::is_same_v<T, database::TrackIterator>) {
auto next = arg.Next();
if (!next) {
enqueued_.erase(enqueued_.begin());
} else {
current_ = *next;
}
}
},
enqueued_.front()); enqueued_.front());
if (src->Previous()) {
QueueUpdate ev{.current_changed = false};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
return;
}
} }
return current_;
}
auto TrackQueue::Previous(Editor& ed) -> std::optional<database::TrackId> {
if (played_.empty()) { if (played_.empty()) {
return; return current_;
} }
ed.has_current_changed_ = true;
auto item = played_.front(); if (current_) {
if (std::holds_alternative<database::TrackId>(item)) { enqueued_.insert(enqueued_.begin(), *current_);
enqueued_.push_front(std::get<database::TrackId>(item));
} else if (std::holds_alternative<
std::shared_ptr<playlist::IResetableSource>>(item)) {
enqueued_.push_front(
std::get<std::shared_ptr<playlist::IResetableSource>>(item));
} }
played_.pop_front(); current_ = played_.back();
played_.pop_back();
QueueUpdate ev{.current_changed = true}; return current_;
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
} }
auto TrackQueue::Clear() -> void { auto TrackQueue::SkipTo(Editor& ed, database::TrackId id) -> void {
const std::lock_guard<std::mutex> lock(mutex_); while ((!current_ || *current_ != id) && !enqueued_.empty()) {
if (enqueued_.empty() && played_.empty()) { Next(ed);
return;
} }
QueueUpdate ev{.current_changed = !enqueued_.empty()};
played_.clear();
enqueued_.clear();
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
} }
auto TrackQueue::Position() -> size_t { auto TrackQueue::Clear(Editor& ed) -> void {
return played_.size() + (enqueued_.empty() ? 0 : 1); ed.has_current_changed_ = current_.has_value();
} current_.reset();
played_.clear();
auto TrackQueue::Size() -> size_t { enqueued_.clear();
return played_.size() + enqueued_.size();
} }
} // namespace audio } // namespace audio

@ -756,6 +756,18 @@ auto Database::dbGetPage(const Continuation& c) -> Result<T>* {
return new Result<T>(std::move(records), next_page, prev_page); return new Result<T>(std::move(records), next_page, prev_page);
} }
auto Database::dbCount(const Continuation& c) -> size_t {
std::unique_ptr<leveldb::Iterator> it{
db_->NewIterator(leveldb::ReadOptions{})};
size_t count = 0;
for (it->Seek({c.start_key.data(), c.start_key.size()});
it->Valid() && it->key().starts_with({c.prefix.data(), c.prefix.size()});
it->Next()) {
count++;
}
return count;
}
template auto Database::dbGetPage<Track>(const Continuation& c) template auto Database::dbGetPage<Track>(const Continuation& c)
-> Result<Track>*; -> Result<Track>*;
template auto Database::dbGetPage<std::pmr::string>(const Continuation& c) template auto Database::dbGetPage<std::pmr::string>(const Continuation& c)
@ -880,6 +892,12 @@ Iterator::Iterator(const Iterator& other)
current_pos_(other.current_pos_), current_pos_(other.current_pos_),
prev_pos_(other.prev_pos_) {} prev_pos_(other.prev_pos_) {}
Iterator& Iterator::operator=(const Iterator& other) {
current_pos_ = other.current_pos_;
prev_pos_ = other.prev_pos_;
return *this;
}
auto Iterator::Next(Callback cb) -> void { auto Iterator::Next(Callback cb) -> void {
auto db = db_.lock(); auto db = db_.lock();
if (!db) { if (!db) {
@ -910,9 +928,6 @@ auto Iterator::NextSync() -> std::optional<IndexRecord> {
if (!db) { if (!db) {
return {}; return {};
} }
return db->worker_task_
->Dispatch<std::optional<IndexRecord>>(
[=]() -> std::optional<IndexRecord> {
std::lock_guard lock{pos_mutex_}; std::lock_guard lock{pos_mutex_};
if (!current_pos_) { if (!current_pos_) {
return {}; return {};
@ -926,8 +941,22 @@ auto Iterator::NextSync() -> std::optional<IndexRecord> {
return {}; return {};
} }
return *res->values()[0]; return *res->values()[0];
}) }
.get();
auto Iterator::PeekSync() -> std::optional<IndexRecord> {
auto db = db_.lock();
if (!db) {
return {};
}
auto pos = current_pos_;
if (!pos) {
return {};
}
std::unique_ptr<Result<IndexRecord>> res{db->dbGetPage<IndexRecord>(*pos)};
if (!res || res->values().empty() || !res->values()[0]) {
return {};
}
return *res->values()[0];
} }
auto Iterator::Prev(Callback cb) -> void { auto Iterator::Prev(Callback cb) -> void {
@ -950,8 +979,82 @@ auto Iterator::Prev(Callback cb) -> void {
}); });
} }
auto Iterator::Size() const -> size_t {
auto db = db_.lock();
if (!db) {
return {};
}
std::optional<Continuation> pos = current_pos_;
if (!pos) {
return 0;
}
return db->dbCount(*pos);
}
auto Iterator::InvokeNull(Callback cb) -> void { auto Iterator::InvokeNull(Callback cb) -> void {
std::invoke(cb, std::optional<IndexRecord>{}); std::invoke(cb, std::optional<IndexRecord>{});
} }
TrackIterator::TrackIterator(const Iterator& it) : db_(it.db_), levels_() {
if (it.current_pos_) {
levels_.push_back(it);
}
NextLeaf();
}
TrackIterator::TrackIterator(const TrackIterator& other)
: db_(other.db_), levels_(other.levels_) {}
TrackIterator& TrackIterator::operator=(TrackIterator&& other) {
levels_ = std::move(other.levels_);
return *this;
}
auto TrackIterator::Next() -> std::optional<TrackId> {
std::optional<TrackId> next{};
while (!next && !levels_.empty()) {
auto next_record = levels_.back().NextSync();
if (!next_record) {
levels_.pop_back();
NextLeaf();
continue;
}
// May still be nullopt_t; hence the loop.
next = next_record->track();
}
return next;
}
auto TrackIterator::Size() const -> size_t {
size_t size = 0;
TrackIterator copy{*this};
while (!copy.levels_.empty()) {
size += copy.levels_.back().Size();
copy.levels_.pop_back();
copy.NextLeaf();
}
return size;
}
auto TrackIterator::NextLeaf() -> void {
while (!levels_.empty()) {
ESP_LOGI(kTag, "check next candidate");
Iterator& candidate = levels_.back();
auto next = candidate.PeekSync();
if (!next) {
ESP_LOGI(kTag, "candidate is empty");
levels_.pop_back();
continue;
}
if (!next->track()) {
ESP_LOGI(kTag, "candidate is a branch");
candidate.NextSync();
levels_.push_back(Iterator{db_, next->Expand(1).value()});
continue;
}
ESP_LOGI(kTag, "candidate is a leaf");
break;
}
}
} // namespace database } // namespace database

@ -12,6 +12,7 @@
#include <future> #include <future>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <stack>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -168,6 +169,8 @@ class Database {
template <typename T> template <typename T>
auto dbGetPage(const Continuation& c) -> Result<T>*; auto dbGetPage(const Continuation& c) -> Result<T>*;
auto dbCount(const Continuation& c) -> size_t;
template <typename T> template <typename T>
auto ParseRecord(const leveldb::Slice& key, const leveldb::Slice& val) auto ParseRecord(const leveldb::Slice& key, const leveldb::Slice& val)
-> std::shared_ptr<T>; -> std::shared_ptr<T>;
@ -193,7 +196,9 @@ class Iterator {
public: public:
Iterator(std::weak_ptr<Database>, const IndexInfo&); Iterator(std::weak_ptr<Database>, const IndexInfo&);
Iterator(std::weak_ptr<Database>, const Continuation&); Iterator(std::weak_ptr<Database>, const Continuation&);
Iterator(const Iterator &); Iterator(const Iterator&);
Iterator& operator=(const Iterator& other);
auto database() const { return db_; } auto database() const { return db_; }
@ -204,8 +209,13 @@ class Iterator {
auto Prev(Callback) -> void; auto Prev(Callback) -> void;
auto PeekSync() -> std::optional<IndexRecord>;
auto Size() const -> size_t;
private: private:
friend class TrackIterator;
auto InvokeNull(Callback) -> void; auto InvokeNull(Callback) -> void;
std::weak_ptr<Database> db_; std::weak_ptr<Database> db_;
@ -215,4 +225,21 @@ class Iterator {
std::optional<Continuation> prev_pos_; std::optional<Continuation> prev_pos_;
}; };
class TrackIterator {
public:
TrackIterator(const Iterator&);
TrackIterator(const TrackIterator&);
TrackIterator& operator=(TrackIterator&& other);
auto Next() -> std::optional<TrackId>;
auto Size() const -> size_t;
private:
auto NextLeaf() -> void;
std::weak_ptr<Database> db_;
std::vector<Iterator> levels_;
};
} // namespace database } // namespace database

@ -22,6 +22,8 @@
#include "property.hpp" #include "property.hpp"
#include "service_locator.hpp" #include "service_locator.hpp"
#include "source.hpp" #include "source.hpp"
#include "track.hpp"
#include "track_queue.hpp"
#include "ui_events.hpp" #include "ui_events.hpp"
namespace lua { namespace lua {
@ -32,11 +34,19 @@ static auto queue_add(lua_State* state) -> int {
Bridge* instance = Bridge::Get(state); Bridge* instance = Bridge::Get(state);
if (lua_isinteger(state, 1)) { if (lua_isinteger(state, 1)) {
instance->services().track_queue().AddLast(luaL_checkinteger(state, 1)); database::TrackId id = luaL_checkinteger(state, 1);
instance->services().bg_worker().Dispatch<void>([=]() {
audio::TrackQueue& queue = instance->services().track_queue();
auto editor = queue.Edit();
queue.Append(editor, id);
});
} else { } else {
database::Iterator* it = db_check_iterator(state, 1); database::Iterator it = *db_check_iterator(state, 1);
instance->services().track_queue().IncludeLast( instance->services().bg_worker().Dispatch<void>([=]() {
std::make_shared<playlist::IteratorSource>(*it)); audio::TrackQueue& queue = instance->services().track_queue();
auto editor = queue.Edit();
queue.Append(editor, database::TrackIterator{it});
});
} }
return 0; return 0;
@ -44,7 +54,9 @@ static auto queue_add(lua_State* state) -> int {
static auto queue_clear(lua_State* state) -> int { static auto queue_clear(lua_State* state) -> int {
Bridge* instance = Bridge::Get(state); Bridge* instance = Bridge::Get(state);
instance->services().track_queue().Clear(); audio::TrackQueue& queue = instance->services().track_queue();
auto editor = queue.Edit();
queue.Clear(editor);
return 0; return 0;
} }

@ -118,9 +118,7 @@ void UiState::react(const audio::PlaybackUpdate& ev) {}
void UiState::react(const audio::QueueUpdate&) { void UiState::react(const audio::QueueUpdate&) {
auto& queue = sServices->track_queue(); auto& queue = sServices->track_queue();
bool had_queue = sPlaybackModel.current_track.get().has_value(); sPlaybackModel.current_track.set(queue.Current());
sPlaybackModel.current_track.set(queue.GetCurrent());
sPlaybackModel.upcoming_tracks.set(queue.GetUpcoming(10));
} }
void UiState::react(const internal::ControlSchemeChanged&) { void UiState::react(const internal::ControlSchemeChanged&) {
@ -283,9 +281,15 @@ void Lua::react(const system_fsm::BatteryStateChanged& ev) {
} }
void Lua::react(const audio::QueueUpdate&) { void Lua::react(const audio::QueueUpdate&) {
sServices->bg_worker().Dispatch<void>([=]() {
auto& queue = sServices->track_queue(); auto& queue = sServices->track_queue();
queue_size_->Update(static_cast<int>(queue.Size())); size_t total_size = queue.GetTotalSize();
queue_position_->Update(static_cast<int>(queue.Position())); size_t current_pos = queue.GetCurrentPosition();
events::Ui().RunOnTask([=]() {
queue_size_->Update(static_cast<int>(total_size));
queue_position_->Update(static_cast<int>(current_pos));
});
});
} }
void Lua::react(const audio::PlaybackStarted& ev) { void Lua::react(const audio::PlaybackStarted& ev) {

Loading…
Cancel
Save