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:
parent
97844417de
commit
72fff6d385
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user