From 2b906a2c19d9c564808d4391be90c84de3b17581 Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Tue, 19 May 2020 08:19:19 +0200 Subject: Async search (#245) * Async search * Define index when it's used--- .../cuchaz/enigma/gui/dialog/SearchDialog.java | 13 ++-- .../cuchaz/enigma/utils/search/SearchUtil.java | 73 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java index b36ebfb4..b283a377 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java @@ -36,12 +36,13 @@ import cuchaz.enigma.utils.search.SearchUtil; public class SearchDialog { private final JTextField searchField; - private final DefaultListModel classListModel; + private DefaultListModel classListModel; private final JList classList; private final JDialog dialog; private final Gui parent; private final SearchUtil su; + private SearchUtil.SearchControl currentSearch; public SearchDialog(Gui parent) { this.parent = parent; @@ -173,11 +174,13 @@ public class SearchDialog { // Updates the list of class names private void updateList() { - classListModel.clear(); + if (currentSearch != null) currentSearch.stop(); - su.search(searchField.getText()) - .limit(100) - .forEach(classListModel::addElement); + DefaultListModel classListModel = new DefaultListModel<>(); + this.classListModel = classListModel; + classList.setModel(classListModel); + + currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx))); } public void dispose() { diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java b/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java index e5ed35fd..a51afbb0 100644 --- a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java +++ b/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java @@ -1,6 +1,12 @@ package cuchaz.enigma.utils.search; import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -11,6 +17,7 @@ public class SearchUtil { private final Map> entries = new HashMap<>(); private final Map hitCount = new HashMap<>(); + private final Executor searchExecutor = Executors.newWorkStealingPool(); public void add(T entry) { Entry e = Entry.from(entry); @@ -46,6 +53,59 @@ public class SearchUtil { .sequential(); } + public SearchControl asyncSearch(String term, SearchResultConsumer consumer) { + Map hitCount = new HashMap<>(this.hitCount); + Map> entries = new HashMap<>(this.entries); + float[] scores = new float[entries.size()]; + Lock scoresLock = new ReentrantLock(); + AtomicInteger size = new AtomicInteger(); + AtomicBoolean control = new AtomicBoolean(false); + AtomicInteger elapsed = new AtomicInteger(); + for (Entry value : entries.values()) { + searchExecutor.execute(() -> { + try { + if (control.get()) return; + float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); + if (score <= 0) return; + score = -score; // sort descending + try { + scoresLock.lock(); + if (control.get()) return; + int dataSize = size.getAndIncrement(); + int index = Arrays.binarySearch(scores, 0, dataSize, score); + if (index < 0) { + index = ~index; + } + System.arraycopy(scores, index, scores, index + 1, dataSize - index); + scores[index] = score; + consumer.add(index, value.searchEntry); + } finally { + scoresLock.unlock(); + } + } finally { + elapsed.incrementAndGet(); + } + }); + } + + return new SearchControl() { + @Override + public void stop() { + control.set(true); + } + + @Override + public boolean isFinished() { + return entries.size() == elapsed.get(); + } + + @Override + public float getProgress() { + return (float) elapsed.get() / entries.size(); + } + }; + } + public void hit(T entry) { if (entries.containsKey(entry)) { hitCount.compute(entry.getIdentifier(), (_id, i) -> i == null ? 1 : i + 1); @@ -192,4 +252,17 @@ public class SearchUtil { } + @FunctionalInterface + public interface SearchResultConsumer { + void add(int index, T entry); + } + + public interface SearchControl { + void stop(); + + boolean isFinished(); + + float getProgress(); + } + } -- cgit v1.2.3