Added the ability for TrackList to make batch queries asynchronously

with built-in debounce. Disabled by default for now because it flickers;
this will be useful in situations where there's high latency.
This commit is contained in:
casey langen 2020-10-09 17:05:59 -07:00
parent 710f237b04
commit 1d69bb4455
5 changed files with 107 additions and 34 deletions

View File

@ -125,7 +125,7 @@ bool TrackList::Delete(size_t index) {
return false;
}
TrackPtr TrackList::Get(size_t index) const {
TrackPtr TrackList::Get(size_t index, bool async) const {
#if 0
/* one at a time */
auto id = this->ids.at(index);
@ -147,14 +147,21 @@ TrackPtr TrackList::Get(size_t index) const {
auto cached = this->GetFromCache(id);
if (cached) { return cached; }
int half = (this->cacheSize - 1) / 2;
int remain = this->cacheSize - 1;
int from = index - half;
int half = ((int) this->cacheSize - 1) / 2;
int remain = (int) this->cacheSize - 1;
int from = (int) index - half;
remain -= from > 0 ? half : (half + from);
int to = index + remain;
this->CacheWindow(std::max(0, from), to);
int to = (int) index + remain;
this->CacheWindow(std::max(0, from), to, async);
cached = this->GetFromCache(id);
if (async && !cached) {
auto loadingTrack = std::make_shared<LibraryTrack>(this->ids[index], this->library);
loadingTrack->SetMetadataState(MetadataState::Loading);
return loadingTrack;
}
return cached;
#endif
}
@ -239,11 +246,14 @@ void TrackList::PruneCache() const {
}
}
void TrackList::CacheWindow(size_t from, size_t to) const {
void TrackList::CacheWindow(size_t from, size_t to, bool async) const {
std::unordered_set<int64_t> idsNotInCache;
for (size_t i = from; i <= std::min(to, this->ids.size() - 1); i++) {
auto id = this->ids[i];
if (this->cacheMap.find(id) == this->cacheMap.end()) {
if (async && currentWindow.Contains(i)) {
continue;
}
idsNotInCache.insert(id);
}
}
@ -252,12 +262,41 @@ void TrackList::CacheWindow(size_t from, size_t to) const {
return;
}
if (async && currentWindow.Valid()) {
this->nextWindow.Set(from, to);
return; /* when this query finishes we'll start up the next one.
this is like a poor man's debounce. or maybe a rich man's debounce? */
}
auto query = std::make_shared<TrackMetadataBatchQuery>(idsNotInCache, this->library);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
auto& result = query->Result();
for (auto& kv : result) {
this->AddToCache(kv.first, kv.second);
if (async) {
currentWindow.Set(from, to);
this->library->Enqueue(query, 0, [this, from, to, query](auto q) {
if (query->GetStatus() == IQuery::Finished) {
auto& result = query->Result();
for (auto& kv : result) {
this->AddToCache(kv.first, kv.second);
}
}
this->currentWindow.Reset();
if (this->nextWindow.Valid()) {
size_t from = nextWindow.from, to = nextWindow.to;
nextWindow.Reset();
this->CacheWindow(from, to, true);
}
this->WindowCached(const_cast<TrackList*>(this), from, to);
});
}
else {
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
auto& result = query->Result();
for (auto& kv : result) {
this->AddToCache(kv.first, kv.second);
}
}
}
}

View File

@ -40,6 +40,8 @@
#include <core/library/track/Track.h>
#include <core/library/ILibrary.h>
#include <sigslot/sigslot.h>
#include <unordered_map>
#include <list>
@ -47,9 +49,12 @@ namespace musik { namespace core {
class TrackList :
public musik::core::sdk::ITrackList,
public std::enable_shared_from_this<TrackList>
public std::enable_shared_from_this<TrackList>,
public sigslot::has_slots<>
{
public:
mutable sigslot::signal3<const TrackList*, size_t, size_t> WindowCached;
TrackList(ILibraryPtr library);
TrackList(TrackList* other);
TrackList(ILibraryPtr library, const int64_t* trackIds, size_t trackIdCount);
@ -73,18 +78,29 @@ namespace musik { namespace core {
void Shuffle();
/* implementation specific */
TrackPtr Get(size_t index) const;
TrackPtr Get(size_t index, bool async = false) const;
TrackPtr GetSync(size_t index) const { return this->Get(index, false); }
TrackPtr GetAsync(size_t index) const { return this->Get(index, true); }
void ClearCache();
void Swap(TrackList& list);
void CopyFrom(const TrackList& from);
void CopyTo(TrackList& to);
void CacheWindow(size_t from, size_t to) const;
void CacheWindow(size_t from, size_t to, bool async) const;
void SetCacheWindowSize(size_t size);
const std::vector<int64_t> GetIds() const { return ids; };
musik::core::sdk::ITrackList* GetSdkValue();
private:
struct QueryWindow {
size_t from{ 0 };
size_t to{ 0 };
bool Contains(size_t i) { return to > 0 && i >= from && i <= to; }
void Reset() { from = to = 0; }
bool Valid() { return to > 0 && to > from; }
void Set(size_t from, size_t to) { this->from = from; this->to = to; }
};
typedef std::list<int64_t> CacheList;
typedef std::pair<TrackPtr, CacheList::iterator> CacheValue;
typedef std::unordered_map<int64_t, CacheValue> CacheMap;
@ -97,6 +113,8 @@ namespace musik { namespace core {
mutable CacheList cacheList;
mutable CacheMap cacheMap;
mutable size_t cacheSize;
mutable QueryWindow currentWindow;
mutable QueryWindow nextWindow;
std::vector<int64_t> ids;
ILibraryPtr library;

View File

@ -52,6 +52,8 @@
#define WINDOW_MESSAGE_SCROLL_TO_PLAYING 1003
static const bool kGetAsync = false;
using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::db;
@ -117,13 +119,17 @@ void TrackListView::SetRowRenderer(TrackRowRenderers::Renderer renderer) {
this->renderer = renderer;
}
void TrackListView::OnTrackListWindowCached(const musik::core::TrackList* track, size_t from, size_t to) {
this->Redraw();
}
void TrackListView::OnQueryCompleted(IQuery* query) {
if (this->query && query == this->query.get()) {
if (this->query->GetStatus() == IQuery::Finished) {
bool hadTracks = this->tracks && this->tracks->Count() > 0;
bool prevQuerySame = this->lastQueryHash == this->query->GetQueryHash();
this->tracks = this->query->GetResult();
this->SetTrackListAndUpateEventHandlers(this->query->GetResult());
this->AdjustTrackListCacheWindowSize();
this->headers.Set(this->query->GetHeaders());
this->lastQueryHash = this->query->GetQueryHash();
@ -153,7 +159,7 @@ std::shared_ptr<TrackList> TrackListView::GetTrackList() {
void TrackListView::SetTrackList(std::shared_ptr<TrackList> trackList) {
if (this->tracks != trackList) {
this->tracks = trackList;
this->SetTrackListAndUpateEventHandlers(trackList);
this->AdjustTrackListCacheWindowSize();
this->ScrollToTop();
this->SelectFirstTrack();
@ -218,6 +224,16 @@ void TrackListView::ScrollToPlaying() {
}
}
void TrackListView::SetTrackListAndUpateEventHandlers(std::shared_ptr<TrackList> trackList) {
if (this->tracks) {
this->tracks->WindowCached.disconnect(this);
}
this->tracks = trackList;
if (this->tracks) {
this->tracks->WindowCached.connect(this, &TrackListView::OnTrackListWindowCached);
}
}
void TrackListView::ProcessMessage(IMessage &message) {
if (message.Type() == WINDOW_MESSAGE_SCROLL_TO_PLAYING) {
this->ScrollToPlaying();
@ -451,11 +467,12 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi
/* the next track at the next logical index will have the album
tracks we're interesetd in. */
auto trackIndex = this->parent.headers.AdapterToTrackListIndex(rawIndex + 1);
TrackPtr track = parent.tracks->Get(trackIndex);
TrackPtr track = parent.tracks->Get(trackIndex, kGetAsync);
if (track) {
std::string album = track->GetString(constants::Track::ALBUM);
std::string album = track->GetMetadataState() == MetadataState::Loaded
? track->GetString(constants::Track::ALBUM) : "-";
if (!album.size()) {
album = _TSTR("tracklist_unknown_album");
}
@ -474,19 +491,7 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi
}
size_t trackIndex = this->parent.headers.AdapterToTrackListIndex(rawIndex);
TrackPtr track = parent.tracks->Get(trackIndex);
if (!track ||
track->GetMetadataState() == MetadataState::Missing ||
track->GetMetadataState() == MetadataState::NotLoaded)
{
auto entry = std::shared_ptr<SingleLineEntry>(new SingleLineEntry("track missing"));
entry->SetAttrs(selected ? Color::ListItemHighlighted : Color::TextError);
return entry;
}
else if (track->GetMetadataState() == MetadataState::Loading) {
return std::shared_ptr<SingleLineEntry>(new SingleLineEntry(" -"));
}
TrackPtr track = parent.tracks->Get(trackIndex, kGetAsync);
Color attrs = Color::Default;
@ -510,6 +515,12 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi
}
}
if (!track || track->GetMetadataState() != MetadataState::Loaded) {
auto entry = std::shared_ptr<SingleLineEntry>(new SingleLineEntry(" -"));
entry->SetAttrs(attrs);
return entry;
}
std::string text = parent.renderer(
track, rawIndex, this->GetWidth(), parent.trackNumType);

View File

@ -97,9 +97,14 @@ namespace musik {
protected:
virtual cursespp::IScrollAdapter& GetScrollAdapter();
void SetTrackListAndUpateEventHandlers(
std::shared_ptr<musik::core::TrackList> trackList);
virtual void OnEntryActivated(size_t index);
virtual void OnEntryContextMenu(size_t index);
virtual void OnDimensionsChanged();
virtual void OnTrackListWindowCached(
const musik::core::TrackList* track, size_t from, size_t to);
void OnQueryCompleted(musik::core::db::IQuery* query);
void ShowContextMenu();

View File

@ -79,7 +79,7 @@ static inline void DrawCursor(IInput* input) {
WINDOW* content = inputWindow->GetContent();
curs_set(1);
wtimeout(content, IDLE_TIMEOUT_MS);
wmove(content, 0, input->Position());
wmove(content, 0, (int) input->Position());
return;
}
}