summaryrefslogtreecommitdiff
path: root/src/playlist
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-12-07 16:57:05 +1100
committerjacqueline <me@jacqueline.id.au>2023-12-07 17:00:30 +1100
commit3f7f199cb940c8d5f6d48f77fd59971adffe49ef (patch)
treeaa22162e46c5e9ccce4c7ee8537b493f437664d9 /src/playlist
parent009f69c929eb1d1b65d75b0937fbf3b8de5d9148 (diff)
downloadtangara-fw-3f7f199cb940c8d5f6d48f77fd59971adffe49ef.tar.gz
Remove pre-iterator concepts
- No more IndexRecord/Result/dbGetPage nonsense - Queue is just track ids - i am so tired and have so much to do
Diffstat (limited to 'src/playlist')
-rw-r--r--src/playlist/CMakeLists.txt10
-rw-r--r--src/playlist/include/shuffler.hpp68
-rw-r--r--src/playlist/include/source.hpp157
-rw-r--r--src/playlist/shuffler.cpp166
-rw-r--r--src/playlist/source.cpp360
5 files changed, 0 insertions, 761 deletions
diff --git a/src/playlist/CMakeLists.txt b/src/playlist/CMakeLists.txt
deleted file mode 100644
index 6c08dd5a..00000000
--- a/src/playlist/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2023 jacqueline <me@jacqueline.id.au>
-#
-# SPDX-License-Identifier: GPL-3.0-only
-
-idf_component_register(
- SRCS "source.cpp" "shuffler.cpp"
- INCLUDE_DIRS "include"
- REQUIRES "database" "util")
-
-target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/playlist/include/shuffler.hpp b/src/playlist/include/shuffler.hpp
deleted file mode 100644
index affc6301..00000000
--- a/src/playlist/include/shuffler.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <variant>
-#include <vector>
-
-#include "bloom_filter.hpp"
-#include "database.hpp"
-#include "future_fetcher.hpp"
-#include "random.hpp"
-#include "source.hpp"
-#include "track.hpp"
-
-namespace playlist {
-
-/*
- * A source composes of other sources and/or specific extra tracks. Supports
- * iteration over its contents in a random order.
- */
-class Shuffler : public ISource {
- public:
- static auto Create() -> Shuffler*;
-
- explicit Shuffler(
- util::IRandom* random,
- std::unique_ptr<util::BloomFilter<database::TrackId>> filter);
-
- auto Current() -> std::optional<database::TrackId> override;
- auto Advance() -> std::optional<database::TrackId> override;
- auto Peek(std::size_t, std::vector<database::TrackId>*)
- -> std::size_t override;
-
- typedef std::variant<database::TrackId, std::shared_ptr<IResetableSource>>
- Item;
- auto Add(Item) -> void;
-
- /*
- * Returns the enqueued items, starting from the current item, in their
- * original insertion order.
- */
- auto Unshuffle() -> std::vector<Item>;
-
- // Not copyable or movable.
-
- Shuffler(const Shuffler&) = delete;
- Shuffler& operator=(const Shuffler&) = delete;
-
- private:
- auto RefillBuffer() -> void;
-
- util::IRandom* random_;
-
- std::unique_ptr<util::BloomFilter<database::TrackId>> already_played_;
- bool out_of_items_;
-
- std::deque<Item> ordered_items_;
- std::deque<database::TrackId> shuffled_items_buffer_;
-};
-
-} // namespace playlist
diff --git a/src/playlist/include/source.hpp b/src/playlist/include/source.hpp
deleted file mode 100644
index ce12faf3..00000000
--- a/src/playlist/include/source.hpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <stack>
-#include <variant>
-#include <vector>
-
-#include "bloom_filter.hpp"
-#include "database.hpp"
-#include "future_fetcher.hpp"
-#include "random.hpp"
-#include "track.hpp"
-
-namespace playlist {
-
-/*
- * Stateful interface for iterating over a collection of tracks by id.
- */
-class ISource {
- public:
- virtual ~ISource() {}
-
- virtual auto Current() -> std::optional<database::TrackId> = 0;
-
- /*
- * Discards the current track id and continues to the next in this source.
- * Returns the new current track id.
- */
- virtual auto Advance() -> std::optional<database::TrackId> = 0;
-
- /*
- * Repeatedly advances until a track with the given id is the current track.
- * Returns false if this source ran out of tracks before the requested id
- * was encounted, true otherwise.
- */
- virtual auto AdvanceTo(database::TrackId id) -> bool {
- for (auto t = Current(); t.has_value(); t = Advance()) {
- if (*t == id) {
- return true;
- }
- }
- return false;
- }
-
- /*
- * Places the next n tracks into the given vector, in order. Does not change
- * the value returned by Current().
- */
- virtual auto Peek(std::size_t n, std::vector<database::TrackId>*)
- -> std::size_t = 0;
-};
-
-/*
- * A Source that supports restarting iteration from its original initial
- * value.
- */
-class IResetableSource : public ISource {
- public:
- virtual ~IResetableSource() {}
-
- virtual auto Previous() -> std::optional<database::TrackId> = 0;
-
- /*
- * Restarts iteration from this source's initial value.
- */
- virtual auto Reset() -> void = 0;
-};
-
-class IteratorSource : public IResetableSource {
- public:
- IteratorSource(const database::Iterator&);
-
- auto Current() -> std::optional<database::TrackId> override;
- auto Advance() -> std::optional<database::TrackId> override;
- auto Peek(std::size_t n, std::vector<database::TrackId>*)
- -> std::size_t override;
-
- auto Previous() -> std::optional<database::TrackId> override;
- auto Reset() -> void override;
-
- private:
- const database::Iterator& start_;
- std::optional<database::TrackId> current_;
- std::stack<database::Iterator, std::vector<database::Iterator>> next_;
-};
-
-auto CreateSourceFromResults(
- std::weak_ptr<database::Database>,
- std::shared_ptr<database::Result<database::IndexRecord>>)
- -> std::shared_ptr<IResetableSource>;
-
-class IndexRecordSource : public IResetableSource {
- public:
- IndexRecordSource(std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>>);
-
- IndexRecordSource(std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>>,
- std::size_t,
- std::shared_ptr<database::Result<database::IndexRecord>>,
- std::size_t);
-
- auto Current() -> std::optional<database::TrackId> override;
- auto Advance() -> std::optional<database::TrackId> override;
- auto Peek(std::size_t n, std::vector<database::TrackId>*)
- -> std::size_t override;
-
- auto Previous() -> std::optional<database::TrackId> override;
- auto Reset() -> void override;
-
- private:
- std::weak_ptr<database::Database> db_;
-
- std::shared_ptr<database::Result<database::IndexRecord>> initial_page_;
- ssize_t initial_item_;
-
- std::shared_ptr<database::Result<database::IndexRecord>> current_page_;
- ssize_t current_item_;
-};
-
-class NestedSource : public IResetableSource {
- public:
- NestedSource(std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>>);
-
- auto Current() -> std::optional<database::TrackId> override;
- auto Advance() -> std::optional<database::TrackId> override;
- auto Peek(std::size_t n, std::vector<database::TrackId>*)
- -> std::size_t override;
-
- auto Previous() -> std::optional<database::TrackId> override;
- auto Reset() -> void override;
-
- private:
- auto CreateChild(std::shared_ptr<database::IndexRecord> page)
- -> std::shared_ptr<IResetableSource>;
-
- std::weak_ptr<database::Database> db_;
-
- std::shared_ptr<database::Result<database::IndexRecord>> initial_page_;
- ssize_t initial_item_;
-
- std::shared_ptr<database::Result<database::IndexRecord>> current_page_;
- ssize_t current_item_;
-
- std::shared_ptr<IResetableSource> current_child_;
-};
-
-} // namespace playlist
diff --git a/src/playlist/shuffler.cpp b/src/playlist/shuffler.cpp
deleted file mode 100644
index b1c92335..00000000
--- a/src/playlist/shuffler.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "shuffler.hpp"
-
-#include <algorithm>
-#include <functional>
-#include <memory>
-#include <set>
-#include <variant>
-
-#include "bloom_filter.hpp"
-#include "database.hpp"
-#include "komihash.h"
-#include "random.hpp"
-#include "track.hpp"
-
-static constexpr std::size_t kShufflerBufferSize = 32;
-
-namespace playlist {
-
-auto Shuffler::Create() -> Shuffler* {
- return new Shuffler(util::sRandom,
- std::make_unique<util::BloomFilter<database::TrackId>>(
- [](database::TrackId id) {
- return komihash(&id, sizeof(database::TrackId), 0);
- }));
-}
-
-Shuffler::Shuffler(util::IRandom* random,
- std::unique_ptr<util::BloomFilter<database::TrackId>> filter)
- : random_(random), already_played_(std::move(filter)) {}
-
-auto Shuffler::Current() -> std::optional<database::TrackId> {
- if (shuffled_items_buffer_.empty()) {
- return {};
- }
- return shuffled_items_buffer_.front();
-}
-
-auto Shuffler::Advance() -> std::optional<database::TrackId> {
- if (shuffled_items_buffer_.empty() && !out_of_items_) {
- RefillBuffer();
- }
-
- auto res = Current();
- if (res) {
- // Mark tracks off in the bloom filter only *after* they've been advanced
- // past. This gives us the most flexibility for reshuffling when adding new
- // items.
- already_played_->Insert(*res);
- shuffled_items_buffer_.pop_front();
- }
- return res;
-}
-
-auto Shuffler::Peek(std::size_t num, std::vector<database::TrackId>* out)
- -> std::size_t {
- if (shuffled_items_buffer_.size() < num) {
- RefillBuffer();
- }
- for (int i = 0; i < num; i++) {
- if (i >= shuffled_items_buffer_.size()) {
- // We must be out of data, since the buffer didn't fill up.
- return i;
- }
- out->push_back(shuffled_items_buffer_.at(i));
- }
- return num;
-}
-
-auto Shuffler::Add(Item item) -> void {
- ordered_items_.push_back(item);
- out_of_items_ = false;
-
- // Empty out the buffer of already shuffled items, since we will need to
- // shuffle again in order to incorporate the newly added item(s). We keep the
- // current item however because we wouldn't want Add() to change the value of
- // Current() unless we're completely out of items.
- if (shuffled_items_buffer_.size() > 1) {
- shuffled_items_buffer_.erase(shuffled_items_buffer_.begin() + 1,
- shuffled_items_buffer_.end());
- }
- RefillBuffer();
-}
-
-auto Shuffler::Unshuffle() -> std::vector<Item> {
- std::vector<Item> ret;
- database::TrackId current = shuffled_items_buffer_.front();
- bool has_found_current = false;
-
- for (const Item& item : ordered_items_) {
- if (!has_found_current) {
- // TODO(jacqueline): *Should* this include previous items? What is the
- // 'previous' button meant to do after unshuffling?
- if (std::holds_alternative<database::TrackId>(item)) {
- has_found_current = current == std::get<database::TrackId>(item);
- } else {
- auto source = std::get<std::shared_ptr<IResetableSource>>(item);
- source->Reset();
- has_found_current =
- std::get<std::shared_ptr<IResetableSource>>(item)->AdvanceTo(
- current);
- }
- } else {
- ret.push_back(item);
- }
- }
-
- return ret;
-}
-
-auto Shuffler::RefillBuffer() -> void {
- // Don't waste time iterating if we know there's nothing new.
- if (out_of_items_) {
- return;
- }
-
- int num_to_sample = kShufflerBufferSize - shuffled_items_buffer_.size();
- int resovoir_offset = shuffled_items_buffer_.size();
-
- std::set<database::TrackId> in_buffer;
- for (const database::TrackId& id : shuffled_items_buffer_) {
- in_buffer.insert(id);
- }
-
- uint32_t i = 0;
- auto consider_item = [&, this](const database::TrackId& item) {
- if (already_played_->Contains(item) || in_buffer.contains(item)) {
- return;
- }
- if (i < num_to_sample) {
- shuffled_items_buffer_.push_back(item);
- } else {
- uint32_t index_to_replace = random_->RangeInclusive(0, i);
- if (index_to_replace < num_to_sample) {
- shuffled_items_buffer_[resovoir_offset + index_to_replace] = item;
- }
- }
- i++;
- };
-
- for (const Item& item : ordered_items_) {
- if (std::holds_alternative<database::TrackId>(item)) {
- std::invoke(consider_item, std::get<database::TrackId>(item));
- } else {
- auto source = std::get<std::shared_ptr<IResetableSource>>(item);
- source->Reset();
- while (source->Advance()) {
- std::invoke(consider_item, *source->Current());
- }
- }
- }
-
- out_of_items_ = i > num_to_sample;
- // We've now got a random *selection*, but the order might be predictable
- // (e.g. if there were only `num_to_sample` new items). Do a final in-memory
- // shuffle.
- std::random_shuffle(shuffled_items_buffer_.begin() + resovoir_offset,
- shuffled_items_buffer_.end());
-}
-
-} // namespace playlist
diff --git a/src/playlist/source.cpp b/src/playlist/source.cpp
deleted file mode 100644
index 2540c3fb..00000000
--- a/src/playlist/source.cpp
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "source.hpp"
-
-#include <algorithm>
-#include <functional>
-#include <memory>
-#include <set>
-#include <variant>
-
-#include "esp_log.h"
-
-#include "bloom_filter.hpp"
-#include "database.hpp"
-#include "komihash.h"
-#include "random.hpp"
-#include "track.hpp"
-
-namespace playlist {
-
-[[maybe_unused]] static constexpr char kTag[] = "queue_src";
-
-IteratorSource::IteratorSource(const database::Iterator& it)
- : start_(it), current_(), next_() {
- Reset();
- Advance();
-}
-
-auto IteratorSource::Current() -> std::optional<database::TrackId> {
- return current_;
-}
-
-auto IteratorSource::Advance() -> std::optional<database::TrackId> {
- ESP_LOGI(kTag, "advancing");
- while (!next_.empty()) {
- auto next = next_.top().NextSync();
- if (!next) {
- ESP_LOGI(kTag, "top was empty");
- next_.pop();
- continue;
- }
- if (next->track()) {
- ESP_LOGI(kTag, "top held track %lu", next->track().value_or(0));
- current_ = next->track();
- return current_;
- }
- ESP_LOGI(kTag, "top held records");
- next_.push(database::Iterator(start_.database(), next->Expand(1).value()));
- }
- ESP_LOGI(kTag, "exhausted");
- return {};
-}
-
-auto IteratorSource::Peek(std::size_t n, std::vector<database::TrackId>*)
- -> std::size_t {
- return 0;
-}
-
-auto IteratorSource::Previous() -> std::optional<database::TrackId> {
- return {};
-}
-
-auto IteratorSource::Reset() -> void {
- while (!next_.empty()) {
- next_.pop();
- }
- next_.push(start_);
-}
-
-auto CreateSourceFromResults(
- std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>> results)
- -> std::shared_ptr<IResetableSource> {
- if (results->values()[0]->track()) {
- return std::make_shared<IndexRecordSource>(db, results);
- } else {
- return std::make_shared<NestedSource>(db, results);
- }
-}
-
-IndexRecordSource::IndexRecordSource(
- std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>> initial)
- : db_(db),
- initial_page_(initial),
- initial_item_(0),
- current_page_(initial_page_),
- current_item_(initial_item_) {}
-
-IndexRecordSource::IndexRecordSource(
- std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>> initial,
- std::size_t initial_index,
- std::shared_ptr<database::Result<database::IndexRecord>> current,
- std::size_t current_index)
- : db_(db),
- initial_page_(initial),
- initial_item_(initial_index),
- current_page_(current),
- current_item_(current_index) {}
-
-auto IndexRecordSource::Current() -> std::optional<database::TrackId> {
- if (current_page_->values().size() <= current_item_) {
- return {};
- }
- if (current_page_ == initial_page_ && current_item_ < initial_item_) {
- return {};
- }
-
- return current_page_->values().at(current_item_)->track();
-}
-
-auto IndexRecordSource::Advance() -> std::optional<database::TrackId> {
- current_item_++;
- if (current_item_ >= current_page_->values().size()) {
- auto next_page = current_page_->next_page();
- if (!next_page) {
- current_item_--;
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return {};
- }
-
- current_page_.reset(db->GetPage<database::IndexRecord>(&*next_page).get());
- current_item_ = 0;
- }
-
- return Current();
-}
-
-auto IndexRecordSource::Previous() -> std::optional<database::TrackId> {
- if (current_page_ == initial_page_ && current_item_ <= initial_item_) {
- return {};
- }
-
- current_item_--;
- if (current_item_ < 0) {
- auto prev_page = current_page_->prev_page();
- if (!prev_page) {
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return {};
- }
-
- current_page_.reset(db->GetPage<database::IndexRecord>(&*prev_page).get());
- current_item_ = current_page_->values().size() - 1;
- }
-
- return Current();
-}
-
-auto IndexRecordSource::Peek(std::size_t n, std::vector<database::TrackId>* out)
- -> std::size_t {
- if (current_page_->values().size() <= current_item_) {
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return 0;
- }
-
- std::size_t items_added = 0;
-
- std::shared_ptr<database::Result<database::IndexRecord>> working_page =
- current_page_;
- std::size_t working_item = current_item_ + 1;
-
- while (n > 0) {
- if (working_item >= working_page->values().size()) {
- auto next_page = current_page_->next_page();
- if (!next_page) {
- break;
- }
- // TODO(jacqueline): It would probably be a good idea to hold onto these
- // peeked pages, to avoid needing to look them up again later.
- working_page.reset(db->GetPage<database::IndexRecord>(&*next_page).get());
- working_item = 0;
- }
-
- auto record = working_page->values().at(working_item);
- if (record->track()) {
- out->push_back(record->track().value());
- n--;
- items_added++;
- }
- working_item++;
- }
-
- return items_added;
-}
-
-auto IndexRecordSource::Reset() -> void {
- current_page_ = initial_page_;
- current_item_ = initial_item_;
-}
-
-NestedSource::NestedSource(
- std::weak_ptr<database::Database> db,
- std::shared_ptr<database::Result<database::IndexRecord>> initial)
- : db_(db),
- initial_page_(initial),
- initial_item_(0),
- current_page_(initial_page_),
- current_item_(initial_item_),
- current_child_(CreateChild(initial->values()[0])) {}
-
-auto NestedSource::Current() -> std::optional<database::TrackId> {
- if (current_child_) {
- return current_child_->Current();
- }
- return {};
-}
-
-auto NestedSource::Advance() -> std::optional<database::TrackId> {
- if (!current_child_) {
- return {};
- }
-
- auto child_next = current_child_->Advance();
- if (child_next) {
- return child_next;
- }
- // Our current child has run out of tracks. Move on to the next child.
- current_item_++;
- current_child_.reset();
-
- if (current_item_ >= current_page_->values().size()) {
- // We're even out of items in this page!
- auto next_page = current_page_->next_page();
- if (!next_page) {
- current_item_--;
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return {};
- }
-
- current_page_.reset(db->GetPage<database::IndexRecord>(&*next_page).get());
- current_item_ = 0;
- }
- current_child_ = CreateChild(current_page_->values()[current_item_]);
-
- return Current();
-}
-
-auto NestedSource::Previous() -> std::optional<database::TrackId> {
- if (current_page_ == initial_page_ && current_item_ <= initial_item_) {
- return {};
- }
-
- current_item_--;
- current_child_.reset();
-
- if (current_item_ < 0) {
- auto prev_page = current_page_->prev_page();
- if (!prev_page) {
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return {};
- }
-
- current_page_.reset(db->GetPage<database::IndexRecord>(&*prev_page).get());
- current_item_ = current_page_->values().size() - 1;
- }
- current_child_ = CreateChild(current_page_->values()[current_item_]);
-
- return Current();
-}
-
-auto NestedSource::Peek(std::size_t n, std::vector<database::TrackId>* out)
- -> std::size_t {
- if (current_page_->values().size() <= current_item_) {
- return {};
- }
-
- auto db = db_.lock();
- if (!db) {
- return 0;
- }
-
- std::size_t items_added = 0;
-
- std::shared_ptr<database::Result<database::IndexRecord>> working_page =
- current_page_;
- std::size_t working_item = current_item_;
- std::shared_ptr<IResetableSource> working_child = current_child_;
-
- while (working_child) {
- auto res = working_child->Peek(n, out);
- n -= res;
- items_added += res;
-
- if (n == 0) {
- break;
- } else {
- working_item++;
- if (working_item < working_page->values().size()) {
- working_child = CreateChild(working_page->values()[working_item]);
- } else {
- auto next_page = current_page_->next_page();
- if (!next_page) {
- break;
- }
- working_page.reset(
- db->GetPage<database::IndexRecord>(&*next_page).get());
- working_item = 0;
- working_child = CreateChild(working_page->values()[0]);
- }
- }
- }
-
- return items_added;
-}
-
-auto NestedSource::Reset() -> void {
- current_page_ = initial_page_;
- current_item_ = initial_item_;
- current_child_ = CreateChild(initial_page_->values()[initial_item_]);
-}
-
-auto NestedSource::CreateChild(std::shared_ptr<database::IndexRecord> record)
- -> std::shared_ptr<IResetableSource> {
- auto cont = record->Expand(10);
- if (!cont) {
- return {};
- }
- auto db = db_.lock();
- if (!db) {
- return {};
- }
- std::shared_ptr<database::Result<database::IndexRecord>> next_level{
- db->GetPage<database::IndexRecord>(&*cont).get()};
- if (!next_level) {
- return {};
- }
- auto next_level_record = next_level->values()[0];
- if (next_level_record->track()) {
- return std::make_shared<IndexRecordSource>(db_, next_level);
- } else {
- return std::make_shared<NestedSource>(db_, next_level);
- }
-}
-
-} // namespace playlist