Added batched storage cache updates to avoid overloading the network when external storage handlers have lots of changes (for example for ProjectE Transmutation Tables)

This commit is contained in:
raoulvdberge
2017-09-01 20:47:07 +02:00
parent 3480d0490b
commit c0a2871462
10 changed files with 129 additions and 57 deletions

View File

@@ -91,10 +91,16 @@ public interface INetwork {
/** /**
* Sends a item storage change to all clients that are watching a grid connected to this network. * Sends a item storage change to all clients that are watching a grid connected to this network.
* *
* @param stack the stack * @param stack the stack
* @param delta the delta * @param delta the delta
* @param batched whether the delta can be batched to be sent all at once using {@link #sendBatchedItemStorageDeltaToClient()}
*/ */
void sendItemStorageDeltaToClient(ItemStack stack, int delta); void sendItemStorageDeltaToClient(ItemStack stack, int delta, boolean batched);
/**
* Sends batched item storage deltas, accumulated through {@link #sendItemStorageDeltaToClient(ItemStack, int, boolean)}.
*/
void sendBatchedItemStorageDeltaToClient();
/** /**
* Sends a grid update packet with all the fluids to all clients that are watching a grid connected to this network. * Sends a grid update packet with all the fluids to all clients that are watching a grid connected to this network.

View File

@@ -32,8 +32,9 @@ public interface IStorageCache<T> {
* @param stack the stack to add, do NOT modify * @param stack the stack to add, do NOT modify
* @param size the size to add * @param size the size to add
* @param rebuilding true if this method is called while rebuilding, false otherwise * @param rebuilding true if this method is called while rebuilding, false otherwise
* @param batched true if this change needs to be batched
*/ */
void add(@Nonnull T stack, int size, boolean rebuilding); void add(@Nonnull T stack, int size, boolean rebuilding, boolean batched);
/** /**
* Removes a stack from the cache. * Removes a stack from the cache.
@@ -41,10 +42,11 @@ public interface IStorageCache<T> {
* Note that this doesn't modify any of the connected storages, but just modifies the cache. * Note that this doesn't modify any of the connected storages, but just modifies the cache.
* Use {@link IStorage#extract(T, int, int, boolean)} to remove a stack from an actual storage. * Use {@link IStorage#extract(T, int, int, boolean)} to remove a stack from an actual storage.
* *
* @param stack the stack to remove, do NOT modify * @param stack the stack to remove, do NOT modify
* @param size the size to remove * @param size the size to remove
* @param batched true if this change needs to be batched, false otherwise
*/ */
void remove(@Nonnull T stack, int size); void remove(@Nonnull T stack, int size, boolean batched);
/** /**
* Adds a listener to be called when this storage cache changes. * Adds a listener to be called when this storage cache changes.

View File

@@ -35,7 +35,7 @@ public abstract class StorageItemExternal implements IStorage<ItemStack> {
// If we exceed the cache size, than that means this item is added // If we exceed the cache size, than that means this item is added
if (i >= cache.size()) { if (i >= cache.size()) {
if (!actual.isEmpty()) { if (!actual.isEmpty()) {
network.getItemStorageCache().add(actual, actual.getCount(), false); network.getItemStorageCache().add(actual, actual.getCount(), false, true);
} }
continue; continue;
@@ -45,29 +45,29 @@ public abstract class StorageItemExternal implements IStorage<ItemStack> {
if (!cached.isEmpty() && actual.isEmpty()) { if (!cached.isEmpty() && actual.isEmpty()) {
// If the cached is not empty but the actual is, we remove this item // If the cached is not empty but the actual is, we remove this item
network.getItemStorageCache().remove(cached, cached.getCount()); network.getItemStorageCache().remove(cached, cached.getCount(), true);
} else if (cached.isEmpty() && !actual.isEmpty()) { } else if (cached.isEmpty() && !actual.isEmpty()) {
// If the cached is empty and the actual isn't, we added this item // If the cached is empty and the actual isn't, we added this item
network.getItemStorageCache().add(actual, actual.getCount(), false); network.getItemStorageCache().add(actual, actual.getCount(), false, true);
network.getCraftingManager().track(actual, actual.getCount()); network.getCraftingManager().track(actual, actual.getCount());
} else if (cached.isEmpty() && actual.isEmpty()) { } else if (cached.isEmpty() && actual.isEmpty()) {
// If they're both empty, nothing happens // If they're both empty, nothing happens
} else if (!API.instance().getComparer().isEqualNoQuantity(cached, actual)) { } else if (!API.instance().getComparer().isEqualNoQuantity(cached, actual)) {
// If both items mismatch, remove the old and add the new // If both items mismatch, remove the old and add the new
network.getItemStorageCache().remove(cached, cached.getCount()); network.getItemStorageCache().remove(cached, cached.getCount(), true);
network.getItemStorageCache().add(actual, actual.getCount(), false); network.getItemStorageCache().add(actual, actual.getCount(), false, true);
network.getCraftingManager().track(actual, actual.getCount()); network.getCraftingManager().track(actual, actual.getCount());
} else if (cached.getCount() != actual.getCount()) { } else if (cached.getCount() != actual.getCount()) {
int delta = actual.getCount() - cached.getCount(); int delta = actual.getCount() - cached.getCount();
if (delta > 0) { if (delta > 0) {
network.getItemStorageCache().add(actual, delta, false); network.getItemStorageCache().add(actual, delta, false, true);
network.getCraftingManager().track(actual, delta); network.getCraftingManager().track(actual, delta);
} else { } else {
network.getItemStorageCache().remove(actual, Math.abs(delta)); network.getItemStorageCache().remove(actual, Math.abs(delta), true);
} }
} }
} }
@@ -77,12 +77,14 @@ public abstract class StorageItemExternal implements IStorage<ItemStack> {
if (cache.size() > newStacks.size()) { if (cache.size() > newStacks.size()) {
for (int i = newStacks.size(); i < cache.size(); ++i) { for (int i = newStacks.size(); i < cache.size(); ++i) {
if (cache.get(i) != ItemStack.EMPTY) { if (cache.get(i) != ItemStack.EMPTY) {
network.getItemStorageCache().remove(cache.get(i), cache.get(i).getCount()); network.getItemStorageCache().remove(cache.get(i), cache.get(i).getCount(), true);
} }
} }
} }
this.cache = newStacks; this.cache = newStacks;
network.sendBatchedItemStorageDeltaToClient();
} }
@Override @Override

View File

@@ -45,7 +45,7 @@ public class StorageCacheFluid implements IStorageCache<FluidStack> {
} }
for (FluidStack stack : storage.getStacks()) { for (FluidStack stack : storage.getStacks()) {
add(stack, stack.amount, true); add(stack, stack.amount, true, false);
} }
} }
@@ -55,7 +55,7 @@ public class StorageCacheFluid implements IStorageCache<FluidStack> {
} }
@Override @Override
public synchronized void add(@Nonnull FluidStack stack, int size, boolean rebuilding) { public synchronized void add(@Nonnull FluidStack stack, int size, boolean rebuilding, boolean batched) {
list.add(stack, size); list.add(stack, size);
if (!rebuilding) { if (!rebuilding) {
@@ -66,7 +66,7 @@ public class StorageCacheFluid implements IStorageCache<FluidStack> {
} }
@Override @Override
public synchronized void remove(@Nonnull FluidStack stack, int size) { public synchronized void remove(@Nonnull FluidStack stack, int size, boolean batched) {
if (list.remove(stack, size)) { if (list.remove(stack, size)) {
network.sendFluidStorageDeltaToClient(stack, -size); network.sendFluidStorageDeltaToClient(stack, -size);

View File

@@ -46,7 +46,7 @@ public class StorageCacheItem implements IStorageCache<ItemStack> {
for (ItemStack stack : storage.getStacks()) { for (ItemStack stack : storage.getStacks()) {
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
add(stack, stack.getCount(), true); add(stack, stack.getCount(), true, false);
} }
} }
} }
@@ -57,20 +57,20 @@ public class StorageCacheItem implements IStorageCache<ItemStack> {
} }
@Override @Override
public synchronized void add(@Nonnull ItemStack stack, int size, boolean rebuilding) { public synchronized void add(@Nonnull ItemStack stack, int size, boolean rebuilding, boolean batched) {
list.add(stack, size); list.add(stack, size);
if (!rebuilding) { if (!rebuilding) {
network.sendItemStorageDeltaToClient(stack, size); network.sendItemStorageDeltaToClient(stack, size, batched);
listeners.forEach(Runnable::run); listeners.forEach(Runnable::run);
} }
} }
@Override @Override
public synchronized void remove(@Nonnull ItemStack stack, int size) { public synchronized void remove(@Nonnull ItemStack stack, int size, boolean batched) {
if (list.remove(stack, size)) { if (list.remove(stack, size)) {
network.sendItemStorageDeltaToClient(stack, -size); network.sendItemStorageDeltaToClient(stack, -size, batched);
listeners.forEach(Runnable::run); listeners.forEach(Runnable::run);
} }

View File

@@ -41,7 +41,7 @@ public class StorageCacheItemPortable implements IStorageCache<ItemStack> {
} }
@Override @Override
public void add(@Nonnull ItemStack stack, int size, boolean rebuilding) { public void add(@Nonnull ItemStack stack, int size, boolean rebuilding, boolean batched) {
list.add(stack, size); list.add(stack, size);
if (!rebuilding) { if (!rebuilding) {
@@ -52,7 +52,7 @@ public class StorageCacheItemPortable implements IStorageCache<ItemStack> {
} }
@Override @Override
public void remove(@Nonnull ItemStack stack, int size) { public void remove(@Nonnull ItemStack stack, int size, boolean batched) {
if (list.remove(stack, size)) { if (list.remove(stack, size)) {
portableGrid.getWatchers().forEach(w -> RS.INSTANCE.network.sendTo(new MessageGridItemDelta(null, stack, -size), (EntityPlayerMP) w)); portableGrid.getWatchers().forEach(w -> RS.INSTANCE.network.sendTo(new MessageGridItemDelta(null, stack, -size), (EntityPlayerMP) w));

View File

@@ -66,7 +66,7 @@ public class StorageDiskItemPortable implements IStorageDisk<ItemStack> {
int inserted = parent.getCacheDelta(storedPre, size, remainder); int inserted = parent.getCacheDelta(storedPre, size, remainder);
if (inserted > 0) { if (inserted > 0) {
portableGrid.getCache().add(stack, inserted, false); portableGrid.getCache().add(stack, inserted, false, false);
} }
} }
@@ -79,7 +79,7 @@ public class StorageDiskItemPortable implements IStorageDisk<ItemStack> {
ItemStack extracted = parent.extract(stack, size, flags, simulate); ItemStack extracted = parent.extract(stack, size, flags, simulate);
if (!simulate && extracted != null) { if (!simulate && extracted != null) {
portableGrid.getCache().remove(extracted, extracted.getCount()); portableGrid.getCache().remove(extracted, extracted.getCount(), false);
} }
return extracted; return extracted;

View File

@@ -13,6 +13,7 @@ import net.minecraftforge.items.ItemHandlerHelper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -30,20 +31,22 @@ public class StorageItemTransmutationTable extends StorageItemExternal {
@Override @Override
public Collection<ItemStack> getStacks() { public Collection<ItemStack> getStacks() {
List<ItemStack> stored = new LinkedList<>();
if (externalStorage.getOwner() != null) { if (externalStorage.getOwner() != null) {
IKnowledgeProvider provider = ProjectEAPI.getTransmutationProxy().getKnowledgeProviderFor(externalStorage.getOwner()); IKnowledgeProvider provider = ProjectEAPI.getTransmutationProxy().getKnowledgeProviderFor(externalStorage.getOwner());
// @todo: https://github.com/sinkillerj/ProjectE/issues/1591 // @todo: https://github.com/sinkillerj/ProjectE/issues/1591
if (!provider.getClass().getName().equals("moze_intel.projecte.impl.TransmutationOffline$1")) { if (!provider.getClass().getName().equals("moze_intel.projecte.impl.TransmutationOffline$1")) {
List<ItemStack> stored = new LinkedList<>();
for (ItemStack knowledge : provider.getKnowledge()) { for (ItemStack knowledge : provider.getKnowledge()) {
stored.add(ItemHandlerHelper.copyStackWithSize(knowledge, (int) Math.floor(provider.getEmc() / (double) ProjectEAPI.getEMCProxy().getValue(knowledge)))); stored.add(ItemHandlerHelper.copyStackWithSize(knowledge, (int) Math.floor(provider.getEmc() / (double) ProjectEAPI.getEMCProxy().getValue(knowledge))));
} }
return stored;
} }
} }
return stored; return Collections.emptyList();
} }
@Nullable @Nullable

View File

@@ -10,16 +10,24 @@ import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
public class MessageGridItemDelta implements IMessage, IMessageHandler<MessageGridItemDelta, IMessage> { public class MessageGridItemDelta implements IMessage, IMessageHandler<MessageGridItemDelta, IMessage> {
@Nullable @Nullable
private INetwork network; private INetwork network;
private List<Pair<ItemStack, Integer>> deltas;
@Nullable
private ItemStack stack; private ItemStack stack;
private int delta; private int delta;
private GridStackItem clientStack; @Nullable
private GridStackItem gridStack;
private List<Pair<GridStackItem, Integer>> gridStacks;
public MessageGridItemDelta() { public MessageGridItemDelta() {
} }
@@ -30,26 +38,64 @@ public class MessageGridItemDelta implements IMessage, IMessageHandler<MessageGr
this.delta = delta; this.delta = delta;
} }
public MessageGridItemDelta(@Nullable INetwork network, List<Pair<ItemStack, Integer>> deltas) {
this.network = network;
this.deltas = deltas;
}
@Override @Override
public void fromBytes(ByteBuf buf) { public void fromBytes(ByteBuf buf) {
clientStack = new GridStackItem(buf); int size = buf.readInt();
delta = buf.readInt();
if (size == 1) {
gridStack = new GridStackItem(buf);
delta = buf.readInt();
} else {
gridStacks = new LinkedList<>();
for (int i = 0; i < size; ++i) {
gridStacks.add(Pair.of(new GridStackItem(buf), buf.readInt()));
}
}
} }
@Override @Override
public void toBytes(ByteBuf buf) { public void toBytes(ByteBuf buf) {
StackUtils.writeItemStack(buf, stack, network, false); if (stack != null) {
buf.writeInt(delta); buf.writeInt(1);
StackUtils.writeItemStack(buf, stack, network, false);
buf.writeInt(delta);
} else {
buf.writeInt(deltas.size());
for (Pair<ItemStack, Integer> delta : deltas) {
StackUtils.writeItemStack(buf, delta.getLeft(), network, false);
buf.writeInt(delta.getRight());
}
}
} }
@Override @Override
public IMessage onMessage(MessageGridItemDelta message, MessageContext ctx) { public IMessage onMessage(MessageGridItemDelta message, MessageContext ctx) {
Item item = message.clientStack.getStack().getItem(); if (message.gridStack != null) {
process(message.gridStack, message.delta);
} else {
message.gridStacks.forEach(p -> process(p.getLeft(), p.getRight()));
}
GuiGrid.markForSorting();
return null;
}
private void process(GridStackItem gridStack, int delta) {
Item item = gridStack.getStack().getItem();
for (GridStackItem stack : GuiGrid.ITEMS.get(item)) { for (GridStackItem stack : GuiGrid.ITEMS.get(item)) {
if (stack.equals(message.clientStack)) { if (stack.equals(gridStack)) {
if (stack.getStack().getCount() + message.delta == 0) { if (stack.getStack().getCount() + delta == 0) {
if (message.clientStack.isCraftable()) { if (gridStack.isCraftable()) {
stack.setDisplayCraftText(true); stack.setDisplayCraftText(true);
} else { } else {
GuiGrid.ITEMS.remove(item, stack); GuiGrid.ITEMS.remove(item, stack);
@@ -58,23 +104,18 @@ public class MessageGridItemDelta implements IMessage, IMessageHandler<MessageGr
if (stack.doesDisplayCraftText()) { if (stack.doesDisplayCraftText()) {
stack.setDisplayCraftText(false); stack.setDisplayCraftText(false);
stack.getStack().setCount(message.delta); stack.getStack().setCount(delta);
} else { } else {
stack.getStack().grow(message.delta); stack.getStack().grow(delta);
} }
} }
GuiGrid.markForSorting(); return;
return null;
} }
} }
message.clientStack.getStack().setCount(message.delta); gridStack.getStack().setCount(delta);
GuiGrid.ITEMS.put(item, message.clientStack); GuiGrid.ITEMS.put(item, gridStack);
GuiGrid.markForSorting();
return null;
} }
} }

View File

@@ -63,6 +63,7 @@ import net.minecraftforge.common.util.Constants;
import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.ItemHandlerHelper;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -132,6 +133,8 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
private ISecurityManager securityManager = new SecurityManager(this); private ISecurityManager securityManager = new SecurityManager(this);
private IStorageCache<ItemStack> itemStorage = new StorageCacheItem(this); private IStorageCache<ItemStack> itemStorage = new StorageCacheItem(this);
private List<Pair<ItemStack, Integer>> batchedItemStorageDeltas = new LinkedList<>();
private IStorageCache<FluidStack> fluidStorage = new StorageCacheFluid(this); private IStorageCache<FluidStack> fluidStorage = new StorageCacheFluid(this);
private Map<String, IReaderWriterChannel> readerWriterChannels = new HashMap<>(); private Map<String, IReaderWriterChannel> readerWriterChannels = new HashMap<>();
@@ -298,10 +301,25 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
} }
@Override @Override
public void sendItemStorageDeltaToClient(ItemStack stack, int delta) { public void sendBatchedItemStorageDeltaToClient() {
world.getMinecraftServer().getPlayerList().getPlayers().stream() if (!batchedItemStorageDeltas.isEmpty()) {
.filter(player -> isWatchingGrid(player, GridType.NORMAL, GridType.CRAFTING, GridType.PATTERN)) world.getMinecraftServer().getPlayerList().getPlayers().stream()
.forEach(player -> RS.INSTANCE.network.sendTo(new MessageGridItemDelta(this, stack, delta), player)); .filter(player -> isWatchingGrid(player, GridType.NORMAL, GridType.CRAFTING, GridType.PATTERN))
.forEach(player -> RS.INSTANCE.network.sendTo(new MessageGridItemDelta(this, batchedItemStorageDeltas), player));
batchedItemStorageDeltas.clear();
}
}
@Override
public void sendItemStorageDeltaToClient(ItemStack stack, int delta, boolean batched) {
if (!batched) {
world.getMinecraftServer().getPlayerList().getPlayers().stream()
.filter(player -> isWatchingGrid(player, GridType.NORMAL, GridType.CRAFTING, GridType.PATTERN))
.forEach(player -> RS.INSTANCE.network.sendTo(new MessageGridItemDelta(this, stack, delta), player));
} else {
batchedItemStorageDeltas.add(Pair.of(stack, delta));
}
} }
@Override @Override
@@ -439,7 +457,7 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
} }
if (!simulate && inserted - insertedExternally > 0) { if (!simulate && inserted - insertedExternally > 0) {
itemStorage.add(stack, inserted - insertedExternally, false); itemStorage.add(stack, inserted - insertedExternally, false, false);
} }
return remainder; return remainder;
@@ -484,7 +502,7 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
} }
if (newStack != null && newStack.getCount() - extractedExternally > 0 && !simulate) { if (newStack != null && newStack.getCount() - extractedExternally > 0 && !simulate) {
itemStorage.remove(newStack, newStack.getCount() - extractedExternally); itemStorage.remove(newStack, newStack.getCount() - extractedExternally, false);
} }
return newStack; return newStack;
@@ -526,7 +544,7 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
} }
if (inserted > 0) { if (inserted > 0) {
fluidStorage.add(stack, inserted, false); fluidStorage.add(stack, inserted, false, false);
} }
return remainder; return remainder;
@@ -567,7 +585,7 @@ public class TileController extends TileBase implements ITickable, INetwork, IRe
} }
if (newStack != null && !simulate) { if (newStack != null && !simulate) {
fluidStorage.remove(newStack, newStack.amount); fluidStorage.remove(newStack, newStack.amount, false);
} }
return newStack; return newStack;