Skip to content

Commit

Permalink
add multithread support with threshold
Browse files Browse the repository at this point in the history
I wonder what is the best value
  • Loading branch information
Jinmo committed May 14, 2019
1 parent d23445c commit aeb60dd
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 71 deletions.
40 changes: 36 additions & 4 deletions palette/include/widgets/palette_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@

#include <action.h>

class SearchService;

class PaletteFilter : public QAbstractItemModel {
Q_OBJECT;

QVector<Action> items_;
std::vector<int> shown_items_;
std::vector<int> shown_items_temp_;

int shown_items_count_;
QString keyword_;

QThread *searcher_;
SearchService* search_service_;

public:
PaletteFilter(QWidget* parent, const QVector<Action>& items);

// Public interface
void setFilter(const QString& keyword);

bool match(const QString& keyword, Action& action);
bool lessThan(const QString& keyword, Action& left, Action& right) const;

// Implementations
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
Expand All @@ -37,8 +38,39 @@ class PaletteFilter : public QAbstractItemModel {
int columnCount(const QModelIndex& parent) const override { return 1; }
int rowCount(const QModelIndex& parent = QModelIndex()) const override;

void onDoneSearching(std::vector<int> *indexes_, int count, int preferred_index);

signals:
void filteringDone(int preferred_index);
};

class SearchService: public QObject {
Q_OBJECT;

std::vector<int> indexes_;
QVector<Action>* actions_;

bool canceled_;

public:
using QObject::moveToThread;
SearchService(QObject* parent, QVector<Action>* actions);

void search(const QString& keyword);

bool lessThan(const QString& keyword, const Action& left, const Action& right) const;

bool match(const QString& keyword, Action& action);

void cancel() {
canceled_ = true;
}

signals:
// Request
void startSearching(const QString& keyword);
// Response
void doneSearching(std::vector<int> *indexes, int count, int preferred_index);
};

#endif // PALETTE_FILTER_H
170 changes: 103 additions & 67 deletions palette/src/widgets/palette_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,133 @@
#include "fts_fuzzy_match.h"

typedef QHash<QPair<QString, QString>, int> DistanceHash;
#define SAME_THREAD_THRESHOLD 5000

int distance(const QString& s1_, const QString& s2_) {
static QThreadStorage<DistanceHash*> distances;
static QThreadStorage<DistanceHash*> distances;

QString s1 = s1_.toLower();
QPair<QString, QString> pair(s1, s2_);
QString s1 = s1_.toLower();
QPair<QString, QString> pair(s1, s2_);

if (!distances.hasLocalData()) {
distances.setLocalData(new DistanceHash());
}
if (!distances.hasLocalData()) {
distances.setLocalData(new DistanceHash());
}

if (distances.localData()->contains(pair))
return (*distances.localData())[pair];
if (distances.localData()->contains(pair))
return (*distances.localData())[pair];

QByteArray s1b = s1.toUtf8();
QByteArray s2b = s2_.toUtf8();
QByteArray s1b = s1.toUtf8();
QByteArray s2b = s2_.toUtf8();

int score;
fts::fuzzy_match(s1b.data(), s2b.data(), score);
int score;
fts::fuzzy_match(s1b.data(), s2b.data(), score);

distances.localData()->insert(pair, -score);
return -score;
distances.localData()->insert(pair, -score);
return -score;
}

bool PaletteFilter::lessThan(const QString & keyword, Action & left, Action & right) const {
return distance(keyword, left.name) < distance(keyword, right.name);
}

void PaletteFilter::setFilter(const QString & keyword) {
long count = 0, preferred_index = 0;

keyword_ = keyword;
emit layoutAboutToBeChanged();

/* Filter the items with fuzzy matching: see PaletteFilter::fuzzy_match_simple,
which is substr with non-neighbor characters support
*/
for (long i = 0; i < items_.size(); i++) {
if (match(keyword, items_[i])) {
shown_items_temp_[count++] = i;
}
}

/* Sort by fuzzy matching if keyword is longer than 1 character
This sorts shown_items_temp_ which is array of indexes, and copies to shown_items_.
The size/capacity of each vector is not changed, just member variable shown_items_count_ is changed.
If the job is cancelled during sort, the compare function raises exception to abort sorting.
TODO: set preferred_index from command history to focus on the recently executed action
*/
if (keyword.size() > 1)
std::sort(shown_items_temp_.begin(), shown_items_temp_.begin() + count, [=](int lhs, int rhs) -> bool {
Action& l = items_[lhs];
Action& r = items_[rhs];
return lessThan(keyword_, l, r);
});

std::copy(shown_items_temp_.begin(), shown_items_temp_.begin() + count, shown_items_.begin());
shown_items_ = shown_items_temp_;
shown_items_count_ = count;

emit filteringDone(preferred_index);
emit layoutChanged();
keyword_ = keyword;
search_service_->cancel();
emit search_service_->startSearching(keyword);
}

void PaletteFilter::onDoneSearching(std::vector<int> *indexes_, int count, int preferred_index) {
emit layoutAboutToBeChanged();

std::copy(indexes_->begin(), indexes_->begin() + count, shown_items_.begin());
shown_items_count_ = count;

emit filteringDone(preferred_index);
emit layoutChanged();
}

QModelIndex PaletteFilter::index(int row, int column, const QModelIndex & parent) const {
return createIndex(row, column);
return createIndex(row, column);
}

QVariant PaletteFilter::data(const QModelIndex & index, int role) const {
if (role == Qt::DisplayRole)
return QVariant::fromValue(items_[shown_items_[index.row()]]);
else if (role == Qt::UserRole)
return keyword_;
return QVariant();
if (role == Qt::DisplayRole)
return QVariant::fromValue(items_[shown_items_[index.row()]]);
else if (role == Qt::UserRole)
return keyword_;
return QVariant();
}

int PaletteFilter::rowCount(const QModelIndex & parent) const {
return shown_items_count_;
return shown_items_count_;
}

bool PaletteFilter::match(const QString & keyword, Action & action) {
if (keyword.isEmpty())
return true;
PaletteFilter::PaletteFilter(QWidget * parent, const QVector <Action> & items)
: QAbstractItemModel(parent), items_(items), shown_items_count_(0),
shown_items_(items.size()) {
search_service_ = new SearchService(nullptr, &items_);

if (items.count() >= SAME_THREAD_THRESHOLD) {
searcher_ = new QThread(this);
searcher_->start();
search_service_->moveToThread(searcher_);
}
else {
searcher_ = nullptr;
search_service_->setParent(this);
}
connect(search_service_, &SearchService::doneSearching, this, &PaletteFilter::onDoneSearching);
setFilter(QString());
}

return fts::fuzzy_match_simple(keyword, action.name);
bool SearchService::lessThan(const QString & keyword, const Action & left, const Action & right) const {
return distance(keyword, left.name) < distance(keyword, right.name);
}

PaletteFilter::PaletteFilter(QWidget * parent, const QVector <Action> & items)
: QAbstractItemModel(parent), items_(items), shown_items_count_(0),
shown_items_(items.size()), shown_items_temp_(items.size()) {
setFilter(QString());
bool SearchService::match(const QString & keyword, Action & action) {
if (keyword.isEmpty())
return true;

return fts::fuzzy_match_simple(keyword, action.name);
}

void SearchService::search(const QString & keyword) {
long count = 0, preferred_index = 0;
auto&& actions = *actions_;

canceled_ = false;

/* Filter the items with fuzzy matching: see PaletteFilter::fuzzy_match_simple,
which is substr with non-neighbor characters support
*/
for (long i = 0; i < indexes_.size(); i++) {
if (canceled_)
return;
if (match(keyword, actions[i])) {
indexes_[count++] = i;
}
}

/* Sort by fuzzy matching if keyword is longer than 1 character
This sorts indexes_ which is array of indexes, and copies to shown_items_.
The size/capacity of each vector is not changed, just member variable shown_items_count_ is changed.
If the job is cancelled during sort, the compare function raises exception to abort sorting.
TODO: set preferred_index from command history to focus on the recently executed action
*/
try {
if (keyword.size() > 1)
std::sort(indexes_.begin(), indexes_.begin() + count, [=](int lhs, int rhs) -> bool {
if (canceled_)
throw std::exception();
return lessThan(keyword, actions[lhs], actions[rhs]);
});
}
catch (std::exception) {
return;
}

emit doneSearching(&indexes_, count, preferred_index);
}

SearchService::SearchService(QObject * parent, QVector <Action> * actions) : QObject(parent), actions_(actions), indexes_(actions->size()), canceled_(false) {
connect(this, &SearchService::startSearching, this, &SearchService::search);
}

0 comments on commit aeb60dd

Please sign in to comment.