Skip to content

Commit

Permalink
#5127: Introduce base class for the threaded population helper. Hide …
Browse files Browse the repository at this point in the history
…the wxThread inheritance from client code.
  • Loading branch information
codereader committed Jan 2, 2021
1 parent 50f4751 commit 4b479e6
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 123 deletions.
99 changes: 99 additions & 0 deletions libs/wxutil/dataview/ResourceTreePopulator.h
@@ -0,0 +1,99 @@
#pragma once

#include <wx/thread.h>
#include <wx/event.h>
#include "TreeModel.h"

namespace wxutil
{

/**
* Threaded helper class to load data into a ResourceTreeView.
* Subclasses need to implement the abstract members to add
* the needed insertion or sorting logic.
*/
class ResourceTreePopulator :
protected wxThread
{
private:
// The event handler to notify on completion
wxEvtHandler* _finishedHandler;

// Column specification struct, used to construct the new tree model
const TreeModel::ColumnRecord& _columns;

// The tree store to populate. We must operate on our own tree store, since
// updating the target tree store from a different thread isn't safe
TreeModel::Ptr _treeStore;

protected:

// Needed method to load data into the allocated tree model
// This is called from within the worker thread
virtual void PopulateModel(const TreeModel::Ptr& model) = 0;

// Optional sorting method which is invoked after PopulateModel() is done
virtual void SortModel(const TreeModel::Ptr& model)
{}

// The worker function that will run in a separate thread
virtual wxThread::ExitCode Entry() override
{
// Create new treestore
_treeStore = new TreeModel(_columns);
_treeStore->SetHasDefaultCompare(false);

PopulateModel(_treeStore);

if (TestDestroy()) return static_cast<ExitCode>(0);

// Sort the model while we're still in the worker thread
SortModel(_treeStore);

if (!TestDestroy())
{
wxQueueEvent(_finishedHandler, new TreeModel::PopulationFinishedEvent(_treeStore));
}

return static_cast<ExitCode>(0);
}

public:
// Construct and initialise variables
ResourceTreePopulator(const TreeModel::ColumnRecord& columns, wxEvtHandler* finishedHandler) :
wxThread(wxTHREAD_JOINABLE),
_finishedHandler(finishedHandler),
_columns(columns)
{}

~ResourceTreePopulator()
{
if (IsRunning())
{
Delete(); // cancel the running thread
}
}

// Blocks until the worker thread is done.
// Returns immediately if the thread is not running
void WaitUntilFinished()
{
if (IsRunning())
{
Wait();
}
}

// Start the thread, if it isn't already running
void Run()
{
if (IsRunning())
{
return;
}

wxThread::Run();
}
};

}
2 changes: 1 addition & 1 deletion libs/wxutil/dataview/ResourceTreeView.h
Expand Up @@ -97,7 +97,7 @@ class ResourceTreeView :
bool _testRemoveFromFavourites();
void _onSetFavourite(bool isFavourite);

// Evaulation function for item visibility
// Evaluation function for item visibility
bool treeModelFilterFunc(TreeModel::Row& row);
};

Expand Down
189 changes: 69 additions & 120 deletions radiant/ui/mediabrowser/MediaBrowserTreeView.cpp
Expand Up @@ -160,151 +160,100 @@ struct ShaderNameFunctor
}
};

class MediaBrowserTreeView::Populator :
public wxThread
class Populator :
public wxutil::ResourceTreePopulator
{
private:
// The event handler to notify on completion
wxEvtHandler* _finishedHandler;

// Column specification struct
const MediaBrowserTreeView::TreeColumns& _columns;

// The set of favourites
std::set<std::string> _favourites;

// The tree store to populate. We must operate on our own tree store, since
// updating the MediaBrowser's tree store from a different thread
// wouldn't be safe
wxutil::TreeModel::Ptr _treeStore;
public:
// Construct and initialise variables
Populator(const MediaBrowserTreeView::TreeColumns& columns, wxEvtHandler* finishedHandler) :
ResourceTreePopulator(columns, finishedHandler),
_columns(columns)
{
_favourites = GlobalFavouritesManager().getFavourites(decl::Type::Material);
}

protected:

// The worker function that will execute in the thread
wxThread::ExitCode Entry()
void PopulateModel(const wxutil::TreeModel::Ptr& model) override
{
// Create new treestoree
_treeStore = new wxutil::TreeModel(_columns);
_treeStore->SetHasDefaultCompare(false);
model->SetHasDefaultCompare(false);

ShaderNameFunctor functor(*_treeStore, _columns, _favourites);
ShaderNameFunctor functor(*model, _columns, _favourites);
GlobalMaterialManager().foreachShaderName(std::bind(&ShaderNameFunctor::visit, &functor, std::placeholders::_1));

if (TestDestroy()) return static_cast<ExitCode>(0);

// Sort the model while we're still in the worker thread
_treeStore->SortModel(std::bind(&MediaBrowserTreeView::Populator::sortFunction,
this, std::placeholders::_1, std::placeholders::_2));

if (!TestDestroy())
{
wxQueueEvent(_finishedHandler, new wxutil::TreeModel::PopulationFinishedEvent(_treeStore));
}

return static_cast<ExitCode>(0);
}

private:
bool sortFunction(const wxDataViewItem& a, const wxDataViewItem& b)
// Special sort algorithm to sort the "Other Materials" separately
void SortModel(const wxutil::TreeModel::Ptr& model) override
{
// Check if A or B are folders
wxVariant aIsFolder, bIsFolder;
_treeStore->GetValue(aIsFolder, a, _columns.isFolder.getColumnIndex());
_treeStore->GetValue(bIsFolder, b, _columns.isFolder.getColumnIndex());

if (aIsFolder)
// Sort the model while we're still in the worker thread
model->SortModel([&](const wxDataViewItem& a, const wxDataViewItem& b)
{
// A is a folder, check if B is as well
if (bIsFolder)
{
// A and B are both folders
wxVariant aIsOtherMaterialsFolder, bIsOtherMaterialsFolder;
// Check if A or B are folders
wxVariant aIsFolder, bIsFolder;
model->GetValue(aIsFolder, a, _columns.isFolder.getColumnIndex());
model->GetValue(bIsFolder, b, _columns.isFolder.getColumnIndex());

_treeStore->GetValue(aIsOtherMaterialsFolder, a, _columns.isOtherMaterialsFolder.getColumnIndex());
_treeStore->GetValue(bIsOtherMaterialsFolder, b, _columns.isOtherMaterialsFolder.getColumnIndex());

// Special treatment for "Other Materials" folder, which always comes last
if (aIsOtherMaterialsFolder)
if (aIsFolder)
{
// A is a folder, check if B is as well
if (bIsFolder)
{
return false;
// A and B are both folders
wxVariant aIsOtherMaterialsFolder, bIsOtherMaterialsFolder;

model->GetValue(aIsOtherMaterialsFolder, a, _columns.isOtherMaterialsFolder.getColumnIndex());
model->GetValue(bIsOtherMaterialsFolder, b, _columns.isOtherMaterialsFolder.getColumnIndex());

// Special treatment for "Other Materials" folder, which always comes last
if (aIsOtherMaterialsFolder)
{
return false;
}

if (bIsOtherMaterialsFolder)
{
return true;
}

// Compare folder names
// greebo: We're not checking for equality here, shader names are unique
wxVariant aName, bName;
model->GetValue(aName, a, _columns.leafName.getColumnIndex());
model->GetValue(bName, b, _columns.leafName.getColumnIndex());

return aName.GetString().CmpNoCase(bName.GetString()) < 0;
}

if (bIsOtherMaterialsFolder)
else
{
// A is a folder, B is not, A sorts before
return true;
}

// Compare folder names
// greebo: We're not checking for equality here, shader names are unique
wxVariant aName, bName;
_treeStore->GetValue(aName, a, _columns.leafName.getColumnIndex());
_treeStore->GetValue(bName, b, _columns.leafName.getColumnIndex());

return aName.GetString().CmpNoCase(bName.GetString()) < 0;
}
else
{
// A is a folder, B is not, A sorts before
return true;
}
}
else
{
// A is not a folder, check if B is one
if (bIsFolder)
{
// A is not a folder, B is, so B sorts before A
return false;
}
else
{
// Neither A nor B are folders, compare names
// greebo: We're not checking for equality here, shader names are unique
wxVariant aName, bName;
_treeStore->GetValue(aName, a, _columns.leafName.getColumnIndex());
_treeStore->GetValue(bName, b, _columns.leafName.getColumnIndex());
// A is not a folder, check if B is one
if (bIsFolder)
{
// A is not a folder, B is, so B sorts before A
return false;
}
else
{
// Neither A nor B are folders, compare names
// greebo: We're not checking for equality here, shader names are unique
wxVariant aName, bName;
model->GetValue(aName, a, _columns.leafName.getColumnIndex());
model->GetValue(bName, b, _columns.leafName.getColumnIndex());

return aName.GetString().CmpNoCase(bName.GetString()) < 0;
return aName.GetString().CmpNoCase(bName.GetString()) < 0;
}
}
}
}

public:

// Construct and initialise variables
Populator(const MediaBrowserTreeView::TreeColumns& cols, wxEvtHandler* finishedHandler) :
wxThread(wxTHREAD_JOINABLE),
_finishedHandler(finishedHandler),
_columns(cols)
{
_favourites = GlobalFavouritesManager().getFavourites(decl::Type::Material);
}

~Populator()
{
if (IsRunning())
{
Delete(); // cancel the running thread
}
}

void waitUntilFinished()
{
if (IsRunning())
{
Wait();
}
}

// Start loading entity classes in a new thread
void populate()
{
if (IsRunning())
{
return;
}

Run();
});
}
};

Expand Down Expand Up @@ -366,7 +315,7 @@ void MediaBrowserTreeView::populate()

// Start the background thread
_populator.reset(new Populator(_columns, this));
_populator->populate();
_populator->Run();
}

void MediaBrowserTreeView::clear()
Expand All @@ -391,7 +340,7 @@ void MediaBrowserTreeView::setSelection(const std::string& fullName)
}

// Make sure the treestore is finished loading
_populator->waitUntilFinished();
_populator->WaitUntilFinished();

ResourceTreeView::setSelection(fullName);
}
Expand Down
4 changes: 2 additions & 2 deletions radiant/ui/mediabrowser/MediaBrowserTreeView.h
@@ -1,6 +1,7 @@
#pragma once

#include "wxutil/dataview/ResourceTreeView.h"
#include "wxutil/dataview/ResourceTreePopulator.h"
#include "wxutil/menu/IconTextMenuItem.h"
#include "wxutil/dataview/TreeModelFilter.h"

Expand All @@ -24,8 +25,7 @@ class MediaBrowserTreeView :

private:
// Populates the Media Browser in its own thread
class Populator;
std::unique_ptr<Populator> _populator;
std::unique_ptr<wxutil::ResourceTreePopulator> _populator;

// false, if the tree is not yet initialised.
bool _isPopulated;
Expand Down
1 change: 1 addition & 0 deletions tools/msvc/wxutillib.vcxproj
Expand Up @@ -140,6 +140,7 @@
<ClInclude Include="..\..\libs\wxutil\ConsoleView.h" />
<ClInclude Include="..\..\libs\wxutil\ControlButton.h" />
<ClInclude Include="..\..\libs\wxutil\dataview\KeyValueTable.h" />
<ClInclude Include="..\..\libs\wxutil\dataview\ResourceTreePopulator.h" />
<ClInclude Include="..\..\libs\wxutil\dataview\ResourceTreeView.h" />
<ClInclude Include="..\..\libs\wxutil\dataview\TreeModel.h" />
<ClInclude Include="..\..\libs\wxutil\dataview\TreeModelFilter.h" />
Expand Down
3 changes: 3 additions & 0 deletions tools/msvc/wxutillib.vcxproj.filters
Expand Up @@ -133,6 +133,9 @@
<ClInclude Include="..\..\libs\wxutil\dataview\VFSTreePopulator.h">
<Filter>dataview</Filter>
</ClInclude>
<ClInclude Include="..\..\libs\wxutil\dataview\ResourceTreePopulator.h">
<Filter>dataview</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\libs\wxutil\dialog\MessageBox.cpp">
Expand Down

0 comments on commit 4b479e6

Please sign in to comment.