diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java b/common/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java index a9086f0311..4b0c677e54 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java @@ -1,7 +1,6 @@ package com.gregtechceu.gtceu.api.capability.recipe; import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import net.minecraft.world.level.Level; import javax.annotation.Nullable; import java.util.List; @@ -29,22 +28,6 @@ public interface IRecipeHandler { */ List handleRecipeInner(IO io, GTRecipe recipe, List left, @Nullable String slotName, boolean simulate); - /** - * The timestamp indicates the time in the world when the last change occurred - */ - long getTimeStamp(); - - /** - * Update to the latest work time. - */ - void setTimeStamp(long timeStamp); - - default void updateTimeStamp(@Nullable Level level) { - if (level != null) { - setTimeStamp(level.getGameTime()); - } - } - /** * Slot name, it makes sense if recipe contents specify a slot name. */ diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java b/common/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java index 64ae8ee251..299ccfb150 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java @@ -46,16 +46,6 @@ public List handleRecipeInner(IO io, GTRecipe recipe, List left, @Nu return sum <= 0 ? null : Collections.singletonList(sum); } - @Override - public long getTimeStamp() { - return steamTank.getTimeStamp(); - } - - @Override - public void setTimeStamp(long timeStamp) { - steamTank.setTimeStamp(timeStamp); - } - @Override public RecipeCapability getCapability() { return EURecipeCapability.CAP; diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableEnergyContainer.java b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableEnergyContainer.java index 70e8bbb61b..000a64104b 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableEnergyContainer.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableEnergyContainer.java @@ -31,9 +31,6 @@ public class NotifiableEnergyContainer extends NotifiableRecipeHandlerTrait storages, IO io, IO capabilityIO) { super(machine); - this.timeStamp = Long.MIN_VALUE; this.handlerIO = io; this.storages = storages.toArray(FluidStorage[]::new); this.capabilityIO = capabilityIO; @@ -80,7 +75,6 @@ public NotifiableFluidTank(MetaMachine machine, List storages, IO public void onContentsChanged() { isEmpty = null; - updateTimeStamp(machine.getLevel()); notifyListeners(); } diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableItemStackHandler.java b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableItemStackHandler.java index 5ec1601336..6b4026d79a 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableItemStackHandler.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableItemStackHandler.java @@ -12,7 +12,6 @@ import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted; import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder; import lombok.Getter; -import lombok.Setter; import net.minecraft.core.Direction; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; @@ -35,15 +34,12 @@ public class NotifiableItemStackHandler extends NotifiableRecipeHandlerTrait transferFactory) { super(machine); - this.timeStamp = Long.MIN_VALUE; this.handlerIO = handlerIO; this.storage = transferFactory.apply(slots); this.capabilityIO = capabilityIO; @@ -65,7 +61,6 @@ public NotifiableItemStackHandler setFilter(Function filter) public void onContentsChanged() { isEmpty = null; - updateTimeStamp(machine.getLevel()); notifyListeners(); } diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java index 31f3819f6c..88bf6dacf6 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java @@ -20,6 +20,7 @@ import lombok.Getter; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.Util; import net.minecraft.network.chat.Component; import net.minecraft.world.item.crafting.RecipeManager; import org.jetbrains.annotations.VisibleForTesting; @@ -28,6 +29,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; public class RecipeLogic extends MachineTrait implements IEnhancedManaged, IWorkable, IFancyTooltip { @@ -65,8 +67,6 @@ public enum Status { protected int fuelTime; @Getter @Persisted protected int fuelMaxTime; - @Getter - protected long timeStamp; @Getter(onMethod_ = @VisibleForTesting) protected boolean recipeDirty; @Persisted @@ -74,11 +74,14 @@ public enum Status { protected long totalContinuousRunningTime; protected TickableSubscription subscription; protected Object workingSound; + @Nullable + protected CompletableFuture> completableFuture = null; + // if storage is dirty while async searching recipe, it will be set to true. + protected boolean dirtySearching = false; public RecipeLogic(IRecipeLogicMachine machine) { super(machine.self()); this.machine = machine; - this.timeStamp = Long.MIN_VALUE; } @Environment(EnvType.CLIENT) @@ -105,7 +108,6 @@ public void resetRecipeLogic() { fuelTime = 0; lastFailedMatches = null; status = Status.IDLE; - this.timeStamp = Long.MIN_VALUE; updateTickSubscription(); } @@ -123,6 +125,9 @@ public void updateTickSubscription() { } } else { subscription = getMachine().subscribeServerTick(subscription, this::serverTick); + if (completableFuture != null) { + dirtySearching = true; + } } } @@ -144,27 +149,19 @@ public RecipeManager getRecipeManager() { return Platform.getMinecraftServer().getRecipeManager(); } - public long getLatestTimeStamp() { - return machine.self().getLevel().getGameTime(); - } - public void serverTick() { if (!isSuspend()) { if (!isIdle() && lastRecipe != null) { - if (isWaiting() && getMachine().getOffsetTimer() % 5 == 0) { - executeDirty(this::handleRecipeWorking); - } else { - if (progress < duration) { - handleRecipeWorking(); - } - if (progress >= duration) { - onRecipeFinish(); - } + if (progress < duration) { + handleRecipeWorking(); + } + if (progress >= duration) { + onRecipeFinish(); } } else if (lastRecipe != null) { findAndHandleRecipe(); } else if (!machine.keepSubscribing() || getMachine().getOffsetTimer() % 5 == 0) { - executeDirty(this::findAndHandleRecipe); + findAndHandleRecipe(); if (lastFailedMatches != null) { for (GTRecipe match : lastFailedMatches) { if (checkMatchedRecipeAvailable(match)) break; @@ -175,14 +172,23 @@ public void serverTick() { if (fuelTime > 0) { fuelTime--; } else { - if (isSuspend() || (lastRecipe == null && isIdle() && !machine.keepSubscribing() && !recipeDirty && lastFailedMatches == null)) { + boolean unsubscribe = false; + if (isSuspend()) { + unsubscribe = true; + if (completableFuture != null) { + completableFuture.cancel(true); + completableFuture = null; + } + } else if (completableFuture == null && lastRecipe == null && isIdle() && !machine.keepSubscribing() && !recipeDirty && lastFailedMatches == null) { // machine isn't working enabled // or // there is no available recipes, so it will wait for notification. - if (subscription != null) { - subscription.unsubscribe(); - subscription = null; - } + unsubscribe = true; + } + + if (unsubscribe && subscription != null) { + subscription.unsubscribe(); + subscription = null; } } } @@ -218,13 +224,6 @@ public void handleRecipeWorking() { totalContinuousRunningTime++; } else { setWaiting(result.reason().get()); - if (progress > 0 && machine.dampingWhenWaiting()) { - if (ConfigHolder.INSTANCE.machines.recipeProgressLowEnergy) { - this.progress = 1; - } else { - this.progress = Math.max(1, progress - 2); - } - } } } else { setWaiting(Component.translatable("gtceu.recipe_logic.insufficient_fuel")); @@ -232,6 +231,9 @@ public void handleRecipeWorking() { } else { setWaiting(result.reason().get()); } + if (isWaiting()) { + doDamping(); + } if (last == Status.WORKING && getStatus() != Status.WORKING) { lastRecipe.postWorking(machine); } else if (last != Status.WORKING && getStatus() == Status.WORKING) { @@ -239,34 +241,14 @@ public void handleRecipeWorking() { } } - private void executeDirty(Runnable changed) { - long latestTS = getLatestTimeStamp(); - if (latestTS < timeStamp) { - timeStamp = latestTS; - changed.run(); - } else { - if (machine.hasProxies() && checkDirty(latestTS)) { - changed.run(); - } - } - } - - public boolean checkDirty(long latestTS) { - boolean execute = false; - for (var handlers : machine.getCapabilitiesProxy().values()) { - if (handlers != null) { - for (var handler : handlers) { - if (handler.getTimeStamp() < latestTS) { - handler.setTimeStamp(latestTS); - } - if (handler.getTimeStamp() > timeStamp) { - execute = true; - timeStamp = handler.getTimeStamp(); - } - } + protected void doDamping() { + if (progress > 0 && machine.dampingWhenWaiting()) { + if (ConfigHolder.INSTANCE.machines.recipeProgressLowEnergy) { + this.progress = 1; + } else { + this.progress = Math.max(1, progress - 2); } } - return execute; } protected List searchRecipe() { @@ -285,23 +267,57 @@ public void findAndHandleRecipe() { lastOriginRecipe = null; setupRecipe(recipe); } else { // try to find and handle a new recipe - List matches = searchRecipe(); - lastRecipe = null; lastOriginRecipe = null; - for (GTRecipe match : matches) { - // try to modify recipe by machine, such as overclock, tier checking. - if (checkMatchedRecipeAvailable(match)) break; - // cache matching recipes. - if (lastFailedMatches == null) { - lastFailedMatches = new ArrayList<>(); + if (completableFuture == null) { + // try to search recipe in threads. + if (ConfigHolder.INSTANCE.machines.asyncRecipeSearching) { + completableFuture = supplyAsyncSearchingTask(); + } else { + handleSearchingRecipes(searchRecipe()); } - lastFailedMatches.add(match); + dirtySearching = false; + } else if (completableFuture.isDone()) { + var lastFuture = this.completableFuture; + completableFuture = null; + if (!lastFuture.isCancelled()) { + // if searching task is done, try to handle searched recipes. + try { + var matches = lastFuture.join().stream().filter(match -> match.matchRecipe(machine).isSuccess()).toList(); + if (!matches.isEmpty()) { + handleSearchingRecipes(matches); + } else if (dirtySearching) { + completableFuture = supplyAsyncSearchingTask(); + } + } catch (Throwable throwable) { + // if error occurred, schedule a new async task. + completableFuture = supplyAsyncSearchingTask(); + } + } else { + handleSearchingRecipes(searchRecipe()); + } + dirtySearching = false; } } recipeDirty = false; } + private CompletableFuture> supplyAsyncSearchingTask() { + return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("Searching recipes", this::searchRecipe), Util.backgroundExecutor()); + } + + private void handleSearchingRecipes(List matches) { + for (GTRecipe match : matches) { + // try to modify recipe by machine, such as overclock, tier checking. + if (checkMatchedRecipeAvailable(match)) break; + // cache matching recipes. + if (lastFailedMatches == null) { + lastFailedMatches = new ArrayList<>(); + } + lastFailedMatches.add(match); + } + } + public boolean handleFuelRecipe() { if (!needFuel() || fuelTime > 0) return true; for (GTRecipe recipe : machine.getRecipeType().searchFuelRecipe(getRecipeManager(), machine)) { diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/misc/IgnoreEnergyRecipeHandler.java b/common/src/main/java/com/gregtechceu/gtceu/api/misc/IgnoreEnergyRecipeHandler.java index eb16fb6791..c324d07dcd 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/misc/IgnoreEnergyRecipeHandler.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/misc/IgnoreEnergyRecipeHandler.java @@ -15,16 +15,6 @@ public List handleRecipeInner(IO io, GTRecipe recipe, List left, @Nu return null; } - @Override - public long getTimeStamp() { - return 0; - } - - @Override - public void setTimeStamp(long timeStamp) { - - } - @Override public RecipeCapability getCapability() { return EURecipeCapability.CAP; diff --git a/common/src/main/java/com/gregtechceu/gtceu/api/misc/ItemRecipeHandler.java b/common/src/main/java/com/gregtechceu/gtceu/api/misc/ItemRecipeHandler.java index 8679971c9f..7b5cb5939d 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/api/misc/ItemRecipeHandler.java +++ b/common/src/main/java/com/gregtechceu/gtceu/api/misc/ItemRecipeHandler.java @@ -36,16 +36,6 @@ public List handleRecipeInner(IO io, GTRecipe recipe, List getCapability() { return ItemRecipeCapability.CAP; diff --git a/common/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java b/common/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java index b907192b8f..ba85a267f2 100644 --- a/common/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java +++ b/common/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java @@ -287,6 +287,9 @@ public static class MachineConfigs { "Other mods can override this to true, regardless of the config file.", "Default: false"}) public boolean highTierContent = false; + @Configurable + @Configurable.Comment({"Whether search for recipes asynchronously.", " Default: true"}) + public boolean asyncRecipeSearching = true; } public static class ClientConfigs {