Skip to content

Commit

Permalink
Async Recipes Searching Task (#578)
Browse files Browse the repository at this point in the history
* AsyncSearchingTask

* magic

* boundary case

* Update RecipeLogic.java
  • Loading branch information
Yefancy authored Dec 3, 2023
1 parent 42cd355 commit 6795c6c
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -29,22 +28,6 @@ public interface IRecipeHandler<K> {
*/
List<K> handleRecipeInner(IO io, GTRecipe recipe, List<K> 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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,6 @@ public List<Long> handleRecipeInner(IO io, GTRecipe recipe, List<Long> 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<Long> getCapability() {
return EURecipeCapability.CAP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ public class NotifiableEnergyContainer extends NotifiableRecipeHandlerTrait<Long
@Getter
protected IO handlerIO;
@Getter
@Setter
private long timeStamp;
@Getter
@Persisted @DescSynced
protected long energyStored;
@Getter
Expand All @@ -46,7 +43,6 @@ public class NotifiableEnergyContainer extends NotifiableRecipeHandlerTrait<Long

public NotifiableEnergyContainer(MetaMachine machine, long maxCapacity, long maxInputVoltage, long maxInputAmperage, long maxOutputVoltage, long maxOutputAmperage) {
super(machine);
this.timeStamp = Long.MIN_VALUE;
this.lastTS = Long.MIN_VALUE;
this.energyCapacity = maxCapacity;
this.inputVoltage = maxInputVoltage;
Expand Down Expand Up @@ -103,7 +99,6 @@ public void checkOutputSubscription() {
public void setEnergyStored(long energyStored) {
if (this.energyStored == energyStored) return;
this.energyStored = energyStored;
updateTimeStamp(machine.getLevel());
checkOutputSubscription();
notifyListeners();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public class NotifiableFluidTank extends NotifiableRecipeHandlerTrait<FluidIngre
public final IO handlerIO;
@Getter
public final IO capabilityIO;
@Getter
@Setter
private long timeStamp;
@Persisted
public final FluidStorage[] storages;
@Setter
Expand All @@ -46,7 +43,6 @@ public class NotifiableFluidTank extends NotifiableRecipeHandlerTrait<FluidIngre

public NotifiableFluidTank(MetaMachine machine, int slots, long capacity, IO io, IO capabilityIO) {
super(machine);
this.timeStamp = Long.MIN_VALUE;
this.handlerIO = io;
this.storages = new FluidStorage[slots];
this.capabilityIO = capabilityIO;
Expand All @@ -58,7 +54,6 @@ public NotifiableFluidTank(MetaMachine machine, int slots, long capacity, IO io,

public NotifiableFluidTank(MetaMachine machine, List<FluidStorage> storages, IO io, IO capabilityIO) {
super(machine);
this.timeStamp = Long.MIN_VALUE;
this.handlerIO = io;
this.storages = storages.toArray(FluidStorage[]::new);
this.capabilityIO = capabilityIO;
Expand All @@ -80,7 +75,6 @@ public NotifiableFluidTank(MetaMachine machine, List<FluidStorage> storages, IO

public void onContentsChanged() {
isEmpty = null;
updateTimeStamp(machine.getLevel());
notifyListeners();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,15 +34,12 @@ public class NotifiableItemStackHandler extends NotifiableRecipeHandlerTrait<Ing
public final IO handlerIO;
@Getter
public final IO capabilityIO;
@Getter @Setter
private long timeStamp;
@Persisted @DescSynced
public final ItemStackTransfer storage;
private Boolean isEmpty;

public NotifiableItemStackHandler(MetaMachine machine, int slots, IO handlerIO, IO capabilityIO, Function<Integer, ItemStackTransfer> transferFactory) {
super(machine);
this.timeStamp = Long.MIN_VALUE;
this.handlerIO = handlerIO;
this.storage = transferFactory.apply(slots);
this.capabilityIO = capabilityIO;
Expand All @@ -65,7 +61,6 @@ public NotifiableItemStackHandler setFilter(Function<ItemStack, Boolean> filter)

public void onContentsChanged() {
isEmpty = null;
updateTimeStamp(machine.getLevel());
notifyListeners();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -65,20 +67,21 @@ public enum Status {
protected int fuelTime;
@Getter @Persisted
protected int fuelMaxTime;
@Getter
protected long timeStamp;
@Getter(onMethod_ = @VisibleForTesting)
protected boolean recipeDirty;
@Persisted
@Getter
protected long totalContinuousRunningTime;
protected TickableSubscription subscription;
protected Object workingSound;
@Nullable
protected CompletableFuture<List<GTRecipe>> 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)
Expand All @@ -105,7 +108,6 @@ public void resetRecipeLogic() {
fuelTime = 0;
lastFailedMatches = null;
status = Status.IDLE;
this.timeStamp = Long.MIN_VALUE;
updateTickSubscription();
}

Expand All @@ -123,6 +125,9 @@ public void updateTickSubscription() {
}
} else {
subscription = getMachine().subscribeServerTick(subscription, this::serverTick);
if (completableFuture != null) {
dirtySearching = true;
}
}
}

Expand All @@ -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;
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -218,55 +224,31 @@ 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"));
}
} 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) {
lastRecipe.preWorking(machine);
}
}

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<GTRecipe> searchRecipe() {
Expand All @@ -285,23 +267,57 @@ public void findAndHandleRecipe() {
lastOriginRecipe = null;
setupRecipe(recipe);
} else { // try to find and handle a new recipe
List<GTRecipe> 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<List<GTRecipe>> supplyAsyncSearchingTask() {
return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("Searching recipes", this::searchRecipe), Util.backgroundExecutor());
}

private void handleSearchingRecipes(List<GTRecipe> 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)) {
Expand Down
Loading

0 comments on commit 6795c6c

Please sign in to comment.