HaikuDepot: async listing of packages in PackageListView

* The UI became unresponsive while the PackageListView was filled
  with all the packages. This was especially apparent when using
  the search function, which clears and refills the view with every
  typed character.

* Add a new worker thread with the task of asynchronously filling the
  PackageListView. When a new data model is adopted, we hand the
  thread a copy of the visible package list. The worker thread then
  goes through the list and sends the package infos via BMessage back
  to the MainWindow, in batches of 20 infos per message. When the 20
  entries were added, it acknowledges this to the worker thread which
  will send the next 20 infos (so UI messages can get in between,
  keeping it responsive). The lists also get a unique ID so that
  model changes while the list is populating will invalidate
  previously sent messages (and cause the worker thread to cancel
  processing the outdated list).

* Search is much nicer to use this way, staying responsive and
  listing packages while typing. Still not perfect since the
  PackageListView is still cleared and refilled each time a character
  is typed, instead of just narrowing down the already displayed
  package set. But that's to be improved on another day...

* Same applies to filling FeaturedPackagesView btw
This commit is contained in:
Julian Harnath 2017-11-24 13:41:57 +01:00
parent 97844417de
commit 72fff6d385
4 changed files with 188 additions and 23 deletions

View File

@ -348,12 +348,12 @@ FeaturedPackagesView::FeaturedPackagesView()
BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
SetLayout(layout);
ScrollableGroupView* containerView = new ScrollableGroupView();
containerView->SetViewUIColor(B_LIST_BACKGROUND_COLOR);
fPackageListLayout = containerView->GroupLayout();
fContainerView = new ScrollableGroupView();
fContainerView->SetViewUIColor(B_LIST_BACKGROUND_COLOR);
fPackageListLayout = fContainerView->GroupLayout();
BScrollView* scrollView = new BScrollView(
"featured packages scroll view", containerView,
"featured packages scroll view", fContainerView,
0, false, true, B_FANCY_BORDER);
BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL);
@ -433,7 +433,8 @@ FeaturedPackagesView::Clear()
void
FeaturedPackagesView::SelectPackage(const PackageInfoRef& package)
FeaturedPackagesView::SelectPackage(const PackageInfoRef& package,
bool scrollToEntry)
{
BString selectedName;
if (package.Get() != NULL)
@ -445,7 +446,16 @@ FeaturedPackagesView::SelectPackage(const PackageInfoRef& package)
break;
BString name = view->PackageName();
view->SetSelected(name == selectedName);
bool match = (name == selectedName);
view->SetSelected(match);
if (match && scrollToEntry) {
// Scroll the view so that the package entry shows up in the middle
fContainerView->ScrollTo(0,
view->Frame().top
- fContainerView->Bounds().Height() / 2
+ view->Bounds().Height() / 2);
}
}
}

View File

@ -13,6 +13,7 @@
class BGroupLayout;
class ScrollableGroupView;
class FeaturedPackagesView : public BView {
@ -24,12 +25,14 @@ public:
void RemovePackage(const PackageInfoRef& package);
void Clear();
void SelectPackage(const PackageInfoRef& package);
void SelectPackage(const PackageInfoRef& package,
bool scrollToEntry = false);
static void CleanupIcons();
private:
BGroupLayout* fPackageListLayout;
ScrollableGroupView* fContainerView;
};

View File

@ -221,6 +221,8 @@ MainWindow::MainWindow(const BMessage& settings)
_RestoreUserName(settings);
_RestoreWindowFrame(settings);
atomic_set(&fPackagesToShowListID, 0);
// start worker threads
BPackageRoster().StartWatching(this,
B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
@ -282,6 +284,11 @@ MainWindow::~MainWindow()
if (fPopulatePackageWorker >= 0)
wait_for_thread(fPopulatePackageWorker, NULL);
delete_sem(fNewPackagesToShowSem);
delete_sem(fShowPackagesAcknowledgeSem);
if (fShowPackagesWorker >= 0)
wait_for_thread(fShowPackagesWorker, NULL);
if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
fScreenshotWindow->Quit();
}
@ -521,6 +528,66 @@ MainWindow::MessageReceived(BMessage* message)
fWorkStatusView->SetIdle();
break;
case MSG_ADD_VISIBLE_PACKAGES:
{
struct SemaphoreReleaser {
SemaphoreReleaser(sem_id semaphore)
:
fSemaphore(semaphore)
{ }
~SemaphoreReleaser() { release_sem(fSemaphore); }
sem_id fSemaphore;
};
// Make sure acknowledge semaphore is released even on error,
// so the worker thread won't be blocked
SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
int32 numPackages = 0;
type_code unused;
status_t status = message->GetInfo("package_ref", &unused,
&numPackages);
if (status != B_OK || numPackages == 0)
break;
int32 listID = 0;
status = message->FindInt32("list_id", &listID);
if (status != B_OK)
break;
if (listID != atomic_get(&fPackagesToShowListID)) {
// list is outdated, ignore
break;
}
for (int i = 0; i < numPackages; i++) {
PackageInfo* packageRaw = NULL;
status = message->FindPointer("package_ref", i,
(void**)&packageRaw);
if (status != B_OK)
break;
PackageInfoRef package(packageRaw, true);
fPackageListView->AddPackage(package);
if (package->IsProminent())
fFeaturedPackagesView->AddPackage(package);
}
break;
}
case MSG_UPDATE_SELECTED_PACKAGE:
{
const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
fFeaturedPackagesView->SelectPackage(selectedPackage, true);
fPackageListView->SelectPackage(selectedPackage);
AutoLocker<BLocker> modelLocker(fModel.Lock());
if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
fPackageInfoView->Clear();
break;
}
default:
BWindow::MessageReceived(message);
break;
@ -727,6 +794,16 @@ MainWindow::_InitWorkerThreads()
resume_thread(fPopulatePackageWorker);
} else
fPopulatePackageWorker = -1;
fNewPackagesToShowSem = create_sem(0, "ShowPackages");
fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
"Good news everyone", B_NORMAL_PRIORITY, this);
if (fShowPackagesWorker >= 0)
resume_thread(fShowPackagesWorker);
} else
fShowPackagesWorker = -1;
}
@ -735,17 +812,17 @@ MainWindow::_AdoptModel()
{
fVisiblePackages = fModel.CreatePackageList();
{
AutoLocker<BLocker> modelLocker(fModel.Lock());
AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
fPackagesToShowList = fVisiblePackages;
atomic_add(&fPackagesToShowListID, 1);
}
fFeaturedPackagesView->Clear();
fPackageListView->Clear();
for (int32 i = 0; i < fVisiblePackages.CountItems(); i++) {
BAutolock locker(fModel.Lock());
const PackageInfoRef& package = fVisiblePackages.ItemAtFast(i);
fPackageListView->AddPackage(package);
if (package->IsProminent())
fFeaturedPackagesView->AddPackage(package);
}
release_sem(fNewPackagesToShowSem);
BAutolock locker(fModel.Lock());
fShowFeaturedPackagesItem->SetMarked(fModel.ShowFeaturedPackages());
@ -759,14 +836,6 @@ MainWindow::_AdoptModel()
fListLayout->SetVisibleItem((int32)0);
else
fListLayout->SetVisibleItem((int32)1);
// Maintain selection
const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
fFeaturedPackagesView->SelectPackage(selectedPackage);
fPackageListView->SelectPackage(selectedPackage);
if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
fPackageInfoView->Clear();
}
@ -1203,6 +1272,78 @@ MainWindow::_PopulatePackageWorker(void* arg)
}
/* static */ status_t
MainWindow::_PackagesToShowWorker(void* arg)
{
MainWindow* window = reinterpret_cast<MainWindow*>(arg);
while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
PackageList packageList;
int32 listID = 0;
{
AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
packageList = window->fPackagesToShowList;
listID = atomic_get(&window->fPackagesToShowListID);
window->fPackagesToShowList.Clear();
}
// Add packages to list views in batches of kPackagesPerUpdate so we
// don't block the window thread for long with each iteration
enum {
kPackagesPerUpdate = 20
};
uint32 packagesInMessage = 0;
BMessage message(MSG_ADD_VISIBLE_PACKAGES);
BMessenger messenger(window);
bool listIsOutdated = false;
for (int i = 0; i < packageList.CountItems(); i++) {
const PackageInfoRef& package = packageList.ItemAtFast(i);
if (packagesInMessage >= kPackagesPerUpdate) {
if (listID != atomic_get(&window->fPackagesToShowListID)) {
// The model was changed again in the meantime, and thus
// our package list isn't current anymore. Send no further
// messags based on this list and go back to start.
listIsOutdated = true;
break;
}
message.AddInt32("list_id", listID);
messenger.SendMessage(&message);
message.MakeEmpty();
packagesInMessage = 0;
// Don't spam the window's message queue, which would make it
// unresponsive (i.e. allows UI messages to get in between our
// messages). When it has processed the message we just sent,
// it will let us know by releasing the semaphore.
acquire_sem(window->fShowPackagesAcknowledgeSem);
}
package->AcquireReference();
message.AddPointer("package_ref", package.Get());
packagesInMessage++;
}
if (listIsOutdated)
continue;
// Send remaining package infos, if any, which didn't make it into
// the last message (count < kPackagesPerUpdate)
if (packagesInMessage > 0) {
message.AddInt32("list_id", listID);
messenger.SendMessage(&message);
acquire_sem(window->fShowPackagesAcknowledgeSem);
}
// Update selected package in list views
messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
}
return 0;
}
void
MainWindow::_NotifyUser(const char* title, const char* message)
{

View File

@ -33,6 +33,8 @@ enum {
MSG_PACKAGE_SELECTED = 'pkgs',
MSG_PACKAGE_WORKER_BUSY = 'pkwb',
MSG_PACKAGE_WORKER_IDLE = 'pkwi',
MSG_ADD_VISIBLE_PACKAGES = 'avpk',
MSG_UPDATE_SELECTED_PACKAGE = 'uspk',
};
@ -82,6 +84,7 @@ private:
static status_t _RefreshModelThreadWorker(void* arg);
static status_t _PackageActionWorker(void* arg);
static status_t _PopulatePackageWorker(void* arg);
static status_t _PackagesToShowWorker(void* arg);
void _NotifyUser(const char* title,
const char* message);
@ -130,6 +133,14 @@ private:
PackageInfoRef fPackageToPopulate;
BLocker fPackageToPopulateLock;
sem_id fPackageToPopulateSem;
thread_id fShowPackagesWorker;
PackageList fPackagesToShowList;
int32 fPackagesToShowListID;
// atomic, counted up whenever fPackagesToShowList is refilled
BLocker fPackagesToShowListLock;
sem_id fNewPackagesToShowSem;
sem_id fShowPackagesAcknowledgeSem;
};
#endif // MAIN_WINDOW_H