From e060a0b58ef117136a268881085415e1c07e2507 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 30 Mar 2024 16:47:03 -0400 Subject: [PATCH 1/4] Add an API to allow mods to do their own EMI searching --- .../src/main/java/dev/emi/emi/api/EmiApi.java | 6 ++ .../emi/api/search/EmiSearchManagerApi.java | 24 +++++ .../dev/emi/emi/mixin/ItemStackMixin.java | 2 +- .../java/dev/emi/emi/screen/ConfigScreen.java | 2 +- .../dev/emi/emi/screen/EmiScreenManager.java | 17 +++- .../emi/screen/widget/EmiSearchWidget.java | 2 +- .../java/dev/emi/emi/search/EmiSearch.java | 86 +++------------- .../dev/emi/emi/search/EmiSearchManager.java | 98 +++++++++++++++++++ 8 files changed, 156 insertions(+), 81 deletions(-) create mode 100644 xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java create mode 100644 xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java diff --git a/xplat/src/main/java/dev/emi/emi/api/EmiApi.java b/xplat/src/main/java/dev/emi/emi/api/EmiApi.java index cd88b30f..65569d37 100644 --- a/xplat/src/main/java/dev/emi/emi/api/EmiApi.java +++ b/xplat/src/main/java/dev/emi/emi/api/EmiApi.java @@ -5,6 +5,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import dev.emi.emi.api.search.EmiSearchManagerApi; +import dev.emi.emi.search.EmiSearchManager; import org.jetbrains.annotations.Nullable; import com.google.common.collect.Lists; @@ -65,6 +67,10 @@ public static void setSearchText(String text) { EmiScreenManager.search.setText(text); } + public static EmiSearchManagerApi createSearchManager() { + return new EmiSearchManager(); + } + public static boolean isSearchFocused() { return EmiScreenManager.search.isFocused(); } diff --git a/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java new file mode 100644 index 00000000..2d15219f --- /dev/null +++ b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java @@ -0,0 +1,24 @@ +package dev.emi.emi.api.search; + +import dev.emi.emi.api.stack.EmiIngredient; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * A search manager controls searching for items using EMI infrastructure. + */ +public interface EmiSearchManagerApi { + /** + * {@return the current list of stacks matching the last search query} + */ + List getStacks(); + + /** + * Search for ingredients matching the given query string. The list returned by {@link EmiSearchManagerApi#getStacks()} + * will not update until after the returned future completes. + * @param query the query string to use when searching + * @return a future that completes with the updated list + */ + CompletableFuture> search(String query); +} diff --git a/xplat/src/main/java/dev/emi/emi/mixin/ItemStackMixin.java b/xplat/src/main/java/dev/emi/emi/mixin/ItemStackMixin.java index 79c3a229..dd08e418 100644 --- a/xplat/src/main/java/dev/emi/emi/mixin/ItemStackMixin.java +++ b/xplat/src/main/java/dev/emi/emi/mixin/ItemStackMixin.java @@ -22,7 +22,7 @@ public class ItemStackMixin { @Inject(at = @At("RETURN"), method = "getTooltip") private void getTooltip(PlayerEntity player, TooltipContext context, CallbackInfoReturnable> info) { - if (EmiConfig.appendItemModId && EmiConfig.appendModId && Thread.currentThread() != EmiSearch.searchThread) { + if (EmiConfig.appendItemModId && EmiConfig.appendModId && !EmiSearch.isSearchThread()) { List text = info.getReturnValue(); String namespace = EmiPort.getItemRegistry().getId(((ItemStack) (Object) this).getItem()).getNamespace(); String mod = EmiUtil.getModName(namespace); diff --git a/xplat/src/main/java/dev/emi/emi/screen/ConfigScreen.java b/xplat/src/main/java/dev/emi/emi/screen/ConfigScreen.java index de776c98..922d07c9 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/ConfigScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/ConfigScreen.java @@ -87,7 +87,7 @@ public void setActiveBind(EmiBind bind, int offset) { @Override public void close() { EmiConfig.writeConfig(); - EmiSearch.update(); + EmiScreenManager.updateSearch(); MinecraftClient.getInstance().setScreen(last); } diff --git a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java index 31ca30ab..92b0f406 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java +++ b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import dev.emi.emi.search.EmiSearchManager; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; @@ -125,17 +126,23 @@ public class EmiScreenManager { () -> true, (w) -> EmiApi.viewRecipeTree(), List.of(EmiPort.translatable("tooltip.emi.recipe_tree"))); + public static EmiSearchManager searchManager = new EmiSearchManager(); + public static boolean isDisabled() { return !EmiReloadManager.isLoaded() || !EmiConfig.enabled; } + public static void updateSearch() { + searchManager.search(search.getText()); + } + public static void recalculate() { updateCraftables(); SidebarPanel searchPanel = getSearchPanel(); if (searchPanel != null && searchPanel.space != null) { - if (searchedStacks != EmiSearch.stacks) { + if (searchedStacks != searchManager.getStacks()) { searchPanel.space.batcher.repopulate(); - searchedStacks = EmiSearch.stacks; + searchedStacks = searchManager.getStacks(); } } @@ -238,7 +245,7 @@ private static void updateCraftables() { if (searchPanel != null && searchPanel.space != null) { searchPanel.space.batcher.repopulate(); if (searchPanel.getType() == SidebarType.CRAFTABLES) { - EmiSearch.update(); + EmiScreenManager.updateSearch(); } } EmiFavorites.updateSynthetic(inv); @@ -808,7 +815,7 @@ private static void renderExclusionAreas(EmiDrawContext context, int mouseX, int private static void renderSlotOverlays(EmiDrawContext context, int mouseX, int mouseY, float delta, EmiScreenBase base) { CompiledQuery query = null; if (EmiScreenManager.search.highlight) { - query = EmiSearch.compiledQuery; + query = searchManager.getCompiledQuery(); } Set ignoredSlots = Sets.newHashSet(); Set synfavs = Sets.newHashSet(); @@ -1380,7 +1387,7 @@ public void setSidebarPage(int page) { } } if (isSearch()) { - EmiSearch.search(search.getText()); + searchManager.search(search.getText()); } if (space != null) { space.batcher.repopulate(); diff --git a/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java b/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java index 2e06a29c..0c247a7e 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java +++ b/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java @@ -131,7 +131,7 @@ public EmiSearchWidget(TextRenderer textRenderer, int x, int y, int width, int h styles.add(new Pair(string.length(), Style.EMPTY.withFormatting(Formatting.WHITE))); } this.styles = styles; - EmiSearch.search(string); + EmiScreenManager.searchManager.search(string); }); } diff --git a/xplat/src/main/java/dev/emi/emi/search/EmiSearch.java b/xplat/src/main/java/dev/emi/emi/search/EmiSearch.java index 46f9b0b6..02c18113 100644 --- a/xplat/src/main/java/dev/emi/emi/search/EmiSearch.java +++ b/xplat/src/main/java/dev/emi/emi/search/EmiSearch.java @@ -2,6 +2,8 @@ import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; @@ -32,14 +34,21 @@ public class EmiSearch { public static final Pattern TOKENS = Pattern.compile("(-?[@#]?\\/(\\\\.|[^\\\\\\/])+\\/|[^\\s]+)"); - private static volatile SearchWorker currentWorker = null; - public static volatile Thread searchThread = null; - public static volatile List stacks = EmiStackList.stacks; - public static volatile CompiledQuery compiledQuery; + private static Thread searchThread; + public static final Executor executor = Executors.newSingleThreadExecutor(task -> { + Thread t = new Thread(task, "EMI Search Thread"); + t.setDaemon(true); + searchThread = t; + return t; + }); public static Set bakedStacks; public static SuffixArray names, tooltips, mods; public static SuffixArray aliases; + public static boolean isSearchThread() { + return searchThread == Thread.currentThread(); + } + public static void bake() { SuffixArray names = new SuffixArray<>(); SuffixArray tooltips = new SuffixArray<>(); @@ -110,31 +119,6 @@ public static void bake() { EmiSearch.bakedStacks = bakedStacks; } - public static void update() { - search(EmiScreenManager.search.getText()); - } - - public static void search(String query) { - synchronized (EmiSearch.class) { - SearchWorker worker = new SearchWorker(query, EmiScreenManager.getSearchSource()); - currentWorker = worker; - - searchThread = new Thread(worker); - searchThread.setDaemon(true); - searchThread.start(); - } - } - - public static void apply(SearchWorker worker, List stacks) { - synchronized (EmiSearch.class) { - if (worker == currentWorker) { - EmiSearch.stacks = stacks; - currentWorker = null; - searchThread = null; - } - } - } - public static class CompiledQuery { public final Query fullQuery; @@ -223,48 +207,4 @@ private static void addQuery(String s, boolean negated, List queries, Fun queries.add(q); } } - - private static class SearchWorker implements Runnable { - private final String query; - private final List source; - - public SearchWorker(String query, List source) { - this.query = query; - this.source = source; - } - - @Override - public void run() { - try { - CompiledQuery compiled = new CompiledQuery(query); - compiledQuery = compiled; - if (compiled.isEmpty()) { - apply(this, source); - return; - } - List stacks = Lists.newArrayList(); - int processed = 0; - for (EmiIngredient stack : source) { - if (processed++ >= 1024) { - processed = 0; - if (this != currentWorker) { - return; - } - } - List ess = stack.getEmiStacks(); - // TODO properly support ingredients? - if (ess.size() == 1) { - EmiStack es = ess.get(0); - if (compiled.test(es)) { - stacks.add(stack); - } - } - } - apply(this, List.copyOf(stacks)); - } catch (Exception e) { - EmiLog.error("Error when attempting to search:"); - e.printStackTrace(); - } - } - } } diff --git a/xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java new file mode 100644 index 00000000..55a725b8 --- /dev/null +++ b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java @@ -0,0 +1,98 @@ +package dev.emi.emi.search; + +import com.google.common.collect.Lists; +import dev.emi.emi.api.search.EmiSearchManagerApi; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.registry.EmiStackList; +import dev.emi.emi.runtime.EmiLog; +import dev.emi.emi.screen.EmiScreenManager; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class EmiSearchManager implements EmiSearchManagerApi { + private EmiSearch.CompiledQuery compiledQuery; + private List stacks = EmiStackList.stacks; + private volatile SearchWorker currentWorker; + + public CompletableFuture> search(String query) { + synchronized (this) { + SearchWorker worker = new SearchWorker(query, EmiScreenManager.getSearchSource()); + currentWorker = worker; + + EmiSearch.executor.execute(worker); + return worker.getCompletionFuture(); + } + } + + public List getStacks() { + return this.stacks; + } + + public EmiSearch.CompiledQuery getCompiledQuery() { + return this.compiledQuery; + } + + class SearchWorker implements Runnable { + private final String query; + private final List source; + private final CompletableFuture> completion; + + SearchWorker(String query, List source) { + this.query = query; + this.source = source; + this.completion = new CompletableFuture<>(); + } + + public CompletableFuture> getCompletionFuture() { + return completion; + } + + private void apply(List stacks) { + synchronized (EmiSearchManager.this) { + if(this == currentWorker) { + EmiSearchManager.this.stacks = stacks; + EmiSearchManager.this.currentWorker = null; + } + } + completion.complete(stacks); + } + + @Override + public void run() { + try { + EmiSearch.CompiledQuery compiled = new EmiSearch.CompiledQuery(query); + compiledQuery = compiled; + if (compiled.isEmpty()) { + apply(source); + return; + } + List stacks = Lists.newArrayList(); + int processed = 0; + for (EmiIngredient stack : source) { + if (processed++ >= 1024) { + processed = 0; + if (this != currentWorker) { + apply(source); + return; + } + } + List ess = stack.getEmiStacks(); + // TODO properly support ingredients? + if (ess.size() == 1) { + EmiStack es = ess.get(0); + if (compiled.test(es)) { + stacks.add(stack); + } + } + } + apply(List.copyOf(stacks)); + } catch (Exception e) { + EmiLog.error("Error when attempting to search:"); + e.printStackTrace(); + apply(source); + } + } + } +} From 53d6fdc4cb2433bbfe1ae689a956f89c06fd7eef Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:44:59 -0400 Subject: [PATCH 2/4] Api -> [none], [none] -> Impl --- xplat/src/main/java/dev/emi/emi/api/EmiApi.java | 8 ++++---- ...{EmiSearchManagerApi.java => EmiSearchManager.java} | 4 ++-- .../main/java/dev/emi/emi/screen/EmiScreenManager.java | 5 ++--- ...EmiSearchManager.java => EmiSearchManagerImpl.java} | 10 +++++----- 4 files changed, 13 insertions(+), 14 deletions(-) rename xplat/src/main/java/dev/emi/emi/api/search/{EmiSearchManagerApi.java => EmiSearchManager.java} (87%) rename xplat/src/main/java/dev/emi/emi/search/{EmiSearchManager.java => EmiSearchManagerImpl.java} (91%) diff --git a/xplat/src/main/java/dev/emi/emi/api/EmiApi.java b/xplat/src/main/java/dev/emi/emi/api/EmiApi.java index 65569d37..e2d8a97a 100644 --- a/xplat/src/main/java/dev/emi/emi/api/EmiApi.java +++ b/xplat/src/main/java/dev/emi/emi/api/EmiApi.java @@ -5,8 +5,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import dev.emi.emi.api.search.EmiSearchManagerApi; -import dev.emi.emi.search.EmiSearchManager; +import dev.emi.emi.api.search.EmiSearchManager; +import dev.emi.emi.search.EmiSearchManagerImpl; import org.jetbrains.annotations.Nullable; import com.google.common.collect.Lists; @@ -67,8 +67,8 @@ public static void setSearchText(String text) { EmiScreenManager.search.setText(text); } - public static EmiSearchManagerApi createSearchManager() { - return new EmiSearchManager(); + public static EmiSearchManager createSearchManager() { + return new EmiSearchManagerImpl(); } public static boolean isSearchFocused() { diff --git a/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java similarity index 87% rename from xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java rename to xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java index 2d15219f..26d72783 100644 --- a/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManagerApi.java +++ b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java @@ -8,14 +8,14 @@ /** * A search manager controls searching for items using EMI infrastructure. */ -public interface EmiSearchManagerApi { +public interface EmiSearchManager { /** * {@return the current list of stacks matching the last search query} */ List getStacks(); /** - * Search for ingredients matching the given query string. The list returned by {@link EmiSearchManagerApi#getStacks()} + * Search for ingredients matching the given query string. The list returned by {@link EmiSearchManager#getStacks()} * will not update until after the returned future completes. * @param query the query string to use when searching * @return a future that completes with the updated list diff --git a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java index 92b0f406..a2527869 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java +++ b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java @@ -7,7 +7,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import dev.emi.emi.search.EmiSearchManager; +import dev.emi.emi.search.EmiSearchManagerImpl; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; @@ -66,7 +66,6 @@ import dev.emi.emi.screen.widget.EmiSearchWidget; import dev.emi.emi.screen.widget.SidebarButtonWidget; import dev.emi.emi.screen.widget.SizedButtonWidget; -import dev.emi.emi.search.EmiSearch; import dev.emi.emi.search.EmiSearch.CompiledQuery; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.Element; @@ -126,7 +125,7 @@ public class EmiScreenManager { () -> true, (w) -> EmiApi.viewRecipeTree(), List.of(EmiPort.translatable("tooltip.emi.recipe_tree"))); - public static EmiSearchManager searchManager = new EmiSearchManager(); + public static EmiSearchManagerImpl searchManager = new EmiSearchManagerImpl(); public static boolean isDisabled() { return !EmiReloadManager.isLoaded() || !EmiConfig.enabled; diff --git a/xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java similarity index 91% rename from xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java rename to xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java index 55a725b8..366e84db 100644 --- a/xplat/src/main/java/dev/emi/emi/search/EmiSearchManager.java +++ b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java @@ -1,7 +1,7 @@ package dev.emi.emi.search; import com.google.common.collect.Lists; -import dev.emi.emi.api.search.EmiSearchManagerApi; +import dev.emi.emi.api.search.EmiSearchManager; import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.stack.EmiStack; import dev.emi.emi.registry.EmiStackList; @@ -11,7 +11,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -public class EmiSearchManager implements EmiSearchManagerApi { +public class EmiSearchManagerImpl implements EmiSearchManager { private EmiSearch.CompiledQuery compiledQuery; private List stacks = EmiStackList.stacks; private volatile SearchWorker currentWorker; @@ -50,10 +50,10 @@ public CompletableFuture> getCompletionFuture() { } private void apply(List stacks) { - synchronized (EmiSearchManager.this) { + synchronized (EmiSearchManagerImpl.this) { if(this == currentWorker) { - EmiSearchManager.this.stacks = stacks; - EmiSearchManager.this.currentWorker = null; + EmiSearchManagerImpl.this.stacks = stacks; + EmiSearchManagerImpl.this.currentWorker = null; } } completion.complete(stacks); From 8f5403fc9a3c0c868ae6265d64157a1a1c7276a6 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:00:52 -0400 Subject: [PATCH 3/4] More search refactoring - remove magic static vars, etc. --- .../emi/emi/api/search/EmiSearchManager.java | 72 +++++++++++++-- .../dev/emi/emi/screen/EmiScreenManager.java | 19 ++-- .../emi/screen/widget/EmiSearchWidget.java | 2 +- .../emi/emi/search/EmiSearchManagerImpl.java | 88 ++++++++++++------- 4 files changed, 137 insertions(+), 44 deletions(-) diff --git a/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java index 26d72783..0e61cb16 100644 --- a/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java +++ b/xplat/src/main/java/dev/emi/emi/api/search/EmiSearchManager.java @@ -1,24 +1,82 @@ package dev.emi.emi.api.search; import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.registry.EmiStackList; +import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** - * A search manager controls searching for items using EMI infrastructure. + * A search manager controls searching for stacks using EMI infrastructure. */ public interface EmiSearchManager { /** - * {@return the current list of stacks matching the last search query} + * Search for ingredients matching the given query string. + * + * @param query the query string to use when searching + * @param ingredients the list of ingredients to search in + * @return a future that completes with the updated list */ - List getStacks(); + SearchFuture search(String query, List ingredients); /** - * Search for ingredients matching the given query string. The list returned by {@link EmiSearchManager#getStacks()} - * will not update until after the returned future completes. + * Search for ingredients matching the given query string in the list of all known ingredients. * @param query the query string to use when searching * @return a future that completes with the updated list */ - CompletableFuture> search(String query); + default SearchFuture search(String query) { + return search(query, EmiStackList.stacks); + } + + interface SearchFuture extends Future> { + /** + * {@return the search results if the search has completed, or the original input list} + */ + List getNow(); + + SearchFuture whenCompleted(Consumer> consumer); + + static SearchFuture completedFuture(List list) { + return new SearchFuture() { + @Override + public List getNow() { + return list; + } + + @Override + public SearchFuture whenCompleted(Consumer> consumer) { + consumer.accept(list); + return this; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public List get() { + return list; + } + + @Override + public List get(long timeout, @NotNull TimeUnit unit) { + return list; + } + }; + } + } } diff --git a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java index a2527869..d0c6b02a 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java +++ b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java @@ -2,11 +2,14 @@ import java.util.List; import java.util.Set; +import java.util.concurrent.Future; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import dev.emi.emi.api.search.EmiSearchManager; +import dev.emi.emi.registry.EmiStackList; import dev.emi.emi.search.EmiSearchManagerImpl; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; @@ -126,22 +129,26 @@ public class EmiScreenManager { List.of(EmiPort.translatable("tooltip.emi.recipe_tree"))); public static EmiSearchManagerImpl searchManager = new EmiSearchManagerImpl(); + public static EmiSearchManager.SearchFuture lastSearch = EmiSearchManager.SearchFuture.completedFuture(EmiScreenManager.getSearchSource()); + public static boolean searchChanged = true; + public static boolean isDisabled() { return !EmiReloadManager.isLoaded() || !EmiConfig.enabled; } public static void updateSearch() { - searchManager.search(search.getText()); + lastSearch = searchManager.search(search.getText(), EmiScreenManager.getSearchSource()).whenCompleted(l -> searchChanged = true); } public static void recalculate() { updateCraftables(); SidebarPanel searchPanel = getSearchPanel(); if (searchPanel != null && searchPanel.space != null) { - if (searchedStacks != searchManager.getStacks()) { + if (searchChanged) { searchPanel.space.batcher.repopulate(); - searchedStacks = searchManager.getStacks(); + searchedStacks = lastSearch.getNow(); + searchChanged = false; } } @@ -813,8 +820,8 @@ private static void renderExclusionAreas(EmiDrawContext context, int mouseX, int @SuppressWarnings({"rawtypes", "unchecked"}) private static void renderSlotOverlays(EmiDrawContext context, int mouseX, int mouseY, float delta, EmiScreenBase base) { CompiledQuery query = null; - if (EmiScreenManager.search.highlight) { - query = searchManager.getCompiledQuery(); + if (EmiScreenManager.search.highlight && lastSearch instanceof EmiSearchManagerImpl.SearchWorker worker) { + query = worker.getCompiledQuery(); } Set ignoredSlots = Sets.newHashSet(); Set synfavs = Sets.newHashSet(); @@ -1386,7 +1393,7 @@ public void setSidebarPage(int page) { } } if (isSearch()) { - searchManager.search(search.getText()); + searchManager.search(search.getText(), EmiScreenManager.getSearchSource()); } if (space != null) { space.batcher.repopulate(); diff --git a/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java b/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java index 0c247a7e..624cb409 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java +++ b/xplat/src/main/java/dev/emi/emi/screen/widget/EmiSearchWidget.java @@ -131,7 +131,7 @@ public EmiSearchWidget(TextRenderer textRenderer, int x, int y, int width, int h styles.add(new Pair(string.length(), Style.EMPTY.withFormatting(Formatting.WHITE))); } this.styles = styles; - EmiScreenManager.searchManager.search(string); + EmiScreenManager.updateSearch(); }); } diff --git a/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java index 366e84db..65299aae 100644 --- a/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java +++ b/xplat/src/main/java/dev/emi/emi/search/EmiSearchManagerImpl.java @@ -4,40 +4,33 @@ import dev.emi.emi.api.search.EmiSearchManager; import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.stack.EmiStack; -import dev.emi.emi.registry.EmiStackList; import dev.emi.emi.runtime.EmiLog; -import dev.emi.emi.screen.EmiScreenManager; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; public class EmiSearchManagerImpl implements EmiSearchManager { - private EmiSearch.CompiledQuery compiledQuery; - private List stacks = EmiStackList.stacks; - private volatile SearchWorker currentWorker; - - public CompletableFuture> search(String query) { + public SearchWorker search(String query, List ingredients) { synchronized (this) { - SearchWorker worker = new SearchWorker(query, EmiScreenManager.getSearchSource()); - currentWorker = worker; + SearchWorker worker = new SearchWorker(query, ingredients); EmiSearch.executor.execute(worker); - return worker.getCompletionFuture(); + return worker; } } - public List getStacks() { - return this.stacks; - } - - public EmiSearch.CompiledQuery getCompiledQuery() { - return this.compiledQuery; - } - - class SearchWorker implements Runnable { + public class SearchWorker implements Runnable, SearchFuture { private final String query; + private EmiSearch.CompiledQuery compiledQuery; private final List source; private final CompletableFuture> completion; + private boolean interrupted; SearchWorker(String query, List source) { this.query = query; @@ -45,20 +38,14 @@ class SearchWorker implements Runnable { this.completion = new CompletableFuture<>(); } - public CompletableFuture> getCompletionFuture() { - return completion; - } - private void apply(List stacks) { - synchronized (EmiSearchManagerImpl.this) { - if(this == currentWorker) { - EmiSearchManagerImpl.this.stacks = stacks; - EmiSearchManagerImpl.this.currentWorker = null; - } - } completion.complete(stacks); } + public EmiSearch.CompiledQuery getCompiledQuery() { + return this.compiledQuery; + } + @Override public void run() { try { @@ -73,7 +60,7 @@ public void run() { for (EmiIngredient stack : source) { if (processed++ >= 1024) { processed = 0; - if (this != currentWorker) { + if (interrupted) { apply(source); return; } @@ -94,5 +81,46 @@ public void run() { apply(source); } } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + this.interrupted = true; + return true; + } + + @Override + public boolean isCancelled() { + return this.interrupted; + } + + @Override + public boolean isDone() { + return this.completion.isDone(); + } + + @Override + public List get() throws InterruptedException, ExecutionException { + return this.completion.get(); + } + + @Override + public List get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return this.completion.get(timeout, unit); + } + + @Override + public List getNow() { + return this.completion.getNow(this.source); + } + + @Override + public SearchWorker whenCompleted(Consumer> consumer) { + this.completion.whenComplete((c, ex) -> { + if(c != null) { + consumer.accept(c); + } + }); + return this; + } } } From 962235f49acf88d1fc1f894837e024382b249694 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:01:50 -0400 Subject: [PATCH 4/4] Explicitly cancel previous search when starting new one --- xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java index d0c6b02a..dcd26936 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java +++ b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java @@ -138,6 +138,9 @@ public static boolean isDisabled() { } public static void updateSearch() { + if(lastSearch != null) { + lastSearch.cancel(true); + } lastSearch = searchManager.search(search.getText(), EmiScreenManager.getSearchSource()).whenCompleted(l -> searchChanged = true); }