Skip to content

Commit

Permalink
Merge remote-tracking branch 'koiNoCirculation/add-oncompletelistener…
Browse files Browse the repository at this point in the history
…' into dev
  • Loading branch information
Dream-Master committed Sep 8, 2024
2 parents 36c4d93 + 64ae98f commit 50a3079
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 25 deletions.
20 changes: 20 additions & 0 deletions src/main/java/appeng/api/networking/crafting/ICraftingCPU.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import appeng.api.networking.security.BaseActionSource;
import appeng.api.networking.storage.IBaseMonitor;
import appeng.api.storage.data.IAEItemStack;
import appeng.api.util.CraftCancelListener;
import appeng.api.util.CraftCompleteListener;
import appeng.api.util.CraftUpdateListener;

public interface ICraftingCPU extends IBaseMonitor<IAEItemStack> {

Expand Down Expand Up @@ -74,4 +77,21 @@ default long getRemainingItemCount() {
default long getStartItemCount() {
return 0;
}

/**
* @param craftCompleteListener a callback that is called when task is complete
*/
default void addOnCompleteListener(CraftCompleteListener craftCompleteListener) {}

/**
* @param onCancelListener a callback that is called when task is canceled
*/
default void addOnCancelListener(CraftCancelListener onCancelListener) {}

/**
* called when craft executes, passing number of tasks executed to Listener
*
* @param onCraftingStatusUpdate
*/
default void addOnCraftingUpdateListener(CraftUpdateListener onCraftingStatusUpdate) {}
}
8 changes: 8 additions & 0 deletions src/main/java/appeng/api/util/CraftCancelListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package appeng.api.util;

import java.io.Serializable;

public interface CraftCancelListener extends Runnable, Serializable {

long serialVersionUID = 932472384532L;
}
21 changes: 21 additions & 0 deletions src/main/java/appeng/api/util/CraftCompleteListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package appeng.api.util;

import java.io.Serializable;

import net.minecraft.item.ItemStack;

@FunctionalInterface
public interface CraftCompleteListener extends Serializable {

long serialVersionUID = 734594276097234589L;

/**
* Applies this function to the given arguments.
*
* @param finalOutput the output of job
* @param numsOfOutput the size of output stack
* @param elapsedTime the
* @return the function result
*/
void apply(ItemStack finalOutput, long numsOfOutput, long elapsedTime);
}
13 changes: 13 additions & 0 deletions src/main/java/appeng/api/util/CraftUpdateListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package appeng.api.util;

import java.io.Serializable;
import java.util.function.IntConsumer;

/**
* consumes an integer, if it's larger than 0, it means the crafting is not stucked.
*/
@FunctionalInterface
public interface CraftUpdateListener extends IntConsumer, Serializable {

long serialVersionUID = 83482599346L;
}
149 changes: 124 additions & 25 deletions src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@

package appeng.me.cluster.implementations;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -23,6 +29,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;

import net.minecraft.entity.player.EntityPlayer;
Expand Down Expand Up @@ -77,6 +84,9 @@
import appeng.api.storage.data.IAEItemStack;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
import appeng.api.util.CraftCancelListener;
import appeng.api.util.CraftCompleteListener;
import appeng.api.util.CraftUpdateListener;
import appeng.api.util.DimensionalCoord;
import appeng.api.util.IInterfaceViewable;
import appeng.api.util.WorldCoord;
Expand Down Expand Up @@ -140,6 +150,34 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU {
private long remainingItemCount;
private long numsOfOutput;

private List<CraftCompleteListener> defaultOnComplete = Arrays.asList((finalOutput, numsOfOutput, elapsedTime) -> {
if (!this.playersFollowingCurrentCraft.isEmpty()) {
final String elapsedTimeText = DurationFormatUtils.formatDuration(
TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS),
GuiText.ETAFormat.getLocal());

final IChatComponent messageWaitToSend = PlayerMessages.FinishCraftingRemind.get(
new ChatComponentText(EnumChatFormatting.GREEN + String.valueOf(numsOfOutput)),
finalOutput.func_151000_E(),
new ChatComponentText(EnumChatFormatting.GREEN + elapsedTimeText));

for (String playerName : this.playersFollowingCurrentCraft) {
// Get each EntityPlayer
EntityPlayer player = getPlayerByName(playerName);
if (player != null) {
// Send message to player
player.addChatMessage(messageWaitToSend);
player.worldObj.playSoundAtEntity(player, "random.levelup", 1f, 1f);
}
}
}
});

private List<CraftCompleteListener> craftCompleteListeners = initializeDefaultOnCompleteListener();

private List<CraftUpdateListener> craftUpdateListeners = new ArrayList<>();

private List<CraftCancelListener> onCancelListeners = new ArrayList<>();
private List<String> playersFollowingCurrentCraft = new ArrayList<>();

public CraftingCPUCluster(final WorldCoord min, final WorldCoord max) {
Expand All @@ -160,6 +198,25 @@ public ICraftingLink getLastCraftingLink() {
return this.myLastLink;
}

private List<CraftCompleteListener> initializeDefaultOnCompleteListener() {
return new ArrayList<>(defaultOnComplete);
}

@Override
public void addOnCompleteListener(CraftCompleteListener craftCompleteListener) {
this.craftCompleteListeners.add(craftCompleteListener);
}

@Override
public void addOnCancelListener(CraftCancelListener onCancelListener) {
this.onCancelListeners.add(onCancelListener);
}

@Override
public void addOnCraftingUpdateListener(CraftUpdateListener onCraftingStatusUpdate) {
this.craftUpdateListeners.add(onCraftingStatusUpdate);
}

/**
* add a new Listener to the monitor, be sure to properly remove yourself when your done.
*/
Expand Down Expand Up @@ -294,7 +351,6 @@ public IAEStack injectItems(final IAEStack input, final Actionable type, final B
} else if (type == Actionable.MODULATE) {
if (is != null && is.getStackSize() > 0) {
this.waiting = false;

this.postChange(what, src);

if (is.getStackSize() >= what.getStackSize()) {
Expand All @@ -303,7 +359,11 @@ public IAEStack injectItems(final IAEStack input, final Actionable type, final B
this.updateElapsedTime(what);
this.markDirty();
this.postCraftingStatusChange(is);

for (CraftUpdateListener craftUpdateListener : craftUpdateListeners) {
// whatever it passes is not important, if it's not 0, it indicates the craft is active rather
// than stuck.
craftUpdateListener.accept(1);
}
if (Objects.equals(finalOutput, what)) {
IAEStack leftover = what;

Expand Down Expand Up @@ -417,27 +477,7 @@ private void completeJob() {
AELog.crafting(LOG_MARK_AS_COMPLETE, logStack);
}

if (!this.playersFollowingCurrentCraft.isEmpty()) {
final String elapsedTimeText = DurationFormatUtils.formatDuration(
TimeUnit.MILLISECONDS.convert(this.getElapsedTime(), TimeUnit.NANOSECONDS),
GuiText.ETAFormat.getLocal());

final IChatComponent messageWaitToSend = PlayerMessages.FinishCraftingRemind.get(
new ChatComponentText(EnumChatFormatting.GREEN + String.valueOf(this.numsOfOutput)),
this.finalOutput.getItemStack().func_151000_E(),
new ChatComponentText(EnumChatFormatting.GREEN + elapsedTimeText));

for (String playerName : this.playersFollowingCurrentCraft) {
// Get each EntityPlayer
EntityPlayer player = getPlayerByName(playerName);
if (player != null) {
// Send message to player
player.addChatMessage(messageWaitToSend);
player.worldObj.playSoundAtEntity(player, "random.levelup", 1f, 1f);
}
}
}

craftCompleteListeners.forEach(f -> f.apply(this.finalOutput.getItemStack(), this.numsOfOutput, elapsedTime));
this.usedStorage = 0;
this.remainingItemCount = 0;
this.startItemCount = 0;
Expand All @@ -446,6 +486,10 @@ private void completeJob() {
this.numsOfOutput = 0;
this.isComplete = true;
this.playersFollowingCurrentCraft.clear();
this.craftCompleteListeners = initializeDefaultOnCompleteListener();
this.onCancelListeners.clear(); // complete listener will clean external state
// so cancel listener is not called here.
this.craftUpdateListeners.clear();
}

private EntityPlayerMP getPlayerByName(String playerName) {
Expand Down Expand Up @@ -567,7 +611,12 @@ public void cancel() {

this.finalOutput = null;
this.updateCPU();

this.craftCompleteListeners = initializeDefaultOnCompleteListener();
for (Runnable onCancelListener : this.onCancelListeners) {
onCancelListener.run();
}
this.onCancelListeners.clear();
this.craftUpdateListeners.clear();
this.storeItems(); // marks dirty
}

Expand Down Expand Up @@ -625,6 +674,7 @@ public void updateCraftingLogic(final IGrid grid, final IEnergyGrid eg, final Cr
private void executeCrafting(final IEnergyGrid eg, final CraftingGridCache cc) {
final Iterator<Entry<ICraftingPatternDetails, TaskProgress>> i = this.workableTasks.entrySet().iterator();

int executedTasks = 0;
while (i.hasNext()) {
final Entry<ICraftingPatternDetails, TaskProgress> e = i.next();

Expand All @@ -642,7 +692,6 @@ private void executeCrafting(final IEnergyGrid eg, final CraftingGridCache cc) {

InventoryCrafting ic = null;
boolean pushedPattern = false;

for (final ICraftingMedium m : cc.getMediums(e.getKey())) {
if (e.getValue().value <= 0 || knownBusyMediums.contains(m)) {
continue;
Expand Down Expand Up @@ -777,6 +826,8 @@ private void executeCrafting(final IEnergyGrid eg, final CraftingGridCache cc) {
if (!pushedPattern) {
// No need to revisit this task on next executeCrafting this tick
i.remove();
} else {
executedTasks += 1;
}

if (ic != null) {
Expand All @@ -789,6 +840,11 @@ private void executeCrafting(final IEnergyGrid eg, final CraftingGridCache cc) {
}
}
}
for (IntConsumer craftingStatusListener : craftUpdateListeners) {
// if executed tasks is 0 for too much long time, we may need to send an alert in callback registered by
// addon mods, like an email.
craftingStatusListener.accept(executedTasks);
}
}

private void storeItems() {
Expand Down Expand Up @@ -853,6 +909,10 @@ public ICraftingLink submitJob(final IGrid g, final ICraftingJob job, final Base
this.playersFollowingCurrentCraft.clear();

if (ci.commit(src)) {
onCancelListeners.clear();
craftUpdateListeners.clear();
craftCompleteListeners = initializeDefaultOnCompleteListener(); // clear all possible listeners
// when it comes to a new craft,
if (job.getOutput() != null) {
this.finalOutput = job.getOutput();
this.waiting = false;
Expand Down Expand Up @@ -1137,13 +1197,32 @@ public IAEItemStack getItemStack(final IAEItemStack what, final CraftingItemList
return is;
}

private NBTTagCompound persistListeners(int from, List<?> listeners) throws IOException {
NBTTagCompound tagListeners = new NBTTagCompound();
for (int i = from; i < listeners.size(); i++) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream saveListener = new ObjectOutputStream(out);
saveListener.writeObject(listeners.get(i));
tagListeners.setByteArray(String.valueOf(i), out.toByteArray());
}
return tagListeners;
}

public void writeToNBT(final NBTTagCompound data) {
data.setTag("finalOutput", this.writeItem(this.finalOutput));
data.setTag("inventory", this.writeList(this.inventory.getItemList()));
data.setBoolean("waiting", this.waiting);
data.setBoolean("isComplete", this.isComplete);
data.setLong("usedStorage", this.usedStorage);
data.setLong("numsOfOutput", this.numsOfOutput);
try {
data.setTag("craftCompleteListeners", persistListeners(1, craftCompleteListeners));
data.setTag("onCancelListeners", persistListeners(0, onCancelListeners));
data.setTag("craftStatusListeners", persistListeners(0, craftUpdateListeners));
} catch (IOException e) {
// should not affect normal persistence even if there's mistake here.
e.printStackTrace();
}

if (!this.playersFollowingCurrentCraft.isEmpty()) {
NBTTagList nbtTagList = new NBTTagList();
Expand Down Expand Up @@ -1217,6 +1296,18 @@ void done() {
this.updateName();
}

private <T> void unpersistListeners(int from, List<T> toAdd, NBTTagCompound tagCompound)
throws IOException, ClassNotFoundException {
if (tagCompound != null) {
int i = from;
byte[] r;
while ((r = tagCompound.getByteArray(String.valueOf(i))).length != 0) {
toAdd.add((T) new ObjectInputStream(new ByteArrayInputStream(r)).readObject());
i++;
}
}
}

public void readFromNBT(final NBTTagCompound data) {
this.finalOutput = AEItemStack.loadItemStackFromNBT((NBTTagCompound) data.getTag("finalOutput"));
for (final IAEItemStack ais : this.readList((NBTTagList) data.getTag("inventory"))) {
Expand Down Expand Up @@ -1277,6 +1368,14 @@ public void readFromNBT(final NBTTagCompound data) {
AEItemStack.loadItemStackFromNBT(pro.getCompoundTag("item")),
DimensionalCoord.readAsListFromNBT(pro));
}
try {
unpersistListeners(1, craftCompleteListeners, data.getCompoundTag("craftCompleteListeners"));
unpersistListeners(0, onCancelListeners, data.getCompoundTag("onCancelListeners"));
unpersistListeners(0, craftUpdateListeners, data.getCompoundTag("craftStatusListeners"));
} catch (IOException | ClassNotFoundException e) {
// should not affect normal persistence even if there's mistake here.
e.printStackTrace();
}
}

public void updateName() {
Expand Down

0 comments on commit 50a3079

Please sign in to comment.